Zumpyx's Blog

Windows Maldev - ITA 隐藏与混淆

maldevWindowsPEITAobfuscationMaldev

简介

ITA(Import Table Address) 包含有关 PE 文件的信息,例如使用的函数名以及 DLL,此类信息常用于对二进制文件进行标记和检测。例如:

image-20241024083658530.png

可以看到,这个 PE 文件导入了 CreateRemoteThreadOpenProcessVirtualAllocExWriteProcessMemory 等函数,Shellcode 加载流程尽收眼底。

想要隐藏这些内容,可以使用 GetProcAddressGetModuleHandleLoadLibrary 在运行时动态加载这些函数。

但这又会出现其他问题。

  • 动态导入的函数相关字符串会出现在 PE 文件中,这个可以用于签名检测
  • GetProcAddressGetModuleHandle 函数的导入信息也会出现在 ITA 中

想隐藏字符串,那只能通过替换,可以通过字符串加密、Hash 运算等方法;对于 GetProcAddressGetModuleHandle 函数的信息,可以通过自实现来隐藏。两者结合。

Hash 函数

Hash 函数的选择,最好是能够轻量实现,并且分布均匀(以防冲突),这里的 Demo 选用 JenkinsOneAtATime32Bit 算法,实现来自 VX-API GitHub 仓库

#define HASHA(API) (HashStringJenkinsOneAtATime32BitA((PCHAR) API))
UINT32 HashStringJenkinsOneAtATime32BitA(_In_ PCHAR String)
{
	SIZE_T Index = 0;
	UINT32 Hash = 0;
	SIZE_T Length = strlen(String);
	while (Index != Length)
	{
		Hash += String[Index++];
		Hash += Hash << 7; // 这里的种子可以更改
		Hash ^= Hash >> 6;
	}
	Hash += Hash << 3;
	Hash ^= Hash >> 11;
	Hash += Hash << 15;
	return Hash;
}

为了灵活和方便使用,设置了 HASHA 宏。

实现 GetModuleHandle

GetModuleHandle 函数用于在内存中检索指定 DLL 的句柄。函数返回 DLL 的句柄 (HMODULE) 或 NULL 。

GetModuleHandle 原理

HMODULE 数据类型是加载的 DLL 的基地址,表示 DLL 在进程地址空间中的位置。因此,替换函数的目标是检索指定 DLL 的基地址。

typedef struct _PEB {
  BYTE                          Reserved1[2];
  BYTE                          BeingDebugged;
  BYTE                          Reserved2[1];
  PVOID                         Reserved3[2];
  PPEB_LDR_DATA                 Ldr; // 该结构包含关于进程加载模块的信息。
  PRTL_USER_PROCESS_PARAMETERS  ProcessParameters;
  PVOID                         Reserved4[3];
  PVOID                         AtlThunkSListPtr;
  PVOID                         Reserved5;
  ULONG                         Reserved6;
  PVOID                         Reserved7;
  ULONG                         Reserved8;
  ULONG                         AtlThunkSListPtr32;
  PVOID                         Reserved9[45];
  BYTE                          Reserved10[96];
  PPS_POST_PROCESS_INIT_ROUTINE PostProcessInitRoutine;
  BYTE                          Reserved11[128];
  PVOID                         Reserved12[1];
  ULONG                         SessionId;
} PEB, *PPEB;

PEB 结构的 PEB_LDR_DATA Ldr 成员包含进程中加载的 DLL 的信息。

typedef struct _PEB_LDR_DATA {
  BYTE       Reserved1[8];
  PVOID      Reserved2[3];
  LIST_ENTRY InMemoryOrderModuleList;  // 内存中模块列表
} PEB_LDR_DATA, *PPEB_LDR_DATA;

对应 LIST_ENTRY 结构是一个双向链表,

typedef struct _LIST_ENTRY {
   struct _LIST_ENTRY *Flink;
   struct _LIST_ENTRY *Blink;
} LIST_ENTRY, *PLIST_ENTRY, *RESTRICTED_POINTER PRLIST_ENTRY;

image-20241025085631337.png

微软文档中说明,_PEB_LDR_DATA 的 InMemeoryOrderModuleList 成员包含进程加载模块的双向链表的头部。列表中的每个项都是指向 LDR_DATA_TABLE_ENTRY 结构的指针。并且给出了 _LDR_DATA_TABLE_ENTRY 结构

typedef struct _LDR_DATA_TABLE_ENTRY {
    PVOID Reserved1[2];
    LIST_ENTRY InMemoryOrderLinks;
    PVOID Reserved2[2];
    PVOID DllBase;     // DLL 基地址
    PVOID EntryPoint;
    PVOID Reserved3;
    UNICODE_STRING FullDllName;  // 已加载 DLL 模块的文件名
    BYTE Reserved4[8];
    PVOID Reserved5[3];
    union {
        ULONG CheckSum;
        PVOID Reserved6;
    };
    ULONG TimeDateStamp;
} LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;

因此,需要关注的两个重点变量已经找到

  • DLL 基地址: PEB -> Ldr -> InMemoryOrderModuleList -> Flink -> DllBase (这个其实不是)
  • DLL 文件名: PEB -> Ldr -> InMemoryOrderModuleList -> Flink -> FullDllName

简单实现

#include <stdio.h>
#include <Windows.h>
#include <winternl.h>

HMODULE Yes_GetModuleHandle(IN LPCWSTR szModuleName) {

	// 获取 peb
#ifdef _WIN64 // 如果编译为 x64
	PPEB            pPeb = (PEB*)(__readgsqword(0x60));
#elif _WIN32 // 如果编译为 x32
	PPEB            pPeb = (PEB*)(__readfsdword(0x30));
#endif
	// 获取 Ldr
	PPEB_LDR_DATA pLdr = (PPEB_LDR_DATA)(pPeb->Ldr);
	// 获取链表中的第一个元素,其中包含第一个模块的信息 
	PLDR_DATA_TABLE_ENTRY pDte = (PLDR_DATA_TABLE_ENTRY)(pLdr->InMemoryOrderModuleList.Flink);

    while (pDte->FullDllName.Length != NULL) {
		printf("[+] Found Dll %-15ls DLLBase: 0x%p Reserved2[0]: 0x%p \n", pDte->FullDllName.Buffer, pDte->DllBase, pDte->Reserved2[0]);
        pDte = *(PLDR_DATA_TABLE_ENTRY*)(pDte);
    }
    return NULL;
}

int main() {
	GetModuleHandleH("");
	getchar();
	return 0;
}

image-20241025093521044.png

对比 Process Hacker 打开的进程信息

image-20241025093502653.png

发现 pDte->Reserved2[0] 成员是需要的 DLL 基地址。

现在枚举 DLL 文件名,通过 Hash 校验来确定目标 DLL 基地址。可以实现对函数名进行 Hash 计算

#define DEBUG
#ifdef DEBUG
#define DLL_NUM 3

void showHash() {
	int i, j;
	char dllName;
	char* dllList[DLL_NUM] = {
		"NTDLL.DLL",
		"USER32.DLL",
		"KERNEL32.DLL",
	};
	for (i = 0; i < DLL_NUM; i++) {
		char temp;
		printf("#define ");
		for (j = 0; dllList[i][j] != '.'; j++)
			printf("%c", dllList[i][j]);
		if (j < 7)
			printf("\t");
		printf("\t0x%0.8X \n", HASHA(dllList[i]));
	}
}
#endif // DEBUG

image-20241025100804977.png

这样比较方便去定义,并且统一转换成大写。

接下来更新一下 Yes_GetModuleHandle 函数为 Yes_GetModuleHandle_FromHash

这里大小写使用三元运算符实现,可以定义为一个宏

#define DLL_MAX_PATH	256
#define TO_UPPER(C) C >= 'a' && C <= 'z' ? C + 'A' - 'a' : C
HMODULE GetModuleHandleH(IN UINT32 ui32DllHash) {

#ifdef _WIN64	// 获取 x64 peb
	PPEB            pPeb = (PEB*)(__readgsqword(0x60));
#elif _WIN32	// 获取 x86 peb
	PPEB            pPeb = (PEB*)(__readfsdword(0x30));
#endif
	
	PPEB_LDR_DATA pLdr = (PPEB_LDR_DATA)(pPeb->Ldr);		// 获取 Ldr
	PLDR_DATA_TABLE_ENTRY pDte = (PLDR_DATA_TABLE_ENTRY)(pLdr->InMemoryOrderModuleList.Flink);

	while (pDte->FullDllName.Length != NULL) {
		
		CHAR UpperCaseDllName[MAX_PATH] = { 0 };
		for (int i = 0; i < pDte->FullDllName.Length; i++)	// 转化成大写
			UpperCaseDllName[i] = (CHAR)TO_UPPER(pDte->FullDllName.Buffer[i]);

		if (ui32DllHash == HASHA(UpperCaseDllName))			// 判断 Hash 是否一致
			return pDte->Reserved2[0];						// 返回 DLL Base Address

		pDte = *(PLDR_DATA_TABLE_ENTRY*)(pDte);				// 链表下一节点
	}
	return NULL;
}

测试一下效果,没啥问题。

image-20241025103841263.png

上文的代码中关于 GetModuleHandle 的实现使用了 winternl.h 头文件,这里可以使用 NirSoftProcess Hacker 的结构体来代替。最终效果 仅需导入 <windows.h> 头文件。

实现 GetProcAddress

GetProcAddress 函数从 DLL 句柄中获取导出函数的地址。如果未找到函数名,返回 NULL

GetProcAddress 原理

要访问导出的函数,需要访问 DLL 的导出表并在其中循环查找目标函数名称。

PE 头模块时提到,导出表是定义为 IMAGE_EXPORT_DIRECTORY 的结构。微软文档

image-20241025110446188.png

typedef struct _IMAGE_EXPORT_DIRECTORY {
    DWORD   Characteristics;
    DWORD   TimeDateStamp;
    WORD    MajorVersion;
    WORD    MinorVersion;
    DWORD   Name;
    DWORD   Base;
    DWORD   NumberOfFunctions;   // 导出地址表中的条目数量
    DWORD   NumberOfNames;
	DWORD AddressOfFunctions;    // 函数地址(相对于镜像基地址的 RVA)
	DWORD AddressOfNames;        // 名称地址(相对于镜像基地址的 RVA)
	DWORD AddressOfNameOrdinals; // 名称序号地址(相对于镜像基地址的 RVA)
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;

需要关注的就是最后三个成员

  • AddressOfFunctions - 指定导出函数地址数组的地址。
  • AddressOfNames - 指定导出函数名称地址数组的地址。
  • AddressOfNameOrdinals - 指定导出函数的序号数组的地址。

简单实现

FARPROC Yes_GetProcAddress(IN HMODULE hModule, IN LPCSTR lpApiName) {
	PBYTE pBase = (PBYTE)hModule;	// 转化一下
	
	PIMAGE_DOS_HEADER pImgDosHdr = (PIMAGE_DOS_HEADER)pBase;	// 获取 DOS 头
	if (pImgDosHdr->e_magic != IMAGE_DOS_SIGNATURE)				// 签名检查
		return NULL;
	PIMAGE_NT_HEADERS pImgNtHdrs = (PIMAGE_NT_HEADERS)(pBase + pImgDosHdr->e_lfanew);	// 获取 NT 头
	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]);						// 获取函数的名称
		WORD wFunctionOrdinal = FunctionOrdinalArray[i];									// 获取函数的序号
		PVOID pFunctionAddress = (PVOID)(pBase + FunctionAddressArray[wFunctionOrdinal]);	// 通过函数序数值获取函数地址
		printf("[ No.%0.4d ] addr:0x%p Name:%s \n", wFunctionOrdinal, pFunctionAddress, pFunctionName);
	}
}

image-20241025122811847.png

效果还可以,与 GetModuleHandle 一样,可以加入 Hash 校验,来确定目标函数。

FARPROC Yes_GetProcAddress_FromHash(IN HMODULE hModule, IN UINT32 ui32FuncHash) {
	PBYTE pBase = (PBYTE)hModule;	// 转化一下
	
	PIMAGE_DOS_HEADER pImgDosHdr = (PIMAGE_DOS_HEADER)pBase;	// 获取 DOS 头
	if (pImgDosHdr->e_magic != IMAGE_DOS_SIGNATURE)				// 签名检查
		return NULL;
	PIMAGE_NT_HEADERS pImgNtHdrs = (PIMAGE_NT_HEADERS)(pBase + pImgDosHdr->e_lfanew);	// 获取 NT 头
	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++)
		if (HASHA(pBase + FunctionNameArray[i]) == ui32FuncHash)							// 比较函数名的 Hash
			return (PVOID)(pBase + FunctionAddressArray[FunctionOrdinalArray[i]]);			// 通过函数序数值获取函数地址
}

在 Main 函数中调用检查

#define KERNEL32        0xD4D19933
#define LoadLibraryA                    0x000000000FA9B202

int main() {
	printf("[+] WinApi: 0x%p - HashApi: 0x%p Func: %s", GetProcAddress(Yes_GetModuleHandle_FromHash(KERNEL32), "LoadLibraryA"), Yes_GetProcAddress_FromHash(Yes_GetModuleHandle_FromHash(KERNEL32), LoadLibraryA), "LoadLibraryA");
	return 0;
}

image-20241025131445933.png

动态加载 Win Api

目前已经实现了 GetModuleHandle 和 GetProcAddress,可以动态加载函数了,在加载之前,需要声明函数类型,以便在获取函数地址后进行强制转换。

Demo 计划实现一个代替 printf 函数的宏实现

#define PRINTA( STR, ... )																        \
    if (1) {																			        \
        LPSTR buf = (LPSTR)pRtlAllocateHeap(pGetProcessHeap(), HEAP_ZERO_MEMORY, 1024 );		\
        if ( buf != NULL ) {															        \
            int len = pwsprintfA( buf, STR, __VA_ARGS__ );								        \
            pWriteConsoleA( pGetStdHandle( STD_OUTPUT_HANDLE ), buf, len, NULL, NULL );	        \
            pHeapFree( pGetProcessHeap(), 0, buf );										        \
        }																				        \
    }

其中用到了以下函数

  • wsprintfA (user32.dll)
  • HeapFree (kernel32.dll)
  • RtlAllocateHeap (ntdll.dll)
  • GetStdHandle (kernel32.dll)
  • GetProcessHeap (kernel32.dll)
  • WriteConsoleA (kernel32.dll)

另外还会用到 LoadLibraryA (kernel32.dll),在某些情况进程中没有 user32.dll 时来加载它。

函数定义

按照声明格式写好模板

typedef HMODULE(WINAPI* fnLoadLibraryA)(LPCSTR lpLibFileName);
typedef BOOL(WINAPI* fnHeapFree)(HANDLE hHeap, DWORD dwFlags, _Frees_ptr_opt_ LPVOID lpMem);
typedef LPVOID(WINAPI* fnRtlAllocateHeap)(PVOID HeapHandle, ULONG Flags, SIZE_T Size);
typedef HANDLE(WINAPI* fnGetProcessHeap)();
typedef HANDLE(WINAPI* fnGetStdHandle)(DWORD nStdHandle);
typedef BOOL(WINAPI* fnWriteConsoleA)(HANDLE  hConsoleOutput, const VOID* lpBuffer, DWORD nNumberOfCharsToWrite, LPDWORD lpNumberOfCharsWritten, LPVOID  lpReserved);
typedef HANDLE(WINAPI* fnGetProcessHeap)();
typedef int(WINAPIV* fnwsprintfA)(LPSTR unnamedParam1, LPCSTR unnamedParam2, ...);

准备好 Hash

// DLL Name Hash
#define HASH_NTDLL              0x4898F593
#define HASH_USER32             0x81E3778E
#define HASH_KERNEL32           0x367DC15A
// WIN API Name Hash
#define Hash_LoadLibraryA       0x19F0EEAF
#define Hash_wsprintfA          0x2AF0CBF4
#define Hash_HeapFree           0x1154459B
#define Hash_HeapAlloc          0x2F9B708A
#define Hash_RtlAllocateHeap    0xF5613878
#define Hash_GetStdHandle       0x0C685B0E
#define Hash_WriteConsoleA      0x65304EA1
#define Hash_GetProcessHeap     0x3BCCFAC6

创建全局函数指针变量,用 api_init 函数初始化所有函数指针

fnLoadLibraryA pLoadLibraryA;
fnHeapFree pHeapFree;
fnHeapAlloc pHeapAlloc;
fnGetStdHandle pGetStdHandle;
fnWriteConsoleA pWriteConsoleA;
fnGetProcessHeap pGetProcessHeap;
fnwsprintfA pwsprintfA;
fnRtlAllocateHeap pRtlAllocateHeap;
void api_init() {
	HMODULE hModule_Kernel32 = Yes_GetModuleHandle_FromHash(HASH_KERNEL32);
	if (hModule_Kernel32 == NULL) {
		printf("[+] K32_DLL Get Fail. \n");
		return -1;
	}
	pLoadLibraryA = (fnLoadLibraryA)Yes_GetProcAddress_FromHash(hModule_Kernel32, Hash_LoadLibraryA);
	pHeapFree = (fnHeapFree)Yes_GetProcAddress_FromHash(hModule_Kernel32, Hash_HeapFree);
	pHeapAlloc = (fnHeapAlloc)Yes_GetProcAddress_FromHash(hModule_Kernel32, Hash_HeapAlloc);
	pGetStdHandle = (fnGetStdHandle)Yes_GetProcAddress_FromHash(hModule_Kernel32, Hash_GetStdHandle);
	pWriteConsoleA = (fnWriteConsoleA)Yes_GetProcAddress_FromHash(hModule_Kernel32, Hash_WriteConsoleA);
	pGetProcessHeap = (fnGetProcessHeap)Yes_GetProcAddress_FromHash(hModule_Kernel32, Hash_GetProcessHeap);

	HMODULE hModule_User32 = Yes_GetModuleHandle_FromHash(HASH_USER32);
	if (hModule_User32 == NULL) {
		printf("[+] U32_DLL Get Fail.\n[+] Try Load U32_DLL.\n");
		char User32_dll[] = { 'U', 'S', 'E', 'R', '3', '2', '.', 'D', 'L', 'L', '\x00' };
		hModule_User32 = pLoadLibraryA(User32_dll);
		if (hModule_User32 == NULL) {
			printf("[-] U32_DLL Load Fail. \n");
			return -1;
		}
	}
	pwsprintfA = (fnwsprintfA)Yes_GetProcAddress_FromHash(hModule_User32, Hash_wsprintfA);

	
	HMODULE hModule_NtDll = Yes_GetModuleHandle_FromHash(HASH_NTDLL);
	if (hModule_NtDll == NULL) {
		printf("[+] U32_DLL Get Fail.\n[+] Try Load U32_DLL.\n");
		char NtDll_dll[] = { 'N', 'T', 'D', 'L', 'L', '.', 'D', 'L', 'L', '\x00' };
		hModule_NtDll = pLoadLibraryA(NtDll_dll);
		if (hModule_NtDll == NULL) {
			printf("[-] U32_DLL Load Fail. \n");
			return -1;
		}
	}
	pRtlAllocateHeap = (fnwsprintfA)Yes_GetProcAddress_FromHash(hModule_NtDll, Hash_RtlAllocateHeap);
}

这样就完成了预期需求。

测试

main.c

int main() {
	api_init();
	PRINTA("[+] pLLA Func Addr: 0x%p \n", pLoadLibraryA);
	PRINTA("[+] pRAH Func Addr: 0x%p \n", pRtlAllocateHeap);
	return 0;
}

main 函数调用 api_init 对自定义 API 进行初始化,然后使用 PRINTA 宏打印信息。

image-20241025163259037.png

整个项目仅需包含 Windows.h 头文件,来到 Pe-Bear 可以看到,Kernel32.dll 的导入表中的敏感函数导入信息已经去掉,同样加载的 User32.dll 和 NTDLL.dll 也没有显示。

image-20241025163633851.png

代码

api.c

#include <Windows.h>

extern void* __cdecl memset(void*, int, size_t);
#pragma intrinsic(memset)
#pragma function(memset)
void* __cdecl memset(void* Destination, int Value, size_t Size) {
    unsigned char* p = (unsigned char*)Destination;
    while (Size > 0) {
        *p = (unsigned char)Value;
        p++;
        Size--;
    }
    return Destination;
}

typedef PVOID PAPI_SET_NAMESPACE;
typedef PVOID PACTIVATION_CONTEXT;
typedef PVOID PRTL_USER_PROCESS_PARAMETERS;

typedef struct _UNICODE_STRING {
    USHORT Length;
    USHORT MaximumLength;
    PWSTR  Buffer;
} UNICODE_STRING;

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;

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;

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;

#define HASHA(API) (HashStringJenkinsOneAtATime32BitA((PCHAR) API))
UINT32 HashStringJenkinsOneAtATime32BitA(IN PCHAR String) {
    SIZE_T Index = 0;
    UINT32 Hash = 0;
    SIZE_T Length = strlen(String);
    while (Index != Length) {
        Hash += String[Index++];
        Hash += Hash << 7; // seed
        Hash ^= Hash >> 6;
    }
    Hash += Hash << 3;
    Hash ^= Hash >> 11;
    Hash += Hash << 15;
    return Hash;
}

#define DLL_MAX_PATH	256
#define TO_UPPER(C) C >= 'a' && C <= 'z' ? C + 'A' - 'a' : C
HMODULE Yes_GetModuleHandle_FromHash(IN UINT32 ui32DllHash) {
#ifdef _WIN64	// 获取 x64 peb
    PPEB            pPeb = (PEB*)(__readgsqword(0x60));
#elif _WIN32	// 获取 x86 peb
    PPEB            pPeb = (PEB*)(__readfsdword(0x30));
#endif
    PPEB_LDR_DATA pLdr = (PPEB_LDR_DATA)(pPeb->Ldr);		// 获取 Ldr
    PLDR_DATA_TABLE_ENTRY pDte = (PLDR_DATA_TABLE_ENTRY)(pLdr->InMemoryOrderModuleList.Flink);
    while (pDte->FullDllName.Length != NULL) {
        CHAR UpperCaseDllName[MAX_PATH] = { 0 };
        for (int i = 0; i < pDte->FullDllName.Length; i++)	// 转化成大写
            UpperCaseDllName[i] = (CHAR)TO_UPPER(pDte->FullDllName.Buffer[i]);
        if (ui32DllHash == HASHA(UpperCaseDllName))			// 判断 Hash 是否一致
            return pDte->InInitializationOrderLinks.Flink;	// 返回 DLL Base Address
        pDte = *(PLDR_DATA_TABLE_ENTRY*)(pDte);				// 链表下一节点
    }
    return NULL;
}

FARPROC Yes_GetProcAddress_FromHash(IN HMODULE hModule, IN UINT32 ui32FuncHash) {
    PBYTE pBase = (PBYTE)hModule;	// 转化一下
    PIMAGE_DOS_HEADER pImgDosHdr = (PIMAGE_DOS_HEADER)pBase;	// 获取 DOS 头
    if (pImgDosHdr->e_magic != IMAGE_DOS_SIGNATURE)				// 签名检查
        return NULL;
    PIMAGE_NT_HEADERS pImgNtHdrs = (PIMAGE_NT_HEADERS)(pBase + pImgDosHdr->e_lfanew);	// 获取 NT 头
    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++)
        if (HASHA(pBase + FunctionNameArray[i]) == ui32FuncHash)							// 比较函数名的 Hash
            return (PVOID)(pBase + FunctionAddressArray[FunctionOrdinalArray[i]]);			// 通过函数序数值获取函数地址
}

main.h

#pragma once
#include <Windows.h>

#define PRINTA( STR, ... )																\
    if (1) {																			\
        LPSTR buf = (LPSTR)pRtlAllocateHeap(pGetProcessHeap(), HEAP_ZERO_MEMORY, 1024 );		\
        if ( buf != NULL ) {															\
            int len = pwsprintfA( buf, STR, __VA_ARGS__ );								\
            pWriteConsoleA( pGetStdHandle( STD_OUTPUT_HANDLE ), buf, len, NULL, NULL );	\
            pHeapFree( pGetProcessHeap(), 0, buf );										\
        }																				\
    }

UINT32 HashStringJenkinsOneAtATime32BitA(IN PCHAR String);
HMODULE Yes_GetModuleHandle_FromHash(IN UINT32 ui32DllHash);
FARPROC Yes_GetProcAddress_FromHash(IN HMODULE hModule, IN UINT32 ui32FuncHash);

#define HASHA(API) (HashStringJenkinsOneAtATime32BitA((PCHAR) API))

// DLL Name Hash
#define HASH_NTDLL              0x4898F593
#define HASH_USER32             0x81E3778E
#define HASH_KERNEL32           0x367DC15A
// WIN API Name Hash
#define Hash_LoadLibraryA       0x19F0EEAF
#define Hash_wsprintfA          0x2AF0CBF4
#define Hash_HeapFree           0x1154459B
#define Hash_HeapAlloc          0x2F9B708A
#define Hash_RtlAllocateHeap    0xF5613878
#define Hash_GetStdHandle       0x0C685B0E
#define Hash_WriteConsoleA      0x65304EA1
#define Hash_GetProcessHeap     0x3BCCFAC6

// https://learn.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-loadlibrarya
typedef HMODULE(WINAPI* fnLoadLibraryA)(LPCSTR lpLibFileName);

// https://learn.microsoft.com/en-us/windows/win32/api/heapapi/nf-heapapi-heapfree
typedef BOOL(WINAPI* fnHeapFree)(HANDLE hHeap, DWORD dwFlags, _Frees_ptr_opt_ LPVOID lpMem);

// https://learn.microsoft.com/en-us/windows/win32/api/heapapi/nf-heapapi-heapalloc
typedef LPVOID(WINAPI* fnHeapAlloc)(HANDLE hHeap, DWORD dwFlags, SIZE_T dwBytes);

// https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ntifs/nf-ntifs-rtlallocateheap
typedef LPVOID(WINAPI* fnRtlAllocateHeap)(PVOID HeapHandle, ULONG Flags, SIZE_T Size);

// https://learn.microsoft.com/en-us/windows/win32/api/heapapi/nf-heapapi-getprocessheap
typedef HANDLE(WINAPI* fnGetProcessHeap)();

// https://learn.microsoft.com/en-us/windows/console/getstdhandle
typedef HANDLE(WINAPI* fnGetStdHandle)(DWORD nStdHandle);

// https://learn.microsoft.com/en-us/windows/console/writeconsole
typedef BOOL(WINAPI* fnWriteConsoleA)(HANDLE  hConsoleOutput, const VOID* lpBuffer, DWORD nNumberOfCharsToWrite, LPDWORD lpNumberOfCharsWritten, LPVOID  lpReserved);

// https://learn.microsoft.com/en-us/windows/win32/api/heapapi/nf-heapapi-getprocessheap
typedef HANDLE(WINAPI* fnGetProcessHeap)();

// https://learn.microsoft.com/zh-cn/windows/win32/api/winuser/nf-winuser-wsprintfw
typedef int(WINAPIV* fnwsprintfA)(LPSTR unnamedParam1, LPCSTR unnamedParam2, ...);

main.c

#include "main.h"

//#define DEBUG
#ifdef DEBUG
#define DLL_NUM 3
#define FUNC_NUM 8

void showHash() {
	int i, j;
	char* dllList[DLL_NUM] = {
		"NTDLL.DLL",
		"USER32.DLL",
		"KERNEL32.DLL",
	};
	char* funcList[FUNC_NUM] = {
		"LoadLibraryA",
		"wsprintfA",
		"HeapFree",
		"HeapAlloc",
		"RtlAllocateHeap",
		"GetStdHandle",
		"WriteConsoleA",
		"GetProcessHeap",
	};
	char* funcSysList[] = {
		0
	};
	printf("// DLL Name Hash\n");
	for (i = 0; i < DLL_NUM; i++) {
		char temp;
		printf("#define HASH_");
		for (j = 0; dllList[i][j] != '.'; j++)
			printf("%c", dllList[i][j]);
		if (j + 5 < 8)
			printf("\t");
		printf("\t\t0x%0.8X \n", HASHA(dllList[i]));
	}
	printf("// WIN API Name Hash\n");
	for (i = 0; i < FUNC_NUM; i++) {
		char temp;
		printf("#define Hash_%s", funcList[i]);
		for (j = 0; funcList[i][j] != '\x00'; j++);
		if (j + 5 < 8)
			printf("\t");
		if (j + 5 < 16)
			printf("\t");
		if (j + 5 < 24)
			printf("\t");

		printf("0x%0.8X \n", HASHA(funcList[i]));
	}
}

int test() {
	HMODULE hModule_Kernel32 = Yes_GetModuleHandle_FromHash(HASH_KERNEL32);
	if (hModule_Kernel32 == NULL) {
		printf("[+] K32_DLL Get Fail. \n");
		return -1;
	}
	else {
		printf("\n[+] Get DLL KERNEL32\t: 0x%p \n+------------------------------------------+\n", hModule_Kernel32);
		printf("[+] LLA func address\t: 0x%p \n", Yes_GetProcAddress_FromHash(hModule_Kernel32, Hash_LoadLibraryA));
		printf("[+] HFr func address\t: 0x%p \n", Yes_GetProcAddress_FromHash(hModule_Kernel32, Hash_HeapFree));
		printf("[+] HAl func address\t: 0x%p \n", Yes_GetProcAddress_FromHash(hModule_Kernel32, Hash_HeapAlloc));
		printf("[+] GSH func address\t: 0x%p \n", Yes_GetProcAddress_FromHash(hModule_Kernel32, Hash_GetStdHandle));
		printf("[+] WCA func address\t: 0x%p \n", Yes_GetProcAddress_FromHash(hModule_Kernel32, Hash_WriteConsoleA));
		printf("[+] GPH func address\t: 0x%p \n", Yes_GetProcAddress_FromHash(hModule_Kernel32, Hash_GetProcessHeap));
	}

	HMODULE hModule_User32 = Yes_GetModuleHandle_FromHash(HASH_KERNEL32);
	if (hModule_User32 == NULL) {
		printf("[+] U32_DLL Get Fail.\n[+] Try Load U32_DLL.\n");
		char User32_dll[] = { 'U', 'S', 'E', 'R', '3', '2', '.', 'D', 'L', 'L', '\x00' };
		hModule_User32 = (fnLoadLibraryA)Yes_GetProcAddress_FromHash(hModule_Kernel32, Hash_LoadLibraryA)(User32_dll);
		if (hModule_User32 == NULL) {
			printf("[-] U32_DLL Load Fail. \n");
			return -1;
		}
		else {
			printf("\n[+] Load DLL USER32\t: 0x%p \n+------------------------------------------+\n", hModule_User32);
		}
	}
	else {
		printf("\n[+] Get DLL USER32\t: 0x%p \n+------------------------------------------+\n", hModule_User32);
	}
	printf("[+] wpf func address\t: 0x%p \n", Yes_GetProcAddress_FromHash(hModule_User32, Hash_wsprintfA));
	printf("+------------------------------------------+\n");
	return 0;
}
#endif // DEBUG

fnLoadLibraryA pLoadLibraryA;
fnHeapFree pHeapFree;
fnHeapAlloc pHeapAlloc;
fnGetStdHandle pGetStdHandle;
fnWriteConsoleA pWriteConsoleA;
fnGetProcessHeap pGetProcessHeap;
fnwsprintfA pwsprintfA;
fnRtlAllocateHeap pRtlAllocateHeap;

int api_init() {
	HMODULE hModule_Kernel32 = Yes_GetModuleHandle_FromHash(HASH_KERNEL32);
	if (hModule_Kernel32 == NULL) {
		return -1;
	}
	pLoadLibraryA = (fnLoadLibraryA)Yes_GetProcAddress_FromHash(hModule_Kernel32, Hash_LoadLibraryA);
	pHeapFree = (fnHeapFree)Yes_GetProcAddress_FromHash(hModule_Kernel32, Hash_HeapFree);
	pHeapAlloc = (fnHeapAlloc)Yes_GetProcAddress_FromHash(hModule_Kernel32, Hash_HeapAlloc);
	pGetStdHandle = (fnGetStdHandle)Yes_GetProcAddress_FromHash(hModule_Kernel32, Hash_GetStdHandle);
	pWriteConsoleA = (fnWriteConsoleA)Yes_GetProcAddress_FromHash(hModule_Kernel32, Hash_WriteConsoleA);
	pGetProcessHeap = (fnGetProcessHeap)Yes_GetProcAddress_FromHash(hModule_Kernel32, Hash_GetProcessHeap);

	HMODULE hModule_User32 = Yes_GetModuleHandle_FromHash(HASH_USER32);
	if (hModule_User32 == NULL) {
		char User32_dll[] = { 'U', 'S', 'E', 'R', '3', '2', '.', 'D', 'L', 'L', '\x00' };
		hModule_User32 = pLoadLibraryA(User32_dll);
		if (hModule_User32 == NULL) {
			return -2;
		}
	}
	pwsprintfA = (fnwsprintfA)Yes_GetProcAddress_FromHash(hModule_User32, Hash_wsprintfA);


	HMODULE hModule_NtDll = Yes_GetModuleHandle_FromHash(HASH_NTDLL);
	if (hModule_NtDll == NULL) {
		char NtDll_dll[] = { 'N', 'T', 'D', 'L', 'L', '.', 'D', 'L', 'L', '\x00' };
		hModule_NtDll = pLoadLibraryA(NtDll_dll);
		if (hModule_NtDll == NULL) {
			return -3;
		}
	}
	pRtlAllocateHeap = (fnwsprintfA)Yes_GetProcAddress_FromHash(hModule_NtDll, Hash_RtlAllocateHeap);
	return 0;
}
int main() {
#ifdef DEBUG
	showHash();
	test();
#endif // DEBUG
	api_init();
	PRINTA("[+] pLLA Func Addr: 0x%p \n", pLoadLibraryA);
	PRINTA("[+] pRAH Func Addr: 0x%p \n", pRtlAllocateHeap);
	//getchar();
	return 0;
}

Good Luck.