PE上IAT如何解析转发的API?

How can I resolve the forwarded API from the IAT on PE?

您好,我正在尝试在 python 脚本中创建一个 Import table 构建器,就像 MacT 的 Import Reconstructor 一样。

但是我很难找到从转发的API中获取原始API信息的方法。

例如,我从 IAT 收到了一个 "ntdll!RtlDecodePointer",但它是从 "kernel32!DecodePointer" 转发的,我不知道如何找到它。

我是否必须在 Import 目录中搜索每个已加载模块的 ForwarderChain?

不,ForwarderChain in Import directory与此无关。

当加载程序从某些 PE 导入解析 kernel32.DecodePointer - 它认为它指向某个地址 inside IMAGE_EXPORT_DIRECTORY of kernel32.dll - 这就是所谓的转发导出。在这种情况下,加载程序理解 kernel32.DecodePointer 不指向代码,而是指向 string 形式 somedll.somefunction 或形式 somedll.#someordinal 作为结果加载器尝试加载 somedll 并搜索 somefunction 按名称或 someordinal 按序号。您如何查看此搜索可以(如果正向导出是)递归的。当我们最终得到函数地址 not inside IMAGE_EXPORT_DIRECTORY 时它停止了 - 这个地址将存储在 IAT 或过程失败 - 我们不会在此 dll 中找到 dll/or 导出。

注意这里的分隔符(在 dllfunction 名称之间 - 不是 !.)有趣问题 - 如果 somedll 在自己的名字中包含 . 会怎样 - 比如 my.x64.dll。 windows 的旧版本不正确的进程名称是这样的 (my.x64.dll.somefunc) 因为它在 first . 中搜索strchr 的字符串 - 因此将在 my dll 中搜索 x64.dll.somefunc 并失败。但现在这是固定的 - 加载程序使用 strrchr - 他在字符串中搜索最后一个 .

作为每年的结果,我们无法指定带扩展名的完整 dll 名称 -

#pragma comment(linker, "/export:fn=kernel32.dll.DecodePointer");
GetProcAddress((HMODULE)&__ImageBase, "fn");

在 xp 上失败。但现在 - win10 确切地说,可能是 win8.1(需要检查)这是正确的并且可以工作 - xp 将在 kernel32 中搜索 dll.DecodePointer 而 win10 将在 [ 中搜索 DecodePointer =36=]。同样从这里指出,早期我们不能转发导出到没有 .dll 扩展名的模块,现在 - 没有这样的限制。 (加载程序默认附加 .DLL 后缀到加载的库名称,如果它不包含 . - 所以当调用 LoadLibrary("my") - 实际上将被打开并加载 "my.DLL",但对于 "my.""my.x64" 后缀 .DLL 将不会附加(. 名称中的字符))

所以如果 return 你的具体例子 - kernel32.DecodePointer 指向 kernel32.dllIMAGE_EXPORT_DIRECTORY 里面的东西。加载程序通过此地址读取 string - NTDLL.RtlDecodePointer - 在此字符串上调用 strrchr(或 strchr 旧版本)以查找 . 和最后加载 NTDLL -> NTDLL.DLL(添加后缀是因为名称中没有 .)并搜索 RtlDecodePointer - 找到地址但它不在 IMAGE_EXPORT_DIRECTORY 内74=]ntdll.dll - 所以这是代码地址。这里进程停止并且 RtlDecodePointer 的地址存储在初始 PE IAT 中。

你自己这边需要重复加载程序步骤。但在现代 os 中存在一个问题 - 许多字符串以 API-MS-* dll 名称开头。这不是真正的 dll,而是 The API Set Schema - 未记录且可变的方式,加载程序如何解析此名称

听起来您希望能够在解析模块的导出时区分 Fowarder 字符串和常规函数地址 table。

我不建议在您知道它是转发器字符串之前尝试解析此转发器字符串,因为有一种更简单的方法。诀窍是检查导出函数的地址是否在导出段内存范围内。这是 PE/COFF 规范的 "Export Address Table" 部分中所述的官方方法。

抱歉,我下面的示例代码是用 C 语言编写的,而不是 Python,但您应该仍能理解。另请注意,下面的检查适用于 PE32 和 PE64 图像。

当您解析导出 table 时,您将已经拥有指向 IMAGE_DATA_DIRECTORY 导出部分的指针。从那里您可以获得指向 IMAGE_EXPORT_DIRECTORY 的指针。例如

IMAGE_DATA_DIRECTORY* pExportEntry = pOptHeader->DataDirectory->arDataDirectory + IMAGE_DIRECTORY_ENTRY_EXPORT;
...
//code to convert pExportEntry->VirtualAddress into file offset and store in dwExportTableFileOffset
...
IMAGE_EXPORT_DIRECTORY* pExportTable = (IMAGE_EXPORT_DIRECTORY*)(ImageFileBase + dwExportTableFileOffset);

假设您已经从 pExportTable->AddressOfFunctions 中检索到函数的数组指针,无论函数是按名称还是按序号导出,下面的检查都有效。如果函数#i 的函数地址(如下面的 arFuncs[i] 所示)在导出部分(您已经在解析)内,则该地址指向格式为 . 的 Forwarder 字符串,否则它是一个常规函数。

if (arFuncs[i] >= pExportEntry->VirtualAddress && arFuncs[i] < pExportEntry->VirtualAddress+pExportEntry->Size)
{
    //function address is RVA to Forwarder String; e.g. NTDLL.RtlDecodePointer
}
else
{
    //function address is RVA to actual code within current module
}