#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);
}
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)