系统:Windows Xp sp3
工具:OD, vs 2008
前几天看到这篇帖子http://bbs.pediy.com/showthread.php?p=833045#post833045让我想来试着hook 一下native api
毋庸置疑,API hook在各种类型项目中使用广泛,已经研究了长一段时间了。参考文献1可以说是APIhook的一个综述。其中主要讲了Ring3层的hook。Ring0层hook难度要大点,驱动程序的稳定性也不能保证,所以我现在还没有勇气去搞驱动层的hook。
关于ring0 api hook 可以参考文献2. 其中详细的讲解了怎样编写驱动程序进行API hook,包含源码。这里主要说Ntdll.dll中的Native API 的hook。Ntdll.dll中函数在window Xp系统中形如:
7C92DF7E > B8 12010000 mov eax,112 ; ZwWriteFile
7C92DF83 BA 0003FE7F mov edx,7FFE0300
7C92DF88 FF12 call dword ptr ds:[edx] ; ntdll.KiFastSystemCall
7C92DF8A C2 2400 retn 24
在windows 2000中有所不同.
hook的基本思路是:修改函数入口处的第512字节,既是把
7C92DF83 BA 0003FE7F mov edx,7FFE0300
7C92DF88 FF12 call dword ptr ds:[edx] ; ntdll.KiFastSystemCall
修改成:
mov edx,[自定义的hook函数地址]
call edx
修改后的代码如下:
7C92DF7E > B8 12010000 mov eax,112 ; ZwWriteFile
7C92DF83 BA 0003FE7F mov edx,xxxxxxxx ;xxxxx是hook函数的地址,定义在你的dll中
7C92DF88 FF12 call edx ;call到你的hook函数中
7C92DF8A C2 2400 retn 24
这个通过打内存补丁来实现,参考文献3。代码如下:
//这是该的看雪的源码 BOOL MemPatch(HANDLE hProcess,const DWORD PatchAddr,const DWORD PatchSize,const BYTE signData[],const BYTE NewData[]) { BYTE ReadBuffer[128]={0}; BOOL bConntiuneRun=TRUE; DWORD Oldpp; int cnt = 0; while (bConntiuneRun) { cnt++; /* ResumeThread(pi.hThread); Sleep(1000); SuspendThread(pi.hThread);*/ ReadProcessMemory(hProcess,(LPVOID)PatchAddr,&ReadBuffer,2,NULL);//读2个字节来比较 if (!memcmp(signData,ReadBuffer,2)) { VirtualProtectEx(hProcess,(LPVOID)PatchAddr,PatchSize,PAGE_EXECUTE_READWRITE,&Oldpp); WriteProcessMemory(hProcess,(LPVOID)PatchAddr,NewData,PatchSize,0); bConntiuneRun = FALSE; } else if(cnt>5) break; } //ResumeThread(pi.hThread); //CloseHandle(pi.hProcess);pi.hProcess=0; //CloseHandle(pi.hThread);pi.hThread=0; if(cnt>5) return FALSE; else return TRUE; }
void HookProc() { //千万不要在这个函数中定义变量,否者会破坏堆栈 __asm { pop g_stack[0] mov g_dispatchNum,eax mov edx,0x7FFE0300 call dword ptr ds:[edx] push g_stack[0] } switch(g_dispatchNum) { case 0x25://ZwCreateFile fprintf_s(g_LogFile,"ZwCreateFile is invoked,Dispatch number is %x\n",g_dispatchNum); fflush(g_LogFile); break; case 0x30://ZwCreateProcessEx fprintf_s(g_LogFile,"ZwCreateProcessEx is invoked,Dispatch number is %x\n",g_dispatchNum); fflush(g_LogFile); break; } }
mov edx,0x7FFE0300
call dword ptr ds:[edx]
这段是拷贝的原函数中的代码,它会call KiFastSystemCall 使程序进入RING0,同时会使用到堆栈中的参数,而原函数已被内存补丁成mov edx,xxxxxxxx,call edx
call时破坏了堆栈,所以在hook函数调用之前必须pop g_stack[0],使堆栈回到原来的样子。如果你的程序在编译时函数开始有push ebp mov EBP,esp,你还需要再次
pop g_stack[1], 之后还需要push g_stack[0]还原堆栈以是程序正常返回。eax中是native api的派遣号,保存在g_dispatchNum中,接下了就可以根据派遣号结合函数
堆栈进行你自己的活动了。
下面用ZwCreateProcessEx,ZwCreateFile来做个测试,代码如下:
VOID HookNativeApi() { HANDLE hProcess; hProcess = GetCurrentProcess(); //获取native api地址 HMODULE hNtdll = GetModuleHandle("ntdll.dll"); g_ApiAddr[0] = (DWORD)GetProcAddress(hNtdll,"ZwCreateProcessEx"); g_ApiAddr[1] = (DWORD)GetProcAddress(hNtdll,"ZwCreateFile"); DWORD hookAddr=(DWORD)HookProc; //补丁2个函数入口 BYTE OrinData[2]={0xba,0x00}; BYTE PatchData[17]; //Mov edx,hookAddr //call edx PatchData[0]=0xba; *((DWORD*)&PatchData[1])=hookAddr; PatchData[5]=0xff; PatchData[6]=0xd2; for(int i=0;i<2;i++) { if(!MemPatch(hProcess,g_ApiAddr[i]+5,7,OrinData,PatchData)) { fprintf_s(g_LogFile,"%d hook is failed\n",i+1); fflush(g_LogFile); } else { fprintf_s(g_LogFile,"%d is ok \n",i+1); fflush(g_LogFile); } } MessageBox(NULL,"Hooked","",MB_OK); }
下面是我注入到explorer中的结果。
.....
ZwCreateFile is invoked,Dispatch number is 25
ZwCreateProcessEx is invoked,Dispatch number is 30
ZwCreateFile is invoked,Dispatch number is 25
ZwCreateFile is invoked,Dispatch number is 25
.....
参考文献
1 :http://www.codeproject.com/KB/system/hooksys.aspx
2 :Undocument windows 2000 secret
3 :加密与解密第三版
4 : dll注入工具:http://www.ysgyfarnog.co.uk/utilities/injector/injector.zip