c++ - 在 Windows DLL 中通过它的签名查找函数

标签 c++ c assembly signature disassembly

已在 DLL 中找到一个函数地址。没有这个 DLL 的源代码,不是我的。这个DLL还真的不是经常改的,但是一改了,我反汇编找就成问题了。在网上看到一些关于给它签名的笔记,然后通过这个保存的签名找到它。 能否就如何实现这一点提供一些想法或工作示例?

最佳答案

你可以通过代码签名扫描来实现,这是我过去做过的事情。该概念主要基于这样一个事实,即函数在更新之间通常不会发生太大变化,而只是简单地重新定位,因为它们被扩展或收缩的其他函数向前或向后推。

我们以MessageBoxA为例,他的反汇编对我来说是这样的:

765DEA11 > 8BFF             MOV EDI,EDI
765DEA13   55               PUSH EBP
765DEA14   8BEC             MOV EBP,ESP
765DEA16   833D 749A5E76 00 CMP DWORD PTR DS:[765E9A74],0
765DEA1D   74 24            JE SHORT USER32.765DEA43
765DEA1F   64:A1 18000000   MOV EAX,DWORD PTR FS:[18]
765DEA25   6A 00            PUSH 0
765DEA27   FF70 24          PUSH DWORD PTR DS:[EAX+24]
765DEA2A   68 A49E5E76      PUSH USER32.765E9EA4
765DEA2F   FF15 34145876    CALL DWORD PTR DS:[<&KERNEL32.Interlocke>; kernel32.InterlockedCompareExchange
765DEA35   85C0             TEST EAX,EAX
765DEA37   75 0A            JNZ SHORT USER32.765DEA43
765DEA39   C705 A09E5E76 01>MOV DWORD PTR DS:[765E9EA0],1
765DEA43   6A 00            PUSH 0
765DEA45   FF75 14          PUSH DWORD PTR SS:[EBP+14]
765DEA48   FF75 10          PUSH DWORD PTR SS:[EBP+10]
765DEA4B   FF75 0C          PUSH DWORD PTR SS:[EBP+C]
765DEA4E   FF75 08          PUSH DWORD PTR SS:[EBP+8]
765DEA51   E8 73FFFFFF      CALL USER32.MessageBoxExA
765DEA56   5D               POP EBP
765DEA57   C2 1000          RETN 10

诀窍是猜测一些您认为可能在更新中保持不变的代码块,但更重要的是该函数是独一无二的。通常,扫描尾声/序言是没有用的。我可能会采用以下 block :

765DEA16   833D 749A5E76 00 CMP DWORD PTR DS:[765E9A74],0
765DEA1D   74 24            JE SHORT USER32.765DEA43
765DEA1F   64:A1 18000000   MOV EAX,DWORD PTR FS:[18]
765DEA25   6A 00            PUSH 0
765DEA27   FF70 24          PUSH DWORD PTR DS:[EAX+24]
765DEA2A   68 A49E5E76      PUSH USER32.765E9EA4
765DEA2F   FF15 34145876    CALL DWORD PTR DS:[<&KERNEL32.Interlocke>; 

在选择 block 的长度时,您必须做出平衡。 block 越长,就越有可能唯一标识一个功能,但也更有可能在更新过程中插入一些代码,这意味着它被拆分等。注意我选择的 block 有多个内存引用。我们不能依赖任何数据或函数地址,因为它们可能会在下一次更新时重新定位,因此我们用通配符填充这些字节:

765DEA16   833D XXXXXXXX 00 CMP DWORD PTR DS:[XXXXXXXX],0
765DEA1D   74 XX            JE SHORT XXXXXXXX
765DEA1F   64:A1 18000000   MOV EAX,DWORD PTR FS:[18]
765DEA25   6A 00            PUSH 0
765DEA27   FF70 24          PUSH DWORD PTR DS:[EAX+24]
765DEA2A   68 XXXXXXXX      PUSH XXXXXXXX
765DEA2F   FF15 XXXXXXXX    CALL DWORD PTR DS:[XXXXXXXX] 

这意味着我们的字节签名现在是:

0x83 0x3D 0x? 0x? 0x? 0x? 0x74 0x? 0x64 0xA1 0x18 0x00 0x00 0x00 0x6A 0x00 0xFF 0x70 0x24 0x68 0x? 0x? 0x? 0x? 0xFF 0x15 0x? 0x? 0x? 0x?

0x? 字节表示通配符,这是我们希望更改的字节。其他的是我们预计不会在更新中改变的字节。要在运行时使用字节定位函数,您需要扫描这些字节(考虑到通配符)。过程大概是这样:

  • 枚举进程的所有可执行页面(VirtualQueryEx)
  • 扫描我们找到的字节签名(考虑到通配符 - 这很容易实现为跳过通配符字节的 for 循环)
  • 要获得真正的函数地址,请使用 block 与原始函数的偏移量修正您获得的地址(在本例中,0x765DEA16 - 0x765DEA11 => 0x5)

实际上,在这种情况下,无需枚举所有可执行页面,通常只需找到函数所在的模块 (user32.dll),然后仅在该模块内搜索。

关于c++ - 在 Windows DLL 中通过它的签名查找函数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9644717/

相关文章:

assembly - 有没有关于指令 `jmpi`的权威文件

assembly - 如何将 ASM 程序包含到我的 Turbo Basic 程序中?

C++11 通过引用定义函数类型捕获

c++ - 优化最近邻大小调整算法以提高速度

c - 关于栈指针的疑问

c - 同时写入和读取数组

c - 程序崩溃

c++ - UML 图中的运算符重载

c++ - 在WM中模拟Java的Thread.sleep()

c++ - 为什么 clang 缺少参数包错误的默认参数?