IAT 隐藏和混淆

一、介绍

导入地址表 (IAT) 包含有关 PE 文件的信息,例如使用过的函数和导出它们的 DLL。此类信息可用于对二进制文件进行签名和检测,如下图所示PE 文件导入被认为高度可疑的函数

二、隐藏混淆方法

(1)IAT 隐藏和混淆—方法 1 自定义函数

  可以在运行时使用 GetProcAddressGetModuleHandle 或 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 结构的指针
下面的代码片段将获取调用进程内已加载的 DLL 的名称。此函数搜索目标模块 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();
}
复制代码

 

从上图中可以发现一些 DLL 名称使用大写,而另一些没有,这会影响获取 DLL 基地址 (HMODULE) 的功能。例如,如果搜索 KERNEL32.DLL DLL,并且传递 Kernel32.DLL,那么 wcscmp 函数会将二者视为不同的字符串,所以使用函数将两个字符串并将其转换为小写表示形式,然后再在此状态下进行比较。如果两个字符串相等,则返回 true,否则返回 false
#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;
 
}
 至此完美解决了字符串硬编码可能被当作ioc的问题,在每次编译时拿到的hash值是不一样的

 

 

 

 
posted @   aoaoaoao  阅读(17)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
点击右上角即可分享
微信分享提示