IAT 隐藏和混淆
一、介绍
导入地址表 (IAT) 包含有关 PE 文件的信息,例如使用过的函数和导出它们的 DLL。此类信息可用于对二进制文件进行签名和检测,如下图所示PE 文件导入被认为高度可疑的函数
二、隐藏混淆方法
(1)IAT 隐藏和混淆—方法 1 自定义函数
可以在运行时使用 GetProcAddress
、GetModuleHandle
或 LoadLibrary
动态加载这些函数。下面的代码段将动态加载 VirtualAllocEx
,因此在检查时它不会出现在 IAT 中
1 2 | typedef LPVOID (WINAPI* fnVirtualAllocEx)(HANDLE hProcess, LPVOID lpAddress, SIZE_T dwSize, DWORD flAllocationType, DWORD flProtect); fnVirtualAllocEx pVirtualAllocEx = GetProcAddress(GetModuleHandleA( "KERNEL32.DLL" ), "VirtualAllocEx" ); |
但该方法有个缺点,VirtualAllocEx
字符串存在于二进制文件中,GetProcAddress
和 GetModuleHandleA
会出现在 IAT 中。
(2)IAT 隐藏和混淆 - 方法 2 自定义GetProcAddress
通过解析PE结构当中的导出表获取函数的地址,代码当中
FunctionNameArray:包含函数名称的地址数组。
FunctionAddressArray:包含函数地址的数组。
FunctionOrdinalArray:包含每个函数的序号。
对于每个函数,首先获取它的名字(通过 FunctionNameArray)。
然后通过它的序号在 FunctionOrdinalArray 中找到它的序号。
使用序号在 FunctionAddressArray 中查找它的地址。
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 | #include <windows.h> #include <iostream> PVOID GetProcAddressReplacement(IN HMODULE hModule, IN LPCSTR lpApiName) { // 这样做是为了避免每次使用 hModule 时进行强制转换 PBYTE pBase = (PBYTE)hModule; // 获取 DOS 头并进行签名检查 PIMAGE_DOS_HEADER pImgDosHdr = (PIMAGE_DOS_HEADER)pBase; if (pImgDosHdr->e_magic != IMAGE_DOS_SIGNATURE) return NULL; // 获取 NT 头并进行签名检查 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 (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; } } return NULL; } int main() { LPVOID lpadder = GetProcAddressReplacement(GetModuleHandleA( "ntdll.DLL" ), "NtAllocateVirtualMemory" ); printf( "GetProcAddressReplacement is: %p\n" , lpadder); LPVOID lpPadder = GetProcAddress(GetModuleHandleA( "ntdll.DLL" ), "NtAllocateVirtualMemory" ); printf( "GetProcAddress is: %p\n" , lpPadder); } |
(3)IAT 隐藏和混淆-自定义 GetModuleHandle
GetModuleHandle
函数获取指定 DLL 的句柄。此函数返回 DLL 的句柄,如果调用进程中不存在此 DLL,则返回 NULL
。
HMODULE
数据类型是加载的 DLL 的基地址,表示 DLL 在进程地址空间中的位置。可以利用进程环境块 (PEB) 获取加载的 DLL 相关的信息,特别是 PEB 结构的 PEB_LDR_DATA Ldr
成员。因此,第一步是通过 PEB 结构访问此成员。
在 64 位系统中获取 PEB
64 位系统中的 PEB结构的指针位于线程环境块 (TEB) 结构中
可以使用 Visual Studio 中的 __readgsqword(0x60)宏从 GS 寄存器读取 0x60
字节)来直接获取 PEB 结构
1 | PPEB pPeb2 = (PPEB)(__readgsqword(0x60)); |
32 位系统中的 PEB
在 32 位系统中,指向 TEB 结构的偏移量存储在 FS
寄存器中
1 | PPEB pPeb2 = (PPEB)(__readfsdword(0x30)); |
拿到PEB结构之后下一步是访问 PEB_LDR_DATA Ldr
成员获取进程中加载的 DLL 的信息
PEB_LDR_DATA
结构如下所示。此结构中的重要成员是 LIST_ENTRY InMemoryOrderModuleList
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; |
双向链表使用 Flink
和 Blink
元素分别作为头指针和尾指针。这意味着 Flink
指向链表中的下一个节点,而 Blink
元素指向链表中的上一个节点。这些指针用于在两个方向上遍历链表。知道了这一点,要开始枚举此链表,应该从访问其第一个元素 InMemoryOrderModuleList.Flink
开始。
根据 微软 对 PEB_LDR_DATA结构体 InMemoryOrderModuleList
成员的定义,它指出列表中的每个项目都是指向 LDR_DATA_TABLE_ENTRY
结构的指针,LDR_DATA_TABLE_ENTRY
结构体表示进程加载的 DLL 的链表中的某个 DLL。每个 LDR_DATA_TABLE_ENTRY
都表示一个唯一的 DLL。
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]; // 保留 1 [2] LIST_ENTRY InMemoryOrderLinks; // 内存驻留顺序的双向链表 PVOID Reserved2[2]; // 保留 2 [2] PVOID DllBase; // DLL 基地址 PVOID EntryPoint; // DLL 入口点 PVOID Reserved3; // 保留 3 UNICODE_STRING FullDllName; // 'UNICODE_STRING' 结构体,包含已加载模块的文件名 BYTE Reserved4[8]; // 保留 4 [8] PVOID Reserved5[3]; // 保留 5 [3] union { ULONG CheckSum; // 校验和 PVOID Reserved6; // 保留 6 }; ULONG TimeDateStamp; // 时间戳 } LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY; |
首先检索 PEB。从PEB 中检索 Ldr 成员 检索链表中的第一个元素由于每个 pDte
在链表中表示一个唯一的 DLL,因此可以使用以下代码行获取下一个元素:
1 | pDte = *(PLDR_DATA_TABLE_ENTRY*)(pDte); |
pDte
所指向的地址处存储的值,然后将结果转换为指向 PLDR_DATA_TABLE_ENTRY
结构的指针szModuleName
。如果有匹配项,此函数返回一个指向 DLL(HMODULE
)的句柄#include <stdio.h> #include <windows.h> #include<winternl.h> HMODULE GetModuleHandleReplacement() { // 获取 PEB #ifdef _WIN64 // 如果编译为 x64 PPEB pPeb = (PEB*)(__readgsqword(0x60)); #elif _WIN32 // 如果编译为 x32 PPEB pPeb = (PEB*)(__readfsdword(0x30)); // 进程环境块 (PEB) #endif // 获取 Ldr PPEB_LDR_DATA pLdr = (PPEB_LDR_DATA)(pPeb->Ldr); // 运行时动态链接器 (Ldr) // 获取链表中指向第一个模块信息的第一个元素 PLDR_DATA_TABLE_ENTRY pDte = (PLDR_DATA_TABLE_ENTRY)(pLdr->InMemoryOrderModuleList.Flink); // 按加载顺序排列的模块链表 while (pDte) { // 如果非空 if (pDte->FullDllName.Length != NULL) { // 打印 DLL 名称 wprintf(L"[i] \"%s\" \n", pDte->FullDllName.Buffer); // 全限定 DLL 名称 } else { break; } // 链表中的下一个元素 pDte = *(PLDR_DATA_TABLE_ENTRY*)(pDte); } return NULL; } int main() { GetModuleHandleReplacement(); }
#include <stdio.h> #include <windows.h> #include<winternl.h> // Convert both strings to lowercase and compare them 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 length. We dont want to overflow the 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; } HMODULE GetModuleHandleReplacement(IN LPCWSTR szModuleName) { // Getting PEB #ifdef _WIN64 // if compiling as x64 // gs > Dedicated x64 register for accessing TEB PPEB pPeb = (PEB*)(__readgsqword(0x60)); #elif _WIN32 // if compiling as x32 PPEB pPeb = (PEB*)(__readfsdword(0x30)); #endif// Getting 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); return (HMODULE)pDte->Reserved2[0]; // Reserved2[0] = dllbase, Reserved2[1] = points to the InInitializationOrderLinks field } } else { break ; } // Next element in the linked list pDte = *(PLDR_DATA_TABLE_ENTRY*)(pDte); } return NULL; } int main() { printf( "[i] Original 0x%p \n" , GetModuleHandleW(L "NTDLL.dll" )); printf( "[i] Replacement 0x%p \n" , GetModuleHandleReplacement(L "ntdll.dll" )); return 0; } |
(4)IAT 隐藏和混淆 - API 哈希
从2、3节当中已经解决了GetModuleHandle GetProcAddress导入表问题,可是调用的字符串还是可以从二进制文件当中识别到字符串。为了解决这个问题,使用哈希值而不是执行字符串比较来获取指定的模块基地址或函数地址。
为了使用算法JenkinsOneAtATime32Bit需要获取模块名称的哈希值(例如 User32.dll
)和函数名称的哈希值(例如 MessageBoxA
)。这可以通过首先将哈希值打印到控制台来完成。确保哈希算法使用相同的种子。
#include <stdio.h> #include <windows.h> #include <winternl.h> #define INITIAL_SEED 7 // RANDOM HASH # DWORD HASHA(_In_ CONST CHAR* String) { SIZE_T Index = 0; DWORD 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; } int main() { printf( "[i] Hash Of \"%s\" Is : 0x%0.8X \n" , "USER32.DLL" , HASHA( "USER32.DLL" )); // Capitalized module name printf( "[i] Hash Of \"%s\" Is : 0x%0.8X \n" , "MessageBoxA" , HASHA( "MessageBoxA" )); getchar(); return 0; } |
这些哈希值现在可以与下面的函数一起使用
1 2 | [i] Hash Of "USER32.DLL" Is : 0x81E3778E [i] Hash Of "MessageBoxA" Is : 0xF10E27CA |
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 | #include <stdio.h> #include <windows.h> #include <winternl.h> #define INITIAL_SEED 7 // RANDOM HASH # DWORD HASHA(_In_ CONST CHAR* String) { SIZE_T Index = 0; DWORD 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; } int main() { printf( "[i] Hash Of \"%s\" Is : 0x%0.8X \n" , "USER32.DLL" , HASHA( "USER32.DLL" )); // Capitalized module name printf( "[i] Hash Of \"%s\" Is : 0x%0.8X \n" , "MessageBoxA" , HASHA( "MessageBoxA" )); getchar(); return 0; } |
很好的规避了字符串硬编码的问题
(5) IAT 隐藏与混淆-编译时 API 哈希
在上一个 API 哈希中,函数和模块的哈希在将其添加到代码之前就生成了,哈希是硬编码的,这可能允许安全解决方案将其用作 IoC,如果它们不在每次实现中更新。然而,使用编译时 API 哈希,则每次编译二进制文件时都会生成动态哈希。
此方法仅适用于 C++ 项目,因为使用了 constexpr 关键字。C++ 中的 constexpr
运算符用于表示可在编译时计算函数或变量。
创建编译时函数
第一步是使用 constexpr
运算符转换将要用于成为编译时函数的哈希函数。在本例中,将修改 Djb2 哈希算法来使用 constexpr
运算符。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | #define SEED 5 // 编译时Djb2散列函数(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; } // 编译时Djb2哈希函数(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; } |
未定义变量 g_KEY
用于作为两个函数中的初始哈希。g_KEY
是一个全局 constexpr
变量,并由一个名为 RandomCompileTimeSeed
(如下所示)的函数在每次二进制编译时随机生成,RandomCompileTimeSeed
用于基于当前时间生成一个随机种子值。它通过提取 __TIME__宏中的数字来完成这一操作,该宏是 C++ 中的一个预定义宏,它会扩展为 HH:MM:SS
格式的当前时间。然后, RandomCompileTimeSeed
函数将每个数字乘以不同的随机常数,并将它们全部加在一起以生成最终的种子值。
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; |
接下来,定义两个宏 RTIME_HASHA
和 RTIME_HASHW
,用于 GetProcAddressH
函数在运行时比较哈希值。宏应按如下方式定义。
#define RTIME_HASHA( API ) HashStringDjb2A((const char*) API) // 调用 HashStringDjb2A
#define RTIME_HASHW( API ) HashStringDjb2W((const wchar_t*) API) // 调用 HashStringDjb2
在建立了一个随机编译时哈希函数后,下一步是在变量中声明编译时哈希值。为了简化该过程,将实现两个宏。
#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);
字符串运算符号
#
符号被称为 字符串化运算符。它用于将预处理器宏参数转换为字符串常量。
例如,如果使用参数 SomeFunction
调用 CTIME_HASHA
宏,如 HASHA(SomeFunction)
,#API
表达式将被替换为字符串常量 "SomeFunction"
。
合并运算符
##
运算符被称为“合并运算符”,它用于将两个预处理器宏合并为一个宏。##
运算符用于将 API 参数分别与字符串 "_Rotr32A"
或 "_Rotr32W"
结合,从而形成正在定义的变量的最终名称。
例如,如果 CTIME_HASHA
宏使用参数 SomeFunction
进行调用,例如 HASHA(SomeFunction)
,则 ##
运算符将 API 与 "_Rotr32A"
结合,形成最终变量名 SomeFunction_Rotr32A
。
#include <Windows.h> #include <stdio.h> #include <winternl.h> #define SEED 5// 生成一个随机密钥(用作初始hash) #include <stdint.h> #include <intrin.h> #include <WinBase.h> 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; // 编译时Djb2散列函数(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; } // 编译时Djb2哈希函数(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; } // 运行时哈希宏 #define RTIME_HASHA( API ) HashStringDjb2A((const char*) API) #define RTIME_HASHW( API ) HashStringDjb2W((const wchar_t*) API)// #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; } //补充 #define HASHA(x) (JenkinsOneAtATime32Bit(x)) uint32_t JenkinsOneAtATime32Bit( const char * key) { size_t i = 0; uint32_t hash = 0; while (key[i] != '\0' ) { hash += key[i++]; hash += hash << 10; hash ^= hash >> 6; } hash += hash << 3; hash ^= hash >> 11; hash += hash << 15; return hash; } 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` 转换成大写 CHAR UpperCaseDllName[MAX_PATH]; DWORD i = 0; while (pDte->FullDllName.Buffer[i]) { UpperCaseDllName[i] = (CHAR)toupper(pDte->FullDllName.Buffer[i]); i++; } UpperCaseDllName[i] = '\0' ; // 哈希' UpperCaseDllName '并将哈希值与输入' dwModuleNameHash '的哈希值进行比较 if (HASHA(UpperCaseDllName) == dwModuleNameHash) //return pDte->Reserved2[0]; 修改,转换成句柄 return (HMODULE)pDte->Reserved2[0]; //return (HMODULE)pDte->DllBase; 修改1,不行 } else { break ; } pDte = *(PLDR_DATA_TABLE_ENTRY*)(pDte); } return NULL; } CTIME_HASHA(MessageBoxA); CTIME_HASHW(MessageBoxW); typedef int (WINAPI* fnMessageBoxA)(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType); typedef int (WINAPI* fnMessageBoxW)(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType); int main() { //---------------------------------------------------------------------------- printf( "<i> Hash Of \"%s\" Is : 0x%0.8X \n" , "USER32.DLL" , HASHA( "USER32.DLL" )); if (LoadLibraryA( "USER32.DLL" ) == NULL) { printf( "[!] LoadLibraryA Failed With Error : %d \n" , GetLastError()); return 0; } #define USER32DLL_HASH 0xA2B1A3C7 HMODULE hUser32Module = GetModuleHandleH(USER32DLL_HASH); if (hUser32Module == NULL) { printf( "[!] Cound'nt Get Handle To User32.dll \n" ); return -1; } //---------------------------------------------------------------------------- printf( "<i>MessageBoxA_Rotr32A 0x%0.8X \n" , MessageBoxA_Rotr32A); printf( "<i>MessageBoxW_Rotr32W 0x%0.8X \n" , MessageBoxW_Rotr32W); fnMessageBoxA pMessageBoxA = (fnMessageBoxA)GetProcAddressH(hUser32Module, MessageBoxA_Rotr32A); if (pMessageBoxA == NULL) { return -1; } fnMessageBoxW pMessageBoxW = (fnMessageBoxW)GetProcAddressH(hUser32Module, MessageBoxW_Rotr32W); if (pMessageBoxW == NULL) { return -1; } // 调用 MessageBoxA pMessageBoxA(NULL, "A Adjust from Maldev" , "Wow" , MB_OK | MB_ICONWARNING); printf( "[#] Press <Enter> To Quit ... " ); getchar(); return 0; } |
本文来自博客园,作者:aoaoaoao,转载请注明原文链接:https://www.cnblogs.com/websecyw/p/18683543
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构