DLL函数调用的间接跳转

问题描述 投票:0回答:1

调用 DLL 函数的地址修复是一个多阶段过程:链接器将调用指令定向到间接跳转指令,并将间接跳转指令定向到 .rdata 节中导入表中的内存字,Windows 程序加载器将在其中放置运行时加载 DLL 时函数的地址。

间接跳转指令必须由链接器生成,因为编译器不知道该函数将位于 DLL 中。通过为每个函数仅生成一个间接跳转指令(无论从多少个位置调用它),可以最大限度地减少程序文件大小。

鉴于此,最明显的方法是在所有目标文件中收集所有编译器生成的代码之后,在文本部分的末尾收集所有间接跳转指令,这似乎就是我尝试时发生的情况一个带有 Microsoft 链接器 /nodefaultlib 开关的简单测试用例(它生成一个足够小的可执行文件,我可以理解完整的反汇编)。

当我以正常方式将一个小程序与C标准库链接时,生成的可执行文件足够大,以至于我无法跟踪所有反汇编,但据我所知,间接跳转指令似乎是分散的以每次可能三个人的小组的形式贯穿整个代码。

这是否是我遗漏的原因?

windows dll linker reverse-engineering portable-executable
1个回答
6
投票

间接跳转指令必须由链接器生成,因为 编译器不知道该函数将位于 DLL 中。

事实上,情况并非总是如此。如果您用

__declspec(dllimport)
标记该函数,编译器确实知道它将是 DLL 导入,在这种情况下它可以生成间接调用:

; HMODULE = LoadLibrary("mylib");
push  offset $SG66630
call  [__imp__LoadLibraryA@4]

__imp__LoadLibraryA@4
是指向IAT中导入的指针)

如果不使用

dllimport
,则编译器会生成相对函数调用:

push  offset $SG66630
call  _LoadLibraryA@4

在这种情况下,链接器必须生成跳转存根:

LoadLibraryA    proc near
                jmp     [__imp__LoadLibraryA@4]
LoadLibraryA    endp

事实上,它确实将此类跳转存根组合在一起(尽管可能是通过编译单元和/或导入的 DLL,但这里不能 100% 确定)。

注意:过去,链接器不会显式生成跳转存根,而是从导入库中获取它们。它们包含完整的目标文件,包括生成 PE 导入目录所需的存根和结构。请参阅这篇文章了解其工作原理:https://web.archive.org/web/20070924090618/https://www.microsoft.com/msj/0498/hood0498.aspx

如今,导入库只有 API 和 DLL 名称,链接器知道如何生成导入它们所需的代码和元数据。

© www.soinside.com 2019 - 2024. All rights reserved.