将延迟加载函数绑定正确写入图像可执行文件 (dlltool)
Get the delayed-load function binding correctly written into the image executable (dlltool)
我一直在研究延迟加载 (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
中执行的任务有两个:
- 为延迟加载函数生成跳转 table 存根(参见下面的示例)
- 生成应部署 __delayLoadHelper2 代码的蹦床(参见下面的示例)
绑定成功后,__delayLoadHelper2
似乎将已解析的函数地址直接写入 executable 代码部分:
extern "C"
FARPROC WINAPI
__delayLoadHelper2(
PCImgDelayDescr pidd,
FARPROC * ppfnIATEntry
) {
...
SetEntryHookBypass:
*ppfnIATEntry = pfnRet; // access violation
...
}
为了executable修改图片,微软开发了一些花哨的函数,临时给相应的内存区域增加写权限。
现在的问题是:要修改的代码在进入“.idata”部分的跳转table存根内,和获取写权限失败:
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 , %rsp
movq %rax, %rdx
leaq __DELAY_IMPORT_DESCRIPTOR_C__Users_marcusmae_dlltool_build_import_test_lib(%rip), %rcx
call __delayLoadHelper2
addq , %rsp
popq %r9
popq %r8
popq %rdx
popq %rcx
jmp *%rax
# DELAY_IMPORT_DESCRIPTOR
.section .text
.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
.long 0
.long 0
__IAT_C__Users_marcusmae_dlltool_build_import_test_lib:
.section .idata
.long 0
.long 0
.section .idata
__INT_C__Users_marcusmae_dlltool_build_import_test_lib:
.section .idata
$ 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 通常有 read-only .idata
(并将延迟加载机制在运行时更新的地址放在 .data
中)。
LLD 恰好有一些额外的代码来从 GNU 导入库中获取 read-write .idata
部分并将它们合并到 LLD 的其余部分 read-only .idata
-这使得普通的 GNU 导入库可以工作,但不幸的是,它无法与 GNU dlltool delayimport 库一起使用。
所以对于 LLD,只需使用 LLD 的 built-in 延迟导入机制,通过例如传递-delayload:user32.dll
链接时。这在使用 MSVC 样式导入库时有效,但不幸的是在使用 GNU 样式导入库(由 GNU dlltool 或 GNU ld 生成的导入库)时无效。
我一直在研究延迟加载 (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
中执行的任务有两个:
- 为延迟加载函数生成跳转 table 存根(参见下面的示例)
- 生成应部署 __delayLoadHelper2 代码的蹦床(参见下面的示例)
绑定成功后,__delayLoadHelper2
似乎将已解析的函数地址直接写入 executable 代码部分:
extern "C"
FARPROC WINAPI
__delayLoadHelper2(
PCImgDelayDescr pidd,
FARPROC * ppfnIATEntry
) {
...
SetEntryHookBypass:
*ppfnIATEntry = pfnRet; // access violation
...
}
为了executable修改图片,微软开发了一些花哨的函数,临时给相应的内存区域增加写权限。
现在的问题是:要修改的代码在进入“.idata”部分的跳转table存根内,和获取写权限失败:
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 , %rsp
movq %rax, %rdx
leaq __DELAY_IMPORT_DESCRIPTOR_C__Users_marcusmae_dlltool_build_import_test_lib(%rip), %rcx
call __delayLoadHelper2
addq , %rsp
popq %r9
popq %r8
popq %rdx
popq %rcx
jmp *%rax
# DELAY_IMPORT_DESCRIPTOR
.section .text
.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
.long 0
.long 0
__IAT_C__Users_marcusmae_dlltool_build_import_test_lib:
.section .idata
.long 0
.long 0
.section .idata
__INT_C__Users_marcusmae_dlltool_build_import_test_lib:
.section .idata
$ 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 通常有 read-only .idata
(并将延迟加载机制在运行时更新的地址放在 .data
中)。
LLD 恰好有一些额外的代码来从 GNU 导入库中获取 read-write .idata
部分并将它们合并到 LLD 的其余部分 read-only .idata
-这使得普通的 GNU 导入库可以工作,但不幸的是,它无法与 GNU dlltool delayimport 库一起使用。
所以对于 LLD,只需使用 LLD 的 built-in 延迟导入机制,通过例如传递-delayload:user32.dll
链接时。这在使用 MSVC 样式导入库时有效,但不幸的是在使用 GNU 样式导入库(由 GNU dlltool 或 GNU ld 生成的导入库)时无效。