免杀基础-IAT隐藏
字数 1235 2025-08-22 12:23:00

IAT隐藏技术详解

1. IAT基础概念

导入地址表(IAT, Import Address Table)是PE文件结构中用于存储程序依赖的外部DLL函数地址的数据结构。当程序运行时,Windows加载器会填充IAT中的地址,使程序能够调用这些外部函数。

1.1 为什么需要隐藏IAT

杀毒软件通常会扫描程序的IAT,查找可疑的API函数调用,如:

  • Kernel32.dll中的CreateRemoteThread
  • VirtualAllocEx
  • 其他与进程注入、内存操作相关的函数

隐藏IAT可以降低程序被识别为恶意软件的概率。

2. 动态调用技术

2.1 基本动态调用方法

使用LoadLibraryGetProcAddress动态加载函数:

typedef BOOL (WINAPI *pVirtualAllocEx)(
    HANDLE hProcess,
    LPVOID lpAddress,
    SIZE_T dwSize,
    DWORD flAllocationType,
    DWORD flProtect
);

pVirtualAllocEx myVirtualAllocEx = (pVirtualAllocEx)GetProcAddress(
    GetModuleHandleA("KERNEL32.DLL"), 
    "VirtualAllocEx"
);

这种方法存在两个问题:

  1. API函数名称字符串("VirtualAllocEx")会出现在程序中
  2. 引入了新的敏感函数GetProcAddressGetModuleHandleA

2.2 解决函数名字符串问题

方法1:使用局部字符数组

char str1[] = {'V','i','r','t','u','a','l','A','l','l','o','c','E','x','\0'};
pVirtualAllocEx myVirtualAllocEx = (pVirtualAllocEx)GetProcAddress(
    GetModuleHandleA("KERNEL32.DLL"), 
    str1
);

注意:必须使用局部变量,全局变量无效。

方法2:字符串加密

可以对API名称字符串进行加密,运行时解密后再使用。

3. 自实现GetProcAddress

3.1 原理分析

GetProcAddress的功能是通过遍历DLL的导出表,根据函数名找到对应的函数地址。我们可以自己实现这个过程。

3.2 获取导出表

void getExportTable(HMODULE module, PIMAGE_EXPORT_DIRECTORY* exportTable) {
    PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)module;
    PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS)((DWORD64)module + pDosHeader->e_lfanew);
    IMAGE_DATA_DIRECTORY dataDir = (IMAGE_DATA_DIRECTORY)pNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
    *exportTable = (PIMAGE_EXPORT_DIRECTORY)((DWORD64)module + dataDir.VirtualAddress);
}

3.3 遍历导出表获取函数地址

FARPROC myGetProcAddr(HMODULE module, PIMAGE_EXPORT_DIRECTORY exportTable, LPCSTR funcName) {
    PDWORD NameArray = (PDWORD)((DWORD64)module + exportTable->AddressOfNames);
    PDWORD AddressArray = (PDWORD)((DWORD64)module + exportTable->AddressOfFunctions);
    PWORD OrdinalArray = (PWORD)((DWORD64)module + exportTable->AddressOfNameOrdinals);
    
    for(SIZE_T i = 0; i < exportTable->NumberOfNames; i++) {
        LPCSTR currentName = (LPCSTR)((DWORD64)module + NameArray[i]);
        if(strcmp(funcName, currentName) == 0) {
            return (FARPROC)((DWORD64)module + AddressArray[OrdinalArray[i]]);
        }
    }
    return NULL;
}

4. 自实现GetModuleHandle

4.1 PEB结构概述

进程环境块(PEB)包含三个链表,描述同一组模块但顺序不同:

  1. InLoadOrderModuleList - 按加载顺序排列
  2. InMemoryOrderModuleList - 按内存顺序排列
  3. InInitializationOrderModuleList - 按初始化顺序排列

4.2 关键结构定义

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

typedef struct _PEB_LDR_DATA {
    ULONG Length;
    BOOLEAN 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;
    BOOLEAN Spare;
    HANDLE Mutant;
    PVOID ImageBaseAddress;
    PPEB_LDR_DATA LoaderData;
    PRTL_USER_PROCESS_PARAMETERS ProcessParameters;
    // ... 其他成员省略
} PEB, *PPEB;

typedef struct _LDR_DATA_TABLE_ENTRY {
    LIST_ENTRY InLoadOrderLinks;
    LIST_ENTRY InMemoryOrderLinks;
    LIST_ENTRY InInitializationOrderLinks;
    PVOID DllBase;
    // ... 其他成员省略
    UNICODE_STRING FullDllName;
    UNICODE_STRING BaseDllName;
    // ... 其他成员省略
} LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;

4.3 实现代码

通过遍历PEB中的模块链表来获取模块基址:

HMODULE myGetModuleHandle(LPCWSTR moduleName) {
#ifdef _WIN64
    PPEB peb = (PPEB)__readgsqword(0x60);
#else
    PPEB peb = (PPEB)__readfsdword(0x30);
#endif

    PPEB_LDR_DATA ldr = peb->LoaderData;
    PLIST_ENTRY listEntry = ldr->InLoadOrderModuleList.Flink;
    
    while(listEntry != &ldr->InLoadOrderModuleList) {
        PLDR_DATA_TABLE_ENTRY entry = CONTAINING_RECORD(listEntry, LDR_DATA_TABLE_ENTRY, InLoadOrderLinks);
        
        if(_wcsicmp(entry->BaseDllName.Buffer, moduleName) == 0) {
            return (HMODULE)entry->DllBase;
        }
        
        listEntry = listEntry->Flink;
    }
    
    return NULL;
}

5. 完整解决方案

结合上述技术,完整的IAT隐藏方案如下:

// 1. 自实现GetModuleHandle
HMODULE myGetModuleHandle(LPCWSTR moduleName) {
    // 实现同上
}

// 2. 自实现GetProcAddress
FARPROC myGetProcAddress(HMODULE module, LPCSTR funcName) {
    PIMAGE_EXPORT_DIRECTORY exportTable;
    getExportTable(module, &exportTable);
    return myGetProcAddr(module, exportTable, funcName);
}

// 3. 安全调用敏感API
void safeCall() {
    // 使用局部字符数组隐藏API名称
    char virtualAllocExName[] = {'V','i','r','t','u','a','l','A','l','l','o','c','E','x','\0'};
    
    // 获取模块句柄
    HMODULE hKernel32 = myGetModuleHandle(L"KERNEL32.DLL");
    
    // 获取函数地址
    pVirtualAllocEx myVirtualAllocEx = (pVirtualAllocEx)myGetProcAddress(
        hKernel32, 
        virtualAllocExName
    );
    
    // 使用获取的函数
    if(myVirtualAllocEx) {
        // 调用函数...
    }
}

6. 进阶技巧

6.1 哈希比较替代字符串比较

在遍历导出表时,可以使用哈希比较替代字符串比较,进一步隐藏API名称:

DWORD calcHash(LPCSTR str) {
    DWORD hash = 0;
    while(*str) {
        hash = ((hash << 5) + hash) + *str++;
    }
    return hash;
}

// 在遍历导出表时比较哈希值而非字符串
if(calcHash(funcName) == calcHash(currentName)) {
    // 匹配成功
}

6.2 延迟加载

将敏感API的加载推迟到实际需要使用时,减少程序启动时的可疑行为。

6.3 API调用混淆

在调用获取的API时,可以使用间接调用或插入无意义指令来混淆调用模式。

7. 注意事项

  1. 不同Windows版本PEB结构可能有差异,需要做好兼容性处理
  2. 32位和64位程序的结构偏移量不同,需要分别处理
  3. 某些安全产品会监控PEB操作,过度操作可能引起怀疑
  4. 在实现过程中要注意错误处理,避免因模块未加载等情况导致崩溃

通过以上技术,可以有效隐藏程序的IAT信息,降低被安全产品检测到的概率。但需要注意,这只是一项基础技术,现代安全产品会使用多种检测手段,实际应用中可能需要结合其他技术共同使用。

IAT隐藏技术详解 1. IAT基础概念 导入地址表(IAT, Import Address Table)是PE文件结构中用于存储程序依赖的外部DLL函数地址的数据结构。当程序运行时,Windows加载器会填充IAT中的地址,使程序能够调用这些外部函数。 1.1 为什么需要隐藏IAT 杀毒软件通常会扫描程序的IAT,查找可疑的API函数调用,如: Kernel32.dll 中的 CreateRemoteThread VirtualAllocEx 其他与进程注入、内存操作相关的函数 隐藏IAT可以降低程序被识别为恶意软件的概率。 2. 动态调用技术 2.1 基本动态调用方法 使用 LoadLibrary 和 GetProcAddress 动态加载函数: 这种方法存在两个问题: API函数名称字符串("VirtualAllocEx")会出现在程序中 引入了新的敏感函数 GetProcAddress 和 GetModuleHandleA 2.2 解决函数名字符串问题 方法1:使用局部字符数组 注意 :必须使用局部变量,全局变量无效。 方法2:字符串加密 可以对API名称字符串进行加密,运行时解密后再使用。 3. 自实现GetProcAddress 3.1 原理分析 GetProcAddress 的功能是通过遍历DLL的导出表,根据函数名找到对应的函数地址。我们可以自己实现这个过程。 3.2 获取导出表 3.3 遍历导出表获取函数地址 4. 自实现GetModuleHandle 4.1 PEB结构概述 进程环境块(PEB)包含三个链表,描述同一组模块但顺序不同: InLoadOrderModuleList - 按加载顺序排列 InMemoryOrderModuleList - 按内存顺序排列 InInitializationOrderModuleList - 按初始化顺序排列 4.2 关键结构定义 4.3 实现代码 通过遍历PEB中的模块链表来获取模块基址: 5. 完整解决方案 结合上述技术,完整的IAT隐藏方案如下: 6. 进阶技巧 6.1 哈希比较替代字符串比较 在遍历导出表时,可以使用哈希比较替代字符串比较,进一步隐藏API名称: 6.2 延迟加载 将敏感API的加载推迟到实际需要使用时,减少程序启动时的可疑行为。 6.3 API调用混淆 在调用获取的API时,可以使用间接调用或插入无意义指令来混淆调用模式。 7. 注意事项 不同Windows版本PEB结构可能有差异,需要做好兼容性处理 32位和64位程序的结构偏移量不同,需要分别处理 某些安全产品会监控PEB操作,过度操作可能引起怀疑 在实现过程中要注意错误处理,避免因模块未加载等情况导致崩溃 通过以上技术,可以有效隐藏程序的IAT信息,降低被安全产品检测到的概率。但需要注意,这只是一项基础技术,现代安全产品会使用多种检测手段,实际应用中可能需要结合其他技术共同使用。