为什么这个 _DllMainCRTStartup 调用约定不像预期的那样?

逆向工程 视窗 dll 调用约定 入口点
2021-07-06 08:56:30

我最近一直在查看计算机上的各种 DLL,我认为一个不错的起点是图像入口点(通常是 _DllMainCRTStartup)。

从我所看到的 CRT 源代码 (MSVC) 来看,它使用相同的参数(HMODULE、DWORD、LPVOID)调用另一个函数(__DllMainCRTStartup)。然而,我看过的许多二进制文件似乎都偏离了这一点:

  push        dword ptr [ebp+8]
  mov         ecx,dword ptr [ebp+10h]
  mov         edx,dword ptr [ebp+0Ch]
  call        10024E5B // __DllMainCRTStartup

这看起来是在推送第一个参数 (HMODULE),然后通过寄存器 ecx/edx 传递另外两个参数。被调用的函数也希望它们在那里。我期望看到的是所有三个都被推送到堆栈上,我相信这就是 _cdecl 约定的工作方式。这是一个例子:

  push        dword ptr [ebp+10h]
  push        dword ptr [ebp+0Ch]
  push        dword ptr [ebp+8]
  call        3DC40E2E // __DllMainCRTStartup

有谁知道第一个变体来自哪里?它是旧版本的 CRT 吗?或者可能是完全不同的编译器?如果您也知道它是什么类型的调用约定,那么对我来说看起来像是 __fastcall 的奇怪表亲!

非常感谢!

编辑:

以为我会从 CRT 源代码中添加函数原型(至少是 VS 2012 附带的版本)

__declspec(noinline)
BOOL __cdecl
__DllMainCRTStartup(
    HANDLE  hDllHandle,
    DWORD   dwReason,
    LPVOID  lpreserved
    )

编辑x2:

我刚刚看到了“相关”链接:什么 x86 调用约定通过 ESI 传递第一个参数?

看起来这可能是 LTCG 的结果,链接器基本上只是决定做任何它喜欢的事情,因为它知道所有组件!

1个回答

通过全局优化(链接时代码生成),编译器可以自由地忽略所有外部不可见的函数的声明调用约定——也就是说,既不导出也不以其他方式通过获取它们的地址并将其传递给外部代码来固定。

这会使生成的二进制文件难以分析,因为即使是启动代码中众所周知的部分也可以在全局优化下变形和融合。它仍然可以通过其行为和嵌入的魔法常量(如用于初始化堆栈保护 cookie 及其补充的那些)来识别,但是通过像 IDA 的FLIRT 之类的简单机制识别库对象代码的成功率需要下降

此外,在这种情况下,从头文件或 IDA 的签名文件中提取的函数原型可能是错误的。这可能导致 IDA 的分析引擎(和读者)误入歧途,从而导致问题甚至几个调用级别远离有问题的函数。

简而言之:对于 LTCG,所有赌注都被取消了,简单的分析代码可能变得不可靠。