已在 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/