inline hook最终目的其实就是修改了机器码,改变原有的执行流程。因此最终目的即使得cpu的eip变化。
编译型的语言,在编译时候都有一个选项叫做热修补。热修补的最终实际实现其实是在每个函数头添加一个一段占位的汇编,且该汇编代码不会影响cpu的执行状态。
说到这里,我们第一反应就是nop。没错,nop就是其中之一。不过一般的程序热修补都不会使用nop,而是一些其他的指令。譬如
USER32.SetRect+36 - CC - int 3
USER32.SetRect+37 - CC - int 3
USER32.SetRect+38 - CC - int 3
USER32.SetRect+39 - CC - int 3
USER32.SetRect+3A - CC - int 3
USER32.SetRect+3B - CC - int 3
USER32.SetRect+3C - CC - int 3
USER32.SetRect+3D - CC - int 3
USER32.SetRect+3E - CC - int 3
USER32.SetRect+3F - CC - int 3
USER32.CallNextHookEx - 8B FF - mov edi,edi
USER32.CallNextHookEx+2 - 55 - push ebp
USER32.CallNextHookEx+3 - 8B EC - mov ebp,esp
USER32.CallNextHookEx+5 - 51 - push ecx
USER32.CallNextHookEx+6 - 51 - push ecx
USER32.CallNextHookEx+7 - 64 8B 0D 18000000 - mov ecx,fs:[00000018] { 24 }
USER32.CallNextHookEx+E - 8B 81 DC0F0000 - mov eax,[ecx+00000FDC]
上文中 CallNextHookEx 函数开头的 mov edi,edi 即为热修补的占位代码。目的是使得第一条指令的大小大于等于2字节。因为jmp 短跳的指令大小即为2字节。
而该两字节仅仅作为跳板,跳到哪里呢,当然是上方的int 3的空地址空间,并且再修改此处的地址的代码再次跳转到真实地址。因为长跳转需要的地址长度超过了5字节。
1、2G空间范围内跳转: 5字节
02520000 - E9 FBFFADFF - jmp 02000000
02520005 - 90 - nop
02520006 - 90 - nop
02520007 - 90 - nop
02520008 - 90 - nop
2、2G空间范围内表跳转:6字节
02520000 - FF 25 FAFFADFF - jmp qword ptr [02000000]
02000000:存放函数地址
3、无限制hook:16字节
02520000 - 50 - push rax
02520001 - 48 B8 F0DEBC9A78563412 - mov rax,123456789ABCDEF0
0252000B - 48 87 04 24 - xchg [rsp],rax
0252000F - C3 - ret
4、直接跳转:14字节
02520000 - FF25 00000000 F0DEBC9A78563412 - jmp 123456789ABCDEF0