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 ?/駚kAkAkA?
00210090 88 13 D2 A2 82 6B 41 A2 81 6B 40 A2 80 6B 41 A2 ?尧俴Ak@kA?
002100A0 8C 39 9E A2 80 6B 41 A2 8C 39 A1 A2 83 6B 41 A2 ?灑€kA9、僰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地址表相关代码