永恒之蓝的勒索病毒tasksche.exe样本分析

内容:
分析病毒结构,写出病毒如何利用漏洞进行攻击,详细剖析勒索病毒的运行过程,使用了什么加密算法,调用了什么系统API。
进阶:
中了该勒索病毒,怎么恢复数据
样本分析
首先是看下病毒样本的哈希值


图片.png

查壳信息,win32程序无壳:


图片.png

载入IDA后,先查看字符串,看到有RSA和AES,猜测之后会使用这两种加密方式:
图片.png

用IDA的findcrypto插件,找到了AES算法的特征:
图片.png

第一部分

之后开始对病毒样本进行详细分析,首先是程序入口点:

图片.png

一开始病毒获取它的运行绝对路径,用OD动态调试时可得:

图片.png

在函数 sub_401225中,会根据当前的计算机名随机生成一组字符串,后面会用到

图片.png

OD动调结果:

图片.png

如果参数有/i,就把病毒自身复制到之前随机字符串的文件夹中,名称为tasksche.exe,之后去运行这个,但是我没用命令行去运行,所以直接就跳到下一步。之后将路径切换到了当前的文件夹下,在sub_4010FD里面会添加一条新的注册表项

图片.png
图片.png

但是在OD调试的时候发现在注册表中没有这一项,跟到函数里面,发现在执行完RegSetValueExA退出了,怀疑是权限问题导致注册表没能成功添加

图片.png

分析下在sub_401DAB里面进行的操作,主要对资源文件进行获取和释放到当前文件夹,用到了FindResourceA,LoadResource等对资源操作的函数,下图的Type是XIA,可以通过Resource Hacker来找到程序释放的资源

图片.png

找到名字叫做XIA的资源段,里面一看是PK头,意识到是一个zip压缩包,直接用foremost从程序中提取出zip压缩包,密码是压到栈里的参数WNcry@2ol7

图片.png

配合OD,执行完释放资源的函数后,文件夹中的内容多了一堆,应该都是之后病毒要使用的文件,十六进制编辑器打开后发现有PE文件,压缩包,被加密的文件等

图片.png

之后执行两条命令

attrib +h .

icacls . /grant Everyone:F /T /C /Q

图片.png

这两条命令搜了一下用法,

attrib为批处理文件命令 具体解释如下:- 清除属性。 R 只读文件属性。 A 存档文件属性。 S 系统文件属性。 H 隐藏文件属性。

这个地方命令用错了,对当前文件下文件隐藏不需要加点,直接attrib +h就可以,这里意思是想把这些病毒使用的文件全部隐藏,结果没什么用

图片.png

第二条指令:

icacls

Intergrity Control Access Control List: 完整性权限控制列表 Windows系统下控制文件及文件夹的访问权限的命令行指令,相当于Linux中的chmod 原命令cacls已经被废弃。

MS documentation: 1, F = Full Control

• 777

2, CI = Container Inherit - This flag indicates that subordinate containers will inherit this ACE.

• 子文件夹继承父文件夹权限

3, OI = Object Inherit - This flag indicates that subordinate files will inherit the ACE.

• 子文件继承父文件夹权限

4, /T = Apply recursively to existing files and sub-folders. (OI and CI only apply to new files and sub-folders).

• 递归传递权限

在函数sub_4014A6里,对之前解压的t.cnry进行解压,并加载到内存里,在内存可以看到PE结构的文件,直接用PEload转存出一份内存文件,再用foremost提取出一个PE文件:

图片.png

之后进入函数sub_4021BD回去判断是一个PE文件,在堆上申请了空间,并把这个PE文件加载到堆上

图片.png

wcry.exe的后半部分的流程如下图:

图片.png

在提取出的dll文件中,打开导出函数可以看到TaskStart,说明在v8处执行了这个函数

图片.png

之后OD运行到call eax,进入了dll的函数,整个病毒就开始进行加密了

图片.png

可以发现wcry.exe其实并没有进行文件加密等病毒操作,大部分功能都是在释放资源和分配空间,直到开始运行释放出来的dll中的导出函数才会开始加密,所以这里直接把这个调用TaskStart的call给nop掉就不会中毒,因为wcry.exe的功能已经分析完了,测试了下想法,修改完保存二进制程序,此时程序就只是释放所有资源文件,不会加密文件

图片.png

这部分的API函数总结:

1.GetModuleFileNameA

函数功能:获取当前运行程序的绝对路径

DWORD GetModuleFileNameA(

HMDULE hModule,     //所需路径的模块的句柄。如果该参数为NULL,函数会获取当前进程的运行文件(.exe文件)的全路径。

LPSTR lpFilename,    //一个指向接收存储模块的全路径的缓冲区的指针。如果路径的长度小于nSize参数定义的长度,返回路径为一个结尾为空终止('\0')的字符串;如果路径的长度超过nSize参数所定义的长度,字符串会截断到nSize个字符的长度,并在最后及nSize-1的位置包含一个终止符('\0')。

DWORD nSize         //lpFilename缓冲区的长度。

); //如果函数运行成功,返回值为字符串的长度。如果字符串的长度大于nSize字节,返回值为nSize。如果函数运行失败,返回值为0。

2.GetComputerNameW

函数功能:获取计算机名

BOOL GetComputerNameA(
  LPSTR   lpBuffer,     //指向接收计算机名称或群集虚拟服务器名称的缓冲区的指针。缓冲区大小应足够大,以包含MAX_COMPUTERNAME_LENGTH + 1个字符。
  LPDWORD nSize    //在输入时,在TCHAR中指定缓冲区的大小。在输出时,复制到目标缓冲区的TCHAR数,不包括终止空字符。如果缓冲区太小,则函数失败,GetLastError返回ERROR_BUFFER_OVERFLOW。所述lpnSize参数指定缓冲器的所需要的大小,包括终止空字符。
);

3.MultiByteToWideChar

函数功能:该函数映射一个字符串到一个宽字符(unicode)的字符串。由该函数映射的字符串没必要是多字节字符组。

函数原型: 

int MultiByteToWideChar(

  UINT CodePage,

  DWORD dwFlags,

  LPCSTR lpMultiByteStr,

  int cchMultiByte,

  LPWSTR lpWideCharStr,

  int cchWideChar

);

4.GetWindowsDirectory

函数功能:获取Windows目录的完整路径名

UINT WINAPI GetWindowsDirectory(__out  LPTSTR lpBuffer, __in   UINT uSize); 
lpBuffer [out] 
一个缓冲区,是一个接收路径的指针。这条路径并没有结束,除非以反斜杠Windows目录是根目录。例如,如果Windows目录被命名为Windows的C驱动器上的Windows目录的路径检索这个函数是C:\窗口。如果系统在驱动器C的根目录,检索安装路径是C:\。 

lpBuffer String,指定一个字串缓冲区,用于装载Windows目录名。除非是根目录,否则目录中不会有一个中止用的“\”字符

uSize [in] 
至于由lpBuffer参数指定的缓冲区的最大尺寸,在TCHARs。此值应设置为MAX_PATH。 
nSize Long,lpBuffer字串的最大长度

5.GetFileAttributes

函数功能:为一个指定的文件或目录返回文件系统的属性。可以使用GetFileAttributesEx 函数获得更多的属性信息。如果要实现交互式操作,可以使用GetFileAttributesTransacted 函数。

DWORD WINAPI GetFileAttributes(
  __in LPCTSTR lpFileName
  );
参数
  lpFileName [in]
  文件或目录的名字,对于ANSI版本,名字不能大于MAX_PATH。
返回值
  如果函数成功,返回值包含文件或目录的属性。如果函数失败,返回值是INVALID_FILE_ATTRIBUTES。

SetFileAttributes

函数功能:设置文件属性

SetFileAttributes(文件名, FILE_ATTRIBUTE_READONLY); // 设定为只读
SetFileAttributes(文件名, FILE_ATTRIBUTE_HIDDEN );//设定为隐藏
SetFileAttributes(文件名, FILE_ATTRIBUTE_SYSTEM);//设定为系统
SetFileAttributes(文件名, FILE_ATTRIBUTE_ARCHIVE);//设定为保存
SetFileAttributes(文件名, FILE_ATTRIBUTE_NORMAL);//设定为一般 (取消前四种属性)
设定二种以上的属性:
设定为只读 + 隐藏
SetFileAttributes(文件名, FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN); 
设定为只读 + 隐藏 + 系统 + 保存
SetFileAttributes(文件名, FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN _
| FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_ARCHIVE);
取消所有设定
SetFileAttributes(文件名, FILE_ATTRIBUTE_NORMAL);

6.GetTempPathW

函数功能:获取为临时文件指定的路径

DWORD WINAPI GetTempPath(
  __in   DWORD nBufferLength,
  __out  LPTSTR lpBuffer
);

nBufferLength:指lpbuffer所占的空间,以TCHAR为单位。

(TCHAR:当UNICODE模式下为wchar_t,非UNICODE模式下为char。)

lpBuffer:保存找的文件夹路径,该文件路径以‘\’结束,例如“C:\temp\”。

其返回值为一个dword类型,当函数执行成功时返回文件夹路径长度,以TCHAR为单位,不包括结束字符在内。如果返回的路径长度大于nbufferlength,那么按返回值保存路径。

7.CreateDirectoryW

函数功能:CreateDirectory这个函数的作用是创建一个新的目录。如果底层文件系统支持文件和目录上的安全描述,该功能可将指定的安全描述到新的目录。

函数原型:
BOOL CreateDirectory(
  LPCTSTR lpPathName,
  LPSECURITY_ATTRIBUTES lpSecurityAttributes
);

参数:
    pPathName:长指针,指向一个空结束的字符串,该字符串指定要创建的目录的路径。有一个默认的字符串大小限制为MAX_PATH字符的路径。此限制是关系到这个函数是如何解析路径。字符串的长度不超过MAX_PATH。

lpSecurityAttributes:忽略,一般设置为NULL。

返回值:
    非零表示成功,零表示失败。若想获得更多的错误信息,调用GetLastError函数。
备注:
    这个函数不是递归的。它可以在一个路径中创建唯一的最终目录。也就是说,如果父目录或中间目录不存在,该函数将失败并显示错误消息ERROR_PATH_NOT_FOUND。

8.SetCurrentDirectory

函数功能:设置当前路径,然后就可以以相对路径访问一些你程序相关的文件,不需要绝对路径

 TCHAR chCurDir[MAX_PATH] = {0};
    GetCurrentDirectory(MAX_PATH, chCurDir);
    SetCurrentDirectory(_T("E:\\test\\"));
    m_hDLL = LoadLibrary(_T("MyTest.dll"));
    SetCurrntDirectory(chCurDir);

9.CopyFileA

函数功能:复制文件

BOOL CopyFile(

LPCTSTR lpExistingFileName, // pointer to name of an existing file
LPCTSTR lpNewFileName, // pointer to filename to copy to
BOOL bFailIfExists // flag for operation if file exists
);
其中各参数的意义:
LPCTSTR lpExistingFileName, // 你要拷贝的源文件名
LPCTSTR lpNewFileName, // 你要拷贝的目标文件名
BOOL bFailIfExists // 如果目标已经存在,不拷贝(True)并返回False,覆盖目标(false)

如:
//拷贝文件c:\log.txt到d:\log.txt,如果D:\log.txt已经存在,就覆盖
CopyFile("c:\\log.txt","d:\\log.txt",false);  

10.GetFullPathName

函数功能:获取指定文件的完整路径名

DWORD WINAPI GetFullPathName(
  _In_   LPCTSTR lpFileName,
  _In_   DWORD nBufferLength,
  _Out_  LPTSTR lpBuffer,
  _Out_  LPTSTR *lpFilePart
);

参数说明:
lpFileName [in]
文件名。该参数既可以是一个短文件名,也可以是一个长文件名,还可以是共享名或卷名。

nBufferLength [in]
接收路径的缓冲区的长度。
lpBuffer [out]

这是一个输出参数,指向路径缓冲区的指针。

lpFilePart [out]
输出参数,指向路径缓冲区中文件名部分的指针。该参数可以是NULL。如果lpBuffer指向的缓冲区内存放的是一个目录而非文件,lpFilePart为0。

11.OpenSCManager

函数功能:函数建立了一个到服务控制管理器的连接,并打开指定的数据库。

SC_HANDLE WINAPI OpenSCManager(
_In_opt_ LPCTSTR lpMachineName,
_In_opt_ LPCTSTR lpDatabaseName,
_In_ DWORD dwDesiredAccess
);
参数:
1. lpMachineName:目标计算机名,NULL表示本地计算机
2. lpDatabaseName:服务管理程序系统组件数据库,可以设为SERVICES_ACTIVE_DATABASE,如果为NULL,表示默认打开SERVICES_ACTIVE_DATABASE数据库
3. dwDesiredAccess:对SCM的权限

12.OpenServiceA

函数功能:打开一个已经存在的服务

SC_HANDLE WINAPI OpenService(
_In_ SC_HANDLE hSCManager,
_In_ LPCTSTR lpServiceName,
_In_ DWORD dwDesiredAccess
);
参数:
hSCManager:SCM数据库句柄;OpenSCManager
lpServiceName:要打开服务的名字,这和CreateService形参lpServiceName一样,不是服务显示名称。
dwDesiredAccess:服务权限

13.StartService

函数功能:启动一个服务

BOOL StartServiceA(
  SC_HANDLE hService,
  DWORD     dwNumServiceArgs,
  LPCSTR    *lpServiceArgVectors
);
参数
hService
服务的句柄。此句柄由OpenService或 CreateService函数返回 ,并且必须具有SERVICE_START访问权限。有关更多信息,请参阅 服务安全性和访问权限。

dwNumServiceArgs
lpServiceArgVectors数组中的字符串数。如果lpServiceArgVectors为NULL,则此参数可以为零。

lpServiceArgVectors
以null结尾的字符串作为参数传递给服务的ServiceMain函数。如果没有参数,则此参数可以为NULL。

14.CloseServiceHandle

函数功能:关闭服务控件管理器或服务对象的句柄。

BOOL CloseServiceHandle(
  SC_HANDLE hSCObject
);

参数
hSCObject
服务控件管理器对象或要关闭的服务对象的句柄。OpenSCManager函数返回服务控制管理器对象的 句柄,OpenService或 CreateService函数返回服务对象的句柄 。

15.RegCreateKey

函数功能:注册表中创建一个新的项

(HKEY_LOCAL_MACHINE,"Software\\mykey",&key); 

RegCreateKeyEx  插入主键
RegSetValueEx  插入键值
RegCloseKey 关闭

16.RegQueryValueExA

函数功能:读取字符串数据出来

WINADVAPI
LONG
APIENTRY
RegQueryValueExA (
    __in HKEY hKey,
    __in_opt LPCSTR lpValueName,
    __reserved LPDWORD lpReserved,
    __out_opt LPDWORD lpType,
    __out_bcount_opt(*lpcbData) LPBYTE lpData,
    __inout_opt LPDWORD lpcbData
    );
WINADVAPI
LONG
APIENTRY
RegQueryValueExW (
    __in HKEY hKey,
    __in_opt LPCWSTR lpValueName,
    __reserved LPDWORD lpReserved,
    __out_opt LPDWORD lpType,
    __out_bcount_opt(*lpcbData) LPBYTE lpData,
    __inout_opt LPDWORD lpcbData
    );
#ifdef UNICODE
#define RegQueryValueEx RegQueryValueExW
#else
#define RegQueryValueEx RegQueryValueExA
#endif // !UNICODE
hKey是主键
lpValueName是键值名称
lpType是类型
lpData是读出来数据保存地方
lpcbData是读取数据多少

17.FindResourceA

函数功能:该函数确定指定模块中指定类型和名称的资源所在位置

函数原型:HRSRC FindResource(HMODULE hModule,LPCTSTR lpName,LPCTSTR lpType)
参数:
hModule:处理包含资源的可执行文件的模块。NULL值则指定模块句柄指向操作系统通常情况下创建最近过程的相关位图文件。

lpName:指定资源名称。若想了解更多的信息,请参见注意部分。

lpType:指定资源类型。若想了解更多的信息,请参见注意部分。作为标准资源类型。这个参数的含义同EnumResLangProc/lpType。

返回值:如果函数运行成功,那么返回值为指向被指定资源信息块的句柄。为了获得这些资源,将这个句柄传递给LoadResource函数。如果函数运行失败,则返回值为NULL。
hMoule=FindResourceA( NULL, 2058, "XIA")

SizeofResource( NULL, hMoule )        //Returns the size, in bytes, of the specified resource.

handle=LoadResource( NULL, hMoule)    //Returns a handle to be used to obtain a pointer  to the first byte of the resource in memory.

LockResource( handle ) // 锁定资源并得到资源在内存中的第一个字节的指针

18.SetFilePointer

函数功能:在一个文件中设置新的读取位置

DWORD SetFilePointer(
HANDLE hFile, // 文件句柄
LONG lDistanceToMove, // 偏移量(低位)
PLONG lpDistanceToMoveHigh, // 偏移量(高位)
DWORD dwMoveMethod // 基准位置FILE_BEGIN:文件开始位置 FILE_CURRENT:文件当前位置 FILE_END:文件结束位置
说明:移动一个打开文件的指针

19.CreateFile

函数功能:这个函数的功能是创建或者打开一个文件或者I/O设备,通常使用的I/O形式有文件、文件流、目录、物理磁盘、卷、终端流等。如执行成功,则返回文件句柄。 INVALID_HANDLE_VALUE 表示出错,会设置 GetLastError 。

HANDLE WINAPI CreateFile(
  _In_      LPCTSTR lpFileName,              
  _In_      DWORD dwDesiredAccess,
  _In_      DWORD dwShareMode,
  _In_opt_  LPSECURITY_ATTRIBUTES lpSecurityAttributes,
  _In_      DWORD dwCreationDisposition,
  _In_      DWORD dwFlagsAndAttributes,
  _In_opt_  HANDLE hTemplateFile
);

20.SetFileTime

函数功能:设置指定文件或目录的创建,上次访问或上次修改的日期和时间。

BOOL SetFileTime(
HANDLE hFile,
const FILETIME *lpCreationTime,
const FILETIME *lpLastAccessTime,
const FILETIME *lpLastWriteTime
);

21.CreateProcess

函数功能:CreateProcess是Windows下用于创建进程的API函数,用来创建一个新的进程和它的主线程,这个新进程运行指定的可执行文件。

函数原型:

BOOL CreateProcess

(
    LPCTSTR lpApplicationName,        
    LPTSTR lpCommandLine,        
    LPSECURITY_ATTRIBUTES lpProcessAttributes。
    LPSECURITY_ATTRIBUTES lpThreadAttributes,        
    BOOL bInheritHandles,        
    DWORD dwCreationFlags,
    LPVOID lpEnvironment,        
    LPCTSTR lpCurrentDirectory,        
    LPSTARTUPINFO lpStartupInfo,        
    LPPROCESS_INFORMATION lpProcessInformation 
);

22.WaitForSingleObject

函数功能:等待一个内核对象变为已通知状态

DWORD WaitForSingleObject(
HANDLE hObject, //指明一个内核对象的句柄
DWORD dwMilliseconds); //等待时间
该函数需要传递一个内核对象句柄,该句柄标识一个内核对象,如果该内核对象处于未通知状态,则该函数导致线程进入阻塞状态;如果该内核对象处于已通知状态,则该函数立即返回WAIT_OBJECT_0。第二个参数指明了需要等待的时间(毫秒),可以传递INFINITE指明要无限期等待下去,如果第二个参数为0,那么函数就测试同步对象的状态并立即返回。如果等待超时,该函数返回WAIT_TIMEOUT。如果该函数失败,返回WAIT_FAILED。

23.TerminateProcess

函数功能:终止|杀死其它进程

 函数原型:

 BOOL TerminateProcess(HANDLE hProcess,UINT uExitCode)

 参数说明:

 1、hProcess:要终止(杀死)进程的句柄,需要有PROCESS_TERMINATE权限。

 2、uExitCode:设置进程的退出值。可通过GetExitCodeProcess函数得到一个进程的退出值。

 返回值:

 如果失败将返回FALSE(0),而成功将返回一个非零值。

 注:不要用if(ret==TRUE)去检测函数是否调用成功,因为函数调用成功会返回一个非零值,但不一定是TRUE(1)

24.GetExitCodeProcess

函数功能:获取指定进程的退出码

hProcess    Long,想获取退出代码的一个进程的句柄 
lpExitCode  Long,用于装载进程退出代码的一个长整数变量。如进程尚未中止,则设为常数STILL_ACTIVE 

25.CloseHandle

函数功能:函数用于关闭一个内核对象

BOOL CloseHandle(HANDLE hObject);
参数
hObject :代表一个已打开对象handle。
返回值
TRUE:执行成功;
FALSE:执行失败,可以调用GetLastError()获知失败原因。

26.GlobalAlloc

函数功能:调用GlobalAlloc函数分配一块内存,该函数会返回分配的内存句柄。简称全局堆分配

HGLOBAL GlobalAlloc(

UINT uFlags, // 分配属性(方式)

DWORD dwBytes // 分配的字节数

);

27.GetModuleHandle

函数功能:功能是获取一个应用程序或动态链接库的模块句柄。只有在当前进程的场景中,这个句柄才会有效。

HMODULE WINAPI GetModuleHandle(

_In_opt_LPCTSTR lpModuleName

);

28.GetProcessHeap

函数功能:它返回调用进程的默认堆句柄。

参数
函数无参数

返回值
如果函数成功,返回调用进程的默认内存堆句柄。
如果函数失败,返回 Null。若想,可以调用GetLastError获得更多错误信息。

之后是病毒的加密模块t.wnry

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,711评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,932评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,770评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,799评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,697评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,069评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,535评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,200评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,353评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,290评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,331评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,020评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,610评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,694评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,927评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,330评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,904评论 2 341

推荐阅读更多精彩内容