1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| #pragma section(".CRT$XCU", read, write)
typedef void(__cdecl* FPTR)(void);
__declspec(noinline) void my_init_function() { volatile PVOID hVEH = AddVectoredExceptionHandler(0, VEH_SIGNLE_STEP); if (!hVEH) { std::cerr << "Failed to add VEH handler." << std::endl; exit(1); } }
__declspec(selectany) __declspec(allocate(".CRT$XCU")) FPTR init_ptr = my_init_function;
void* force_init_ptr_ref = &init_ptr;
|
如上代码,函数 my_init_function 在 main 函数之前执行。
MSVC 编译器的 CRT 入口点通常为 wmainCRTstartup / mainCRTstartup
1 2 3 4 5
| int __usercall wmainCRTStartup@<eax>(int Code@<esi>) { __security_init_cookie(); return _scrt_common_main_seh(Code); }
|
跟进 _scrt_common_main_seh 可以看到如下代码
1 2 3 4 5 6 7 8 9 10 11 12
| call __initterm_e pop ecx pop ecx test eax, eax jz short loc_401E02 mov [ebp+ms_exc.registration.TryLevel], 0FFFFFFFEh mov eax, 0FFh jmp loc_401EF1 loc_401E02: push offset ___xc_z ; Last push offset ___xc_a ; First call __initterm
|
这部分代码有大部分冗余,重要的是以下部分
1 2 3 4
| call __initterm_e push offset ___xc_z ; Last push offset ___xc_a ; First call __initterm
|
___xc_a 和 ___xc_z 分别为初始化函数指针的头尾地址
1 2 3 4 5 6 7 8 9
| .rdata:00403138 ___xc_a dd 0 ; DATA XREF: __scrt_common_main_seh+75↑o .rdata:0040313C ; void (__cdecl *pre_cpp_initializer)() .rdata:0040313C pre_cpp_initializer dd offset pre_cpp_initialization .rdata:00403140 ; void (__cdecl *init_ptr)() .rdata:00403140 ?init_ptr@@3P6AXXZA dd offset ?my_init_function@@YAXXZ .rdata:00403140 ; DATA XREF: .data:void * force_init_ptr_ref↓o .rdata:00403140 ; my_init_function(void) .rdata:00403144 ; void (__cdecl *__xc_z[])() .rdata:00403144 ___xc_z dd 0 ; DATA XREF: __scrt_common_main_seh:loc_401E02↑o
|
可以看到, 连同语言初始化函数 pre_cpp_initializer , 我们自定义的 init_ptr也在里面
__initterm 是将函数指针压栈后调用它们的过程,逻辑在ucrtbase.dll中,主要就是通过迭代将数组中的函数全都执行一遍。