HITCON ENT 惡意軟體新型API呼叫行為實作(Patten式GetProcAddress找碴呼叫API)
動機
這次HITCON Enterprise正好坐在R0場有幸聽到PK大神講的議程XD。不過本來想聽這場是想知道實際Google Drive C2 API呼叫過程的Olly分析,不過似乎整場議程重點不放在那Otz...但正好聽到一件有趣的事情,議程內提到新型惡意軟體呼叫不再像以前是用GetProcAddress,而是自幹了一個叫做GetProcAddressEx呼叫方式會像是:
GetProcAddressEx( ModuleBase, "0=L;3=d;4=L;11=A"); //找尋一個API根據函數名,第0個為L、第3個為d、第4個為L、第11個為A
這手法聽起來像是在逃避正常靜態分析軟體(如IDA Pro)可以很容易根據Import Address Table得知呼叫過程或者透過對GetProcessAddress下斷點動態反查API過程,所以我想這個GetProcAddressEx內部應該:
- 不該使用到任何API來增加分析難度
- 暴力分析DLL模組結構體找碴外導函數
- 暴力列舉API過程中根據Pattern(也就是0=L;3=d...)找到第一筆合適的API名
Visual C++實作
關於這種奇怪的要求,想到之前有研究過類似的發文 [Windows][PE][ASM]純組語手幹Dll Header解析外導函數表撈出函數地址(如LoadLibraryA動態地址)索性我就拿這個的部分Code來改成VC++函數版本了XD首先Pattern檢測部份,我寫了一支簡單的函數
int StrVal(std::string Data) { return std::stoi(Data); } bool ChkPatten(char* Str, char* Patten) { bool retn = true; std::string n = ""; for (int i = 0; Patten[i]; i++) { if (*(char*)(Patten + i) == '=') { int LPos = StrVal(n); char RChar = *(char*)(Patten + i + 1); if (Str[LPos] != RChar) { retn = false; break; } n = ""; i += 2; } else { n += *(char*)(Patten + i); } } return retn; }這支ChkPatten寫法重點就是一直複製左邊的數字到n的String結構體內,直到遇到”=“時就解析右邊的一個Char看指定位置的字詞是否為該Char然後不停的確認Pattern,就這樣而已XD
那再來就是重點的DLL結構體解析,根據DLL結構體的定義可以寫出以下Code:
int GetProcAddrEx(int ModBase, char* Patten) { int Data = 0; _asm { mov esi, ModBase mov edx, [esi + 0x3c]//e_ifanw add edx, esi//Point To NTHeader Address. add edx, 0x18//OptionalHeader add edx, 0x60//IMAGE_OPTIONAL_HEADER -> DataDirectory add edx, 0x00//Export Directory Offset Addr mov edx, [edx]//Export Directory Addr add edx, esi mov ecx, [edx + 0x18]//Number Of Names FindAddrOfNames: mov ebx, [edx + 0x20]//Addr Of Name Offset add ebx, esi//Point to Addr Of Name mov ebx, [ebx + ecx * 4] add ebx, esi//Current Name Addr dec ecx jl ExitFunc pushad push Patten push ebx call ChkPatten add esp, 0x08 test al, al popad jz FindAddrOfNames GetAddr : inc ecx mov ebx, [edx + 0x24]//AddressOfNameOrdinals Offset add ebx, esi mov cx, [ebx + ecx * 2]//ECX = AddressOfNameOrdinals + Index As WORD(2 BYTE) mov ebx, [edx + 0x1c]//AddressOfFunction Array Offset. add ebx, esi mov ebx, [ebx + ecx * 4]//Set EDX = Value Of AddressOfFunction[Index] = Offset. add esi, ebx mov[Data], esi ExitFunc : } return Data; }如果不懂的話可以去翻 [Windows][PE][ASM]純組語手幹Dll Header解析外導函數表撈出函數地址(如LoadLibraryA動態地址)來看一下,這邊主要就是改了一下那篇部落格文內的比對文字。
pushad push Patten push ebx call ChkPatten add esp, 0x08 test al, al popad jz FindAddrOfNames
去呼叫我們前面寫好的ChkPatten函數來確認我們當前列舉的API ESI對應的文字名字是否為Patten所求的API,比較特別的是add esp, 0x08部分是為了平衡VC內函數寫法固定都得平衡參數使用的空間(我記得C++ Builder好像不用這樣平衡?看IDE)
最後就是Demo一下這兩個函數的猛猛效果了
DEMO
首先,為了做到完美的不使用API我還使用到了之前PO過的[Windows][PE][ASM][CE]從PE架構到模組架構到暴力列舉模組找模組位置(如Kernel32.dll)來找碴模組在當前記憶體中的位置。
int GetKernel32Mod() { int dRetn = 0; _asm { Main: mov ebx, fs:[0x30] //PEB mov ebx, [ebx + 0x0c]//Ldr mov ebx, [ebx + 0x1c]//InInitializationOrderModuleList Search : mov eax, [ebx + 0x08]//Point to Current Modual Base. mov ecx, [ebx + 0x20]//Point to Current Name. mov ecx, [ecx + 0x18] cmp cl, 0x00//Test if Name[25] == \x00. mov ebx, [ebx + 0x00] jne Search mov[dRetn], eax } return dRetn; }那最後就是實作怎麼不用任何API(應該說IAT表、靜態工具分析看不出來)怎麼做到漂亮的呼叫API了,這邊以Console程式要呼叫MessageBoxA為例,因為MessageBoxA在User32.dll之下但是Console程式預設是不會載入這個模組的,所以實際程式碼我們得先透過LoadLibraryA把User32.dll給載入進記憶體在找碴MessageBoxA的實際記憶體地址然後做呼叫:
int _tmain(int argc, char* argv[]) { int Kernel32Base = GetKernel32Mod(); HMODULE(WINAPI*MyLoadLibraryA)(LPCTSTR lpFileName) = (HMODULE(WINAPI *)(LPCTSTR))(GetProcAddrEx(Kernel32Base, "0=L;3=d;4=L;11=A")); int User32Base = (int)MyLoadLibraryA((LPCTSTR)"User32.dll"); int(WINAPI*MyMessageBoxA)(HWND hWnd,LPCTSTR lpText,LPCTSTR lpCaption,UINT uType) = (int(WINAPI *)(HWND, LPCTSTR, LPCTSTR, UINT))(GetProcAddrEx(User32Base, "0=M;2=s;3=s;7=B;10=A")); MyMessageBoxA(0,(LPCTSTR)"Adr Handsome!", (LPCTSTR)"Adr Say", 0); return 0; }實作效果:
效果拔擢啊!!
如果想下載完整的專案代碼,可以到我的GitHub:
https://github.com/aaaddress1/Virus-Patten-API-Call/tree/master
留言
張貼留言