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

可以看到,这个 PE 文件导入了 CreateRemoteThread、OpenProcess、VirtualAllocEx、WriteProcessMemory 等函数,Shellcode 加载流程尽收眼底。
想要隐藏这些内容,可以使用 GetProcAddress、GetModuleHandle 或 LoadLibrary 在运行时动态加载这些函数。
但这又会出现其他问题。
- 动态导入的函数相关字符串会出现在 PE 文件中,这个可以用于签名检测
GetProcAddress和GetModuleHandle函数的导入信息也会出现在 ITA 中
想隐藏字符串,那只能通过替换,可以通过字符串加密、Hash 运算等方法;对于 GetProcAddress 和 GetModuleHandle 函数的信息,可以通过自实现来隐藏。两者结合。
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;

微软文档中说明,_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;
}

对比 Process Hacker 打开的进程信息

发现 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

这样比较方便去定义,并且统一转换成大写。
接下来更新一下 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;
}
测试一下效果,没啥问题。

上文的代码中关于
GetModuleHandle的实现使用了winternl.h头文件,这里可以使用 NirSoft 与 Process Hacker 的结构体来代替。最终效果 仅需导入<windows.h>头文件。
实现 GetProcAddress
GetProcAddress 函数从 DLL 句柄中获取导出函数的地址。如果未找到函数名,返回 NULL。
GetProcAddress 原理
要访问导出的函数,需要访问 DLL 的导出表并在其中循环查找目标函数名称。
PE 头模块时提到,导出表是定义为 IMAGE_EXPORT_DIRECTORY 的结构。微软文档

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

效果还可以,与 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;
}

动态加载 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 宏打印信息。

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

代码
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.
