mov edi,edi - hook api
系统: windows xp
查看系统函数的反汇编代码时会发现开头有个"mov edi,edi"(2字节),再往前则是5个nop指令(当然这不会引人注意),可是"mov edi,edi"有什么用了.
上网搜索"mov edi,edi",结果让人惊奇,据说系统函数都添加了这段"无用"的代码,为的是Hot Patching,详细内容请自行搜索.
通过改写代码来挂接API时,一般是移动前几个字节加入jump指令,对于"mov edi,edi"开头的函数也习惯性的这样处理.可现在情况不同了,这7个字节提供了方便,文中提到了更好的办法,在mov指令处(即函数开始处)使用一个短跳转指令(2字节)到上面的nop位置,这5个字节可以插入长跳转或call到远处.
想做个什么,现在这类型的函数修改都可以这样千篇一律,那我要做一个可以hook所有这些函数的接口,之后在自己的dll内根据函数地址分流. 以后想要hook某api只要修改这7个字节,就可以在我们自己的dll函数内收到信息(包括原函数地址,参数列表),还可以选择是否执行原函数.
实现包含几部分
1. 修改待hook api地址处的7个字节(5个nop和mov edi,edi),呼叫接口函数,call偏移为(接口函数地址-原函数地址)
2. 接口函数的编写,需要取得原函数地址,参数列表后呼叫分流函数
3. 分流函数,根据函数地址区分函数,做后续处理
====================================================================
api开头修改,以MessageBoxA为例
77D50506 90 NOP
77D50507 90 NOP
77D50508 90 NOP
77D50509 90 NOP
77D5050A 90 NOP
77D5050B > 8BFF MOV EDI,EDI
77D5050D /. 55 PUSH EBP
77D5050B为MessageBoxA的地址,改为:
77D50506 E8 3527B304 CALL kernel32.7C882C40 ;接口函数
77D5050B > ^ EB F9 JMP SHORT USER32.77D50506
77D5050D /. 55 PUSH EBP
====================================================================
接口函数,写入kernel32.dll,windows下都会加载
将以下汇编的字节码写入kernel32的.text段的尾部,可能大家xp版本不一样会有些差别
7C882C40 83EC 08 SUB ESP,8 ; 保存fRet,addEspNum
7C882C43 E8 38000000 CALL kernel32.7C882C80 ; 获取HookFun的地址
7C882C48 85C0 TEST EAX,EAX
7C882C4A 75 08 JNZ SHORT kernel32.7C882C54
7C882C4C 83C4 08 ADD ESP,8
7C882C4F 830424 02 ADD DWORD PTR SS:[ESP],2 ; 返回到原函数地址+2
7C882C53 C3 RETN
7C882C54 8D4C24 04 LEA ECX,DWORD PTR SS:[ESP+4]
7C882C58 54 PUSH ESP ; addEspNum
7C882C59 51 PUSH ECX ; fRet
7C882C5A 8D4C24 18 LEA ECX,DWORD PTR SS:[ESP+18]
7C882C5E 51 PUSH ECX ; args
7C882C5F 8B4C24 14 MOV ECX,DWORD PTR SS:[ESP+14]
7C882C63 51 PUSH ECX ; 原函数地址
7C882C64 FFD0 CALL EAX
7C882C66 8B4C24 04 MOV ECX,DWORD PTR SS:[ESP+4] ; fRet
7C882C6A 85C9 TEST ECX,ECX
7C882C6C ^ 74 DE JE SHORT kernel32.7C882C4C
7C882C6E 8B5424 0C MOV EDX,DWORD PTR SS:[ESP+C] ; 原函数返回地址
7C882C72 8B0C24 MOV ECX,DWORD PTR SS:[ESP] ; addEspNum
7C882C75 8D640C 10 LEA ESP,DWORD PTR SS:[ESP+ECX+10]
7C882C79 FFE2 JMP EDX ; 修改ESP后,跳回去,不用ret
7C882C7B 90 NOP
7C882C7C 90 NOP
7C882C7D 90 NOP
7C882C7E 90 NOP
7C882C7F 90 NOP
7C882C80 A1 0472887C MOV EAX,DWORD PTR DS:[7C887204] ; 保存 fun,位于.data段
7C882C85 85C0 TEST EAX,EAX
7C882C87 74 01 JE SHORT kernel32.7C882C8A
7C882C89 C3 RETN
7C882C8A A1 0072887C MOV EAX,DWORD PTR DS:[7C887200] ; 保存 hModule
7C882C8F 85C0 TEST EAX,EAX
7C882C91 75 14 JNZ SHORT kernel32.7C882CA7
7C882C93 68 212C887C PUSH kernel32.7C882C21 ; ASCII "myHookFun.dll"
7C882C98 E8 DAF0F7FF CALL kernel32.LoadLibraryA
7C882C9D A3 0072887C MOV DWORD PTR DS:[7C887200],EAX
7C882CA2 85C0 TEST EAX,EAX
7C882CA4 75 01 JNZ SHORT kernel32.7C882CA7
7C882CA6 C3 RETN
7C882CA7 68 302C887C PUSH kernel32.7C882C30 ; ASCII "HookFun"
7C882CAC 50 PUSH EAX
7C882CAD E8 767FF8FF CALL kernel32.GetProcAddress
7C882CB2 A3 0472887C MOV DWORD PTR DS:[7C887204],EAX
7C882CB7 C3 RETN
7C882C20 00 6D 79 68 6F 6F 6B 61 70 69 2E 64 6C 6C 00 00 .myHookFun.dll
7C882C30 48 6F 6F 6B 46 75 6E 00 00 00 00 00 00 00 00 90 HookFun
7C882C40 83 EC 08 E8 38 00 00 00 85 C0 75 08 83 C4 08 83
7C882C50 04 24 02 C3 8D 4C 24 04 54 51 8D 4C 24 18 51 8B
7C882C60 4C 24 14 51 FF D0 8B 4C 24 04 85 C9 74 DE 8B 54
7C882C70 24 0C 8B 0C 24 8D 64 0C 10 FF E2 90 90 90 90 90
7C882C80 A1 04 72 88 7C 85 C0 74 01 C3 A1 00 72 88 7C 85
7C882C90 C0 75 14 68 21 2C 88 7C E8 DA F0 F7 FF A3 00 72
7C882CA0 88 7C 85 C0 75 01 C3 68 30 2C 88 7C 50 E8 76 7F
7C882CB0 F8 FF A3 04 72 88 7C C3
以上地址为内存中的虚拟地址,7c882c20的文件地址为82020h(我的文件是这样)
====================================================================
分流函数,自己编写的dll,文件名"myHookFun.dll",导出函数"HookFun"
typedef int (__stdcall *PROC4)(int,int,int,int);
//proc 原函数地址; args 参数列表; fRet 非0返回上级,否则继续执行原函数; addEspNum 退栈数量
//如需执行原函数应该使用proc+2,从"mov edi,edi"之后执行
__declspec(dllexport) int __stdcall HookFun(PROC proc,int *args,BOOL *fRet,int *addEspNum)
{
if (proc==(PROC)0x77D5050B) //MessageBoxA的地址,暂时直接指明
{
PROC4 p4=(PROC4)((BYTE*)proc+2);
*fRet=1;
*addEspNum=16; //不从原函数返回的话要告知退栈数量,调用者退栈的(__cdecl)设为0
return p4(args[0],args[1],(int)"HookFun",args[3]); //修改对话框的标题
}
*fRet=0;
return 0;
}
这个HookFun只是个示例
====================================================================
我修改了kernel32.dll文件,在dos下覆盖了原来的(之前有备份),新添加的汇编码只在修改了api开头的字节后才会被调用.测试程序可通过od启动,或者使用winHex修改待hook程序的进程内存(就7个字节,注意call地址的计算).
动态修改所有的内存也行(包括kernel32),接口函数也可以放在其它地方,甚至和分流函数合并,都放入自己的dll也行,注入dll就可以了