利用 r2 逆向分析框架分析 Windows Minidumps

birdpwn 系统安全 2019年11月17日发布
Favorite收藏

导语:用Microsoft使用minidump格式存储用户模式的内存转储(dump),它是一种公开记录的文件格式,以前几乎都是在WinDbg进行分析。 本文介绍如何使用radare2 **mdmp**模块进行Minidumps转储文件分析。

用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  ........[email protected]
 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  [email protected]
 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/ 如若转载,请注明原文地址: https://www.4hou.com/system/21556.html
点赞 0
  • 分享至
取消

感谢您的支持,我会继续努力的!

扫码支持

打开微信扫一扫后点击右上角即可分享哟

发表评论