上一篇分析了浅析了一下Windows系统调用的过程,让我们看看如何在这个流程中做点手脚,达到我们的过滤的目的。
现在主流的杀软在X86下都是首先下钩Kifastcallentry,配合自己的重载内核,接管ServerTable,从而废除ServerTable钩子。
那么首先如何获得Kifastcallentry函数?
从看雪论坛的资料以及寒假逆向某安全软件模块发现现在主流的仍然是先SSDT HOOK然后调用Fake函数,然后在Fake函数里通过栈回溯找到Kifastcallentry,然后在恢复SSDT HOOK,最后暴力搜索下钩点,完成HOOK。
首先我们看看Kifastcallentry函数
nt!KiFastCallEntry: 8053e540 b923000000 mov ecx,23h 8053e545 6a30 push 30h 8053e547 0fa1 pop fs 8053e549 8ed9 mov ds,cx 8053e54b 8ec1 mov es,cx 8053e54d 8b0d40f0dfff mov ecx,dword ptr ds:[0FFDFF040h] 8053e553 8b6104 mov esp,dword ptr [ecx+4] 8053e556 6a23 push 23h 8053e558 52 push edx 8053e559 9c pushfd 8053e55a 6a02 push 2 8053e55c 83c208 add edx,8 8053e55f 9d popfd 8053e560 804c240102 or byte ptr [esp+1],2 8053e565 6a1b push 1Bh 8053e567 ff350403dfff push dword ptr ds:[0FFDF0304h] 8053e56d 6a00 push 0 8053e56f 55 push ebp 8053e570 53 push ebx 8053e571 56 push esi 8053e572 57 push edi 8053e573 8b1d1cf0dfff mov ebx,dword ptr ds:[0FFDFF01Ch] 8053e579 6a3b push 3Bh 8053e57b 8bb324010000 mov esi,dword ptr [ebx+124h] 8053e581 ff33 push dword ptr [ebx] 8053e583 c703ffffffff mov dword ptr [ebx],0FFFFFFFFh 8053e589 8b6e18 mov ebp,dword ptr [esi+18h] 8053e58c 6a01 push 1 8053e58e 83ec48 sub esp,48h 8053e591 81ed9c020000 sub ebp,29Ch 8053e597 c6864001000001 mov byte ptr [esi+140h],1 8053e59e 3bec cmp ebp,esp 8053e5a0 759a jne nt!KiFastCallEntry2+0x47 (8053e53c) 8053e5a2 83652c00 and dword ptr [ebp+2Ch],0 8053e5a6 f6462cff test byte ptr [esi+2Ch],0FFh 8053e5aa 89ae34010000 mov dword ptr [esi+134h],ebp 8053e5b0 0f854afeffff jne nt!Dr_FastCallDrSave (8053e400) 8053e5b6 8b5d60 mov ebx,dword ptr [ebp+60h] 8053e5b9 8b7d68 mov edi,dword ptr [ebp+68h] 8053e5bc 89550c mov dword ptr [ebp+0Ch],edx 8053e5bf c74508000ddbba mov dword ptr [ebp+8],0BADB0D00h 8053e5c6 895d00 mov dword ptr [ebp],ebx 8053e5c9 897d04 mov dword ptr [ebp+4],edi 8053e5cc fb sti 8053e5cd 8bf8 mov edi,eax 8053e5cf c1ef08 shr edi,8 8053e5d2 83e730 and edi,30h 8053e5d5 8bcf mov ecx,edi 8053e5d7 03bee0000000 add edi,dword ptr [esi+0E0h] 8053e5dd 8bd8 mov ebx,eax 8053e5df 25ff0f0000 and eax,0FFFh 8053e5e4 3b4708 cmp eax,dword ptr [edi+8] 8053e5e7 0f8345fdffff jae nt!KiBBTUnexpectedRange (8053e332) 8053e5ed 83f910 cmp ecx,10h 8053e5f0 751a jne nt!KiFastCallEntry+0xcc (8053e60c) 8053e5f2 8b0d18f0dfff mov ecx,dword ptr ds:[0FFDFF018h] 8053e5f8 33db xor ebx,ebx 8053e5fa 0b99700f0000 or ebx,dword ptr [ecx+0F70h] 8053e600 740a je nt!KiFastCallEntry+0xcc (8053e60c) 8053e602 52 push edx 8053e603 50 push eax 8053e604 ff15e43f5580 call dword ptr [nt!KeGdiFlushUserBatch (80553fe4)] 8053e60a 58 pop eax 8053e60b 5a pop edx 8053e60c ff0538f6dfff inc dword ptr ds:[0FFDFF638h] 8053e612 8bf2 mov esi,edx 8053e614 8b5f0c mov ebx,dword ptr [edi+0Ch] 8053e617 33c9 xor ecx,ecx 8053e619 8a0c18 mov cl,byte ptr [eax+ebx] 8053e61c 8b3f mov edi,dword ptr [edi] 8053e61e 8b1c87 mov ebx,dword ptr [edi+eax*4] 8053e621 2be1 sub esp,ecx 8053e623 c1e902 shr ecx,2 8053e626 8bfc mov edi,esp 8053e628 3b35d4995580 cmp esi,dword ptr [nt!MmUserProbeAddress (805599d4)] 8053e62e 0f83a8010000 jae nt!KiSystemCallExit2+0x9f (8053e7dc) 8053e634 f3a5 rep movs dword ptr es:[edi],dword ptr [esi] 8053e636 ffd3 call ebx // ebx 系统服务 8053e638 8be5 mov esp,ebp 8053e63a 8b0d24f1dfff mov ecx,dword ptr ds:[0FFDFF124h]
然后我们看看如何通过栈回溯找到Kifastcall函数
1 uIndex = SYSCALL_INDEX(ZwSetEvent); // 获得SetEvent的系统索引号 2 3 Irql = WPOFF(); 4 HOOK_SYSCALL(uIndex,HookZwSetEvent,OrgZwSetEvent); //SSDT HOOK 5 WPON(Irql); 6 7 ZwSetEvent(&hEvent,NULL); // 调用一次Fake函数 8 9 Irql = WPOFF(); 10 UNHOOK_SYSCALL(uIndex,OrgZwSetEvent); //恢复hook 11 WPON(Irql); 12
接下来我们看看FakeSetEvent中做了些什么
1 NTSTATUS 2 HookZwSetEvent( 3 IN PHANDLE EventHandle, 4 OUT PLONG PreviousState OPTIONAL) 5 { 6 7 8 if (*EventHandle!=(HANDLE)0x12345678||ExGetPreviousMode()!=KernelMode) 9 { 10 OrgZwSetEvent(EventHandle,PreviousState); 11 } 12 13 else 14 { 15 16 __asm 17 { 18 mov eax,dword ptr[ebp + 4] //得到EIP 19 mov KiFastCallEntryRetAddress,eax 20 21 } 22 23 DbgPrint("%x\r\n",KiFastCallEntryRetAddress); //测试 24 } 25 26 27 return STATUS_SUCCESS; 28 29 }
调试可以看到KiFastCallEntryRetAddress值为0x8053e638
其实就是进入函数后被push的eip,可以回头看看kifasycallentry函数
8053e634 f3a5 rep movs dword ptr es:[edi],dword ptr [esi]
8053e636 ffd3 call ebx // ebx 系统服务
8053e638 8be5 mov esp,ebp
这样我们就进入Kifastcallentry函数了,剩下的就是搜索下钩点进行InlineHook了
CHAR SearchCode[5] = {0x2b,0xe1,0xc1,0xe9,0x02}; SearchAddress = KiFastCallEntryRetAddress; while (1) { if (memcmp((PVOID)SearchAddress,SearchCode,5) == 0) { PatchAddress = SearchAddress; PatchAddress1 = SearchAddress + 5; //PatchAddress1 == 8053e626 *(PULONG)&JmpCode[1] = (ULONG)FakeKiFastCallEntry - (PatchAddress + 5); memcpy((PVOID)PatchAddress,JmpCode,5); break; } SearchAddress --; //栈回溯 } WPON(Irql);
让我们看看挂钩后的kifastcallentry函数部分
1 8053e619 8a0c18 mov cl,byte ptr [eax+ebx] 2 8053e61c 8b3f mov edi,dword ptr [edi] 3 8053e61e 8b1c87 mov ebx,dword ptr [edi+eax*4] 4 8053e621 e9ea194077 jmp KifastcallentryHook!FakeKiFastCallEntry (f7940010) 5 8053e626 8bfc mov edi,esp 6 8053e628 3b35d4995580 cmp esi,dword ptr [nt!MmUserProbeAddress (805599d4)] 7 8053e62e 0f83a8010000 jae nt!KiSystemCallExit2+0x9f (8053e7dc) 8 8053e634 f3a5 rep movs dword ptr es:[edi],dword ptr [esi] 9 8053e636 ffd3 call ebx 10 8053e638 8be5 mov esp,ebp 11 8053e63a 8b0d24f1dfff mov ecx,dword ptr ds:[0FFDFF124h] 12 8053e640 8b553c mov edx,dword ptr [ebp+3Ch]
我们现在已经挂钩Kifastcallentry函数了,并且已经把返回地址保存进了PatchAddress1,我们接下里看看FakeKifastcallentry函数做了什么
1 _declspec(naked) VOID FakeKiFastCallEntry() 2 { 3 __asm 4 { 5 mov edi,edi //对齐内存 6 pushfd 7 pushad 8 push edi 9 push ebx 10 push eax 11 call SysCallfilter 12 mov dword ptr [esp+10h],eax // esb+10h == ebx eax == SysCallfilter的返回值 13 popad 14 popfd 15 sub esp, ecx 16 shr ecx, 2 17 push PatchAddress1; // 相当于 PatchAddress1 所保存的值当成了EIP 18 ret // ret == pop eip 19 } 20 }
FakeKifastcallentry函数里就是保存下现场,Push edi,push ebx,push eax,call SysCallfilter 最后把返回结果保存进了 esp+10 (其实就是真正的系统函数服务ebx里),其中eax是系统服务索引号,ebx 真正的系统服务服务地址,edi ServiceBase。
其他的注释也已经写的很清楚了。
最后我们看看 SysCallfilter 过滤函数
1 ULONG SysCallfilter(ULONG SysCallNum,ULONG FunAddr,ULONG ServiceBase) 2 { 3 4 //得到Index,判断Index是不是NtCreateFile,如果是,返回一个新的函数地址,而不是SSDT原先的函数地址 5 ULONG Index = SYSCALL_INDEX(ZwCreateFile); 6 if (Index == SysCallNum) 7 { 8 return (ULONG)HookNtCreateFile; 9 } 10 11 return FunAddr; 12 }
好了,到此基本分析完了,至于HookNtCreateFile函数,自然是想怎么过滤就怎么过滤了,过滤完后记着再把原函数调用一边,不然容易发生蓝屏。
这只是一个小Demo,对于大型的hook框架可以参考看雪论坛的Fypher大神Hook Kifastcallentry监视系统调用 源码,或者逆向分析下主流杀软的hook框架,定获益匪浅。