Windows Maldev - ITA 隐藏与混淆

简介

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

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

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

但这又会出现其他问题。

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

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

Hash 函数

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#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 的基地址。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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 的信息。

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

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

1
2
3
4
typedef struct _LIST_ENTRY {
struct _LIST_ENTRY *Flink;
struct _LIST_ENTRY *Blink;
} LIST_ENTRY, *PLIST_ENTRY, *RESTRICTED_POINTER PRLIST_ENTRY;

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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

简单实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#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;
}

对比 Process Hacker 打开的进程信息

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#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

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#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;
}

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

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

实现 GetProcAddress

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

GetProcAddress 原理

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
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 - 指定导出函数的序号数组的地址。

简单实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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);
}
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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 函数中调用检查

1
2
3
4
5
6
7
#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;
}

动态加载 Win Api

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

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

1
2
3
4
5
6
7
8
9
#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 时来加载它。

函数定义

按照声明格式写好模板

1
2
3
4
5
6
7
8
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

1
2
3
4
5
6
7
8
9
10
11
12
13
// 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 函数初始化所有函数指针

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
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

1
2
3
4
5
6
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 宏打印信息。

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

代码

api.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
#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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
#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.