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.

#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:

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.