55. IAT Hiding & Obfuscation - API Hashing
IAT Hiding & Obfuscation - API Hashing
Introduction
In the previous two modules, two custom functions were created GetProcAddressReplacement
and GetModuleHandleReplacement
which replaced GetProcAddress
and GetModuleHandle
. This was sufficient for performing Run-Time Dynamic Linking which hides the imported functions from the IAT. However, the strings used within the code reveal which functions are being used. For example, the line below uses the functions to retrieve VirtualAllocEx
.
GetProcAddressReplacement(GetModuleHandleReplacement("ntdll.dll"),"VirtualAllocEx")
Security solutions can easily retrieve the strings within the compiled binary and recognize that VirtualAllocEx
is being used. To solve this problem, a string hashing algorithm will be applied to both GetProcAddressReplacement
and GetModuleHandleReplacement
. Instead of performing string comparisons to acquire the specified module base address or function address, the functions will work with hash values instead.
Implementing JenkinsOneAtATime32Bit
The GetProcAddressReplacement
and GetModuleHandleReplacement
functions are renamed in this module to GetProcAddressH
and GetModuleHandleH
, respectively. These updated functions utilize the Jenkins One At A Time string hashing algorithm to replace the function and module name with a hash value that represents them. Recall that this algorithm was utilized through the JenkinsOneAtATime32Bit
function that was introduced in the String Hashing module.
Hashing Strings
In order to use the functions shown in this module, it is necessary to obtain the hash value of a module name (e.g. User32.dll
) and the hash value of the function name (e.g. MessageBoxA
). This can be done by first printing the hashed values to the console. Ensure that the hashing algorithm uses the same seed.
// ...
int main(){
printf("[i] Hash Of \"%s\" Is : 0x%0.8X \n", "USER32.DLL", HASHA("USER32.DLL")); // Capitalized module name
printf("[i] Hash Of \"%s\" Is : 0x%0.8X \n", "MessageBoxA", HASHA("MessageBoxA"));
return 0;
}
The above main function will output the following:
[i] Hash Of "USER32.DLL" Is : 0x81E3778E
[i] Hash Of "MessageBoxA" Is : 0xF10E27CA
These hash values can now be used with the functions below.
Usage
The functions would be used the same way except now the hash value is passed rather than the string value.
// 0x81E3778E is the hash of USER32.DLL
// 0xF10E27CA is the hash of MessageBoxA
fnMessageBoxA pMessageBoxA = GetProcAddressH(GetModuleHandleH(0x81E3778E),0xF10E27CA);
GetProcAddressH Function
GetProcAddressH
is a function that is equivalent to GetProcAddressReplacement
with the main difference being that the hash values of the JenkinsOneAtATime32Bit
string hashing algorithm are employed to compare the exported function names to the input hash.
It's also worth noting that the code uses two macros to make the code cleaner and easier to update in the future.
HASHA
- Calling HashStringJenkinsOneAtATime32BitA (ASCII)
HASHW
- Calling HashStringJenkinsOneAtATime32BitW (UNICODE)
#define HASHA(API) (HashStringJenkinsOneAtATime32BitA((PCHAR) API))#define HASHW(API) (HashStringJenkinsOneAtATime32BitW((PWCHAR) API))
With that in mind, the GetProcAddressH
is shown below. The function takes two parameters:
hModule
- A handle to the DLL module that contains the function.
dwApiNameHash
- The hash value of the function name to get the address of.
FARPROC GetProcAddressH(HMODULE hModule, DWORD dwApiNameHash) {
if (hModule == NULL || dwApiNameHash == NULL)
return NULL;
PBYTE pBase = (PBYTE)hModule;
PIMAGE_DOS_HEADER pImgDosHdr = (PIMAGE_DOS_HEADER)pBase;
if (pImgDosHdr->e_magic != IMAGE_DOS_SIGNATURE)
return NULL;
PIMAGE_NT_HEADERS pImgNtHdrs = (PIMAGE_NT_HEADERS)(pBase + pImgDosHdr->e_lfanew);
if (pImgNtHdrs->Signature != IMAGE_NT_SIGNATURE)
return NULL;
IMAGE_OPTIONAL_HEADER ImgOptHdr = pImgNtHdrs->OptionalHeader;
PIMAGE_EXPORT_DIRECTORY pImgExportDir = (PIMAGE_EXPORT_DIRECTORY)(pBase + ImgOptHdr.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
PDWORD FunctionNameArray = (PDWORD)(pBase + pImgExportDir->AddressOfNames);
PDWORD FunctionAddressArray = (PDWORD)(pBase + pImgExportDir->AddressOfFunctions);
PWORD FunctionOrdinalArray = (PWORD)(pBase + pImgExportDir->AddressOfNameOrdinals);
for (DWORD i = 0; i < pImgExportDir->NumberOfFunctions; i++) {
CHAR* pFunctionName = (CHAR*)(pBase + FunctionNameArray[i]);
PVOID pFunctionAddress = (PVOID)(pBase + FunctionAddressArray[FunctionOrdinalArray[i]]);
// Hashing every function name pFunctionName
// If both hashes are equal then we found the function we want
if (dwApiNameHash == HASHA(pFunctionName)) {
return pFunctionAddress;
}
}
return NULL;
}
GetModuleHandleH
The GetModuleHandleH
function is the same as GetModuleHandleReplacement
with the main difference being that the hash values of the JenkinsOneAtATime32Bit
string hashing algorithm will be used to compare the enumerated DLL names to the input hash. Notice how the function capitalizes the string in FullDllName.Buffer
, therefore, the dwModuleNameHash
parameter must be the hash value of a capitalized module name (e.g. USER32.DLL).
HMODULE GetModuleHandleH(DWORD dwModuleNameHash) {
if (dwModuleNameHash == NULL)
return NULL;
#ifdef _WIN64
PPEB pPeb = (PEB*)(__readgsqword(0x60));
#elif _WIN32
PPEB pPeb = (PEB*)(__readfsdword(0x30));
#endif
PPEB_LDR_DATA pLdr = (PPEB_LDR_DATA)(pPeb->Ldr);
PLDR_DATA_TABLE_ENTRY pDte = (PLDR_DATA_TABLE_ENTRY)(pLdr->InMemoryOrderModuleList.Flink);
while (pDte) {
if (pDte->FullDllName.Length != NULL && pDte->FullDllName.Length < MAX_PATH) {
// Converting `FullDllName.Buffer` to upper case string
CHAR UpperCaseDllName[MAX_PATH];
DWORD i = 0;
while (pDte->FullDllName.Buffer[i]) {
UpperCaseDllName[i] = (CHAR)toupper(pDte->FullDllName.Buffer[i]);
i++;
}
UpperCaseDllName[i] = '\0';
// hashing `UpperCaseDllName` and comparing the hash value to that's of the input `dwModuleNameHash`
if (HASHA(UpperCaseDllName) == dwModuleNameHash)
return pDte->Reserved2[0];
}
else {
break;
}
pDte = *(PLDR_DATA_TABLE_ENTRY*)(pDte);
}
return NULL;
}
Demo
This demo uses GetModuleHandleH
and GetProcAddressH
to call MessageBoxA
.
#define USER32DLL_HASH 0x81E3778E#define MessageBoxA_HASH 0xF10E27CAint main() {
// Load User32.dll to the current process so that GetModuleHandleH will work
if (LoadLibraryA("USER32.DLL") == NULL) {
printf("[!] LoadLibraryA Failed With Error : %d \n", GetLastError());
return 0;
}
// Getting the handle of user32.dll using GetModuleHandleH
HMODULE hUser32Module = GetModuleHandleH(USER32DLL_HASH);
if (hUser32Module == NULL){
printf("[!] Cound'nt Get Handle To User32.dll \n");
return -1;
}
// Getting the address of MessageBoxA function using GetProcAddressH
fnMessageBoxA pMessageBoxA = (fnMessageBoxA)GetProcAddressH(hUser32Module, MessageBoxA_HASH);
if (pMessageBoxA == NULL) {
printf("[!] Cound'nt Find Address Of Specified Function \n");
return -1;
}
// Calling MessageBoxA
pMessageBoxA(NULL, "Building Malware With Maldev", "Wow", MB_OK | MB_ICONEXCLAMATION);
printf("[#] Press <Enter> To Quit ... ");
getchar();
return 0;
}

Searching For MessageBox String
Using the Strings.exe Sysinternal Tool search for the string "MessageBox".

It can be observed that there is no corresponding string in our binary. MessageBoxA
was successfully called without being imported into the IAT or exposed as a string in our binary. This is applicable for both 32-bit and 64-bit systems.