57. IAT Hiding & Obfuscation - Compile Time API Hashing

IAT Hiding & Obfuscation - Compile Time API Hashing

Introduction

In the previous API Hashing module, the hashes of the functions and modules were generated before adding them to the code. Unfortunately, that can be highly time-consuming and can be avoided by using Compile Time API Hashing.

Furthermore, in the previous module hashes were hard coded which can allow security solutions to use them as IoC, if they are not updated in each implementation. With compile time API hashing, however, dynamic hashes are generated every time the binary is compiled.

Caveat

This method only works with C++ projects due to the use of the constexpr keyword. The constexpr operator in C++ is used to indicate that a function or variable can be evaluated at compile time. In addition, the constexpr operator on functions and variables improves the performance of an application by allowing the compiler to perform certain calculations at compile time rather than at runtime.

Compile Time Hashing Walkthrough

The sections below walk through the steps required to implement compile time hashing.

Create Compile Time Functions

The first step is to convert the hashing functions that will be used to become compile time functions using the constexpr operator. In this case, the Dbj2 hashing algorithm will be modified to use the constexpr operator.

#define        SEED       5// Compile time Djb2 hashing function (WIDE)
constexpr DWORD HashStringDjb2W(const wchar_t* String) {
	ULONG Hash = (ULONG)g_KEY;
	INT c = 0;
	while ((c = *String++)) {
		Hash = ((Hash << SEED) + Hash) + c;
	}

	return Hash;
}

// Compile time Djb2 hashing function (ASCII)
constexpr DWORD HashStringDjb2A(const char* String) {
	ULONG Hash = (ULONG)g_KEY;
	INT c = 0;
	while ((c = *String++)) {
		Hash = ((Hash << SEED) + Hash) + c;
	}

	return Hash;
}

The undefined variable, g_KEY, is used as the initial hash in both functions. g_KEY is a global constexpr variable and is randomly generated by a function named RandomCompileTimeSeed (explained below), on each compilation of the binary.

Generating a Random Seed Value

RandomCompileTimeSeed is used to generate a random seed value based on the current time. It does this by extracting the digits from the TIME macro, which is a predefined macro in C++ that expands to the current time in the HH:MM:SS format. Then, the RandomCompileTimeSeed function multiplies each digit by a different random constant and adds them all together to produce a final seed value.

// Generate a random key at compile time which is used as the initial hash
constexpr int RandomCompileTimeSeed(void)
{
	return '0' * -40271 +
		__TIME__[7] * 1 +
		__TIME__[6] * 10 +
		__TIME__[4] * 60 +
		__TIME__[3] * 600 +
		__TIME__[1] * 3600 +
		__TIME__[0] * 36000;
};

// The compile time random seed
constexpr auto g_KEY = RandomCompileTimeSeed() % 0xFF;

Creating Macros

Next, define two macros, RTIME_HASHA and RTIME_HASHW, to be used by the GetProcAddressH function during runtime to compare hashes. The macros should be defined as follows.

#define RTIME_HASHA( API ) HashStringDjb2A((const char*) API)       // Calling HashStringDjb2A#define RTIME_HASHW( API ) HashStringDjb2W((const wchar_t*) API)    // Calling HashStringDjb2W

Once a random compile time hashing function is established, the next step is to declare compile time hash values in variables. To streamline the process, two macros will be implemented.

#define CTIME_HASHA( API ) constexpr auto API##_Rotr32A = HashStringDjb2A((const char*) #API);#define CTIME_HASHW( API ) constexpr auto API##_Rotr32W = HashStringDjb2W((const wchar_t*) L#API);

Stringizing Operator

The # symbol is known as the stringizing operator. It is used to convert a preprocessor macro parameter into a string literal.

For example, if the CTIME_HASHA macro is called with the argument SomeFunction, like HASHA(SomeFunction), the #API expression would be replaced with the string literal "SomeFunction".

Merging Operator

The ## operator is known as the merging operator. It is used to combine two preprocessor macros into a single macro. The ## operator is used to combine the API parameter with the string "_Rotr32A" or "_Rotr32W", respectively, to form the final name of the variable being defined.

For example, if the CTIME_HASHA macro is called with the argument SomeFunction, like HASHA(SomeFunction), the ## operator would combine API with "_Rotr32A" to form the final variable name SomeFunction_Rotr32A.

Macro Expansion Demo

To better understand how the previous macros work, the image below shows an example using the CTIME_HASHA macro to create a hash for MessageBoxA by creating a variable called MessageBoxA_Rotr32A that will hold the compile time hash value.

Compile Time Hashing - Code

After putting all the pieces together, the code will be as shown below.

#include <Windows.h>#include <stdio.h>#include <winternl.h>#define        SEED       5// generate a random key (used as initial hash)
constexpr int RandomCompileTimeSeed(void)
{
	return '0' * -40271 +
		__TIME__[7] * 1 +
		__TIME__[6] * 10 +
		__TIME__[4] * 60 +
		__TIME__[3] * 600 +
		__TIME__[1] * 3600 +
		__TIME__[0] * 36000;
};

constexpr auto g_KEY = RandomCompileTimeSeed() % 0xFF;


// Compile time Djb2 hashing function (WIDE)
constexpr DWORD HashStringDjb2W(const wchar_t* String) {
	ULONG Hash = (ULONG)g_KEY;
	INT c = 0;
	while ((c = *String++)) {
		Hash = ((Hash << SEED) + Hash) + c;
	}

	return Hash;
}

// Compile time Djb2 hashing function (ASCII)
constexpr DWORD HashStringDjb2A(const char* String) {
	ULONG Hash = (ULONG)g_KEY;
	INT c = 0;
	while ((c = *String++)) {
		Hash = ((Hash << SEED) + Hash) + c;
	}

	return Hash;
}


// runtime hashing macros
#define RTIME_HASHA( API ) HashStringDjb2A((const char*) API)#define RTIME_HASHW( API ) HashStringDjb2W((const wchar_t*) API)// compile time hashing macros (used to create variables)
#define CTIME_HASHA( API ) constexpr auto API##_Rotr32A = HashStringDjb2A((const char*) #API);#define CTIME_HASHW( API ) constexpr auto API##_Rotr32W = HashStringDjb2W((const wchar_t*) L#API);


FARPROC GetProcAddressH(HMODULE hModule, DWORD dwApiNameHash) {

	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]]);

		if (dwApiNameHash == RTIME_HASHA(pFunctionName)) { // runtime hash value check
			return (FARPROC)pFunctionAddress;
		}
	}

	return NULL;
}

Demo

This demo calls MessageBoxA and MessageBoxW using compile time API hashing using the MessageBoxA_Rotr32A compile time variable.

Check for IoCs

Use the Sysinternal Strings tool to search for the "MessageBox".

Use the Dumpbin tool to check the IAT for anything related to MessageBox.

Running The Binary

Run the binary and see in fact MessageBox is being used.

Verify Dynamic Hash Value

Print the hash values to the console in order to verify it's being modified every time the code is compiled.

Rebuild the Visual Studio Project, check the hash values again and notice that the hash values are different from the previous run.