在Windows DLL中通过签名查找函数

Find a function by it signature in Windows DLL

本文关键字:查找 函数 Windows DLL      更新时间:2023-10-16

在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

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

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>; 

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

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 0x6A0x00 0xFF 0x70 0x24 0x68 0x?0x?0x?0x?0xFF 0x15 0x?0x?0x?0x?

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

  • 枚举进程的所有可执行页面(VirtualQueryEx
  • 扫描我们找到的字节签名(考虑通配符-这对于作为跳过通配符字节的for循环实现来说是微不足道的)
  • 要获得真正的函数地址,请用块与原始函数的偏移量(在本例中为0x765DEA16 - 0x765DEA11 => 0x5)来固定您获得的地址

实际上,在这种情况下,与其枚举所有可执行页面,不如查找函数所在的模块(user32.dll),并仅在该模块中搜索。