DLL 函数调用的间接跳转

rwa*_*ace 5 windows dll linker reverse-engineering portable-executable

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

间接跳转指令必须由链接器生成,因为编译器不知道该函数会出现在 DLL 中。通过为每个函数只生成一条间接跳转指令来最小化程序文件的大小,无论从多少地方调用它。

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

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

有没有我失踪的原因?

Igo*_*sky 6

间接跳转指令必须由链接器生成,因为编译器不知道该函数会出现在 DLL 中。

实际上,情况并非总是如此。如果用 标记函数__declspec(dllimport),编译器知道它将是一个 DLL 导入,在这种情况下它可以生成一个间接调用:

; HMODULE = LoadLibrary("mylib");
push  offset $SG66630
call  [__imp__LoadLibraryA@4]
Run Code Online (Sandbox Code Playgroud)

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

如果不使用,dllimport那么编译器会生成一个相对函数调用:

push  offset $SG66630
call  _LoadLibraryA@4
Run Code Online (Sandbox Code Playgroud)

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

LoadLibraryA    proc near
                jmp     [__imp__LoadLibraryA@4]
LoadLibraryA    endp
Run Code Online (Sandbox Code Playgroud)

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

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

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