|
记得上次我发了个帖子叫《Call Hook KeInsertQueueApc总蓝屏》的帖子,地址http://www.m5home.com/bbs/thread-8517-1-1.html
这段时间重写了一遍黑客防线上胡文亮的文章《函数CALL地址替换实现深度钩子》,发现这里面有一些小BUG的存在。和Head Inline Hook一样,我认为Call Hook也需要有锁定页面,提升中断请求级并使用自旋锁。由于Call Hook是替换四个字节的Inline Hook,所以最好使用InterlockedExchange函数来替换值,而不是用memcpy或者*(PULONG)(Address)=0xXXXXXXXX的方式暴力修改。
本文以Hook NtOpenProcess和NtOpenThread为例,其替换的地址是PsLookupProcessByProcessId和PsLookupThreadByThreadId,主要是因为XueTr(现在的PCHunter)的自我保护是替换了ObOpenObjectByPointer,我们替换这个地址没意思。
首先先讲一下经过我修改之后的Call Hook的过程:
在这个函数里搜索每一个call指令,若call指令后面跟着的相对地址是我们要替换的地址,那么就开始HOOK操作。
Hook操作的流程是先用MDL锁定页面,然后提升中断请求级到DPC级别,在这个级别下申请并使用自旋锁,然后计算call指令后的相对地址,并用InterlockedExchange函数替换地址,替换完成后,释放自旋锁,再降低中断请求级,解锁页面,释放MDL就好了。
代码如下:
- BOOL CallAddrHook(PVOID StartAddr,PVOID OldAddr,PVOID NewAddr)
- {
- PUCHAR cPtr, pOpcode;
- ULONG Length,Tmp;
- PMDL pMdl;
- for (cPtr=StartAddr;(ULONG)cPtr<(ULONG)StartAddr+0x1000;cPtr += Length)
- {
- Length = SizeOfCode(cPtr, &pOpcode);//计算当前指令长度
- if (!Length) break;
- if (Length ==5 && *cPtr==0xE8)// 当前长度5 且第一字节为E8
- {//因为CALL用的是相对偏移 所以我们还需要进行计算相对偏移
- if ( (ULONG)OldAddr-(ULONG)cPtr-5 == *(PULONG)(cPtr+1)) //判断当前是否为OldAddr的CALL相对地址
- {
- Tmp=(ULONG)NewAddr-(ULONG)cPtr-5;//我们的CALL地址相对偏移
- pMdl=IoAllocateMdl((PVOID)(cPtr+1),4,FALSE,FALSE,NULL);
- if(pMdl)
- {
- __try
- {
- MmProbeAndLockPages(pMdl,KernelMode,IoWriteAccess);
- }
- __except(EXCEPTION_EXECUTE_HANDLER)
- {
- IoFreeMdl(pMdl);
- }
- //进行HOOK操作
- f_oldirql=KeRaiseIrqlToDpcLevel();
- KeInitializeSpinLock(&f_spinlock);
- KeAcquireSpinLockAtDpcLevel(&f_spinlock);
- __asm
- {
- cli
- mov eax,cr0
- and eax,not 10000h
- mov cr0,eax
- }
- InterlockedExchange((PULONG)(cPtr+1),Tmp);//直接替换为我们的FAKE函数地址
- __asm
- {
- mov eax,cr0
- or eax,10000h
- mov cr0,eax
- sti
- }
- KeReleaseSpinLockFromDpcLevel(&f_spinlock);
- KeLowerIrql(f_oldirql);
- IoFreeMdl(pMdl);
- return TRUE;
- }
- }
- }
- }
- return FALSE;
- }
复制代码
而UnHook的时候,把原本OldAddr改成NewAddr就可以了。
接下来,是演示Hook和Unhook的过程,这里用到了二次跳转:
- void StartHook()
- {
- //deal with NTKERNELAPI
- UNICODE_STRING uniFuncName1;
- UNICODE_STRING uniFuncName2;
- UNICODE_STRING uniFuncName3;
- UNICODE_STRING uniFuncName4;
- RtlInitUnicodeString(&uniFuncName1,L"PsLookupProcessByProcessId");
- RtlInitUnicodeString(&uniFuncName2,L"PsLookupThreadByThreadId");
- RtlInitUnicodeString(&uniFuncName3,L"ZwOpenProcess");
- RtlInitUnicodeString(&uniFuncName4,L"ZwOpenThread");
- PsLookupProcessByProcessId=MmGetSystemRoutineAddress(&uniFuncName1);
- PsLookupThreadByThreadId=MmGetSystemRoutineAddress(&uniFuncName2);
- ZwOpenProcess=MmGetSystemRoutineAddress(&uniFuncName3);
- ZwOpenThread=MmGetSystemRoutineAddress(&uniFuncName4);
- //deal with SSDT Function
- RtlCopyMemory(&IndexOf_NtOpenProcess,(PVOID)((ULONG)ZwOpenProcess+1),4);
- RtlCopyMemory(&IndexOf_NtOpenThread,(PVOID)((ULONG)ZwOpenThread+1),4);
- pNtOpenProcess=(PVOID)GetSSDTCurAddr(IndexOf_NtOpenProcess);
- pNtOpenThread=(PVOID)GetSSDTCurAddr(IndexOf_NtOpenThread);
- //deal with second jump
- *(PULONG)((ULONG)&SjBytes+1)=(ULONG)fake_PsLookupProcessByProcessId;
- Sj1=ExAllocatePool(NonPagedPool,6);
- RtlCopyMemory(Sj1,&SjBytes,6);
- *(PULONG)((ULONG)&SjBytes+1)=(ULONG)fake_PsLookupThreadByThreadId;
- Sj2=ExAllocatePool(NonPagedPool,6);
- RtlCopyMemory(Sj2,&SjBytes,6);
- //Start to Hook
- CallAddrHook(pNtOpenProcess,PsLookupProcessByProcessId,Sj1);
- CallAddrHook(pNtOpenThread,PsLookupThreadByThreadId,Sj2);
- }
- void StopHook()
- {
- CallAddrHook(pNtOpenProcess,Sj1,PsLookupProcessByProcessId);
- CallAddrHook(pNtOpenThread,Sj2,PsLookupThreadByThreadId);
- //deal with second jump
- ExFreePool(Sj1);
- ExFreePool(Sj2);
- }
复制代码
我刚看到二次跳转这玩意的时候觉得好牛逼。。。现在觉得其实也就那么一回事,这里的二次跳转原理很简单,申请一段六字节的NonPagedPool,然后在第一个字节写上0x68,第六个字节写上0xC3,第二到第五个字节写上伪函数的绝对地址,它就是一个push xxxxxxxx ret方式的跳转!当然也可以在第一和第二个字节写上0xFF和0x25,第三到第六个字节写上绝对地址,也就是一个jmp dword ptr方式跳转。
伪函数和上次的TP学习笔记的差不多,代码如下:
- NTSTATUS fake_PsLookupProcessByProcessId(IN ULONG ProcessId,OUT PEPROCESS *Process)
- {
- NTSTATUS st;
- PEPROCESS txps;
- st=PsLookupProcessByProcessId(ProcessId,&txps);
- if(txps==ProtectedProcess)
- {
- st=0xC0000022;
- }
- else
- {
- st=PsLookupProcessByProcessId(ProcessId,Process);
- }
- return st;
- }
- NTSTATUS fake_PsLookupThreadByThreadId(IN ULONG ThreadId,OUT PETHREAD *Thread)
- {
- NTSTATUS st;
- PETHREAD txtd;
- PEPROCESS txps;
- st=PsLookupThreadByThreadId(ThreadId,&txtd);
- txps=IoThreadToProcess(txtd);
- if(txps==ProtectedProcess)
- {
- st=0xC0000022;
- }
- else
- {
- st=PsLookupThreadByThreadId(ThreadId,Thread);
- }
- return st;
- }
复制代码
效果很不错,在Windows XP和Windows Server 2003上均测试通过,若要在Windows 7等NT6的操作系统上使用,请Call Hook PsOpenProcess和PsOpenThread的PsLookupProcessByProcessId和PsLookupThreadByThreadId。
测试效果如图所示:
XP的测试效果:
xp测试效果
2003的测试效果:
2k3测试效果
PCHunter还是不错的,连跳的第二个地址都检测到了。。。
p.s:本文所带的附件描述摘自Hovi.Delphic的帖子,由于本文是安利给大家的,不设权限,不设水晶币{:soso_e113:} |
评分
-
查看全部评分
|