birdpwn 嘶吼专业版
用Microsoft使用minidump格式存储用户模式的内存转储(dump),它是一种公开记录的文件格式,以前几乎都是在WinDbg进行分析。
本文介绍如何使用radare2 mdmp模块进行Minidumps转储文件分析。
0x01 文件格式
该格式其实很简单,因为基本上都是数据流,这些数据流根据其内容而有所不同。
$ r2 mini.dmp [0x00690efa]> i blksz 0x0 block 0x100 fd 6 file mini.dmp format mdmp iorw false mode -r-- size 0xc53a7be humansz 197.2M type MDMP (MiniDump crash report data) arch x86 binsz 206809022 bintype mdmp bits 32 canary false crypto false endian little flags 0x00061826 havecode true hdr.csum 0x00000000 linenum false lsyms false machine i386 maxopsz 16 minopsz 1 nx false os Windows NT Workstation 6.1.7601 pcalign 0 pic false relocs false rpath NONE static false streams 13 stripped false va true
我们可以看到上面的文件mini.dmp包含13个stream,并且文件类型为MiniDuMP(MDMP)。有关MDMP格式基础的其他知识,请参阅Brendan Dolan-Gavitt撰写的文章:
http://moyix.blogspot.co.uk/2008/05/parsing-windows-minidumps.html
0x02 分析
MDMP格式会生成一个信息量很大的文件,因此对文件的简单解析就足够了。使用radare2的pf命令很容易做到这一点,请参阅使用radare2解析文件格式的这篇文章:
http://radare.today/posts/parsing-a-fileformat-with-radare2/
以下是当前支持的文件结构的列表:
[0x00000000]> pf. pf.mdmp_memory_descriptor pf.mdmp_misc_info pf.mdmp_thread_info pf.mdmp_thread_list pf.mdmp_module pf.mdmp_unloaded_module_list pf.mdmp_system_info pf.mdmp_module_list pf.mdmp_thread_info_list pf.mdmp_location_descriptor pf.mdmp_string pf.mdmp_memory64_list pf.mdmp_vs_fixedfileinfo pf.mdmp_location_descriptor64 pf.mdmp_header pf.mdmp_unloaded_module pf.mdmp_memory_descriptor64 pf.mdmp_directory pf.mdmp_memory_info pf.mdmp_thread pf.mdmp_handle_data_stream pf.mdmp_memory_info_list
通过radare2的预定义二进制结构模式打开文件,可以查看MDMP文件的结构。mini.dmp文件具有如下所示的格式:
[0x00000000]> pf.mdmp_header Signature : 0x00000000 = MDMP Version : 0x00000004 = 1812113299 NumberOfStreams : 0x00000008 = 13 StreamDirectoryRVA : 0x0000000c = 32 CheckSum : 0x00000010 = 0 TimeDateStamp : 0x00000014 = Tue Jan 31 18:44:24 2017 Flags : 0x00000018 = Flags (bitfield) = 0x00061826 : MiniDumpWithFullMemory | MiniDumpWithHandleData | MiniDumpWithUnloadedModule | MiniDumpWithFullMemoryInfo | MiniDumpWithThreadInfo | MiniDumpIgnoreInaccessibleMemory | MiniDumpWithTokenInformation
上面的标识记录了将被写入MDMP文件的信息,因此,需要某些标识来执行不同类型的分析,可以注意到文件项NumberOfStreams的file匹配项。使用NumberOfStreams和StreamDirectoryRVA可以打印出目录结构列表。
以下是线程信息和模块列表的结构,描述了它们的位置和大小:
[0x00000000]> pf.mdmp_stream_directory [13]? (mdmp_directory)directories [0x00000000]> pf.mdmp_stream_directory @ 32 directories : [ [snip] struct<mdmp_directory> StreamType : 0x0000002c = StreamType (enum mdmp_stream_type) = 0x11 ; ThreadInfoListStream Location : struct<mdmp_location_descriptor> DataSize : 0x00000030 = 1676 RVA : 0x00000034 = 1728 struct<mdmp_directory> StreamType : 0x00000038 = StreamType (enum mdmp_stream_type) = 0x4 ; ModuleListStream Location : struct<mdmp_location_descriptor> DataSize : 0x0000003c = 21820 RVA : 0x00000040 = 3404 [snip]
模块列表包含有关已加载的二进制文件和库的信息。对于此过程转储,有202个模块,第一个模块很可能是可执行文件,而其余的是库文件。
NumberOfModule : 0x00000d4c = 202 Modules : [ struct<mdmp_module> BaseOfImage : 0x00000d50 = (qword)0x0000000000660000 SizeOfImage : 0x00000d58 = 2625536 CheckSum : 0x00000d5c = 2648680 TimeDateStamp : 0x00000d60 = Sat Nov 20 09:37:55 2010 ModuleNameRVA : 0x00000d64 = 28646 VersionInfo : struct<mdmp_vs_fixedfileinfo> dwSignature : 0x00000d68 = 4277077181 dwStrucVersion : 0x00000d6c = 65536 dwFileVersionMs : 0x00000d70 = 393217 dwFileVersionLs : 0x00000d74 = 498156650 dwProductVersionMs : 0x00000d78 = 393217 dwProductVersionLs : 0x00000d7c = 498156650 dwFileFlagsMask : 0x00000d80 = 63 dwFileFlags : 0x00000d84 = 0 dwFileOs : 0x00000d88 = 262148 dwFileType : 0x00000d8c = 1 dwFileSubtype : 0x00000d90 = 0 dwFileDateMs : 0x00000d94 = 0 dwFileDateLs : 0x00000d98 = 0 CvRecord : struct<mdmp_location_descriptor> DataSize : 0x00000d9c = 37 RVA : 0x00000da0 = 64538 MiscRecord : struct<mdmp_location_descriptor> DataSize : 0x00000da4 = 0 RVA : 0x00000da8 = 0 Reserved0 : 0x00000dac = (qword)0x0000000000000000 Reserved1 : 0x00000db4 = (qword)0x0000000000000000 [snip]
这些是结构遍历的基础,还可以手动解析MDMP格式
0x03 EAT Hooking
MDMP插件将数据流解析为可在radare2中呈现的信息。虽然这样做是有好处的,但解析数据段可能会很慢,因此可以使用-z参数减少加载时间,从而防止了字符串的加载。
使用radare2的i命令时,对MDMP格式的深入解析可能会显示大量信息。
演示此插件功能的最佳方法是通过示例。进程转储文件mini.dmp是explorer.exe某些未知模块hooking而得到的,区段列表包含所有映射的可执行文件和DLL,并带有一个简单的grep,可以确认转储确实适用于explorer.exe:
[0x00690efa]> iSq~exe 0x660000 0x8e1000 m---- C:_Windows_explorer.exe
此外,还显示了每个模块的布局,如下所示explorer.exe:
[0x00690efa]> iSq [snip] 0x660000 0x8e1000 m---- C:_Windows_explorer.exe 0x661000 0x710600 m-r-x .text 0x711000 0x713c00 m-rw- .data 0x714000 0x8d7000 m-r-- .rsrc 0x8d7000 0x8e0400 m-r-- .reloc [snip]
有问题的hook函数之一是InternetReadFileEAT hooking类型(导出地址表)。因此,该函数应该在已使用的库的导出表中可见。通过grep快速查询导出DLL显示:
[0x00690efa]> iE~InternetReadFile vaddr=0x757bfa90 paddr=0x0ac8b24e ord=176 fwd=NONE sz=0 bind=GLOBAL type=FUNC name=WININET.dll_InternetReadFile
可以看到该函数立即跳转到低页内存区域中的地址:
[0x00690efa]> pd 10 @ 0x757bfa90 └─< ;-- WININET.dll_InternetReadFile: └─< 0x757bfa90 e99a099b8e jmp 0x417042f 0x757bfa95 83ec38 sub esp, 0x38 ; '8' 0x757bfa98 56 push esi 0x757bfa99 6a38 push 0x38 ; '8' ; '8' 0x757bfa9b 8d45c8 lea eax, [ebp - 0x38] 0x757bfa9e 6a00 push 0 0x757bfaa0 50 push eax 0x757bfaa1 e88c2c0200 call 0x757e2732 0x757bfaa6 83c40c add esp, 0xc 0x757bfaa9 8d45c8 lea eax, [ebp - 0x38]
通过查找jmp可以检查内存部分的合法性:
[0x0416fbcf]> s 0x417042f [0x0417042f]> S. 0x069787be 0x069797be Memory_Section_203
这个区块不位于有效的DLL中,但是可以看看它是否包含有效的程序代码块
[0x0417042f]> pd 20 0x0417042f 51 push ecx 0x04170430 9c pushfd 0x04170431 b9e4131704 mov ecx, 0x41713e4 0x04170436 f60101 test byte [ecx], 1 ; [0x1:1]=68 ┌─< 0x04170439 7531 jne 0x417046c │ 0x0417043b 55 push ebp │ 0x0417043c 57 push edi │ 0x0417043d 56 push esi │ 0x0417043e 52 push edx │ 0x0417043f 53 push ebx │ 0x04170440 50 push eax │ 0x04170441 64ff35000000. push dword fs:[0] │ 0x04170448 64ff35340000. push dword fs:[0x34] │ 0x0417044f 51 push ecx │ 0x04170450 e8abfbf2ff call 0x40a0000 │ 0x04170455 83c404 add esp, 4 │ 0x04170458 648f05340000. pop dword fs:[0x34] │ 0x0417045f 648f05000000. pop dword fs:[0] │ 0x04170466 58 pop eax │ 0x04170467 5b pop ebx
确实包含有效的程序代码块。此外,还有一个立即值ecx作为函数call的第一个参数压入了堆栈地址0x40a0000,似乎有一些变量对存储在ecx中的值有关系。
[0x0417042f]> px 64 @ 0x041713e4 - offset - 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF 0x041713e4 0004 0000 0000 0a04 1810 0a04 0000 0000 ................ 0x041713f4 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x04171404 5027 a466 0000 0000 0000 0000 90fa 7b75 P'.f..........{u 0x04171414 2f04 1704 0000 0000 8bff 558b ec83 ec38 /.........U....8
在函数中,0x40a0000会访问这些潜在地址,偏移量为0x20。
[0x0417042f]> pd 80 @ 0x40a0000 ;-- section_end.Memory_Section_191: ;-- section.Memory_Section_192: 0x040a0000 55 push ebp ; section 192 va=0x040a0000 pa=0x027c37be sz=4096 vsz=4096 rwx=m-r-x Memory_Section_192 0x040a0001 8bec mov ebp, esp 0x040a0003 83ec48 sub esp, 0x48 ; 'H' 0x040a0006 8b4508 mov eax, dword [ebp + 8] ; [0x8:4]=13 ; "\r" [snip] ││ 0x040a00a3 8b4508 mov eax, dword [ebp + 8] ; [0x8:4]=13 ; "\r" ││ 0x040a00a6 8b4820 mov ecx, dword [eax + 0x20] ; [0x20:4]=3 ││ 0x040a00a9 ffd1 call ecx
下面是该偏移量保留的地址。
[0x0417042f]> pxw 4 @ 0x041713e4+0x20 0x04171404 0x66a42750 P'.f
通过删除2个最低有效字节,可以在部分列表中进行模糊搜索:
[0x00690efa]> iSq~0x66a4 0x66a40000 0x66a41000 m-r-- Memory_Section_506 0x66a41000 0x66a45000 m-r-x Memory_Section_507 0x66a45000 0x66a46000 m-r-- Memory_Section_508 0x66a46000 0x66a47000 m-rw- Memory_Section_509 0x66a47000 0x66a49000 m-r-- Memory_Section_510 0x66a40000 0x66a49000 m---- C:_Program_Files_McAfee_Endpoint_Security_Threat_Prevention_IPS_EpMPThe.dll 0x66a41000 0x66a44400 m-r-x .text_141 0x66a45000 0x66a45e00 m-r-- .rdata_12 0x66a46000 0x66a46200 m-rw- .data_140 0x66a47000 0x66a47400 m-r-- .rsrc_147 0x66a48000 0x66a48400 m-r-- .reloc_141
DLL- EpMPThe.dll是McAfee Endpoint Security Suite的一部分,AV也使用这种类似恶意软件的技术;)
0x04 EXE Carving
从内存转储中Carving二进制文件是一种非常有用的技术,尤其是在涉及分析注入恶意软件时。r2使得这种技术的工作非常轻松。为了简单起见,使用MDMP测试套件的样本,可从此处获得:
https://github.com/radare/radare2-regressions/blob/master/bins/mdmp/hello64.dmp
$ r2 hello64.dmp -z [0x7ff7aebd0f04]> iS~exe idx=53 vaddr=0x00400000 paddr=0x00092c8f sz=151552 vsz=151552 perm=m---- name=C:_Users_Developer_Desktop_hello64.exe
使用上面的数据,hello64.exe可以从MDMP文件中Carving,这是通过查找段的起始地址并将其转储来实现的。
[0x00401500]> s 0x00400000 [0x00400000]> px @ 0x00400000 - offset - 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF 0x00400000 4d5a 9000 0300 0000 0400 0000 ffff 0000 MZ.............. 0x00400010 b800 0000 0000 0000 4000 0000 0000 0000 ........@....... 0x00400020 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0x00400030 0000 0000 0000 0000 0000 0000 8000 0000 ................ 0x00400040 0e1f ba0e 00b4 09cd 21b8 014c cd21 5468 ........!..L.!Th 0x00400050 6973 2070 726f 6772 616d 2063 616e 6e6f is program canno 0x00400060 7420 6265 2072 756e 2069 6e20 444f 5320 t be run in DOS 0x00400070 6d6f 6465 2e0d 0d0a 2400 0000 0000 0000 mode....$....... 0x00400080 5045 0000 6486 1200 b79c 2058 00aa 0100 PE..d..... X.... 0x00400090 e605 0000 f000 2700 0b02 0219 001e 0000 ......'......... 0x004000a0 0044 0000 000c 0000 0015 0000 0010 0000 .D.............. 0x004000b0 0000 4000 0000 0000 0010 0000 0002 0000 ..@............. 0x004000c0 0400 0000 0000 0000 0500 0200 0000 0000 ................ 0x004000d0 0050 0200 0006 0000 1946 0200 0300 0000 .P.......F...... 0x004000e0 0000 2000 0000 0000 0010 0000 0000 0000 .. ............. 0x004000f0 0000 1000 0000 0000 0010 0000 0000 0000 ................ [0x00400000]> wtf hello64.exe 151552 dumped 0x429000 bytes Dumped 151552 bytes from 0x00400000 into hello64.exe
上面就是从内存转储中Carving二进制文件的全部内容,但是为了在隔离状态下分析r2中的二进制文件,需要做一些额外的工作,区块需要打补丁以将其PointerToRawData和SizeOfRawData分别更新为VirtualAddress和VirtualSize。
struct IMAGE_SECTION_HEADER { char Name[IMAGE_SIZEOF_SHORT_NAME]; union { long PhysicalAddress; long VirtualSize; } Misc; long VirtualAddress; long SizeOfRawData; long PointerToRawData; long PointerToRelocations; long PointerToLinenumbers; short NumberOfRelocations; short NumberOfLinenumbers; long Characteristics; }
通过利用r2pipe我们可以编写此脚本。以下是patch从bin carver脚本获取的函数,该脚本就是修补PE的节区块所需的步骤:
https://github.com/countercept/radare2-scripts/blob/master/r2_bin_carver.py
def patch(file_path): r2 = r2pipe.open(file_path) info = r2.cmdj('ij') if info['bin']['bintype'] != 'pe': print("[+] Patching not possible, only PE files supported!") exit() r2 = r2pipe.open(file_path, ['-w', '-nn']) print('[+] Patching...') # TODO: The pf structs don't exist at the time of writing! And pfsj does # not exist. For these reasons lets just seek and be lazy # pf [8]zxxxxxxwwx e_elfnew_addr = r2.cmdj('pfj x @ 0x3c')[0]['value'] numberOfSections = r2.cmdj('pfj w @ %i' % (e_elfnew_addr + 0x6))[0]['value'] sizeOfOptionalHeader = r2.cmdj('pfj w @ %i' % (e_elfnew_addr + 0x14))[0]['value'] base_addr = e_elfnew_addr + 24 + sizeOfOptionalHeader print('[+] Found %i sections to patch' % (numberOfSections)) for i in range(0, numberOfSections): addr = base_addr + 40 * i print('[+] Patching Section %i.' % (i)) VirtualSize = r2.cmdj('pfj x @ %i' % (addr + 0x08))[0]['value'] print('\tSetting VirtualSize to 0x%x' % (VirtualSize)) r2.cmd('wv %i @ %i' % (VirtualSize, addr + 0x10)) VirtualAddress = r2.cmdj('pfj x @ %i' % (addr + 0x0c))[0]['value'] print('\tSetting PointerToRawData to 0x%x' % (VirtualAddress)) r2.cmd('wv %i @ %i' % (VirtualAddress, addr + 0x14)) print('[+] Patching done')
执行脚本后就可以patch二进制文件。
$ ./r2_bin_carver.py hello64.dmp 0x00400000 0xe25000 -b MZ -p [+] Checking for magic: MZ - 4d5a [+] Magic found, carving... [+] Carving to hello64.dmp.0x00400000 [+] Patching... [+] Found 18 sections to patch [+] Patching Section 0. Setting VirtualSize to 0x1d38 Setting PointerToRawData to 0x1000 [+] Patching Section 1. Setting VirtualSize to 0x98 Setting PointerToRawData to 0x3000 [snip] [+] Patching Section 16. Setting VirtualSize to 0x2c70 Setting PointerToRawData to 0x21000 [+] Patching Section 17. Setting VirtualSize to 0x4f0 Setting PointerToRawData to 0x24000 [+] Patching done
修补完成后,现在就可以分析二进制文件了。
本文翻译自:https://radareorg.github.io/blog/posts/minidump/