压在透明的玻璃上c-国产精品国产一级A片精品免费-国产精品视频网-成人黄网站18秘 免费看|www.tcsft.com

對(duì)某HWP漏洞樣本的shellcode分析

前言

最近拿到一個(gè)新的HWP樣本,樣本本身利用的是一個(gè)老漏洞,這個(gè)樣本吸引我們的是shellcode部分。相關(guān)漏洞的細(xì)節(jié)我們?cè)?a >之前的文章中已有描述。需要注意的是,這次的樣本和上次的樣本在最終的執(zhí)行流切換方面有一些差異。前一段時(shí)間我們?cè)鴮徲?jì)過(guò)一些HWP樣本,發(fā)現(xiàn)不同HWP樣本在觸發(fā)該漏洞后具體的執(zhí)行流切換上存在4種不同的情況。上次的漏洞分析文章是第1種情況,本次的樣本是第2種情況,此外還有2種其他情況,相關(guān)的MD5示例如下:

第1種情況:
33874577bf54d3c209925c9def880eb9

第2種情況:
660b607e74c41b032a63e3af8f32e9f5
e488c2d80d8c33208e2957d884d1e918 (本次調(diào)試樣本)

第3種情況:
f58e86638a26eb1f11dd1d18c47fa592

第4種情況:
14b985d7ae9b3024da487f851439dc04
本次調(diào)試環(huán)境為 windows7_sp1_x86 + HWP2010英文版 (hwpapp.dll 8.0.0.466) + windbg x86

這個(gè)樣本在漏洞觸發(fā)成功后執(zhí)行的shellcode讓我們眼前一亮,樣本在漏洞觸發(fā)后先執(zhí)行第1階段shellcode去解密第2階段的shellcode。在第2階段的shellcode中,通過(guò)hash比對(duì)的方式從kernel32.dll中獲取功能函數(shù),然后創(chuàng)建?C:Windowssystem32userinit.exe?進(jìn)程并且在創(chuàng)建時(shí)掛起,接著從文檔內(nèi)容中查找標(biāo)志頭,定位到被加密的PE文件數(shù)據(jù),隨后通過(guò)兩輪解密解出PE文件,將其寫入userinit.exe進(jìn)程的0x400000處,隨后修改userinit.exe進(jìn)程的Peb.ImageBaseAddress為新寫入的PE文件,并且修改userinit.exe的主線程的線程上下背景文的Context.eax為新寫入PE文件的AddressOfEntryPoint,然后恢復(fù)userinit.exe的主線程,從而將執(zhí)行流切換到注入的PE文件的入口地址,這是一種Process Hollowing技術(shù),相關(guān)原理在這個(gè)網(wǎng)頁(yè)中有描述。這種方法讓分析人員較難提取到注入的PE文件,在沙箱中跑時(shí)也不會(huì)顯式drop出PE文件,可以說(shuō)有效躲避了檢測(cè)。注入的PE文件啟動(dòng)后,會(huì)收集系統(tǒng)信息保存到%appdata%MicrosoftNetworkxyz,隨后發(fā)給遠(yuǎn)程C2(online[-]business.atwebpages[.]com),然后在一個(gè)while循環(huán)中進(jìn)行等待,如果收集的信息顯示當(dāng)前目標(biāo)存在價(jià)值,遠(yuǎn)程C2會(huì)下發(fā)一個(gè)動(dòng)態(tài)庫(kù)保存到%appdata%MicrosoftNetworkzyx.dll并使之加載。比較遺憾的是,我們?cè)谡{(diào)試時(shí)并沒(méi)有得到zyx.dll

文檔信息

HwpScan2工具打開該文檔,先看一下基本屬性部分。可以看到原始文檔在2016年就已經(jīng)生成。

基本信息

原文檔是限制編輯的,打開后文檔內(nèi)容無(wú)法復(fù)制,實(shí)際的段落內(nèi)容被存儲(chǔ)在”ViewText”流下,而不是常規(guī)的”BodyText”流下:

限制編輯

關(guān)于這一點(diǎn),VB2018的一個(gè)PPT上有詳細(xì)的介紹:

Section1和Section2這兩個(gè)Section里面含有被壓縮后的堆噴射數(shù)據(jù),在文檔打開期間解壓后的數(shù)據(jù)會(huì)被噴射到指定的內(nèi)存。

內(nèi)存布局

這個(gè)樣本用到了堆噴射來(lái)布局內(nèi)存,我們?cè)谡{(diào)試器里面看一下堆噴射的具體細(xì)節(jié):

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號(hào)堆塊幾乎被完全用完
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

// 推測(cè)Section1和Section2分別被映射了兩次,我們來(lái)看一下堆噴射的總大小
0:000> ? 42003b0 / 400 / 400
Evaluate expression: 66 = 00000042

// 可以看到堆噴射的大小總大小為264MB,單個(gè)堆塊大小為66MB,0x20142014地址穩(wěn)定位于0x200c0030左右開始的噴射區(qū)域,所以可以很方便地劫持控制流。
0:000> ? 42 * 4
Evaluate expression: 264 = 00000108

第1階段shellcode

漏洞觸發(fā)成功之后,首先跳轉(zhuǎn)到0x20142014這個(gè)地址,由于前面已經(jīng)通過(guò)堆噴射布局內(nèi)存,所以執(zhí)行流可以一路滑行到0x242bf714(這里再?gòu)?qiáng)調(diào)一下,HWP2010并未開啟DEP,所以可以直接在堆上執(zhí)行shellcode)以執(zhí)行第1階段的shellcode。下面來(lái)看一下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通過(guò)第1輪循環(huán)(loc_A)定位到第2階段shellcode地址,然后通過(guò)第2輪循環(huán)(loc_22)去解密第2階段的shellcode。

第1階段shellcode

我們用python模擬了一下上述shellcode的解密過(guò)程:

# -*- 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))

實(shí)際解密時(shí)從下述數(shù)據(jù)開始:

加密的2階段shellcode

解密完成后,我們可以得到如下數(shù)據(jù):

解密后的2階段shellcode

第2階段shellcode

獲取功能函數(shù)

得到解密后的第2階段shellcode后,就可以愉快地在IDA里進(jìn)行后續(xù)分析了。

第2階段shellcode上來(lái)就是一系列hash,看起來(lái)貌似是要通過(guò)hash比對(duì)搜索功能函數(shù)。

一系列hash

一番調(diào)試和逆向后,我們明白shellcode封裝了一個(gè)輔助函數(shù)用來(lái)查找所需的功能函數(shù):

解密后的hash對(duì)應(yīng)情況

GetFuncAddrFromEATByHash函數(shù)內(nèi)部,作者用循環(huán)右移13位的方式計(jì)算hash,并查找滿足指定hash的動(dòng)態(tài)庫(kù)(kernel32.dll)內(nèi)的滿足指定hash的函數(shù),然后將它們的地址保存到棧的指定位置,如上圖所示。我們這里用C語(yǔ)言還原一下dll的hash的計(jì)算過(guò)程和api的hash的計(jì)算過(guò)程:

// 部分代碼借鑒自網(wǎng)絡(luò),此處表示致謝
#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;
}

定位PE文件并解密

獲得需要的功能函數(shù)后,shellcode首先通過(guò)GlobalAlloc函數(shù)申請(qǐng)一段內(nèi)存,用來(lái)存儲(chǔ)后面將要讀入的PE數(shù)據(jù)。隨后,從4開始,遍歷句柄,暴力搜索文件大小等于當(dāng)前hwp文件大小的文件句柄并保存,然后將文件指針移動(dòng)到0x9DE1偏移處,并將大小為3E40A Bytes的內(nèi)容讀入之前申請(qǐng)的內(nèi)存處。

定位文檔中的shellcode

然后,shellcode從讀入的文檔內(nèi)容開始搜索兩個(gè)連續(xù)的標(biāo)志0x42594F4A,0x4D545245,并將第2個(gè)標(biāo)志結(jié)束+2的地址處作為PE數(shù)據(jù)的首地址保存到[ebp-18]處。

定位真正的加密PE處

可以在HWP文檔中定位到相應(yīng)數(shù)據(jù)區(qū)域:

在HWP文檔中定位PE數(shù)據(jù)

不過(guò)此時(shí)的PE文件數(shù)據(jù)仍為被加密的狀態(tài),shellcode隨后用兩輪解密將解密的PE文件進(jìn)行解密,相關(guān)匯編代碼如下:

對(duì)PE的兩輪解密

在理解上述代碼的基礎(chǔ)上一樣可以用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數(shù)據(jù)如下:

解密前的PE數(shù)據(jù)

解密后的PE數(shù)據(jù)如下:

解密后的PE數(shù)據(jù)

創(chuàng)建userinit.exe進(jìn)程并掛起

得到解密的PE文件后,shellcode做了一系列準(zhǔn)備并最終去啟動(dòng)userinit.exe進(jìn)程,啟動(dòng)時(shí)傳入CREATE_SUSPENDED標(biāo)志,指明將userinit.exe啟動(dòng)后掛起:

準(zhǔn)備進(jìn)程啟動(dòng)信息

啟動(dòng)userinit.exe進(jìn)程

替換userinit.exe主模塊

隨后shellcode調(diào)用GetThreadContext獲取userinit.exe主線程的線程上下文并保存到棧的指定位置:

獲取線程上下文

接著讀取userinit.exe的Peb.ImageBaseAddress

讀取peb內(nèi)的鏡像基地址

然后動(dòng)態(tài)獲取ntdll!ZwUnmapViewOfSection,并判斷操作系統(tǒng)版本,如果操作系統(tǒng)主版本小于6(相關(guān)原理可以參考這篇文章),則調(diào)用該API對(duì)主模塊基地址的內(nèi)存進(jìn)行解映射,否則直接跳到后續(xù)步驟:

判斷操作系統(tǒng)版本

接著shellcode在userinit.exe進(jìn)程內(nèi)0x400000地址處(即PE文件中寫入的進(jìn)程默認(rèn)加載基址)申請(qǐng)一片內(nèi)存,內(nèi)存大小等于解密出來(lái)的PE文件,并先將PE文件的頭部寫入所申請(qǐng)的內(nèi)存(0x400000):

寫入PE頭部

隨后往上述內(nèi)存區(qū)域循環(huán)寫入PE文件的各個(gè)節(jié)區(qū):

循環(huán)寫入各個(gè)Section

每寫完一個(gè)節(jié)區(qū)后,shellcode獲取PE文件中該節(jié)區(qū)的原始讀寫屬性(通過(guò)Characteristics字段),并在內(nèi)存中相應(yīng)更新這些節(jié)區(qū)對(duì)應(yīng)的內(nèi)存屬性:

更新內(nèi)存屬性

完成上述步驟后,shellcode將userinit.exe進(jìn)程的Peb.ImageBaseAddress域改寫為0x400000(即注入后的PE基地址),并將線程上下文中Context.eax更新為所注入PE的AddressOfEntryPoint,這部分的原理可以參考這篇文章。

改寫OEP

最后恢復(fù)userinit.exe的主線程,并關(guān)閉剛才打開的userinit.exe進(jìn)程句柄,從而使主線程去執(zhí)行Process Hollowing后的PE文件,達(dá)到偷天換日的目的。相關(guān)代碼可以參考這里

恢復(fù)線程

 

注入的PE文件

前面我們已經(jīng)靜態(tài)解密出了PE文件,我們現(xiàn)在來(lái)看一下解密出的PE文件的基本信息,用pestudio打開該P(yáng)E文件,看一下這個(gè)PE文件的基本信息:

可以看到該P(yáng)E文件的編譯時(shí)間是2017.12.26 10:13:17,此外還可以知道該P(yáng)E文件的鏈接器版本是9.0。

PE基本信息1

逆向PE文件

整個(gè)PE文件既沒(méi)有加殼,也沒(méi)有加花指令,整體邏輯非常清晰明了,拖進(jìn)IDA基本上就原形畢露了。

PE文件主入口函數(shù)如下:

PE文件主入口

正如函數(shù)名所示,它首先調(diào)用AdjustTokenPrivileges提升自己的權(quán)限,然后分別從Kernel32.dll/Wininet.dll/Advapi32.dll獲取所需的功能函數(shù)并保存到全局變量,最后啟動(dòng)一個(gè)新的線程,并在10秒后退出當(dāng)前函數(shù)。

kernel32

wininet

(以下幾個(gè)函數(shù)貌似并沒(méi)有被用到)

advapi32

來(lái)看一下啟動(dòng)的線程干了哪些事情,如下圖所示,這個(gè)線程的主要目的就是先收集系統(tǒng)信息,并保存到%appdata%MicrosoftNetworkxyz,隨后將這些信息發(fā)送給遠(yuǎn)程C2,傳完之后刪除xyz文件。隨后進(jìn)入一個(gè)循環(huán),每隔30分鐘從遠(yuǎn)程服務(wù)器嘗試下載一個(gè)zyx.dll并保存到%appdata%MicrosoftNetworkzyx.dll并嘗試加載之。這里推測(cè)是C2端需要先判斷目標(biāo)用戶是否有價(jià)值,然后才決定是否將下一階段的載荷發(fā)送給目標(biāo)用戶。

線程函數(shù)

收集信息部分的代碼也很直接,如下:

收集信息

隨后將收集的信息發(fā)送給遠(yuǎn)程C2:

構(gòu)造HTTP頭部

post

http報(bào)文

最后,一旦遠(yuǎn)程dll被下發(fā)到目標(biāo)機(jī)器,PE文件會(huì)立即加載之,并在3分鐘后卸載對(duì)應(yīng)的dll并刪除文件。由于我們調(diào)試期間并沒(méi)有獲得下發(fā)的dll,所以dll里面具體執(zhí)行了什么邏輯不得而知。

寫入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

上一篇:保障IDC安全:分布式HIDS集群架構(gòu)設(shè)計(jì)

下一篇:調(diào)查:加密貨幣挖礦仍居惡意軟件威脅前列