### 如何动态地去找导入表

**为什么要动态地查找？**

静态地查找导入表非常简单，我们只需要解析 PE 文件头中的数据目录（Data Directory），找到导入表的结构体 `IMAGE_IMPORT_DESCRIPTOR`，然后就可以找到所有的导入函数。但这种方法有几个局限性：

1. **脱壳（Unpacking）：** 许多恶意软件会使用加壳技术（packer），将原始的 PE 文件压缩或加密。这种情况下，原始的导入表会被隐藏或破坏，静态分析工具无法找到它。当程序运行时，加壳器会自行解压和修复导入表，因此只有在内存中才能找到真正的导入表
2. **动态加载（Dynamic Loading）：** 程序可能会使用 `LoadLibrary` 和 `GetProcAddress` 等函数在运行时动态加载 DLL 和获取函数地址。这种方式下，导入的函数根本不会出现在 PE 文件的静态导入表中
3. **防止逆向工程：** 有些程序开发者故意混淆或破坏导入表，以增加逆向工程的难度

因此，动态地查找导入表是进行脱壳、恶意软件分析和深入逆向工程的必备技能

**方法一：利用 `EAT` (导出地址表)**

这是最直接、也是最不寻常的方法。如果一个程序（比如一个 DLL）将自己的导入表中的函数地址作为导出函数暴露出来，你就可以通过解析它的**导出表 (Export Address Table, EAT)** 来找到导入表。但这种情况非常少见，通常只在一些特殊的系统 DLL 或驱动程序中出现。这种方法不具有通用性

**方法二：利用函数调用指令**

这是最常见、最实用的方法。当程序调用一个导入函数时，通常会使用 `CALL` 指令，其目标地址就是导入表中的一个条目

**具体步骤：**

1. **调试器附加：** 使用调试器（如 OllyDbg、x64dbg、IDA Pro Debugger）附加到目标进程
2. **设置断点：** 在程序执行的早期，比如 `main` 函数或 `WinMain` 函数的入口点设置断点
3. **单步调试/跟踪：** 逐步执行（Step Over）程序，并密切关注 `CALL` 指令
4. **识别导入调用：**
   - **相对 `CALL`:** 如果你看到 `CALL [地址]` 这样的指令，并且这个地址是一个外部函数的地址，那么这个 `[地址]` 就是一个导入表项。例如，`CALL DWORD PTR [EAX]`
   - **直接 `CALL`:** 如果你看到 `CALL MessageBoxA`，这通常是 IDA Pro 这样的反汇编器帮你标记的，它已经识别出这个调用指向了一个导入函数
5. **内存转储和分析：** 当你找到一个导入表项的地址后，你可以从这个地址开始，向前和向后扫描内存，寻找连续的、看起来像函数指针的地址序列。这个序列很可能就是完整的导入表。然后你可以将这一块内存转储出来进行进一步分析

这种方法需要对汇编语言有深入理解，并且需要耐心和细致的调试

**方法三：利用内存断点和内存扫描**

当程序加载时，加载器会向导入表写入外部函数的真实地址。我们可以利用这个特性

**具体步骤：**

1. **调试器附加：** 附加到目标进程
2. **查找 `IMAGE_IMPORT_DESCRIPTOR`：** 使用静态分析工具（如 PE Explorer、CFF Explorer）找到 PE 文件中导入表的 `RVA` (Relative Virtual Address)
3. **计算内存地址：** 将 `RVA` 加上基址（ImageBase）得到导入表在内存中的实际地址
4. **设置硬件断点：** 在导入表的第一个条目上设置一个硬件写入断点（Hardware Write Breakpoint）
5. **运行程序：** 运行程序。当加载器填充导入表时，断点会被触发。这通常发生在 `LoadLibrary` 函数调用之后，但在 `main` 函数之前
6. **分析内存：** 断点触发后，你就可以检查内存中的导入表，它的内容已经被加载器填充好了。如果程序进行了脱壳，此时的导入表才是真实的

**另一种高级变体是：**

- 在程序执行早期，在整个 `.text` 段（代码段）设置一个硬件写入断点。当断点触发时，检查写入的地址是否在代码段内部，并分析写入的指令。这可以用来检测自修改代码，是更高级的逆向技术