文件MD5: 6735d02b856b0e7fffefdfc940843d74
IDA载入样本 发现所有函数都是白的 没有发现什么可疑信息
运行 用行为检测工具 发现样本创建了一个子进程 所有行为都是在子进程中完成
下面来看样本的行为
CreateProcess函数创建子进程

子进程是以挂起形式创建的(在挂起状态下 OD是无法附加进程的)

在当前内存申请一段空间

获取进程上下文

从子进程中读内存到当刚才申请的空间

利用ZwUnmapViewOfSection取消子进程的内存映射

第二参数为0x400000 是取消0x400000处的内存映射

在子进程0x400000处开辟0x13000字节大小的空间(这个数据在下面会用到)


利用多次WriteProcessMemory向子进程0x400000后的内存写数据,实际是在每次循环将一个section写入

设置进程上下文

跟到context结构体中看 EP为0xBO处的数据 0x408665

恢复进程

到此为止 外壳程序的任务已经完成 由子进程完成恶意代码操作
其实外壳程序完成了一种代码注入操作 只是和常见的注入方式不同
来总结一下外壳程序到底做了什么
1. 通过给CreateProcess传递CREATE_SUSPENDED参数来创建一个处于挂起状态的进程。(这里称新进程为进程B)
2. 通过调用GetThreadContext来获取被挂起进程(进程B)的CONTEXT。
3. 通过调用ZwUnmapViewOfSection来取消进程B的映像映射。
4. 通过VirtualAllocEx在进程B中分配足够的空间。
5. 通过WriteProcessMemory将恶意代码写到进程B刚分配的空间中
6. 通过调用SetThreadContext来设置线程的上下文。
7. 通过调用ResumeThread来执行被挂起的进程。
所以到现在为止,是没有办法直接对子进程进行调试的,因为OD无法附加挂起的进程,所以只能选择dump的方式,在对子进程的Write操作结束之后,进程恢复之前,将子进程dump下来
用LoadPE选择对应的PID

选择部分dump
地址和大小是刚才VirtuallAllocEx的参数

因为此时dump下来的文件是解压的 所以要修改对齐粒度和偏移 并且要把EP修改成刚才SetThreadContext设置的

并将所有区段的文件偏移修改为和内存偏移相同的值 并保存


保存 然后可以直接对该文件进行分析来分析实际的恶意行为
下面是实际的恶意行为分析
使用FindFirstFile和FindNextFile对硬盘进行遍历

根据dwFileAttributes
判断是文件还是文件夹
附上文件属性
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | #define FILE_SHARE_READ                 0x00000001   #define FILE_SHARE_WRITE                0x00000002   #define FILE_SHARE_DELETE               0x00000004   #define FILE_ATTRIBUTE_READONLY             0x00000001   #define FILE_ATTRIBUTE_HIDDEN               0x00000002   #define FILE_ATTRIBUTE_SYSTEM               0x00000004   #define FILE_ATTRIBUTE_DIRECTORY            0x00000010   #define FILE_ATTRIBUTE_ARCHIVE              0x00000020   #define FILE_ATTRIBUTE_DEVICE               0x00000040   #define FILE_ATTRIBUTE_NORMAL               0x00000080   #define FILE_ATTRIBUTE_TEMPORARY            0x00000100   #define FILE_ATTRIBUTE_SPARSE_FILE          0x00000200   #define FILE_ATTRIBUTE_REPARSE_POINT        0x00000400   #define FILE_ATTRIBUTE_COMPRESSED           0x00000800   #define FILE_ATTRIBUTE_OFFLINE              0x00001000   #define FILE_ATTRIBUTE_NOT_CONTENT_INDEXED  0x00002000   #define FILE_ATTRIBUTE_ENCRYPTED            0x00004000   #define FILE_ATTRIBUTE_INTEGRITY_STREAM     0x00008000   #define FILE_ATTRIBUTE_VIRTUAL              0x00010000   #define FILE_ATTRIBUTE_NO_SCRUB_DATA        0x00020000 | 
如果为0x10则为文件夹
这里有个问题 为什么要用&而不用==
我在网上找到了答案
但是当程序运行起来的时候却发现有一些文件夹是会忽略掉的,并没有被当作文件夹处理。而且在Debug的时候通过跟踪dwFileAttributes可以看到它的值会在16与32之间出现,所以使用“==”来判断一个目标是否为文档或目录是出现问题。
为文件夹时继续递归该函数0x407B98继续搜索

如果不是文件夹 而且属性不是删除属性 继续向下判断

在0x407872函数中判断文件类型
在0x406622函数中将所有文件类型解压出来

在这里比较文件类型

所有加密类型大体如下



对于符合加密类型的文件在0x407984函数中进行加密操作
利用CreateFile打开文件

将文件内容映射到内存

执行加密操作的地方 每次加密0x10个字节

具体的加密函数


具体加密过程我就不分析了……..密码学是真渣
取消映射并回写

将加密后的文件用MoveFile改名

在目录下所有文件都被加密之后 调用两次0x4077D8处的函数 其中一个写key 另一个写勒索信息

所有key相关的在0x406622函数中

总结一下,其实写这个样本的分析是因为这种自身代码注入的行为很有意思,并且对于调试也很有针对性,算是见过的比较有趣的一个样本,所以记录下来方便以后遇到相同的问题时来解决。