问题描述
我已经通过以下示例研究延迟加载(delayimp)管道作为Windows上缺少的RPATH功能的可能后端:
#include <stdio.h>
int __declspec(dllimport) foo(int arg);
int main(int argc,char* argv[])
{
printf("foo() = %d\n",foo(foo(argc)));
return 0;
}
GNU和LLVM都实现了与“ dlltool”类似的延迟加载(但是,LLVM的dlltool似乎已合并到“ ld-link”中)。本质上,在LLVM的lld/COFF/DLL.cpp
或BinUtil的dlltool.c
中执行的任务是双重的:
在成功绑定之后,__delayLoadHelper2
似乎将已解析的函数地址直接写入了可执行代码部分:
extern "C"
FARPROC WINAPI
__delayLoadHelper2(
PCImgDelayDescr pidd, FARPROC * ppfnIATEntry
) {
...
SetEntryHookBypass:
*ppfnIATEntry = pfnRet; // access violation
...
}
为了修改可执行映像,Microsoft开发了一些精美的功能,可以临时向相应的内存区域添加写权限。
现在的问题是::要修改的代码位于跳转到“ .idata”部分的跳转表存根中,它无法获得写权限:
if ((characteristics & IMAGE_SCN_MEM_WRITE) == 0) {
//
// This delay load helper module does not support merging the delay
// load section to a read only section because memory management
// would not guarantee that there is commit available - and thus a
// low memory failure path where the delay load failure hook Could
// not be safely invoked (the delay load section would still be
// read only) might be encountered.
//
// It is a build time configuration problem to produce such a
// binary so abort here and Now so that the problem can be
// identified & fixed.
//
/* Exception thrown at 0x000000013F3B3F3F in dlltool_test_executable.exe: 0xC0000005: Access violation reading */
__fastfail(FAST_FAIL_DLOAD_PROTECTION_FAILURE);
}
因此,当前硬绑定不起作用,并给出“写访问冲突”。我想知道我在这里缺少哪种二进制配置吗?
我的测试配置:github上的LLVM上游,git上的BinUtils上游,MSVC2019,Windows 7。
$ cat trampoline.s
# Import trampoline
.section .text
.global __tailMerge_C__Users_marcusmae_dlltool_build_import_test_lib
__tailMerge_C__Users_marcusmae_dlltool_build_import_test_lib:
pushq %rcx
pushq %rdx
pushq %r8
pushq %r9
subq $40,%rsp
movq %rax,%rdx
leaq __DELAY_IMPORT_DESCRIPTOR_C__Users_marcusmae_dlltool_build_import_test_lib(%rip),%rcx
call __delayLoadHelper2
addq $40,%rsp
popq %r9
popq %r8
popq %rdx
popq %rcx
jmp *%rax
# DELAY_IMPORT_DESCRIPTOR
.section .text$2
.global __DELAY_IMPORT_DESCRIPTOR_C__Users_marcusmae_dlltool_build_import_test_lib
__DELAY_IMPORT_DESCRIPTOR_C__Users_marcusmae_dlltool_build_import_test_lib:
.long 1 # grAttrs
.rva __C__Users_marcusmae_dlltool_build_import_test_lib_iname # rvaDLLName
.rva __DLL_HANDLE_C__Users_marcusmae_dlltool_build_import_test_lib # rvaHmod
.rva __IAT_C__Users_marcusmae_dlltool_build_import_test_lib # rvaIAT
.rva __INT_C__Users_marcusmae_dlltool_build_import_test_lib # rvaINT
.long 0 # rvaBoundIAT
.long 0 # rvaUnloadIAT
.long 0 # dwTimeStamp
.section .data
__DLL_HANDLE_C__Users_marcusmae_dlltool_build_import_test_lib:
.long 0 # Handle
.long 0
#Stuff for compatibility
.section .idata$5
.long 0
.long 0
__IAT_C__Users_marcusmae_dlltool_build_import_test_lib:
.section .idata$4
.long 0
.long 0
.section .idata$4
__INT_C__Users_marcusmae_dlltool_build_import_test_lib:
.section .idata$2
$ objdump -d dorks00000.o
dorks00000.o: file format pe-x86-64
disassembly of section .text:
0000000000000000 <foo>:
0: ff 25 00 00 00 00 jmpq *0x0(%rip) # 6 <foo+0x6>
6: 48 8d 05 00 00 00 00 lea 0x0(%rip),%rax # d <foo+0xd>
d: e9 00 00 00 00 jmpq 12 <foo+0x12>
...
解决方法
因此,您正在使用GNU dlltool生成延迟导入结构,但使用LLD或MS link.exe与之链接吗?
我认为这里的区别在于,GNU dlltool将运行时更新的地址放在.idata
内,而GNU ld通常将.idata
链接为可写,而LLD和MS link.exe通常具有只读的.idata
(并且将在运行时通过延迟加载机制更新的地址放在.data
中。)
LLD碰巧有一些额外的代码,可以从GNU导入库中获取可读写的.idata
部分,并将它们合并到LLD的只读.idata
的其余部分中,这使得普通的GNU导入库可以正常工作,但不幸的是,它无法与GNU dlltool delayimport库一起使用。
因此,对于LLD,只需通过传递以下内容即可使用LLD内置的延迟导入机制:链接时为-delayload:user32.dll
。使用MSVC样式导入库时,此方法有效,但不幸的是,使用GNU样式导入库(由GNU dlltool或GNU ld生成的导入库)时,此方法无效。