前言:
这两天看了一个github上的项目,记录一下学习的心得。
Win32 API大多数都要从Ring3层进入Ring0层,在内核中完成主要操作。这中间肯定要经过KiFastSystemCall这个过程,这个调用是Ring3层的。这些知识在《Windows内核安全与驱动开发》21章中。
项目:
HookSystemCall大致原理就是自己构建一个类似SSDT的东西,当有函数通过KiFastSystemCall进入内核时,就进入自己构建的SSDT中的函数。代码如下,dwHookSystemCall中保存的是自己的函数地址,dwOrigSystemCall中保存的是SSDT中原始的函数地址。Zw函数都是通过索引在SSDT中找相应的Nt函数,索引即在函数的第二个字节。
//HOOK后跳入的函数地址 DWORD dwHookSystemCall[0x4C8*10] = {0}; //保存原始函数调用地址 DWORD *dwOrigSystemCall[0x4C8*10] = {0}; //调用函数GetDllFuncAddr以便获得服务号 int HookSystemCall::GetSysCallIndex( PCHAR FuncName ) { DWORD FuncAddr; //函数地址 int SysCallIndex;//服务号 FuncAddr = (DWORD)GetProcAddress(GetModuleHandleA("ntdll.dll"),FuncName); SysCallIndex = *( (WORD*)(FuncAddr + 1) ); return SysCallIndex; }
接下来要Hook KiFastSystemCall,这个调用代码如下:
77866190 > 8BD4 MOV EDX,ESP 77866192 0F34 SYSENTER 77866194 > C3 RETN
可以看到只有短短5个字节,所以要 Hook就要先进行短跳转,再来一发长跳转,如下:
// Hook KiFastSystemCall *(PWORD)ULongToPtr(dwKiFastSystemCall) = 0xF9EB; //即 EB F9 --> 向前跳5个字节 *(PBYTE)ULongToPtr(dwKiFastSystemCall - 0x5) = 0xE9; *(PDWORD)ULongToPtr(dwKiFastSystemCall - 0x4) = PtrToUlong(KiFastSystemCall) - (dwKiFastSystemCall - 0x5) - 5;//跳到自己的KiFastSystemCall中
自己的KiFastSystemCall如下:
DWORD HookFunctionAdress; __declspec(naked) void KiFastSystemCall() { __asm{ pushad pushfd cmp dword ptr [eax*4+dwHookSystemCall],0;比较是否已经有Hook过的函数 jz Label popfd popad add esp,4 jmp dword ptr [dwHookSystemCall+eax*4] Label: push eax call HookFunctionAdress popfd popad jmp KiFastSystemCallEx } }
对于KiFastSystemCall需要补充一点,Ring3层的堆栈空间如何被传入Ring0层代码中,这里用的方法是MOV EDX,ESP。在内核代码中直接MOV ESP,EDX就可以得到Ring3层的堆栈空间,完全不用拷贝就能直接得到所有的参数。而上面代码 add esp,4 是因为当调用Zw函数时进入Zw再进入KiFastSystemCall中已经发生一次call,esp向上抬了4个字节,需要加回来才能调用正确的参数。
下面就是安装钩子的代码了:
// 安装钩子 BOOL HookSystemCall::InstallHook(PCHAR FunName, //NTAPI DWORD pHookFunc, //HOOK的函数 DWORD * pOrigFunc) //返回的函数 { DWORD dwOldProtect; DWORD dwIndex = GetSysCallIndex(FunName); DWORD dwFunadd =PtrToUlong(GetProcAddress(GetModuleHandleA("NTDLL.dll"), FunName)); //保存各种信息 dwHookSystemCall[dwIndex] = pHookFunc; dwOrigSystemCall[dwIndex] = (PDWORD)malloc(0x10); //将原本的ZW函数保存进去 memcpy(dwOrigSystemCall[dwIndex],(PVOID)dwFunadd,0x10); //野猪改造开始 VirtualProtect(ULongToPtr(dwOrigSystemCall[dwIndex]), 0x10, PAGE_EXECUTE_READWRITE, &dwOldProtect); *(PBYTE)((DWORD)dwOrigSystemCall[dwIndex] + 5) = 0xE8; *(PDWORD)((DWORD)dwOrigSystemCall[dwIndex] + 6) = (DWORD)KiFastSystemCallEx - (DWORD)dwOrigSystemCall[dwIndex] -0xA;//修改系统的KiFastSystemCall为自己的 *(PWORD)((DWORD)dwOrigSystemCall[dwIndex] + 10) = 0x9090; * pOrigFunc = (DWORD)dwOrigSystemCall[dwIndex]; return 0; } // Ex安装钩子 BOOL HookSystemCall::InstallHookEx(DWORD dwIndex, //NTAPI WORD dwRetIndex, //返回堆栈平衡 DWORD pHookFunc, //HOOK的函数 DWORD * pOrigFunc) //返回的函数 { DWORD dwOldProtect; //保存各种信息 dwHookSystemCall[dwIndex] = pHookFunc; dwOrigSystemCall[dwIndex] = (PDWORD)malloc(0x10); //将原本的ZW函数保存进去 memcpy(dwOrigSystemCall[dwIndex],(PVOID)SystemCall,0x10); //野猪改造开始 VirtualProtect(ULongToPtr(dwOrigSystemCall[dwIndex]), 0x10, PAGE_EXECUTE_READWRITE, &dwOldProtect); *(PDWORD)((DWORD)dwOrigSystemCall[dwIndex] + 2) = (DWORD)dwIndex; *(PDWORD)((DWORD)dwOrigSystemCall[dwIndex] + 7) = (DWORD)KiFastSystemCallEx - (DWORD)dwOrigSystemCall[dwIndex] - 0xB; *(PWORD)((DWORD)dwOrigSystemCall[dwIndex] + 12) = dwRetIndex; * pOrigFunc = (DWORD)dwOrigSystemCall[dwIndex]; return 0; }
EX这个主要是为了HOOK 一些不是NTDLL 导出的函数,比如NtUserFindWindowEx之类的函数,因为Shadow SSDT也是经由KiFastSystemCall进入内核,原理大概一致.
不过因为没办法直接获取函数INDEX所以必须根据系统版本自己确定,而且也无法获取函数地址,所以使用我们之前构造的函数原形来处理.ret的平衡也只能手动了!
最后附上源代码的地址:https://github.com/xiaomagexiao/GameDll/blob/master/HookSystemCall.cpp