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 导出。
注意这里的分隔符(在 dll 和 function 名称之间 - 不是 !
但 .
)有趣问题 - 如果 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.dll 的 IMAGE_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
}
您好,我正在尝试在 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 导出。
注意这里的分隔符(在 dll 和 function 名称之间 - 不是 !
但 .
)有趣问题 - 如果 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.dll 的 IMAGE_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] 所示)在导出部分(您已经在解析)内,则该地址指向格式为
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
}