### 如何不在编码时直接导入相关 API 的前提下进行攻击 从攻击者的角度来看,**不在编码时直接导入相关 API** 是一个核心的规避手段。这种技术通常被称为**动态 API 调用**或**运行时 API 解析**,其主要目的是: 1. **绕过签名检测:** 传统的杀毒软件和安全工具会扫描可执行文件中的**导入表**。如果导入表里有 `CreateRemoteThread`、`WriteProcessMemory`、`LoadLibrary` 等高危函数,文件就会被标记为可疑。动态调用 API 可以让导入表看起来非常“干净”,从而躲过静态扫描 2. **增加逆向分析难度:** 逆向工程师通常会从导入表入手,快速了解程序的功能。如果导入表是空的或只导入了少数几个基础函数,逆向分析师就必须花费大量时间去跟踪程序的运行时行为,才能发现其真正意图 3. **支持多操作系统版本和架构:** 有些 API 的地址在不同版本的 Windows 上可能会有细微差异。动态获取 API 地址可以确保代码在不同系统上都能正确运行,提高攻击的通用性 4. **按需加载:** 只有在需要执行特定恶意行为时才去获取和调用相应的 API,这可以减少不必要的代码和数据,使恶意程序更小、更精简 下面是几种具体的技术实现,从初级到高级: **1. 使用 `LoadLibrary` 和 `GetProcAddress`** 这是最基础、最常见的方法。攻击者只需要在代码中静态导入 `LoadLibraryA/W` 和 `GetProcAddress` 这两个函数,然后用它们来动态获取所有其他需要的 API **实现步骤:** 1. **加载 DLL:** 调用 `LoadLibraryA`,传入需要加载的 DLL 名称(例如 "kernel32.dll")。这个函数会返回该 DLL 在内存中的基址 2. **获取函数地址:** 调用 `GetProcAddress`,传入 DLL 的基址和需要获取的函数名称(例如 "CreateRemoteThread")。这个函数会返回该 API 的内存地址 3. **函数指针调用:** 将获取到的地址赋值给一个函数指针,然后通过这个指针像调用普通函数一样来调用它 ```c // 伪代码 #include #include int main() { HMODULE hKernel32 = LoadLibraryA("kernel32.dll"); if (hKernel32 == NULL) { // 处理错误 return 1; } // 定义一个函数指针类型 typedef HANDLE (WINAPI* CreateRemoteThread_t)(HANDLE, LPSECURITY_ATTRIBUTES, SIZE_T, LPTHREAD_START_ROUTINE, LPVOID, DWORD, LPDWORD); // 获取 CreateRemoteThread 的地址 CreateRemoteThread_t pCreateRemoteThread = (CreateRemoteThread_t)GetProcAddress(hKernel32, "CreateRemoteThread"); if (pCreateRemoteThread == NULL) { // 处理错误 return 1; } // 现在可以使用 pCreateRemoteThread 来调用 CreateRemoteThread 函数了 // pCreateRemoteThread(..., ..., ...); return 0; } ``` 这种方法虽然简单,但 `LoadLibrary` 和 `GetProcAddress` 依然会出现在程序的导入表中,因此安全软件仍然可以进行识别 **2. 手动解析 PEB** 这是更高级、更隐蔽的方法。其核心是**完全不依赖任何静态导入**,通过**手动遍历内存中的数据结构**来找到所需的 API 地址 **实现步骤:** 1. **获取 PEB 地址:** 在 32 位系统上,PEB 的地址可以通过 `FS:[0x30]` 寄存器来获取;在 64 位系统上,可以通过 `GS:[0x60]` 来获取 2. **遍历 PEB LDR 数据结构:** PEB 结构体中包含一个指向已加载模块列表的指针(`LDR_DATA`)。攻击者可以遍历这个列表,找到 `ntdll.dll`、`kernel32.dll` 等已加载的 DLL 模块 3. **解析 DLL 的导出表(EAT):** 找到目标 DLL 的基址后,手动解析其**导出地址表 (EAT)**。EAT 是一个包含所有导出函数名称和地址的结构 4. **哈希值匹配:** 为了避免在代码中硬编码函数名称字符串(字符串会暴露恶意意图),攻击者通常会为每个函数名称计算一个哈希值。遍历 EAT 中的函数名称,计算哈希值,然后与预设的目标哈希值进行匹配。如果匹配成功,就找到了所需 API 的地址 5. **函数指针调用:** 获取到地址后,同样通过函数指针进行调用