最近OD用腻了,想要换换口味于是有了这篇文章。这篇文章只是记录了分析过程中windbg的实用技巧,不会细致完整的将病毒跟踪分析。感兴趣的朋友可以自己抽时间跟踪学习。
locky勒索病毒MD5: 5DA38A8EF49834A13219BEDA15A1303C
首先查壳,发现病毒样本并没有加壳。
查看资源,发现可疑数据。
查看各个区段熵值,只有代码段有较大波动可能代码段存在加密,具体需要调试行为确定。
加载病毒到内存,确定模块基址。
观察病毒模块详细信息,可以看到病毒具体编译的时间戳未16年。
获取病毒文件DOS头相对PE头的偏移值,该值记录在e_lfanew字段中。
计算验证是否为NT头的起始位置,可以看到PE字符串标识证明0x400080为PE头。
解析NT头结构体信息,因为我是使用的win7 x64位查看的32位进程所以需要使用_IMAGE_NT_HEADERS64解析结构体。
根据结构体树,我们可以清晰的看到拓展头中的入口点RVA为0x2af8。
windbg虽然不能像OD一样在程序载入的时候自动在入口点设置断点,但是却为我们提供了一个名为$exentry的伪寄存器,它可以帮我们计算入口点的VA地址。直接利用该伪寄存器在入口点下断并按下F5让寄存器跑起来就可以断在OEP。
windbg还给我们提供了拓展指令!dh用来详细解析PE文件。例如我们可以使用!dh -s详细查看各个区段内容。
进入入口点代码,发现病毒通过计算差值获取全局变量的数据地址。固定差值为0x9864e321。
正式分析前我们需要考虑到有的病毒样本中存在睡眠方式反沙箱,我们可以设置条件断点,当触发触发睡眠事件时打印出要睡眠的时间,并且将睡眠时间修改为0。
设置完断点可以让调试器运行起来可以观察到反沙箱睡眠时间。
病毒首先调用GetModuleHandleW函数获取kernel32.dll模块在内存中的基址。
查看返回值与kernel32.dlll中模块基址相同。
病毒会在栈中解密以下数据,然后调用call指令跳转到该地址。
解密后的地址会申请一块虚拟地址,我这里分配的随机地址为0x3d0000。
以每次4字节加减异或解密,并将解密后的数据用pop指令弹入esi寄存器指向的虚拟内存中。
设置条件断点,让程序解密完成后自动断住。
可以看到中断后的解密数据如下。
解密完成后弹出shellcode的首地址到esi,并将LoadLibraryA的API地址作为参数传入压栈,最后调用call指令跳入shellcode区域执行。干扰分析人员的静态分析。
使用以下命令将shellcode从内存中转存出来。
查看dump出来的shellcode。
比较数据一致。
可以将shellcode拖入ida观察病毒接下来的恶意逻辑。
根据PE数据结构获取各个字段数据在内存中的地址。
接下来申请一段虚拟内存,内存大小为0x688字节。
将第一段shellcode拷贝到申请的内存空间。
调用ret指令弹出新的地址到eip执行第二段恶意逻辑。
第一段shellcode整体逻辑如下,用来获取指定API并执行第二段shellcode逻辑。
计算基址。
申请新的内存空间用于存放被加密的数据。
从全局数据段拷贝第三段恶意代码到申请的虚拟内存。
拷贝到虚拟内存后循环加减异或解密出PE。
修改自身保护属性,目的是为了将当前进程替换成勒索病毒locky。
将解密后的PE拷贝到当前进程的模块地址进行PE替换。
循环替换PE各个区段。
最后将PE装载修复替换后通过jmp esi跳转到替换的PE入口点,我们可以通过$exentry伪寄存器查看入口点VA与ESI一致。
查看dump下来的勒索程序,发现为VC++6.0编写。
ida查看locky勒索软件的WinMain线程,可以看到有两个函数。
考虑到病毒本体一般会有检测沙箱和调试器等动作。首先设置一个条件断点检测是否有反调试操作。
设置好条件断点让程序run起来,发现打印以下指令并且调试器中断则证明存在调试器检测代码。
利用ida定位到打印指令的上一条指令地址,即调试器检测。
我们查看以下peb结构体的BeingDebugged字段。
我们可以在windbg入口点设置断点,让程序一运行到入口点就立即清理BeingDebugged字段达到反反调试的目的。
如果我们希望在调试的时候不漏掉病毒关键的细节,可以利用windbg的条件断点对病毒行为进行过滤,实现API行为监控的效果。
CreateFileA函数原型。
第5个参数属性。
设置好条件断点后让程序运行起来,可以发现控制台导出了加密的文件信息。
进入勒索病毒入口点后发现有大量的垃圾指令,干扰分析人员分析。
为了方便演示windbg的条件断点功能,我这里就偷懒不细致分析了。直接把下述断点设置好让windbg自动运行打印出其他关键信息。
文件写入操作,可以看到写入的关键属性。
将文件dump出来发现为html勒索信内容,该勒索信详细的介绍了受害者是被何种算法加密以及解密链接的下载以及加密公钥等内容。
勒索信内容如下。
动态方式获取API。
根据windbg动态跟踪API断点,可以发现病毒调用了微软官方加密库使用RSA+AES加密算法对文件进行加密。
移动方式覆盖原文件,完成加密。
创建注册表子键并设置值为locky。
使用命令行显示勒索信息。
还有一些其他行为感兴趣的可以自己跟踪研究,这里不做赘述。
参考资料:windbg分析locky勒索病毒
bp kernelbase!sleepex
".printf \"Application tried to sleep: %u seconds...\",poi(esp+4)/1000;.echo;ed esp+4 0x0;g"
bp kernelbase!sleepex
".printf \"Application tried to sleep: %u seconds...\",poi(esp+4)/1000;.echo;ed esp+4 0x0;g"
bp
004052b5
".if(edx==0x688){};.else{gc;}"
bp
004052b5
".if(edx==0x688){};.else{gc;}"
.writemem c:\shellcode.dump
0x3d0000
L0x668
/
/
将数据写到c:\shellcode.dump文件中,从
0x3d0000
字节开始写,一共写
0x688
个字节
.writemem c:\shellcode.dump
0x3d0000
L0x668
/
/
将数据写到c:\shellcode.dump文件中,从
0x3d0000
字节开始写,一共写
0x688
个字节
bp $exentry
"ba r 1 $peb+0x02-0x1000 \"ub\";g"
/
/
当程序执行到入口点对peb所在地址的BeingDebugged字段设置硬件访问断点并打印出读取该字段的汇编代码
(
64
位系统用windbg获取wow64进程的peb会额外产生
0x1000
的地址偏差,所以要减去
0x1000
)
bp $exentry
"ba r 1 $peb+0x02-0x1000 \"ub\";g"
/
/
当程序执行到入口点对peb所在地址的BeingDebugged字段设置硬件访问断点并打印出读取该字段的汇编代码
(
64
位系统用windbg获取wow64进程的peb会额外产生
0x1000
的地址偏差,所以要减去
0x1000
)
bp $exentry
"eb $peb-0x1000+0x02 0x0"
bp $exentry
"eb $peb-0x1000+0x02 0x0"
/
/
创建文件行为过滤,一般文件创建依赖于CreateFileA
/
W API函数。但是该函数不止有创建文件行为,所以需要通过API的第
5
个参数进行过滤减少打开文件误报
/
/
0n3
代表打开文件行为属性,过滤成功后打印第二个参数指向的字符串,不支持
%
s格式化字符串只能通过
%
mu进行传递
bp kernelbase!CreateFileW
".if(poi(esp+0x14) != 0n3){.printf \"Creating File: %mu\",poi(esp+0x4);.echo};g"
bp kernelbase!CreateFileA
".if(poi(esp+0x14) != 0n3){.printf \"Creating File: %mu\",poi(esp+0x4);.echo};g"
/
/
创建文件行为过滤,一般文件创建依赖于CreateFileA
/
W API函数。但是该函数不止有创建文件行为,所以需要通过API的第
5
个参数进行过滤减少打开文件误报
/
/
0n3
代表打开文件行为属性,过滤成功后打印第二个参数指向的字符串,不支持
%
s格式化字符串只能通过
%
mu进行传递
bp kernelbase!CreateFileW
".if(poi(esp+0x14) != 0n3){.printf \"Creating File: %mu\",poi(esp+0x4);.echo};g"
bp kernelbase!CreateFileA
".if(poi(esp+0x14) != 0n3){.printf \"Creating File: %mu\",poi(esp+0x4);.echo};g"
bp kernelbase!sleepex
".printf \"Application tried to sleep: %u seconds...\",poi(esp+4)/1000;.echo;ed esp+4 0x0;g"
/
/
创建文件行为过滤,一般文件创建依赖于CreateFileA
/
W API函数。但是该函数不止有创建文件行为,所以需要通过API的第
5
个参数进行过滤减少打开文件误报
/
/
0n3
代表打开文件行为属性,过滤成功后打印第二个参数指向的字符串,不支持
%
s格式化字符串只能通过
%
mu进行传递
bp kernelbase!CreateFileW
".if(poi(esp+0x14) != 0n3){.printf \"Creating File: %mu\",poi(esp+0x4);.echo};g"
bp kernelbase!CreateFileA
".if(poi(esp+0x14) != 0n3){.printf \"Creating File: %ma\",poi(esp+0x4);.echo};g"
/
/
过滤写文件行为
bp kernelbase!WriteFile
".printf \"Dumping file contents from 0x%p as ASCII: %ma\",poi(esp+0x8),poi(esp+0x8);.echo;g"
/
/
过滤删除文件行为
bp kernel32!DeleteFileW
".printf \"Deleting File: %mu\",poi(esp+4);.echo;g"
bp kernel32!DeleteFileA
".printf \"Deleting File: %ma\",poi(esp+4);.echo;g"
/
/
过滤移动文件行为
bp kernel32!MoveFileExW
".printf \"File moved.\";.echo;.printf \"From: %mu\",poi(esp+0x4);.echo;.printf \"To: %mu\",poi(esp+0x8);.echo;g"
bp kernel32!MoveFileExA
".printf \"File moved.\";.echo;.printf \"From: %ma\",poi(esp+0x4);.echo;.printf \"To: %ma\",poi(esp+0x8);.echo;g"
/
/
过滤拷贝文件行为
bp kernel32!CopyFileW
".printf \" Copying file: \";.echo;.printf \"\tFrom: %mu\",poi(esp+0x4);.echo;.printf \"\tTo: %mu\",poi(esp+0x8);.echo;g"
bp kernel32!CopyFileA
".printf \" Copying file: \";.echo;.printf \"\tFrom: %ma\",poi(esp+0x4);.echo;.printf \"\tTo: %ma\",poi(esp+0x8);.echo;g"
/
/
过滤创建注册表子键行为
bp kernel32!RegCreateKeyExA
".printf \"Creating RegKey: %ma\",poi(esp+0x8);.echo;g"
bp kernel32!RegCreateKeyExW
".printf \"Creating RegKey: %mu\",poi(esp+0x8);.echo;g"
/
/
打开注册表键值行为
bp kernel32!RegOpenKeyExA
".printf \"Accessed RegKey: %ma\",poi(esp+0x8);.echo;g"
bp kernel32!RegOpenKeyExW
".printf \"Accessed RegKey: %mu\",poi(esp+0x8);.echo;g"
/
/
过滤键值请求行为
bp kernel32!RegQueryValueExA
".printf \"\tAccessed RegValue: %ma\",poi(esp+0x8);.echo;g"
bp kernel32!RegQueryValueExW
".printf \"\tAccessed RegValue: %mu\",poi(esp+0x8);.echo;g"
/
/
过滤设置注册表键值行为
bp kernel32!RegSetValueExA
".printf \"Setting RegKey %ma to value: %ma\",poi(esp+0x8),poi(esp+0x14);.echo;g"
bp kernel32!RegSetValueExW
".printf \"Setting RegKey %mu to value: %mu\",poi(esp+0x8),poi(esp+0x14);.echo;g"
/
/
过滤创建服务行为
bp advapi32!CreateServiceA
".printf \"Creating Service: \";.echo;.printf \"\tService Name: %ma\",poi(esp+0x4);.echo;.printf \"\tDisplay Name: %ma\",poi(esp+0x8);.echo;g"
bp advapi32!CreateServiceW
".printf \"Creating Service: \";.echo;.printf \"\tService Name: %mu\",poi(esp+0x4);.echo;.printf \"\tDisplay Name: %mu\",poi(esp+0x8);.echo;g"
/
/
过滤创建进程行为
bp kernel32!CreateProcessW
".printf \"Creating Process: %mu\",poi(esp+0x8);.echo;g"
bp kernel32!CreateProcessA
".printf \"Creating Process: %ma\",poi(esp+0x8);.echo;g"
/
/
过滤shell行为以及命令行
bp shell32!shellexecuteW
".printf \"Running Command:\";.echo;.printf \"\tOperation: %mu\",poi(esp+0x8);.echo;.printf \"\tTarget: %mu\",poi(esp+0xC);.echo;.printf \"\tParams: %mu\",poi(esp+0x10);.echo;g"
bp shell32!shellexecuteA
".printf \"Running Command:\";.echo;.printf \"\tOperation: %ma\",poi(esp+0x8);.echo;.printf \"\tTarget: %ma\",poi(esp+0xC);.echo;.printf \"\tParams: %ma\",poi(esp+0x10);.echo;g"
bp shell32!shellExecuteExW
".printf \"Running Command:\";.echo;.printf \"\tOperation: %mu\",poi(poi(esp+0x4)+0xC);.echo;.printf \"\tTarget: %mu\",poi(poi(esp+0x4)+0x10);.echo;.printf \"\tParams: %mu\",poi(poi(esp+0x4)+0x14);.echo;g"
bp shell32!shellExecuteExA
".printf \"Running Command:\";.echo;.printf \"\tOperation: %ma\",poi(poi(esp+0x4)+0xC);.echo;.printf \"\tTarget: %ma\",poi(poi(esp+0x4)+0x10);.echo;.printf \"\tParams: %ma\",poi(poi(esp+0x4)+0x14);.echo;g"
/
/
过滤模块加载行为
bp kernel32!LoadLibraryA
".printf \"Loading LibraryA: %ma\",poi(esp+0x4);.echo;g"
bp kernel32!LoadLibraryW
".printf \"Loading LibraryW: %mu\",poi(esp+0x4);.echo;g"
/
/
过滤动态获取API函数行为
bp kernel32!GetProcAddress
".printf \"\t Looking up function: %ma\",poi(esp+0x8);.echo;g"
bp kernelbase!sleepex
".printf \"Application tried to sleep: %u seconds...\",poi(esp+4)/1000;.echo;ed esp+4 0x0;g"
/
/
创建文件行为过滤,一般文件创建依赖于CreateFileA
/
W API函数。但是该函数不止有创建文件行为,所以需要通过API的第
5
个参数进行过滤减少打开文件误报
/
/
0n3
代表打开文件行为属性,过滤成功后打印第二个参数指向的字符串,不支持
%
s格式化字符串只能通过
%
mu进行传递
bp kernelbase!CreateFileW
".if(poi(esp+0x14) != 0n3){.printf \"Creating File: %mu\",poi(esp+0x4);.echo};g"
bp kernelbase!CreateFileA
".if(poi(esp+0x14) != 0n3){.printf \"Creating File: %ma\",poi(esp+0x4);.echo};g"
/
/
过滤写文件行为
bp kernelbase!WriteFile
".printf \"Dumping file contents from 0x%p as ASCII: %ma\",poi(esp+0x8),poi(esp+0x8);.echo;g"
/
/
过滤删除文件行为
bp kernel32!DeleteFileW
".printf \"Deleting File: %mu\",poi(esp+4);.echo;g"
bp kernel32!DeleteFileA
".printf \"Deleting File: %ma\",poi(esp+4);.echo;g"
/
/
过滤移动文件行为
bp kernel32!MoveFileExW
".printf \"File moved.\";.echo;.printf \"From: %mu\",poi(esp+0x4);.echo;.printf \"To: %mu\",poi(esp+0x8);.echo;g"
bp kernel32!MoveFileExA
".printf \"File moved.\";.echo;.printf \"From: %ma\",poi(esp+0x4);.echo;.printf \"To: %ma\",poi(esp+0x8);.echo;g"
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!