Skip to content
Permalink
master
Switch branches/tags
Go to file
 
 
Cannot retrieve contributors at this time
333 lines (283 sloc) 18.2 KB
Emotet 脱壳及IOC提取(一)

加载过程

<<<<<<< HEAD

示例样本: ./emotet/2019_0918

  • Layer1解密Layer2和Payload代码并转入Layer2代码
  • Layer2加载Payload转由Payload执行。

5dfe9172b26b96762150b1463d1f6dd7297b27a5

Layer1

Layer1会做一些简单的对抗并解密Layer2,最后把控制权交给Layer2。

Tricks

绕过特定用户

  • 可能是针对某特定沙箱的用户名做对抗。当用户名第1个字符为J,第3个字符为H,第5个字符为D时直接退出。
 GetUserNameA(char *char_name, &pcbBuffer);
 if ( char_name[0] != 'J' || char_name[2] != 'H' || char_name[4] != 'D' )
   result = satrt_4011D0((int)char_J);
 else
   result = 0;

垃圾API调用

  • 随机的API组合调用,使得基于API序列的检测方法失效
Unhooked function: GetDC (0x00000001)
Unhooked function: VkKeyScanW (0x00000001)
Unhooked function: IsClipboardFormatAvailable (0x00000001)
Unhooked function: GetKeyboardType (0x00000001)
Unhooked function: GetWindowContextHelpId (0x00000001)
Unhooked function: DestroyCursor (0x00000001)
Unhooked function: GetSystemPaletteUse (0x00000001)
Unhooked function: IsIconic (0x00000001)
Unhooked function: VkKeyScanA (0x00000001)
Unhooked function: CreateSolidBrush (0x00000001)
Unhooked function: IsWindowEnabled (0x00000001)
Unhooked function: GetUserNameA (0x5fffff2c, 0x5fffff98)
Unhooked function: GetStockObject (0x000011ab)
  • 在超大循环中调用API(循环次数为0x00001B2E5,十万次数量级调用),对抗基于API序列的检测。同时也可以对抗轻量级模拟器引擎的检测。
00401220: 8B45FC                        7 mov          eax,[ebp][-4]
00401223: 83C001                          add          eax,1
00401226: 8945FC                          mov          [ebp][-4],eax 
00401229: 817DFCE5B20100                5 cmp          d,[ebp][-4],00001B2E5
00401230: 730A                            jnc         .00040123C --↓6 
00401232: 6A00                            push         0    
00401234: FF15E4074300                    call         GetTextAlign
0040123A: EBE4                            jmps        .000401220 --↑7

检查注册表项

  • 检测冷门的注册表键值是否存在,对抗轻量级模拟检测
RegOpenKeyA (hKey=0x80000000, lpSubkey=00430220:"Interface\{aa5b6a80-b834-11d0-932f-00a0c90dcaa9}", pHandle=0x004312d0) = 0 => 00401083
RegQueryValueExA (hKey=0x00000000, lpValueName=0x004312cc:"", lpReserved=0x00000000, lpValueType=0x5ffffefc, lpBuffer=5ffffe30:"IActiveScriptParseProcedure32", lpBufSize=5ffffef8:29) = 0 => 00401452

解密Layer2

加密状态的Layer2数据块在0x0041AA8C, 0x41AA88保存了数据块的长度0x14800。

  • 第一轮解密算法中包含很多无效指令干扰分析,而算法本身非常简单。循环剔除每隔0x4B处的一个字节的数据,即下列数据中尖括号中的0x42。
0041AA80  00 00 00 00 00 00 00 00  [00 48 01 00] [3F 39 39 39
0041AA90  2F 39 33 34 72 66 73 65  54 64 67 66 51 31 31 31
0041AAA0  17 31 32 63 5A 64 64 72  F7 30 31 00 06 00 6D 6E
0041AAB0  F7 30 32 32 30 76 34 34  31 63 00 00 06 00 00 6E
0041AAC0  23 6A 6B 31 1F 62 31 61  E8 65 45 78 C7 FF FF FF
0041AAD0  E8 65 67 67 35 6C 65 <42>  50 F1 68 6E 74 E3 71 00
0041AAE0  00 06 6C 73 74 D4 6B 65  6E C7 FF FF FF 06 00 00
0041AAF0  00 06 00 6C 73 B2 71 63  61 9A 40 00 00 06 00 00
0041AB00  00 06 00 00 56 9F 71 74  75 67 6C 50 72 69 74 65
0041AB10  63 7A 00 00 00 53 6E 6D  61 56 56 69 65 51 4F 66
...
加密状态的Layer2

  • 第二轮解密同样掺杂了大量无效指令,分析可以发现使用如下公式可以解密。
decrypted[index] = (encrypted[index]+index)^(index+6)
解密后数据如下
00000000:  39 39 39 39-39 39 33 34-74 66 73 65-72 64 67 66  99999934tfserdgf
00000010:  77 47 65 74-50 72 6F 63-41 64 64 72-65 73 73 00  wGetProcAddress 
00000020:  00 00 56 69-72 74 75 61-6C 41 6C 6C-6F 63 00 00    VirtualAlloc  
00000030:  00 00 00 4C-6F 61 64 4C-69 62 72 61-72 79 45 78     LoadLibraryEx
00000040:  41 00 00 00-53 65 74 46-69 6C 65 50-6F 69 6E 74  A   SetFilePoint
00000050:  65 72 00 00-00 6C 73 74-72 6C 65 6E-41 00 00 00  er   lstrlenA   
00000060:  00 00 00 00-00 00 6C 73-74 72 63 61-74 41 00 00        lstrcatA  
00000070:  00 00 00 00-00 00 00 56-69 72 74 75-61 6C 50 72         VirtualPr
00000080:  6F 74 65 63-74 00 00 00-55 6E 6D 61-70 56 69 65  otect   UnmapVie
00000090:  77 4F 66 46-69 6C 65 00-00 47 65 74-4D 6F 64 75  wOfFile  GetModu
000000A0:  6C 65 48 61-6E 64 6C 65-41 00 57 72-69 74 65 46  leHandleA WriteF
000000B0:  69 6C 65 00-00 00 00 00-00 00 00 43-6C 6F 73 65  ile        Close
000000C0:  48 61 6E 64-6C 65 00 00-00 00 00 00-56 69 72 74  Handle      Virt
000000D0:  75 61 6C 46-72 65 65 00-00 00 00 00-00 47 65 74  ualFree      Get
000000E0:  54 65 6D 70-50 61 74 68-41 00 00 00-00 00 43 72  TempPathA     Cr
000000F0:  65 61 74 65-46 69 6C 65-41 00 00 00-00 00 00 00  eateFileA       
...
第二轮解密后的Layer2

Layer2

Layer2解密Payload并把Payload映射到Layer1内存空间,最后把控制权交给Payload。

Tricks

垃圾指令

  • 使用超大指令循环(循环次数为0x32DCD5=3333333,百万数量级)执行垃圾指令,对抗轻量级模拟引擎检测。
001E45A7  jmp              short 001E45B2
001E45A9  mov              eax, dword ptr [ebp-C]
001E45AC  add              eax, 1
001E45AF  mov              dword ptr [ebp-C], eax
001E45B2  cmp              dword ptr [ebp-C], 32DCD5 <- big number
001E45B9  jnb              short 001E45CD
001E45BB  push             58
001E45BD  push             0
001E45BF  lea              ecx, dword ptr [ebp-68]
001E45C2  push             ecx
001E45C3  call             001E3E20 <- trash call
001E45C8  add              esp, 0C
001E45CB  jmp              short 001E45A9

解密Payload

Layer2解密Payload的算法和Layer1第二轮解密的算法是一样的,只能在异或时的参数略有不同。

  • 解密算法公式如下。
decrypted[index] = (encrypted[index]+index)^(index+0x3E9)

解密后可以看到明显的PE文件特征,到此Payload解密完成。

00210000  4D 5A 90 00 03 00 00 00 04 00 00 00 FF FF 00 00  MZ?�...�.
00210010  B8 00 00 00 00 00 00 00 40 00 00 00 00 00 00 00  ?......@.......
00210020  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00210030  00 00 00 00 00 00 00 00 00 00 00 00 D0 00 00 00  ............?..
00210040  0E 1F BA 0E 00 B4 09 CD 21 B8 01 4C CD 21 54 68  ��?.???L?Th
00210050  69 73 20 70 72 6F 67 72 61 6D 20 63 61 6E 6E 6F  is program canno
00210060  74 20 62 65 20 72 75 6E 20 69 6E 20 44 4F 53 20  t be run in DOS
00210070  6D 6F 64 65 2E 0D 0D 0A 24 00 00 00 00 00 00 00  mode....$.......
00210080  C5 0A 2F F1 81 6B 41 A2 81 6B 41 A2 81 6B 41 A2  ?/駚kAkAkA?
00210090  88 13 D2 A2 82 6B 41 A2 81 6B 40 A2 80 6B 41 A2  ?尧俴Ak@kA?
002100A0  8C 39 9E A2 80 6B 41 A2 8C 39 A1 A2 83 6B 41 A2  ?灑€kA9、僰A?
002100B0  FC 12 A4 A2 A5 6B 41 A2 FC 12 9F A2 80 6B 41 A2  ?あAⅫ�煝€kA?
002100C0  52 69 63 68 81 6B 41 A2 00 00 00 00 00 00 00 00  Rich乲A?.......
002100D0  50 45 00 00 4C 01 05 00 0A 37 7F 5D 00 00 00 00  PE..L��

完成解密后,Layer2会把Payload映射到主模块内存,最后通过push edx, retn来转入Payload代码入口0x0040F072。

001E469D    5D              pop     ebp
001E469E    58              pop     eax
001E469F    58              pop     eax
001E46A0    52              push    edx ==> OEP 0040F072 
001E46A1    C3              retn

自动提取IOC

对照分析

利用上述思路我们分析了更多的样本,发现Layer1依然采用了之前的对抗技术(参考d00rt的分析报告),不同样本变形非常严重。想编写一个通用的脚本脱壳并提取IOC几乎不可能。奇怪的是Emotet放弃了Payload阶段一直使用的函数调用Hook技术,Layer2解密后就已经完成了脱壳,所以无需对Dump做额外的修复工作。我们尝试使用轻量级仿真技术来完成这一工作。

可以看到仿真API日志显示,不同样本在展开Payload之前的行为差异非常大。

GetModuleHandleA(lpModuleName=0x00000000:"") = 0x00400000
Unhooked function: GetDC (0x00000001)
Unhooked function: VkKeyScanW (0x00000001)
Unhooked function: IsClipboardFormatAvailable (0x00000001)
Unhooked function: GetKeyboardType (0x00000001)
Unhooked function: GetWindowContextHelpId (0x00000001)
Unhooked function: DestroyCursor (0x00000001)
Unhooked function: GetSystemPaletteUse (0x00000001)
Unhooked function: IsIconic (0x00000001)
Unhooked function: VkKeyScanA (0x00000001)
Unhooked function: CreateSolidBrush (0x00000001)
Unhooked function: IsWindowEnabled (0x00000001)
Unhooked function: GetUserNameA (0x5fffff2c, 0x5fffff98)
Unhooked function: GetStockObject (0x000011ab)
# patch to bypass trash code -> 0x00401229: 	cmp	dword ptr [ebp - 4], 0x1b2e5
Unhooked function: GetTextAlign (0x00000000)
#Load Library:"ADVAPI32")
LoadLibraryA (lpFileName="ADVAPI32") = 70000000 => 0x00401046
GetProcAddress (hModule=0x70000000, lpProcName=0x00430290:"RegOpenKeyA") = 0x7002c41b => 0x0040104d
Unhooked function: LoadCursorW (0x00000000, 0x00000d05)
RegOpenKeyA (hKey=0x80000000, lpSubkey=00430220:"Interface\{aa5b6a80-b834-11d0-932f-00a0c90dcaa9}", pHandle=0x004312d0) = 0 => 00401083
#Load Library:"ADVAPI32")
LoadLibraryA (lpFileName="ADVAPI32") = 70000000 => 0x00401251
GetProcAddress (hModule=0x70000000, lpProcName=0x004302a8:"RegQueryValueExA") = 0x700ee5b3 => 0x00401258
Unhooked function: SetErrorMode (0x00000002)
#Load Library:"KERNEL32")
LoadLibraryA (lpFileName="KERNEL32") = 7009b000 => 0x004013e9
GetProcAddress (hModule=0x7009b000, lpProcName=0x004302c8:"GetModuleHandleA") = 0x700e7f41 => 0x004013f0
GetModuleHandleA(lpModuleName=0x00000000:"") = 0x00400000
RegQueryValueExA (hKey=0x00000000, lpValueName=0x004312cc:"", lpReserved=0x00000000, lpValueType=0x5ffffefc, lpBuffer=5ffffe30:"IActiveScriptParseProcedure32", lpBufSize=5ffffef8:29) = 0 => 00401452
VirtualAlloc (lpAddress=0x00000000, dwSize=0x00014800, flAllocationType=0x00003000, flProtect=0x00000040) = 0x00d50434 => 0x00401a17
# patch to bypass trash code -> 0x00d649e6: 	cmp	dword ptr [ebp - 0xc], 0x32dcd5
GetProcAddress (hModule=0x7009b000, lpProcName=0x5ffffe68:"LoadLibraryExA") = 0x700df7fa => 0x00d640ad
Unhooked function: LoadLibraryExA (0x5ffffe78, 0x00000000, 0x00000000)
GetProcAddress (hModule=0x00000000, lpProcName=0x00d50434:"99999934tfserdgfwGetProcAddress") = 0x00000000 => 0x00d640f4
GetProcAddress (hModule=0x00000000, lpProcName=0x00d50445:"GetProcAddress") = 0x700ee3d3 => 0x00d640f4
GetProcAddress (hModule=0x00000000, lpProcName=0x00d50456:"VirtualAlloc") = 0x700edfb6 => 0x00d640f4
GetProcAddress (hModule=0x00000000, lpProcName=0x00d50467:"LoadLibraryExA") = 0x700df7fa => 0x00d640f4
GetProcAddress (hModule=0x00000000, lpProcName=0x00d50478:"SetFilePointer") = 0x700e8b36 => 0x00d640f4
GetProcAddress (hModule=0x00000000, lpProcName=0x00d50489:"lstrlenA") = 0x700e5611 => 0x00d640f4
GetProcAddress (hModule=0x00000000, lpProcName=0x00d5049a:"lstrcatA") = 0x700e519f => 0x00d640f4
GetProcAddress (hModule=0x00000000, lpProcName=0x00d504ab:"VirtualProtect") = 0x700dd341 => 0x00d640f4
GetProcAddress (hModule=0x00000000, lpProcName=0x00d504bc:"UnmapViewOfFile") = 0x700e8b13 => 0x00d640f4
GetProcAddress (hModule=0x00000000, lpProcName=0x00d504cd:"GetModuleHandleA") = 0x700e7f41 => 0x00d640f4
GetProcAddress (hModule=0x00000000, lpProcName=0x00d504de:"WriteFile") = 0x700ec400 => 0x00d640f4
GetProcAddress (hModule=0x00000000, lpProcName=0x00d504ef:"CloseHandle") = 0x700e7a7c => 0x00d640f4
GetProcAddress (hModule=0x00000000, lpProcName=0x00d50500:"VirtualFree") = 0x700ecda4 => 0x00d640f4
GetProcAddress (hModule=0x00000000, lpProcName=0x00d50511:"GetTempPathA") = 0x70101a65 => 0x00d640f4
GetProcAddress (hModule=0x00000000, lpProcName=0x00d50522:"CreateFileA") = 0x700e7ee8 => 0x00d640f4
GetProcAddress (hModule=0x7009b000, lpProcName=0x5ffffe78:"VirtualAlloc") = 0x700edfb6 => 0x00d64160
VirtualAlloc (lpAddress=0x00000000, dwSize=0x00013880, flAllocationType=0x00003000, flProtect=0x00000040) = 0x00d64c34 => 0x00d64173
VirtualAlloc (lpAddress=0x00000000, dwSize=0x00019000, flAllocationType=0x00003000, flProtect=0x00000040) = 0x00d784b4 => 0x00d6469b
VirtualProtect (lpAddress=00001000, dwSize=0000f0a8, flNewProtect=00d50564, lpflOldProtect=5ffffe64)
VirtualProtect (lpAddress=00011000, dwSize=00000b2e, flNewProtect=00d50544, lpflOldProtect=5ffffe64)
VirtualProtect (lpAddress=00012000, dwSize=00004e60, flNewProtect=00d5054c, lpflOldProtect=5ffffe64)
VirtualProtect (lpAddress=00017000, dwSize=00000004, flNewProtect=00d50544, lpflOldProtect=5ffffe64)
VirtualProtect (lpAddress=00018000, dwSize=00000418, flNewProtect=00d50544, lpflOldProtect=5ffffe64)
Unhooked function: LoadLibraryExA (0x00d89fd4, 0x00000000, 0x00000000)
GetProcAddress (hModule=0x00000000, lpProcName=0x00d89fba:"IsProcessorFeaturePresent") = 0x700f26b5 => 0x00d64487
Unhooked function: UnmapViewOfFile (0x00400000)
#Invalid memory mapping (UC_ERR_MAP)
VirtualAlloc (lpAddress=0x00400000, dwSize=0x00019000, flAllocationType=0x00003000, flProtect=0x00000040) = 0x00400000 => 0x00d647a8
sha1 4f3adc27bdbdc1235b9d997a251531e77a72088f API log

# Load Library:"NCOBJAPI.DLL")
NCOBJAPI.DLL is loaded @ 0x7041b000
LoadLibraryExA (lpLibFileName=00402218 -> "NCOBJAPI.DLL") = 7041b000 => 0x004011ff
# Load Library:"KERNEL32.DLL")
LoadLibraryA (lpFileName="KERNEL32.DLL") = 70094000 => 0x0040121a
Unhooked function: MoveFileW (0x00402279, 0x00402288)
Unhooked function: MoveFileW (0x00402279, 0x00402288)
Unhooked function: MoveFileW (0x00402279, 0x00402288)
VirtualAlloc (lpAddress=0x00000000, dwSize=0x000008d0, flAllocationType=0x00001000, flProtect=0x00000040) = 0x00d50628 => 0x00401347
# Load Library:"KERNEL32.DLL")
LoadLibraryA (lpFileName="KERNEL32.DLL") = 70094000 => 0x00d50636
GetProcAddress (hModule=0x70094000, lpProcName=0x00d50d8a:"HeapAlloc") = 0x00000000 => 0x00d50667
GetProcAddress (hModule=0x70094000, lpProcName=0x00d50d94:"HeapFree") = 0x700dfbd0 => 0x00d50671
GetProcAddress (hModule=0x70094000, lpProcName=0x00d50d9d:"GetTickCount") = 0x700dfa60 => 0x00d5067c
VirtualAlloc (lpAddress=0x00000000, dwSize=0x000008d0, flAllocationType=0x00001000, flProtect=0x00000040) = 0x00d50ef8 => 0x00d50690
VirtualAlloc (lpAddress=0x00000000, dwSize=0x00010000, flAllocationType=0x00001000, flProtect=0x00000004) = 0x00d517c8 => 0x00d514d8
VirtualProtect (lpAddress=00400000, dwSize=00000400, flNewProtect=00000004, lpflOldProtect=5ffffef8)
VirtualProtect (lpAddress=00400000, dwSize=00000400, flNewProtect=00000002, lpflOldProtect=5ffffef8)
VirtualProtect (lpAddress=00401000, dwSize=0000e000, flNewProtect=00000004, lpflOldProtect=5ffffed8)
VirtualProtect (lpAddress=00401000, dwSize=0000e000, flNewProtect=00000020, lpflOldProtect=5ffffed8)
VirtualProtect (lpAddress=0040f000, dwSize=00001000, flNewProtect=00000004, lpflOldProtect=5ffffed8)
VirtualProtect (lpAddress=0040f000, dwSize=00001000, flNewProtect=00000002, lpflOldProtect=5ffffed8)
VirtualProtect (lpAddress=00410000, dwSize=00005000, flNewProtect=00000004, lpflOldProtect=5ffffed8)
VirtualProtect (lpAddress=00410000, dwSize=00005000, flNewProtect=00000004, lpflOldProtect=5ffffed8)
VirtualProtect (lpAddress=00415000, dwSize=00001000, flNewProtect=00000004, lpflOldProtect=5ffffed8)
VirtualProtect (lpAddress=00415000, dwSize=00001000, flNewProtect=00000002, lpflOldProtect=5ffffed8)
VirtualProtect (lpAddress=00416000, dwSize=00001000, flNewProtect=00000004, lpflOldProtect=5ffffed8)
VirtualProtect (lpAddress=00416000, dwSize=00001000, flNewProtect=00000002, lpflOldProtect=5ffffed8)
GetModuleHandleA(lpModuleName=0x0040fb20:"KERNEL32.dll") = 0x70094000
VirtualProtect (lpAddress=0040f000, dwSize=00000008, flNewProtect=00000004, lpflOldProtect=5ffffecc)
GetProcAddress (hModule=0x70094000, lpProcName=0x0040fb06:"IsProcessorFeaturePresent") = 0x700eb6b5 => 0x00d51138
VirtualProtect (lpAddress=0040f000, dwSize=00000008, flNewProtect=00000000, lpflOldProtect=5ffffed4)
sha1 f52e80a79f3685b19fa6cd5fecb093ef3b1ae4da API log

这里可以看到更多完整的API日志.

IOC定位及提取

我们感兴趣的是C&C地址表和RSA密钥。通过仿真脱壳后扫描Payload特征码定位非常方便。

.text:00401FA1 68 00 80 00 00                          push    8000h
.text:00401FA6 6A 6A                                   push    6Ah
.text:00401FA8 68 D0 F8 40 00                          push    offset unk_40F8D0 <- RSA key
.text:00401FAD 6A 13                                   push    13h
.text:00401FAF 68 01 00 01 00                          push    10001h
.text:00401FB4 FF 15 F4 05 41 00                       call    CryptDecodeObjectEx
RSA密钥相关代码

.text:004060C5 B8 C0 F3 40 00                          mov     eax, offset stru_40F3C0 <- C&C list
.text:004060CA A3 E0 26 41 00                          mov     off_4126E0, eax
.text:004060CF A3 E4 26 41 00                          mov     off_4126E4, eax
.text:004060D4 33 C0                                   xor     eax, eax
C&C地址表相关代码

把上述代码转为特征码,通过写脚本来提取相关IOC信息。具体的实现代码点这里