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

留言
張貼留言