IAT Hiding, Obfuscation & Camouflage
The Import Address Table (IAT) contains information regarding a PE file, such as the functions used and the DLLs exporting them. This type of information can be used to signature and detect the binary.
To hide functions from the IAT, it's possible to use GetProcAddress, GetModuleHandle or LoadLibrary to load these functions dynamically during runtime or we can create custom functions that perform the same actions as GetProcAddress and GetModuleHandle WinAPIs so we can dynamically load functions without having these two functions appear in the IAT.
Custom GetProcAddress
The GetProcAddress WinAPI retrieves the address of an exported function from a specified module handle. The function returns NULL if the function name is not found in the specified module handle.
The following function replaces GetProcAddress:
#include <Windows.h>
#include <stdio.h>
#include <winternl.h>
FARPROC GetProcAddressReplacement(IN HMODULE hModule, IN LPCSTR lpApiName) {
// we do this to avoid casting at each time we use 'hModule'
PBYTE pBase = (PBYTE)hModule;
// getting the dos header and doing a signature check
PIMAGE_DOS_HEADER pImgDosHdr = (PIMAGE_DOS_HEADER)pBase;
if (pImgDosHdr->e_magic != IMAGE_DOS_SIGNATURE)
return NULL;
// getting the nt headers and doing a signature check
PIMAGE_NT_HEADERS pImgNtHdrs = (PIMAGE_NT_HEADERS)(pBase + pImgDosHdr->e_lfanew);
if (pImgNtHdrs->Signature != IMAGE_NT_SIGNATURE)
return NULL;
// getting the optional header
IMAGE_OPTIONAL_HEADER ImgOptHdr = pImgNtHdrs->OptionalHeader;
// we can get the optional header like this as well
// PIMAGE_OPTIONAL_HEADER pImgOptHdr = (PIMAGE_OPTIONAL_HEADER)((ULONG_PTR)pImgNtHdrs + sizeof(DWORD) + sizeof(IMAGE_FILE_HEADER));
// getting the image export table
PIMAGE_EXPORT_DIRECTORY pImgExportDir = (PIMAGE_EXPORT_DIRECTORY) (pBase + ImgOptHdr.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
// getting the function's names array pointer
PDWORD FunctionNameArray = (PDWORD)(pBase + pImgExportDir->AddressOfNames);
// getting the function's addresses array pointer
PDWORD FunctionAddressArray = (PDWORD)(pBase + pImgExportDir->AddressOfFunctions);
// getting the function's ordinal array pointer
PWORD FunctionOrdinalArray = (PWORD)(pBase + pImgExportDir->AddressOfNameOrdinals);
// looping through all the exported functions
for (DWORD i = 0; i < pImgExportDir->NumberOfFunctions; i++){
// getting the name of the function
CHAR* pFunctionName = (CHAR*)(pBase + FunctionNameArray[i]);
// getting the address of the function through its ordinal
PVOID pFunctionAddress = (PVOID)(pBase + FunctionAddressArray[FunctionOrdinalArray[i]]);
// searching for the function specified
if (strcmp(lpApiName, pFunctionName) == 0){
// printf("[ %0.4d ] FOUND API -\t NAME: %s -\t ADDRESS: 0x%p -\t ORDINAL: %d\n", i, pFunctionName, pFunctionAddress, FunctionOrdinalArray[i]);
return pFunctionAddress;
}
// printf("[ %0.4d ] NAME: %s -\t ADDRESS: 0x%p -\t ORDINAL: %d\n", i, pFunctionName, pFunctionAddress, FunctionOrdinalArray[i]);
}
return NULL;
}
int main() {
printf("[+] Original GetProcAddress : 0x%p \n", GetProcAddress(GetModuleHandleA("NTDLL.DLL"), "NtAllocateVirtualMemory"));
printf("[+] GetProcAddress Replacement : 0x%p \n", GetProcAddressReplacement(GetModuleHandleA("NTDLL.DLL"), "NtAllocateVirtualMemory"));
/*
New function's prototype:
FARPROC GetProcAddressReplacement(IN HMODULE hModule, IN LPCSTR lpApiName) {}
*/
return 0;
}
Custom GetModuleHandle
The GetModuleHandle function retrieves a handle for a specified DLL. The function returns a handle to the DLL or NULL if the DLL does not exist in the calling process.
The following function replaces GetModuleHandle:
#include <Windows.h>
#include <stdio.h>
// "Structs.h" defines some structs from <winternl.h>, so if both included there will be redefinition errors
// to use winternl instead, comment the following (#include "Structs.h")
#include "Structs.h"
#ifndef STRUCTS
#include <winternl.h>
#endif // !STRUCTS
// https://learn.microsoft.com/en-us/windows/win32/api/ntdef/nf-ntdef-containing_record
#define CONTAINING_RECORD(address, type, field) \
((type *)((PCHAR)(address) - (ULONG_PTR)(&((type *)0)->field)))
// function helper that takes 2 strings
// convert them to lower case strings
// compare them, and return true if both are equal
// and false otherwise
BOOL IsStringEqual (IN LPCWSTR Str1, IN LPCWSTR Str2) {
WCHAR lStr1 [MAX_PATH],
lStr2 [MAX_PATH];
int len1 = lstrlenW(Str1),
len2 = lstrlenW(Str2);
int i = 0,
j = 0;
// checking - we dont want to overflow our buffers
if (len1 >= MAX_PATH || len2 >= MAX_PATH)
return FALSE;
// converting Str1 to lower case string (lStr1)
for (i = 0; i < len1; i++){
lStr1[i] = (WCHAR)tolower(Str1[i]);
}
lStr1[i++] = L'\0'; // null terminating
// converting Str2 to lower case string (lStr2)
for (j = 0; j < len2; j++) {
lStr2[j] = (WCHAR)tolower(Str2[j]);
}
lStr2[j++] = L'\0'; // null terminating
// comparing the lower-case strings
if (lstrcmpiW(lStr1, lStr2) == 0)
return TRUE;
return FALSE;
}
// function that replaces GetModuleHandle
// it uses pointers to enumerate in the dlls
HMODULE GetModuleHandleReplacement(IN LPCWSTR szModuleName) {
// getting peb
#ifdef _WIN64 // if compiling as x64
PPEB pPeb = (PEB*)(__readgsqword(0x60));
#elif _WIN32 // if compiling as x32
PPEB pPeb = (PEB*)(__readfsdword(0x30));
#endif
// geting Ldr
PPEB_LDR_DATA pLdr = (PPEB_LDR_DATA)(pPeb->Ldr);
// getting the first element in the linked list (contains information about the first module)
PLDR_DATA_TABLE_ENTRY pDte = (PLDR_DATA_TABLE_ENTRY)(pLdr->InMemoryOrderModuleList.Flink);
while (pDte) {
// if not null
if (pDte->FullDllName.Length != NULL) {
// check if both equal
if (IsStringEqual(pDte->FullDllName.Buffer, szModuleName)) {
wprintf(L"[+] Found Dll \"%s\" \n", pDte->FullDllName.Buffer);
#ifdef STRUCTS
return (HMODULE)(pDte->InInitializationOrderLinks.Flink);
#else
return (HMODULE)pDte->Reserved2[0];
#endif // STRUCTS
}
// wprintf(L"[i] \"%s\" \n", pDte->FullDllName.Buffer);
}
else {
break;
}
// next element in the linked list
pDte = *(PLDR_DATA_TABLE_ENTRY*)(pDte);
}
return NULL;
}
// function that replaces GetModuleHandle
// it uses head and node to enumerate in the dlls (doubly linked list concept)
// if you are not familiar with doubly linked lists its ok, you have the above implemantation
// that is more friendly
HMODULE GetModuleHandleReplacement2(IN LPCWSTR szModuleName) {
#ifdef _WIN64
PPEB pPeb = (PEB*)(__readgsqword(0x60));
#elif _WIN32
PPEB pPeb = (PEB*)(__readfsdword(0x30));
#endif
PLDR_DATA_TABLE_ENTRY pDte = (PLDR_DATA_TABLE_ENTRY)(pPeb->Ldr->InMemoryOrderModuleList.Flink);
// getting the head of the linked list ( used to get the node & to check the end of the list)
PLIST_ENTRY pListHead = (PLIST_ENTRY)&pPeb->Ldr->InMemoryOrderModuleList;
// getting the node of the linked list
PLIST_ENTRY pListNode = (PLIST_ENTRY)pListHead->Flink;
do
{
if (pDte->FullDllName.Length != NULL) {
if (IsStringEqual(pDte->FullDllName.Buffer, szModuleName)) {
wprintf(L"[+] Found Dll \"%s\" \n", pDte->FullDllName.Buffer);
#ifdef STRUCTS
return (HMODULE)(pDte->InInitializationOrderLinks.Flink);
#else
return (HMODULE)pDte->Reserved2[0];
#endif // STRUCTS
}
//wprintf(L"[i] \"%s\" \n", pDte->FullDllName.Buffer);
// updating pDte to point to the next PLDR_DATA_TABLE_ENTRY in the linked list
pDte = (PLDR_DATA_TABLE_ENTRY)(pListNode->Flink);
// updating the node variable to be the next node in the linked list
pListNode = (PLIST_ENTRY)pListNode->Flink;
}
// when the node is equal to the head, we reached the end of the linked list, so we break out of the loop
} while (pListNode != pListHead);
return NULL;
}
int main() {
printf("[i] Original 0x%p \n", GetModuleHandleW(L"NTDLL.DLL"));
printf("[i] Replacement 0x%p \n", GetModuleHandleReplacement(L"NTDLL.DLL"));
printf("[i] Replacement 2 0x%p \n", GetModuleHandleReplacement2(L"NTDLL.DLL"));
printf("[#] Press <Enter> To Quit ... ");
getchar();
/*
New function's prototype:
HMODULE GetModuleHandleReplacement(IN LPCWSTR szModuleName){}
*/
return 0;
}
And the needed header file strusts.h:
#pragma once
#include <Windows.h>
/*
header file that contains our own definition of LDR_DATA_TABLE_ENTRY & PEB ( and the needed structs to define them )
*/
#define STRUCTS
typedef struct _UNICODE_STRING {
USHORT Length;
USHORT MaximumLength;
PWSTR Buffer;
} UNICODE_STRING, * PUNICODE_STRING;
typedef PVOID PACTIVATION_CONTEXT;
typedef PVOID PRTL_USER_PROCESS_PARAMETERS;
typedef PVOID PAPI_SET_NAMESPACE;
// https://www.nirsoft.net/kernel_struct/vista/PEB_LDR_DATA.html
typedef struct _PEB_LDR_DATA {
ULONG Length;
ULONG Initialized;
PVOID SsHandle;
LIST_ENTRY InLoadOrderModuleList;
LIST_ENTRY InMemoryOrderModuleList;
LIST_ENTRY InInitializationOrderModuleList;
} PEB_LDR_DATA, * PPEB_LDR_DATA;
// https://www.nirsoft.net/kernel_struct/vista/LDR_DATA_TABLE_ENTRY.html
typedef struct _LDR_DATA_TABLE_ENTRY {
LIST_ENTRY InLoadOrderLinks;
LIST_ENTRY InMemoryOrderLinks;
LIST_ENTRY InInitializationOrderLinks;
PVOID DllBase;
PVOID EntryPoint;
ULONG SizeOfImage;
UNICODE_STRING FullDllName;
UNICODE_STRING BaseDllName;
ULONG Flags;
WORD LoadCount;
WORD TlsIndex;
union {
LIST_ENTRY HashLinks;
struct {
PVOID SectionPointer;
ULONG CheckSum;
};
};
union {
ULONG TimeDateStamp;
PVOID LoadedImports;
};
PACTIVATION_CONTEXT EntryPointActivationContext;
PVOID PatchInformation;
LIST_ENTRY ForwarderLinks;
LIST_ENTRY ServiceTagLinks;
LIST_ENTRY StaticLinks;
} LDR_DATA_TABLE_ENTRY, * PLDR_DATA_TABLE_ENTRY;
// https://github.com/processhacker/phnt/blob/master/ntpebteb.h#L69
typedef struct _PEB
{
BOOLEAN InheritedAddressSpace;
BOOLEAN ReadImageFileExecOptions;
BOOLEAN BeingDebugged;
union
{
BOOLEAN BitField;
struct
{
BOOLEAN ImageUsesLargePages : 1;
BOOLEAN IsProtectedProcess : 1;
BOOLEAN IsImageDynamicallyRelocated : 1;
BOOLEAN SkipPatchingUser32Forwarders : 1;
BOOLEAN IsPackagedProcess : 1;
BOOLEAN IsAppContainer : 1;
BOOLEAN IsProtectedProcessLight : 1;
BOOLEAN IsLongPathAwareProcess : 1;
};
};
HANDLE Mutant;
PVOID ImageBaseAddress;
PPEB_LDR_DATA Ldr;
PRTL_USER_PROCESS_PARAMETERS ProcessParameters;
PVOID SubSystemData;
PVOID ProcessHeap;
PRTL_CRITICAL_SECTION FastPebLock;
PSLIST_HEADER AtlThunkSListPtr;
PVOID IFEOKey;
union
{
ULONG CrossProcessFlags;
struct
{
ULONG ProcessInJob : 1;
ULONG ProcessInitializing : 1;
ULONG ProcessUsingVEH : 1;
ULONG ProcessUsingVCH : 1;
ULONG ProcessUsingFTH : 1;
ULONG ProcessPreviouslyThrottled : 1;
ULONG ProcessCurrentlyThrottled : 1;
ULONG ProcessImagesHotPatched : 1; // REDSTONE5
ULONG ReservedBits0 : 24;
};
};
union
{
PVOID KernelCallbackTable;
PVOID UserSharedInfoPtr;
};
ULONG SystemReserved;
ULONG AtlThunkSListPtr32;
PAPI_SET_NAMESPACE ApiSetMap;
ULONG TlsExpansionCounter;
PVOID TlsBitmap;
ULONG TlsBitmapBits[2];
PVOID ReadOnlySharedMemoryBase;
PVOID SharedData; // HotpatchInformation
PVOID* ReadOnlyStaticServerData;
PVOID AnsiCodePageData; // PCPTABLEINFO
PVOID OemCodePageData; // PCPTABLEINFO
PVOID UnicodeCaseTableData; // PNLSTABLEINFO
ULONG NumberOfProcessors;
ULONG NtGlobalFlag;
ULARGE_INTEGER CriticalSectionTimeout;
SIZE_T HeapSegmentReserve;
SIZE_T HeapSegmentCommit;
SIZE_T HeapDeCommitTotalFreeThreshold;
SIZE_T HeapDeCommitFreeBlockThreshold;
ULONG NumberOfHeaps;
ULONG MaximumNumberOfHeaps;
PVOID* ProcessHeaps; // PHEAP
PVOID GdiSharedHandleTable;
PVOID ProcessStarterHelper;
ULONG GdiDCAttributeList;
PRTL_CRITICAL_SECTION LoaderLock;
ULONG OSMajorVersion;
ULONG OSMinorVersion;
USHORT OSBuildNumber;
USHORT OSCSDVersion;
ULONG OSPlatformId;
ULONG ImageSubsystem;
ULONG ImageSubsystemMajorVersion;
ULONG ImageSubsystemMinorVersion;
KAFFINITY ActiveProcessAffinityMask;
ULONG GdiHandleBuffer[60];
PVOID PostProcessInitRoutine;
PVOID TlsExpansionBitmap;
ULONG TlsExpansionBitmapBits[32];
ULONG SessionId;
ULARGE_INTEGER AppCompatFlags;
ULARGE_INTEGER AppCompatFlagsUser;
PVOID pShimData;
PVOID AppCompatInfo; // APPCOMPAT_EXE_DATA
UNICODE_STRING CSDVersion;
PVOID ActivationContextData; // ACTIVATION_CONTEXT_DATA
PVOID ProcessAssemblyStorageMap; // ASSEMBLY_STORAGE_MAP
PVOID SystemDefaultActivationContextData; // ACTIVATION_CONTEXT_DATA
PVOID SystemAssemblyStorageMap; // ASSEMBLY_STORAGE_MAP
SIZE_T MinimumStackCommit;
PVOID SparePointers[2]; // 19H1 (previously FlsCallback to FlsHighIndex)
PVOID PatchLoaderData;
PVOID ChpeV2ProcessInfo; // _CHPEV2_PROCESS_INFO
ULONG AppModelFeatureState;
ULONG SpareUlongs[2];
USHORT ActiveCodePage;
USHORT OemCodePage;
USHORT UseCaseMapping;
USHORT UnusedNlsField;
PVOID WerRegistrationData;
PVOID WerShipAssertPtr;
union
{
PVOID pContextData; // WIN7
PVOID pUnused; // WIN10
PVOID EcCodeBitMap; // WIN11
};
PVOID pImageHeaderHash;
union
{
ULONG TracingFlags;
struct
{
ULONG HeapTracingEnabled : 1;
ULONG CritSecTracingEnabled : 1;
ULONG LibLoaderTracingEnabled : 1;
ULONG SpareTracingBits : 29;
};
};
ULONGLONG CsrServerReadOnlySharedMemoryBase;
PRTL_CRITICAL_SECTION TppWorkerpListLock;
LIST_ENTRY TppWorkerpList;
PVOID WaitOnAddressHashTable[128];
PVOID TelemetryCoverageHeader; // REDSTONE3
ULONG CloudFileFlags;
ULONG CloudFileDiagFlags; // REDSTONE4
CHAR PlaceholderCompatibilityMode;
CHAR PlaceholderCompatibilityModeReserved[7];
struct _LEAP_SECOND_DATA* LeapSecondData; // REDSTONE5
union
{
ULONG LeapSecondFlags;
struct
{
ULONG SixtySecondEnabled : 1;
ULONG Reserved : 31;
};
};
ULONG NtGlobalFlag2;
ULONGLONG ExtendedFeatureDisableMask; // since WIN11
} PEB, * PPEB;
API Hashing
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.
#include <Windows.h>
#include <stdio.h>
#include <winternl.h>
#define INITIAL_SEED 7
UINT32 HashStringJenkinsOneAtATime32BitA(_In_ PCHAR String)
{
SIZE_T Index = 0;
UINT32 Hash = 0;
SIZE_T Length = lstrlenA(String);
while (Index != Length)
{
Hash += String[Index++];
Hash += Hash << INITIAL_SEED;
Hash ^= Hash >> 6;
}
Hash += Hash << 3;
Hash ^= Hash >> 11;
Hash += Hash << 15;
return Hash;
}
UINT32 HashStringJenkinsOneAtATime32BitW(_In_ PWCHAR String)
{
SIZE_T Index = 0;
UINT32 Hash = 0;
SIZE_T Length = lstrlenW(String);
while (Index != Length)
{
Hash += String[Index++];
Hash += Hash << INITIAL_SEED;
Hash ^= Hash >> 6;
}
Hash += Hash << 3;
Hash ^= Hash >> 11;
Hash += Hash << 15;
return Hash;
}
// macros used to make the code neater & cleaner
// HASHA pass the input string to HashStringJenkinsOneAtATime32BitA
// HASHW pass the input string to HashStringJenkinsOneAtATime32BitW
#define HASHA(API) (HashStringJenkinsOneAtATime32BitA((PCHAR) API))
#define HASHW(API) (HashStringJenkinsOneAtATime32BitW((PWCHAR) API))
/*
- `dwApiNameHash` is the hash value of the function name
of the function specified to get it's address.
- The function is exported by a dll of a handle `hModule`
(`hModule` is returned by GetModuleHandleH)
*/
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;
}
/*
- dwModuleNameHash is the hash of the dll name to get the handle of.
the name should be hashed in *UPPER* case letters - capitalized;
HASHA("NTDLL.DLL") - HASHA("USER32.DLL") - HASHA("KERNEL32.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;
}
// https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-messageboxa
typedef int (WINAPI* fnMessageBoxA)(
HWND hWnd,
LPCSTR lpText,
LPCSTR lpCaption,
UINT uType
);
/*
printf("[i] Hash Of \"%s\" Is : 0x%0.8X \n", "USER32.DLL", HASHA("USER32.DLL"));
printf("[i] Hash Of \"%s\" Is : 0x%0.8X \n", "MessageBoxA", HASHA("MessageBoxA"));
// [OUTPUT] //
[i] Hash Of "USER32.DLL" Is : 0x81E3778E
[i] Hash Of "MessageBoxA" Is : 0xF10E27CA
*/
// hard coded hashes
#define USER32DLL_HASH 0x81E3778E
#define MessageBoxA_HASH 0xF10E27CA
int main() {
// load user32.dll to the current process, so that GetModuleHandleH sill 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, "Hello", "Bye", MB_OK | MB_ICONEXCLAMATION);
printf("[#] Press <Enter> To Quit ... ");
getchar();
return 0;
}
Compile Time API Hashing
With compile time API hashing, however, dynamic hashes are generated every time the binary is compiled so module hashes are not hard coded and the binary signature changes every time its compiled.
This works with the constexpr operator in C++, which is used to indicate that a function or variable can be evaluated at compile time.
#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;
}
// https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-messageboxa
typedef int (WINAPI* fnMessageBoxA)(
HWND hWnd,
LPCSTR lpText,
LPCSTR lpCaption,
UINT uType
);
// https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-messageboxw
typedef int (WINAPI* fnMessageBoxW)(
HWND hWnd,
LPCWSTR lpText,
LPCWSTR lpCaption,
UINT uType
);
// create compile time variables
CTIME_HASHA(MessageBoxA) // this will create `MessageBoxA_Rotr32A` variable
CTIME_HASHW(MessageBoxW) // this will create `MessageBoxW_Rotr32W` variable
// The above CTIME_HASHA(MessageBoxA) will do the following:
// constexpr auto MessageBoxA_Rotr32A = HashStringDjb2A((const char*)"MessageBoxA");
// The above CTIME_HASHW(MessageBoxW) will do the following:
// constexpr auto MessageBoxW_Rotr32W = HashStringDjb2W((const wchar_t*)L"MessageBoxW");
int main() {
HMODULE hUser32Module = NULL;
if ((hUser32Module = LoadLibraryA("USER32.DLL")) == NULL) {
printf("[!] LoadLibraryA Failed With Error : %d \n", GetLastError());
return 0;
}
/*
// printing values of hashes (to verify it is changing every time it is compiled)
printf("[i] MessageBoxA_Rotr32A : 0x%0.8X \n", MessageBoxA_Rotr32A);
printf("[i] MessageBoxW_Rotr32W : 0x%0.8X \n", MessageBoxW_Rotr32W);
*/
// MessageBoxA_Rotr32A created by CTIME_HASHA(MessageBoxA)
fnMessageBoxA pMessageBoxA = (fnMessageBoxA)GetProcAddressH(hUser32Module, MessageBoxA_Rotr32A);
if (pMessageBoxA == NULL) {
return -1;
}
// MessageBoxW_Rotr32W created by CTIME_HASHW(MessageBoxW)
fnMessageBoxW pMessageBoxW = (fnMessageBoxW)GetProcAddressH(hUser32Module, MessageBoxW_Rotr32W);
if (pMessageBoxW == NULL) {
return -1;
}
pMessageBoxA(NULL, "Building Malware With Maldev", "Wow", MB_OK | MB_ICONINFORMATION);
pMessageBoxW(NULL, L"Malware Is Bad For Your Health", L"Danger", MB_OK | MB_ICONWARNING);
printf("[#] Press <Enter> To Quit ... ");
getchar();
return 0;
}
IAT Camouflage
Hiding or obfuscating the IAT can raise suspicion if the binary file imports very few or zero WinAPI functions. Having an implementation with a fake IAT is more effective than having no imported functions.
This can be done by using benign WinAPIs that do not change the behavior of the program. For this, we can useWinAPIs with NULL parameters or using the WinAPIs on dummy data that will not affect the program.
Example:
#include <Windows.h>
// generate a random compile-time seed
int RandomCompileTimeSeed(void)
{
return '0' * -40271 +
__TIME__[7] * 1 +
__TIME__[6] * 10 +
__TIME__[4] * 60 +
__TIME__[3] * 600 +
__TIME__[1] * 3600 +
__TIME__[0] * 36000;
}
// a dummy function that makes the if-statement in 'IatCamouflage' interesting
PVOID Helper(PVOID *ppAddress) {
PVOID pAddress = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, 0xFF);
if (!pAddress)
return NULL;
// setting the first 4 bytes in pAddress to be equal to a random number (less than 255)
*(int*)pAddress = RandomCompileTimeSeed() % 0xFF;
// saving the base address by pointer, and returning it
*ppAddress = pAddress;
return pAddress;
}
// function that imports WinAPIs function, but never calls them
VOID IatCamouflage() {
PVOID pAddress = NULL;
// this is the same as saying : A = RandomCompileTimeSeed() % 0xFF
int* A = (int*)Helper(&pAddress);
// impposible if-statement that will never run
if (*A > 350) {
// some random whitelisted WinAPIs
unsigned __int64 i = MessageBoxA(NULL, NULL, NULL, NULL);
i = GetLastError();
i = SetCriticalSectionSpinCount(NULL, NULL);
i = GetWindowContextHelpId(NULL);
i = GetWindowLongPtrW(NULL, NULL);
i = RegisterClassW(NULL);
i = IsWindowVisible(NULL);
i = ConvertDefaultLocale(NULL);
i = MultiByteToWideChar(NULL, NULL, NULL, NULL, NULL, NULL);
i = IsDialogMessageW(NULL, NULL);
}
// freeing the buffer allocated in 'Helper'
HeapFree(GetProcessHeap(), 0, pAddress);
}
int main() {
IatCamouflage();
return 0;
}
Last updated