调用混淆技术

2025-8-30 64 8/30

调用混淆技术

知识卡片

  1. 杀毒软件(AV)/EDR 识别恶意代码的核心手段之一就是函数调用特征匹配

  2. 调用混淆的本质:打乱/隐藏恶意代码的函数调用逻辑,让AV/EDR无法识别

  3. 调用混淆核心思路:从"直接调用"变为"动态解析+间接调用",来绕过特征

技能详解

一、基础解析

为什么要混淆调用?

杀软会检测调用的API遇到高危API(如VirtualAlloc、WriteProcessMemory、CreateRemoteThread、LoadLibrary等)或者高危API调用逻辑(如OpenProcess+VirtualAllocEx+CreateRemoteThread等),混淆后程序看着"干净"。

本质就是将静态可见的调用变成运行时动态解析的

直接调用混淆调用的区别:

直接调用时,编译器把API地址固定写进PE文件的导入地址表(IAT),API名称字符串明文也会存在.rdata节中,汇编里也是直接call [IAT偏移],静态分析器一样就能看到调用了什么。

混淆调用后,IAT里几乎没有恶意API地址,API名称被hash/加密/分割,只有调用的时候才会运行计算出来,静态看不出调用了什么。直接破坏了静态可观测的性质。

调用混淆技术

二、实战测试

直接调用

Sleep(1000);

混淆调用:动态解析(哈希遍历)

第一步:写哈希函数(这里以DJB2哈希为例)

DWORD GetApiHash(const char* str) {
    DWORD hash = 5381;  // 初始种子(固定值)
    int c;
    while ((c = *str++)) // 遍历字符串每个字符
        hash = ((hash << 5) + hash) + c; // hash = hash*33 + c
    return hash;
}

第二步:按照哈希找API

FARPROC GetApiByHash(HMODULE hMod, DWORD targetHash) {
    if (!hMod) return NULL;
    // 解析PE头,定位导出表
    PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)hMod;
    PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)((BYTE*)hMod + pDos->e_lfanew);
    
    PIMAGE_EXPORT_DIRECTORY pExp = (PIMAGE_EXPORT_DIRECTORY)((BYTE*)hMod +
        pNt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);

    DWORD* pNames  = (DWORD*)((BYTE*)hMod + pExp->AddressOfNames);         // 函数名 RVA 数组
    WORD*  pOrds   = (WORD*)((BYTE*)hMod + pExp->AddressOfNameOrdinals);   // 序号数组
    DWORD* pFuncs  = (DWORD*)((BYTE*)hMod + pExp->AddressOfFunctions);     // 函数地址 RVA 数组

    // 遍历所有导出的函数名
    for (DWORD i = 0; i < pExp->NumberOfNames; i++) {
        char* pName = (char*)((BYTE*)hMod + pNames[i]);   // 取出函数名字符串
        if (GetApiHash(pName) == targetHash) {            // 哈希匹配成功
            return (FARPROC)((BYTE*)hMod + pFuncs[pOrds[i]]);  // 返回函数真实地址
        }
    }
    return NULL;
}

第三步:声明函数指针类型

typedef void (WINAPI* pSleep)(DWORD);   
// 注意 WINAPI 调用约定须保留,要和Sleep原型一致
// Sleep原型  VOID WINAPI Sleep(  [in] DWORD dwMilliseconds );

第四步:动态解析并调用混淆后的Sleep

HMODULE hKernel32 = GetModuleHandleA("kernel32.dll");   // 获取 kernel32 模块基址
DWORD sleepHash = GetApiHash("Sleep");                  // 计算 Sleep 的哈希值

pSleep fSleep = (pSleep)GetApiByHash(hKernel32, sleepHash);  // 动态获取 Sleep 地址

if (fSleep) {
    fSleep(1000);   //和直接写 Sleep(1000) 效果完全一样
}

静态分析时完全看不到 “Sleep” 这个字符串,也看不到任何 Sleep 导入,只看到你在遍历 kernel32 的导出表和算哈希

在这个层面上再进一步的话就是将GetModuleHandleA在IAT表上去掉,毕竟GetModuleHandleA+kernel32还是暴露的,过杀软还是可能被看出来(疑似动态解析API)的恶意行为。

总结

上述动态解析仅算作中级技术,接下来在混淆阶段的提升方向如下:

Stage 1:基础动态解析 核心技术:只静态导入 LoadLibraryA + GetProcAddress,用明文字符串动态解析其他 API。 可见性:IAT 只剩 LoadLibrary/GetProcAddress,但 API 名仍明文存在。 主要对抗:静态字符串扫描 + 简单启发式。

Stage 2:API Hashing + Export Walking(上述技术) 核心技术:用 djb2/CRC32 等哈希算法 + 遍历 DLL Export Directory 匹配函数。 可见性:IAT 可能还剩 GetModuleHandle/LoadLibrary,无任何 API 明文字符串,只剩哈希值。 主要对抗:静态 IAT/YARA 规则 + 简单字符串扫描。

Stage 3:Importless PEB + LDR 手动解析 核心技术:通过 PEB → LDR InMemoryOrderModuleList 手动找 DLL 基址 + 自定义/双哈希 + 编译期字符串加密。 可见性:IAT 彻底干净(无任何 LoadLibrary/GetModuleHandle),字符串 0 明文。 主要对抗:静态全特征 + 部分行为(LoadLibrary Hook)。

Stage 4:Direct Syscall 核心技术:SysWhispers2/3 生成 stub,直接用 syscall 指令调用 Nt* 函数(绕过 ntdll.dll)。 可见性:无 ntdll 导入,连 syscall 号都硬编码在 stub 里。 主要对抗:user-mode Hook(EDR 对 ntdll 的 Hook)。

Stage 5:Indirect Syscall + 栈欺骗 核心技术:动态从 ntdll EAT 取 SSN + Indirect syscall(jmp rax) + Return Address Spoofing(调用栈伪装)。 可见性:无任何固定 syscall 号,调用栈看起来像合法线程。 主要对抗:Hook + 调用栈分析 + 行为序列。

Stage 6:多态 + 函数窃取 + 行为层对抗 核心技术:运行时复制 ntdll 机器码到 RWX + 随机哈希算法 + 全调用栈欺骗 + 轻量虚拟化。 可见性:连内存签名都随机化,几乎无固定特征。 主要对抗:全链路(静态 + 行为 + 云 + 内核)。

- THE END -
0

非特殊说明,本博所有文章均为博主原创。

共有 0 条评论