前言
最近拿到一個新的HWP樣本,樣本本身利用的是一個老漏洞,這個樣本吸引我們的是shellcode部分。相關漏洞的細節我們在之前的文章中已有描述。需要注意的是,這次的樣本和上次的樣本在最終的執行流切換方面有一些差異。前一段時間我們曾審計過一些HWP樣本,發現不同HWP樣本在觸發該漏洞后具體的執行流切換上存在4種不同的情況。上次的漏洞分析文章是第1種情況,本次的樣本是第2種情況,此外還有2種其他情況,相關的MD5示例如下:
第1種情況:
33874577bf54d3c209925c9def880eb9
第2種情況:
660b607e74c41b032a63e3af8f32e9f5
e488c2d80d8c33208e2957d884d1e918 (本次調試樣本)
第3種情況:
f58e86638a26eb1f11dd1d18c47fa592
第4種情況:
14b985d7ae9b3024da487f851439dc04
本次調試環境為 windows7_sp1_x86 + HWP2010英文版 (hwpapp.dll 8.0.0.466) + windbg x86
這個樣本在漏洞觸發成功后執行的shellcode讓我們眼前一亮,樣本在漏洞觸發后先執行第1階段shellcode去解密第2階段的shellcode。在第2階段的shellcode中,通過hash比對的方式從kernel32.dll中獲取功能函數,然后創建?C:Windowssystem32userinit.exe
?進程并且在創建時掛起,接著從文檔內容中查找標志頭,定位到被加密的PE文件數據,隨后通過兩輪解密解出PE文件,將其寫入userinit.exe進程的0x400000
處,隨后修改userinit.exe進程的Peb.ImageBaseAddress
為新寫入的PE文件,并且修改userinit.exe的主線程的線程上下背景文的Context.eax
為新寫入PE文件的AddressOfEntryPoint
,然后恢復userinit.exe的主線程,從而將執行流切換到注入的PE文件的入口地址,這是一種Process Hollowing
技術,相關原理在這個網頁中有描述。這種方法讓分析人員較難提取到注入的PE文件,在沙箱中跑時也不會顯式drop出PE文件,可以說有效躲避了檢測。注入的PE文件啟動后,會收集系統信息保存到%appdata%MicrosoftNetworkxyz
,隨后發給遠程C2(online[-]business.atwebpages[.]com
),然后在一個while循環中進行等待,如果收集的信息顯示當前目標存在價值,遠程C2會下發一個動態庫保存到%appdata%MicrosoftNetworkzyx.dll
并使之加載。比較遺憾的是,我們在調試時并沒有得到zyx.dll
。
文檔信息
用HwpScan2
工具打開該文檔,先看一下基本屬性部分。可以看到原始文檔在2016年就已經生成。
原文檔是限制編輯的,打開后文檔內容無法復制,實際的段落內容被存儲在”ViewText”流下,而不是常規的”BodyText”流下:
關于這一點,VB2018的一個PPT上有詳細的介紹:
Section1和Section2這兩個Section里面含有被壓縮后的堆噴射數據,在文檔打開期間解壓后的數據會被噴射到指定的內存。
內存布局
這個樣本用到了堆噴射來布局內存,我們在調試器里面看一下堆噴射的具體細節:
sxe ld:hwpapp.dll
...
ModLoad: 046f0000 04ad1000 C:Program FilesHncHwp80HwpApp.dll
eax=0012ee68 ebx=00000000 ecx=00000006 edx=00000000 esi=7ffdf000 edi=0012eff4
eip=772270b4 esp=0012ef0c ebp=0012ef60 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
ntdll!KiFastSystemCallRet:
772270b4 c3 ret
0:000> bp hwpapp+1122f3 ".if(edx == hwpapp+bded0){g;}.else{}"
0:000> g
DllMain() : DLL_PROCESS_ATTACH - ABase Start!
(d8c.468): C++ EH exception - code e06d7363 (first chance)
eax=20142014 ebx=0012f6bc ecx=20142014 edx=20142014 esi=02c86d18 edi=00000098
eip=048022f3 esp=0012ed90 ebp=02d881a8 iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000206
HwpApp!HwpCreateParameterArray+0x8c433:
048022f3 ffd2 call edx {20142014}
0:000> !heap
NtGlobalFlag enables following debugging aids for new heaps: stack back traces
Index Address Name Debugging options enabled
1: 00230000
2: 00010000
3: 01980000
4: 02650000
5: 02770000
6: 02950000
7: 025d0000
8: 028f0000
9: 03250000
10: 028d0000
11: 04e20000
12: 06720000
13: 07440000
14: 07590000
// 可以看到5號堆塊幾乎被完全用完
0:000> !heap -stat -h 02770000
heap @ 02770000
group-by: TOTSIZE max-display: 20
size #blocks total ( %) (percent of total busy bytes)
42003b0 2 - 8400760 (49.47)
420035c 2 - 84006b8 (49.47)
e4 926 - 825d8 (0.19)
f0 38a - 35160 (0.08)
194 1cf - 2daac (0.07)
24000 1 - 24000 (0.05)
18 15d9 - 20c58 (0.05)
20000 1 - 20000 (0.05)
100 190 - 19000 (0.04)
aa00 2 - 15400 (0.03)
28 6e1 - 11328 (0.03)
10bc0 1 - 10bc0 (0.02)
10000 1 - 10000 (0.02)
fda0 1 - fda0 (0.02)
fb50 1 - fb50 (0.02)
a8 158 - e1c0 (0.02)
4400 3 - cc00 (0.02)
2200 6 - cc00 (0.02)
800 17 - b800 (0.02)
82 13b - 9ff6 (0.01)
0:000> !heap -flt s 420035c
_HEAP @ 230000
_HEAP @ 10000
_HEAP @ 1980000
_HEAP @ 2650000
_HEAP @ 2770000
HEAP_ENTRY Size Prev Flags UserPtr UserSize - state
invalid allocation size, possible heap corruption
121b0018 84006d 0000 [00] 121b0030 420035c - (busy VirtualAlloc)
invalid allocation size, possible heap corruption
242d0018 84006d 006d [00] 242d0030 420035c - (busy VirtualAlloc)
_HEAP @ 2950000
_HEAP @ 25d0000
_HEAP @ 28f0000
_HEAP @ 3250000
_HEAP @ 28d0000
_HEAP @ 4e20000
_HEAP @ 6720000
_HEAP @ 7440000
_HEAP @ 7590000
0:000> !heap -flt s 42003b0
_HEAP @ 230000
_HEAP @ 10000
_HEAP @ 1980000
_HEAP @ 2650000
_HEAP @ 2770000
HEAP_ENTRY Size Prev Flags UserPtr UserSize - state
0a6d0018 840078 0000 [00] 0a6d0030 42003b0 - (busy VirtualAlloc)
200c0018 840078 0078 [00] 200c0030 42003b0 - (busy VirtualAlloc)
_HEAP @ 2950000
_HEAP @ 25d0000
_HEAP @ 28f0000
_HEAP @ 3250000
_HEAP @ 28d0000
_HEAP @ 4e20000
_HEAP @ 6720000
_HEAP @ 7440000
_HEAP @ 7590000
// 推測Section1和Section2分別被映射了兩次,我們來看一下堆噴射的總大小
0:000> ? 42003b0 / 400 / 400
Evaluate expression: 66 = 00000042
// 可以看到堆噴射的大小總大小為264MB,單個堆塊大小為66MB,0x20142014地址穩定位于0x200c0030左右開始的噴射區域,所以可以很方便地劫持控制流。
0:000> ? 42 * 4
Evaluate expression: 264 = 00000108
第1階段shellcode
漏洞觸發成功之后,首先跳轉到0x20142014
這個地址,由于前面已經通過堆噴射布局內存,所以執行流可以一路滑行到0x242bf714
(這里再強調一下,HWP2010并未開啟DEP,所以可以直接在堆上執行shellcode)以執行第1階段的shellcode。下面來看一下shellcode部分。
0:000> u 242bf714
242bf714 52 push edx
242bf715 53 push ebx
242bf716 56 push esi
242bf717 50 push eax
242bf718 57 push edi
242bf719 ba14201420 mov edx,20142014h
...
第1階段的shellcode的主要目的是定位并解密第2階段的shellcode。從下圖可以看到,第1階段shellcode通過第1輪循環(loc_A)定位到第2階段shellcode地址,然后通過第2輪循環(loc_22)去解密第2階段的shellcode。
我們用python模擬了一下上述shellcode的解密過程:
# -*- coding: utf-8 -*-
import os
import binascii
cur_dir = os.path.dirname(__file__)
path_encode = os.path.join(cur_dir, "sc_encode.bin")
with open(path_encode, "rb") as f:
bin_data = f.read()
bin_data = binascii.b2a_hex(bin_data)
i = 0
j = 0
k = 0
while k < 0x60D:
a = ((int(bin_data[i:i+2], 16) & 0x0F) << 4) & 0xF0
b = int(bin_data[i+2:i+4], 16) & 0x0F
c = '{:02x}'.format(a + b)
bin_data = bin_data[:j] + c[0] + c[1] + bin_data[j+2:]
i += 2 * 2
j += 2 * 1
k += 1
path_decode = os.path.join(cur_dir, "sc_decode.bin")
with open(path_decode, "wb") as f:
f.write(binascii.a2b_hex(bin_data))
實際解密時從下述數據開始:
解密完成后,我們可以得到如下數據:
第2階段shellcode
得到解密后的第2階段shellcode后,就可以愉快地在IDA里進行后續分析了。
第2階段shellcode上來就是一系列hash,看起來貌似是要通過hash比對搜索功能函數。
一番調試和逆向后,我們明白shellcode封裝了一個輔助函數用來查找所需的功能函數:
在GetFuncAddrFromEATByHash
函數內部,作者用循環右移13位的方式計算hash,并查找滿足指定hash的動態庫(kernel32.dll)內的滿足指定hash的函數,然后將它們的地址保存到棧的指定位置,如上圖所示。我們這里用C語言還原一下dll的hash的計算過程和api的hash的計算過程:
// 部分代碼借鑒自網絡,此處表示致謝
#include <stdio.h>
#include <windows.h>
#define ROTATE_RIGHT(x, s, n) ((x) >> (n)) | ((x) << ((s) - (n)))
DWORD GetHashHWPUnicode(WCHAR *wszName)
{
printf("%S", wszName);
DWORD dwRet = 0;
WCHAR* wszCur = 0;
do
{
dwRet = ROTATE_RIGHT(dwRet, 32, 0x0D);
dwRet += *wszName;
wszCur = wszName;
wszName++;
} while (*wszCur);
printf(" function's hash is 0x%.8xn", dwRet);
return dwRet;
}
DWORD GetHashHWPAscii(CHAR *szName)
{
printf("%s", szName);
DWORD dwRet = 0;
CHAR* szCur = 0;
do
{
dwRet = ROTATE_RIGHT(dwRet, 32, 0x0D);
dwRet += *szName;
szCur = szName;
szName++;
} while (*szCur);
printf(" function's hash is 0x%.8xn", dwRet);
return dwRet;
}
int main(int argc, char* argv[])
{
GetHashHWPUnicode(L"KERNEL32.dll");
GetHashHWPUnicode(L"KERNEL32.DLL");
GetHashHWPUnicode(L"kernel32.DLL");
GetHashHWPUnicode(L"kernel32.dll");
GetHashHWPAscii("ResumeThread");
GetHashHWPAscii("SetThreadContext");
GetHashHWPAscii("VirtualProtectEx");
GetHashHWPAscii("WriteProcessMemory");
GetHashHWPAscii("GetVersionExA");
GetHashHWPAscii("ReadProcessMemory");
GetHashHWPAscii("TerminateProcess");
GetHashHWPAscii("GetThreadContext");
GetHashHWPAscii("GetLastError");
GetHashHWPAscii("GetProcAddress");
GetHashHWPAscii("GetSystemDirectoryA");
GetHashHWPAscii("GetModuleHandleA");
GetHashHWPAscii("CreateProcessA");
GetHashHWPAscii("GlobalAlloc");
GetHashHWPAscii("GetFileSize");
GetHashHWPAscii("SetFilePointer");
GetHashHWPAscii("CloseHandle");
GetHashHWPAscii("VirtualAllocEx");
GetHashHWPAscii("ReadFile");
return 0;
}
獲得需要的功能函數后,shellcode首先通過GlobalAlloc
函數申請一段內存,用來存儲后面將要讀入的PE數據。隨后,從4開始,遍歷句柄,暴力搜索文件大小等于當前hwp文件大小的文件句柄并保存,然后將文件指針移動到0x9DE1
偏移處,并將大小為3E40A Bytes
的內容讀入之前申請的內存處。
然后,shellcode從讀入的文檔內容開始搜索兩個連續的標志0x42594F4A
,0x4D545245
,并將第2個標志結束+2
的地址處作為PE數據的首地址保存到[ebp-18]處。
可以在HWP文檔中定位到相應數據區域:
不過此時的PE文件數據仍為被加密的狀態,shellcode隨后用兩輪解密將解密的PE文件進行解密,相關匯編代碼如下:
在理解上述代碼的基礎上一樣可以用python寫出解密程序,如下:
# -*- coding: utf-8 -*-
import os
import binascii
cur_dir = os.path.dirname(__file__)
path_encode = os.path.join(cur_dir, "pe_encode.bin")
with open(path_encode, "rb") as f:
bin_data = f.read()
bin_data = binascii.b2a_hex(bin_data)
i = 2 * 2
while (i / 2) < 0x18400:
a = int(bin_data[i-4:i-4+2], 16)
b = int(bin_data[i-2:i], 16)
c = int(bin_data[i:i+2], 16)
c = '{:02x}'.format((a ^ b ^ c) & 0xFF)
bin_data = bin_data[:i] + c[0] + c[1] + bin_data[i+2:]
i += 2 * 1
i = 2 * 2
while (i / 2) < 0x18400:
c = int(bin_data[i:i+2], 16)
c = ((c << ((i / 2) & 7)) & 0xFF) + ((c >> (8 - ((i / 2) & 7))) & 0xFF) ^ (i / 2)
c = '{:02x}'.format(c & 0xFF)
bin_data = bin_data[:i] + c[0] + c[1] + bin_data[i+2:]
i += 2 * 1
path_decode = os.path.join(cur_dir, "pe_decode.bin")
with open(path_decode, "wb") as f:
f.write(binascii.a2b_hex(bin_data[4:]))
解密前的PE數據如下:
解密后的PE數據如下:
得到解密的PE文件后,shellcode做了一系列準備并最終去啟動userinit.exe進程,啟動時傳入CREATE_SUSPENDED
標志,指明將userinit.exe啟動后掛起:
隨后shellcode調用GetThreadContext
獲取userinit.exe主線程的線程上下文并保存到棧的指定位置:
接著讀取userinit.exe的Peb.ImageBaseAddress
:
然后動態獲取ntdll!ZwUnmapViewOfSection
,并判斷操作系統版本,如果操作系統主版本小于6(相關原理可以參考這篇文章),則調用該API對主模塊基地址的內存進行解映射,否則直接跳到后續步驟:
接著shellcode在userinit.exe進程內0x400000
地址處(即PE文件中寫入的進程默認加載基址)申請一片內存,內存大小等于解密出來的PE文件,并先將PE文件的頭部寫入所申請的內存(0x400000
):
隨后往上述內存區域循環寫入PE文件的各個節區:
每寫完一個節區后,shellcode獲取PE文件中該節區的原始讀寫屬性(通過Characteristics
字段),并在內存中相應更新這些節區對應的內存屬性:
完成上述步驟后,shellcode將userinit.exe進程的Peb.ImageBaseAddress
域改寫為0x400000
(即注入后的PE基地址),并將線程上下文中Context.eax
更新為所注入PE的AddressOfEntryPoint
,這部分的原理可以參考這篇文章。
最后恢復userinit.exe的主線程,并關閉剛才打開的userinit.exe進程句柄,從而使主線程去執行Process Hollowing
后的PE文件,達到偷天換日的目的。相關代碼可以參考這里。
注入的PE文件
前面我們已經靜態解密出了PE文件,我們現在來看一下解密出的PE文件的基本信息,用pestudio
打開該PE文件,看一下這個PE文件的基本信息:
可以看到該PE文件的編譯時間是2017.12.26 10:13:17
,此外還可以知道該PE文件的鏈接器版本是9.0。
整個PE文件既沒有加殼,也沒有加花指令,整體邏輯非常清晰明了,拖進IDA基本上就原形畢露了。
PE文件主入口函數如下:
正如函數名所示,它首先調用AdjustTokenPrivileges
提升自己的權限,然后分別從Kernel32.dll/Wininet.dll/Advapi32.dll
獲取所需的功能函數并保存到全局變量,最后啟動一個新的線程,并在10秒后退出當前函數。
(以下幾個函數貌似并沒有被用到)
來看一下啟動的線程干了哪些事情,如下圖所示,這個線程的主要目的就是先收集系統信息,并保存到%appdata%MicrosoftNetworkxyz
,隨后將這些信息發送給遠程C2,傳完之后刪除xyz文件。隨后進入一個循環,每隔30分鐘從遠程服務器嘗試下載一個zyx.dll
并保存到%appdata%MicrosoftNetworkzyx.dll
并嘗試加載之。這里推測是C2端需要先判斷目標用戶是否有價值,然后才決定是否將下一階段的載荷發送給目標用戶。
收集信息部分的代碼也很直接,如下:
隨后將收集的信息發送給遠程C2:
最后,一旦遠程dll被下發到目標機器,PE文件會立即加載之,并在3分鐘后卸載對應的dll并刪除文件。由于我們調試期間并沒有獲得下發的dll,所以dll里面具體執行了什么邏輯不得而知。
IOC
HWP: e488c2d80d8c33208e2957d884d1e918
PE: 72d44546ca6526cdc0f6e21ba8a0f25d
Domain: online[-]business.atwebpages[.]com
IP: 185[.]176.43.82
參考鏈接
https://github.com/m0n0ph1/Process-Hollowing
https://cysinfo.com/detecting-deceptive-hollowing-techniques/
https://blog.csdn.net/lixiangminghate/article/details/42121929
https://www.virusbulletin.com/uploads/pdf/conference_slides/2018/KimKwakJang-VB2018-Dokkaebi.pdf