【问题标题】:How can I resolve the forwarded API from the IAT on PE?如何从 PE 上的 IAT 解析转发的 API?
【发布时间】:2017-05-31 08:13:17
【问题描述】:

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

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

例如,我从 IAT 获得了“ntdll!RtlDecodePointer”,但它是从“kernel32!DecodePointer”转发的,我不知道如何找到它。

我应该在 Import 目录中搜索每个加载的模块的 ForwarderChain 吗?

【问题讨论】:

    标签: python portable-executable import-table


    【解决方案1】:

    不,ForwarderChain in Import directory 与此无关。

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

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

    因此,我们无法每年指定完整的 dll 名称和扩展名 -

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

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

    所以,如果返回您的具体示例 - kernel32.DecodePointer 指向 kernel32.dllIMAGE_EXPORT_DIRECTORY 内部的内容。加载器通过这个地址读取字符串 - NTDLL.RtlDecodePointer - 在这个字符串上调用strrchr(或strchr旧版本)以查找.,最后加载NTDLL -> NTDLL.DLL(添加后缀是因为名称中没有 .)并搜索 RtlDecodePointer - 找到地址,但它不在 ntdll.dllIMAGE_EXPORT_DIRECTORY 内 - 所以这是代码地址。这里进程停止,RtlDecodePointer 的地址存储在初始 PE IAT 中。

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

    【讨论】:

    • 感谢您的友好解释。我想我需要更多地研究操作原理......:'
    【解决方案2】:

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

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

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

    当您解析导出表时,您已经有了一个指向 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] 显示)在导出部分(您已经在解析)内,则地址指向格式为 . 的转发器字符串,否则它是一个常规功能。

    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
    }
    

    【讨论】:

    • 非常感谢。你的例子对我很有帮助!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-04-21
    • 1970-01-01
    相关资源
    最近更新 更多