前几天晚上看intel手册突然想到了一种另类的对抗debug port的方法,理论上蛮通用的,于是写了几百行代码实验了下
但是结果很悲催,IDT HOOK死活蓝屏,群里几个人帮我查那段IDT HOOK的代码的错误,结果一致认为代码没错,说我RP不行
哎……好吧,我认了,我很久没扶老奶奶过马路了
我简单说下思路,如果你能搞定,请联系我habo.amy@gmail.com,让我把这些代码close,好睡个安稳觉,谢了
前提:你必须知道目标驱动保护(简称213.sys)的哪个进程(简称213.exe),从而得到此进程的debugport地址
你必须确认213.sys是否创建了一个线程来不断清零这个debugport
好了,不卖关子了,嘿嘿,进入正题
1:先hook 掉NtGetContextThread,NtSetContextThread,如果目标驱动没对这两个函数做手脚,此步略过
2:IDT HOOK 一号向量
3:枚举系统线程,对每个线程下硬件写入断点,地址就是213.sys要保护的进程的debugport
理论流程是这样的,保护线程对213.exe的debugport清零,触发断点-->1号向量接管-->进入我们的函数->简单的处理是把debugport改回来,复杂的处理是自动寻找213.sys清零213.exe的debugport的函数体内可以modify的位置,改掉它,使清零操作无效,我用XDE(一个反汇编引擎)试过,后来把这块代码去掉了,太浪费生命了……因为我觉得硬件断点速度很快,所以也就直接使用简单的办法,我承认我很懒,因为使用后者,又要多百多行代码,几千个字母,我手抽筋了
你们在后面的代码会看到我是针对TP做的,也许你们会说干掉TP有很多种方法,但是我要的不是结果,我要的过程……所以才有了这段尝试
现在思路有了,写代码就简单了。但是我很悲催的没有完成……我承认代码写的很烂很烂很烂,但是我第一次发帖,大家还是低调点给我找找错误吧,因为我的确不喜欢太傲的人,我会说他很213,怎么说呢,有兴趣就读完我的垃圾代码吧,没兴趣,飘过吧~
贴代码会不会太多了……而且我写代码不习惯写很多注释。呃……将就着看吧
#include <Ntddk.h> #include <winDef.h> #include <strsafe.h> #define MAKELONG(a,b) ((ULONG)(((USHORT)(a))|((ULONG)((USHORT)(b))) << 16)) void Recover_NtGetContextThread(); //用SDT HOOK来防止驱动对硬件断点的检测 void IDT_HOOK(); void Modify_Code(); //修改DEBUGPORT为非0 ULONG NtGetContextThread_Addr; //SDT中,原来的NtGetContextThread地址,下同 ULONG NtSetContextThread_Addr; ULONG Old_Addr; //IDT 1号向量原来的ISR的地址, ULONG DebugPort_Addr; //驱动保护的进程的debugport的 地址 typedef struct _ServiceDescriptorTable { PVOID ServiceTable; PULONG Counter; ULONG LimitNum; PBYTE ArgsTable; } ServiceDescriptorTable, *PServiceDescriptorTable, **PPServiceDescriptorTable; extern PServiceDescriptorTable KeServiceDescriptorTable; #pragma pack(1) typedef struct _IDTENTRY{ USHORT offsetLow; USHORT selector; UCHAR NoUseful; UCHAR segType:4; UCHAR segFlag:1; UCHAR DPL:2; UCHAR present:1; USHORT offsetHigh; }IDTENTRY,*PIDTENTRY; //定义IDT中的每个向量的格式,每个向量占8字节,我们只关心高位偏移和低位偏移就可以了 //加起来就是这个ISR的地址 typedef struct _IDT{ USHORT limit; USHORT baseLow; USHORT baseHigh; }IDT,*PIDT; #pragma pack() //定义IDT的格式,指出基址,大小 void PPON(){ _asm{ cli mov eax,cr0 or eax,10000h mov cr0,eax sti } } void PPOFF(){ _asm{ cli mov eax,cr0 and eax,not 10000h mov cr0,eax sti } } //这两个函数不解释,你懂得 ULONG GetFuncAddrFromSDT(ULONG index){ ULONG Addr; _asm{ push eax mov eax,KeServiceDescriptorTable mov eax,[eax] shr eax,2 //-->eax/4 add eax,index //-->eax/4+index shl eax,2 //-->(eax/4+index)*4=eax+index*4 mov eax,[eax] mov Addr,eax pop eax } return Addr; } //从SDT中取得函数地址 ULONG GetAddr(){ //Get the list link start address #define DEBUG_PORT 0xBC #define IMAGE_NAME 0x174 #define LIST_ADDR 0x88 ULONG cur=(ULONG)PsGetCurrentProcess(); //Current process must be SYSTEM ULONG curAddr,startAddr; curAddr=startAddr=(ULONG)(cur+LIST_ADDR); //save the start address do{ if(strcmp((char*)(curAddr-LIST_ADDR+IMAGE_NAME),"DNF.EXE")) curAddr=*(ULONG*)curAddr; //如果是DNF进程,就搞到它的debugport地址,大家蛋定,DNF是垃圾 else{ DebugPort_Addr=(ULONG)(curAddr-LIST_ADDR+DEBUG_PORT); break; } }while(curAddr!=startAddr); return *(ULONG*)(cur+0x114)+0x22C; //return the thread list head //返回system的线程当前线程链表首地址,供遍历系统线程使用 } ULONG SetAllSystemThreads(ULONG ListAddr){ #define THREADLIST_ADDR 0x22C #define CID_OFFSET 0x1EC ULONG curAddr,startAddr; OBJECT_ATTRIBUTES ObjAttr; PCLIENT_ID cid; HANDLE h; CONTEXT con; NTSTATUS status; InitializeObjectAttributes(&ObjAttr,0,OBJ_CASE_INSENSITIVE|OBJ_KERNEL_HANDLE,0,0); curAddr=startAddr=ListAddr; //你懂得,开始遍历系统线程,没得到一个,都设置对其下硬件断点 do{ cid=(PCLIENT_ID)(ULONG)(curAddr-THREADLIST_ADDR+CID_OFFSET); //h=NtOpenThread(h,THREAD_ADLL_ACCESS,&ObjAttr,cid); _asm{ push eax push edx push 80h call GetFuncAddrFromSDT //得到NtOpenThread的函数地址,0x80是它的index mov edx,eax push cid lea eax,ObjAttr push eax push THREAD_ALL_ACCESS push h call edx //这几句代码就是 NtOpenThread(h,THEAD_ALL_SUCCESS,&ObjAttr,cid); //这里主要起作用的是CLIENT_ID,它能标识出正确的线程 pop edx pop eax } if(h){ //如果成功打开 _asm{ push eax lea eax,con push eax push h call NtGetContextThread_Addr //等价于NtGetContextThread(h,&con); mov status,eax pop eax } if(NT_SUCCESS(status)){ //如果成功取得上下文 con.Dr3=DebugPort_Addr; con.Dr7=0xD0002080; //至于为什么这样设置,可以去翻Intel手册,因为实在不好解释 //NtSetContextThread(h,&con); _asm{ push eax lea eax,con push eax push h call NtSetContextThread_Addr pop eax } } ZwClose(h); //关闭句柄 } }while(curAddr!=startAddr); } NTSTATUS Jmp_NtGetContextThread( IN HANDLE ThreadHandle, OUT PCONTEXT Context ){ if(strcmp((char*)((ULONG)PsGetCurrentProcess()+0x174),"DNF.EXE")){ //不要让DNF得到自身的上下文,否则,否则我也不知道会怎样 _asm jmp Jmp_NtGetContextThread } else return STATUS_UNSUCCESSFUL; } NTSTATUS Jmp_NtSetContextThread( IN HANDLE ThreadHandle, IN PCONTEXT Context ){ if(strcmp((char*)((ULONG)PsGetCurrentProcess()+0x174),"DNF.EXE")){ //同上 _asm jmp Jmp_NtSetContextThread } else return STATUS_UNSUCCESSFUL; } void Rcover_NtGetAndSetContextThread(){ //简单的SDT HOOK,至于那段汇编,和前面GetFuncAddrFromSDT雷同 //SSDT HOOK KIRQL irq; irq=KeRaiseIrqlToDpcLevel(); PPOFF(); _asm{ sti push eax push ebx mov eax,KeServiceDescriptorTable mov eax,[eax] shr eax,2 add eax,0x55 shl eax,2 mov ebx,[eax] mov NtGetContextThread_Addr,ebx mov ebx,Jmp_NtGetContextThread mov [eax],ebx shr eax,2 add eax,0x90 shl eax,2 mov ebx,[eax] mov NtSetContextThread_Addr,ebx mov ebx,Jmp_NtSetContextThread mov [eax],ebx pop ebx pop eax sti } PPON(); KeLowerIrql(irq); } void Unhook_NtGetAndSetContextThread(){ KIRQL irq=KeRaiseIrqlToDpcLevel(); PPOFF(); _asm{ push eax push ebx mov eax,KeServiceDescriptorTable mov eax,[eax] shr eax,2 add eax,55h shl eax,2 mov ebx,NtGetContextThread_Addr mov [eax],ebx shr eax,2 add eax,90h shl eax,2 mov ebx,NtSetContextThread_Addr mov [eax],ebx pop ebx pop eax } PPON(); KeLowerIrql(irq); } void _stdcall Modify_Code(){ //这个驱动核心的功能,把debugport改回来,是不是有点杀鸡用牛刀的感觉了 = = _asm mov dword ptr[DebugPort_Addr],0x44 //set the value of debugport to nozero } _declspec(naked) IDT_ProxyFunc(){ //代理函数,用它替换IDT 一号向量 _asm{ pushad pushfd call Modify_Code popfd popad jmp Old_Addr } } void IDT_HOOK(){ //IDT HOOK,简单解释下 IDT idt; PIDTENTRY entry; _asm sidt idt //储存IDT信息到变量idt entry=(PIDTENTRY)MAKELONG(idt.baseLow,idt.baseHigh); //得到基址 KdPrint(("the base address of IDT is %X",(ULONG)entry)); Old_Addr=(ULONG)MAKELONG(entry[1].offsetLow,entry[1].offsetHigh); //保存一号向量的原ISR地址 KdPrint(("the NO.1 ISR address is %X",Old_Addr)); PPOFF(); _asm{ //entry[1].offsetLow=(USHORT)(ULONG)&IDT_ProxyFunc; //entry[1].offsetHigh=(USHORT)((ULONG)&IDT_ProxyFunc>>16); //分别修改高位和低位偏移,地址就是那个啥,呃,代理函数IDT_ProxyFunc cli push eax push edx mov eax,entry add eax,8 //move to next ISR mov edx,IDT_ProxyFunc mov word ptr[eax],dx //****注意了哈****就是这个鸟指令,蓝屏的关键,肯定是eax问题 //但是,运行到此时eax的地址是正确的,为什么?我用windbg看的呗 add eax,6 //move to offsetHigh shr edx,16 mov word ptr[eax],dx pop edx pop eax sti } PPON(); //上面的就是导致蓝屏的代码,我实在郁闷~咋就蓝了呢,我用汇编改写了上面注释的两句C代码 //虽然ASM写的很累赘,但是我只是为了找出哪里导致了蓝屏,大家不要鄙视 //我用notepad++写了几小时代码通宵也不容易诶,一个字一个字母敲,呜呜 } void IDT_UNHOOK(){ //不解释了,对应IDT_HOOK IDT idt; PIDTENTRY entry; _asm sidt idt entry=(PIDTENTRY)(ULONG)MAKELONG(idt.baseLow,idt.baseHigh); _asm cli entry[1].offsetLow=(USHORT)Old_Addr; entry[1].offsetHigh=(USHORT)((ULONG)Old_Addr>>16); _asm sti } NTSTATUS DriverUnload(PDRIVER_OBJECT driver){ Unhook_NtGetAndSetContextThread(); IDT_UNHOOK(); } NTSTATUS DriverEntry(PDRIVER_OBJECT driver,PUNICODE_STRING reg){ ULONG ListAddr; #if DBG _asm int 3 #endif driver->DriverUnload=DriverUnload; Rcover_NtGetAndSetContextThread(); IDT_HOOK(); ListAddr=GetAddr(); if(DebugPort_Addr) SetAllSystemThreads(ListAddr); }