免杀基础-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中的CreateRemoteThreadVirtualAllocEx- 其他与进程注入、内存操作相关的函数
隐藏IAT可以降低程序被识别为恶意软件的概率。
2. 动态调用技术
2.1 基本动态调用方法
使用LoadLibrary和GetProcAddress动态加载函数:
typedef BOOL (WINAPI *pVirtualAllocEx)(
HANDLE hProcess,
LPVOID lpAddress,
SIZE_T dwSize,
DWORD flAllocationType,
DWORD flProtect
);
pVirtualAllocEx myVirtualAllocEx = (pVirtualAllocEx)GetProcAddress(
GetModuleHandleA("KERNEL32.DLL"),
"VirtualAllocEx"
);
这种方法存在两个问题:
- API函数名称字符串("VirtualAllocEx")会出现在程序中
- 引入了新的敏感函数
GetProcAddress和GetModuleHandleA
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)包含三个链表,描述同一组模块但顺序不同:
InLoadOrderModuleList- 按加载顺序排列InMemoryOrderModuleList- 按内存顺序排列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. 注意事项
- 不同Windows版本PEB结构可能有差异,需要做好兼容性处理
- 32位和64位程序的结构偏移量不同,需要分别处理
- 某些安全产品会监控PEB操作,过度操作可能引起怀疑
- 在实现过程中要注意错误处理,避免因模块未加载等情况导致崩溃
通过以上技术,可以有效隐藏程序的IAT信息,降低被安全产品检测到的概率。但需要注意,这只是一项基础技术,现代安全产品会使用多种检测手段,实际应用中可能需要结合其他技术共同使用。