首页
社区
课程
招聘
[原创]新人报道,Patch GDT钩3号INT中断
发表于: 2018-1-1 18:13 5674

[原创]新人报道,Patch GDT钩3号INT中断

2018-1-1 18:13
5674
大拿们新年快乐!
前言


在看雪潜了一年的水了,一直在看大佬们的帖子,学了很多东西,可以说看雪就是我的半个师傅。
感谢看雪给新手提供了学习的平台。
入内核不久,这段时间一直在迷分段和分页机制,光看理论学起来真的累。看了大佬们的帖子,知道了怎么写调用门和中断门,正好写个东西练练手,加深下映像。
看《X86_X64体系探索及编程》了解到的分页分段机制,里面很详细。
这次做的是IDTHook,用的是win7X86虚拟机,预计X64会很难,还是算了…(⊙_⊙;)…
思路很简单,尝试了两种方式。
一、直接Patch IDT,改3号中断描述符的offset。
二、改IDT 3号中断描述符的选择子字段但是不改offset,同时Patch GDT的base,总之让段描述符的Base + 中断描述符的offset等于跳转的函数地址就成。

图一、中断例程寻址是这样的:

Patch GDT有两个选择,找个空位重写一个描述符或者直接改Index == 1的描述符(IDT里的选择子清一色08。index==1 TI==0 RPL==00)
图二 、windbg 读 IDT:


第二个显然是不行的...我还试了下,刚开始写虚拟机就报警提示CPU不愿意,然后重启了(忘截图了)
首先尝试直接Patch IDT:
首先通过windbg查看三号中断处理例程,照葫芦画瓢写个类似的
具体什么作用先不看了。
图三、
_declspec(naked)
VOID NewTrap()
{
	_asm
	{
		push    0; ErrorCode
		push    ebp
		push    ebx
		push    esi
		push    edi
		push    fs
		;Ring0下 fs:[0]指向的是_KPCR的地址
		mov     ebx, 30h
		mov     fs, bx
		mov     ebx, dword ptr fs : [0]
		push    ebx
		sub     esp, 4
		push    eax
		push    ecx
		push    edx
		push    ds
		push    es
		push    gs
		sub     esp, 30h    //对齐TrapFrame首地址
		
		call   TrapCall   
		add     esp, 0x30
		pop        gs
		pop        es
		pop        ds
		pop        edx
		pop        ecx
		pop        eax
		add        esp, 4
		pop        ebx
		pop        fs
		pop        edi
		pop        esi
		pop        ebx
		pop        ebp
		add     esp, 0x4

		//跳回原例程入口
		//push 08h
		//push g_RealTrap
		//retf
		jmp g_RealTrap
	}
}

VOID TrapCall()
{
	DbgPrint("TrapCall\r\n");
}
我们的目的是将3号中断钩到以上代码中。
现在就要找IDT然后写东西进去。找IDT可以用sidt将IDT地址保存到栈里。有了地址就可以直接写了。
图四、中断描述符:

根据这个我们可以定义出中段描述符结构体。
//中断门描述符 仅做参考
typedef struct _INTER_DESC
{
	short OffsetLow;
	short selector;
	char IST : 3;
	char dumb : 5;
	char Attr0 : 1;
	char Attr1 : 1;
	char Attr2 : 1;
	char Attr3 : 1;
	char Attrzero : 1;
	char AttrDPL : 2;
	char Attrp : 1;
	short OffsetHigh;
}INTER_DESC, *PINTER_DESC;


只要改offset其他的依旧照葫芦画瓢,先不研究。
VOID WriteTrap(ULONG32 FuncAddr)
{
//参数传NewTrap
	_asm
	{
		;写调用门用到的索引
		mov ebx,g_eax
	}
	_asm
	{
		
		sub esp, 20h
		push esi
		; mov esi, ptr IDT
		sidt[esp - 2]
		pop esi

		; 找到第三个中断描述符的位置
		add esi, 18h
		mov ebx, [esi]
		mov g_DescLow, ebx
		mov ebx, [esi + 4]
		;用的时候取高16位
		mov g_DescHigh, ebx
		mov ebx, FuncAddr
		mov[esi + INTERGATE_OFFSETLOW], bx
		shr ebx, 16
		mov[esi + INTERGATE_OFFSETHIGH], bx
		mov[esi + INTERGATE_ATTR], 0xee; ? ?
		mov[esi + INTERGATE_DUMB], 00
		; mov ebx, g_eax
		; shr ebx, 3
		; 1 0 00   Index == 1,RPL == 0
		mov [esi + INTERGATE_SELECTOR], 08h
		
		//保存真实例程地址
		lea eax, g_RealTrap
		mov ebx, g_DescLow
		mov[eax], bx
		mov ebx, g_DescHigh
		shr ebx, 16
		mov[eax + 2], bx

	/*	
		Hook INT 3的时候不好调试,在这先调试下恢复
		mov ebx, g_DescLow
		mov[esi + INTERGATE_OFFSETLOW], bx
		mov ebx, g_DescHigh
		shr ebx, 16
		mov[esi + INTERGATE_OFFSETHIGH], bx
		*/

		add esp,20h
	}
}
WriteTrap后再在windbg单击单步中断就能看到DbgPrint("TrapCall\r\n")了

图五、 成功钩出。

然后我们来看第二种方法,Path GDT:
用windbg输入! idt -a可以查看各INT中断处理例程入口。
db idtr可以看到很多描述符的selector字段是0x0008。
都是找GDT表索引为1号的描述符项,那么一号描述符不让写也很容易理解。
但是空的表项很多,找到第一个空的写进去便是(见图二)。
图六、GDT

图七、段描述符

由此我们可以定义出段描述符结构体:
//段描述符要用
typedef struct _SEG_DESC
{
	short limit;
	ULONG32 BaseLow : 24;
	
	union OneByte
	{
		struct onebyte
		{
			char type0 : 1;
			char type1 : 1;
			char type2 : 1;
			char type3 : 1;
			char s : 1;
			char DPL : 2;
			char P : 1;
		};
		char dumb0;            //赋值时用
	};
	
	union anotherone
	{
		struct ByteAgain
		{
			char lim : 4;
			char AVL : 1;
			char L : 1;
			char DB : 1;
			char G : 1;
		};
		char dumb1;
	};
	
	char BaseHigh;
}SEG_DESC, *PSEG_DESC;


VOID CoverGDT(ULONG32 FuncAddr)
{
	//修改SegmentBase里的描述符第16~39位 26~63位的base字段
	//本来想直接修改索引号为1的段描述符,这样就不用改IDT了
	//然而一改虚拟机就提示CPU不干....
	ULONG32 Pointer = 0;
	PSEG_DESC pSeg = NULL;
	ULONG32 uTemp = 0;
	_asm
	{
		pushfd
		pushad
		; 找到三号中断入口地址s
		push esi
		; mov esi, ptr IDT
		sidt[esp - 2]
		pop esi

		; 找到第三个中断描述符的位置
		add esi, 18h
		mov ebx, [esi]
		mov g_DescLow, ebx
		mov ebx, [esi + 4]
		mov g_DescHigh, ebx
		;计算原先的中断处理例程地址
		lea eax, g_RealTrap
		mov ebx, g_DescLow
		mov [eax], bx
		mov ebx, g_DescHigh
		shr ebx, 16
		mov[eax + 2], bx
		;算出函数地址偏移 准备赋给Segment Descriptor的base字段
		mov eax, g_RealTrap
		mov ebx, FuncAddr
		sub ebx, eax
		mov uTemp,ebx
		lea ebx,uTemp
		;开始遍历GDT
		push esi
		sgdt[esp - 2]
		; esi: ptr Segment Descriptor
		pop esi
		;eax作计数器
		mov eax, 8
LoopStart :
		lea edx, [esi + eax]
		xor ecx, ecx
		mov cl, [edx + 5]
		test cl, 80h
		; 字面上来看就是 P位为1,其它为0的就过,但是实际上找的是cl == 0的,就是8Bytes全0
		jnz notZero
		; 计数and写段描述符
		mov g_eax, eax
		; 找到空的位置就写我们的描述符
		; 这里只管我们算出来的Base字段,其它的都抄Index == 1的Segment Descriptor了
		mov[edx + SEGDESC_LIMIT], 0xff
		mov[edx + SEGDESC_LIMIT + 1], 0xff
		mov eax,[ebx]
		mov[edx + SEGDESC_BASELOW], ax
		mov al,[ebx + 2]
		mov[edx + SEGDESC_BASELOW + 2],al 
		mov al,[ebx + 3]
		mov[edx + SEGDESC_BASEHIGH], al
		mov[edx + SEGDESC_TYPE], 9bh
		mov[edx + SEGDESC_FLAGS], 0xcf; 为啥有的后面加h就行,有的必须写0x ?
		
                ;开始PatchIDT
		push esi
		; mov esi, ptr IDT
		sidt[esp - 2]
		pop esi
		add esi, 18h
		mov ebx, g_eax
		; 因为描述符大小是8字节 除8得索引
		shr ebx, 3
		mov[esi + INTERGATE_SELECTOR], bx
		
		popad
		popfd
		mov esp, ebp
		pop ebp
		ret

notZero :
		add eax, 8
		test eax, GDT_LIMIT
		jnz LoopStart
		;找不到空的,那就算了
		mov eax, 0
		popad
		popfd
		ret
	}

}

图八、写入GDT:

调试结果可见,原3号中断描述符里的Offset字段存在g_RealTrap全局里面,值为0x8485bc90
跳转的目标地址是0x9eb031f0,二者相减得0x1a2a7560。
查看gdt表,80b95048处已经写入了我们自己构造的段描述符,base字段在第3~5和第8字节为写入的offset。成功(`・ω・´)

ResumeHook只要将原先的中断描述符放回去就行了,GDT就不用管它了。而原先的数据都保存在全局变量里了。
VOID DriverUnload(PDRIVER_OBJECT DriverObject)
{
	DbgPrint("DriverUnload()\r\n");
//	WPOFF();
	_asm
	{
		;Resume IDT
		sub esp, 20h
		push esi
		; mov esi, ptr IDT
		sidt[esp - 2]
		pop esi
		; 找到第三个中断描述符的位置
		add esi, 18h

		mov ebx, g_DescLow
		mov[esi + INTERGATE_OFFSETLOW], bx
		mov ebx, g_DescHigh
		shr ebx, 16
		mov[esi + INTERGATE_OFFSETHIGH], bx

		add esp, 20h
	}
//	WPON();
}




最后这个纯属无聊...
之前学习完写CallGate之后又看到了图一,于是脑抽,正常情况下IDT的中断门描述符自己提供选择子,然后找GDT里段描述符的Base
俩人一加,得到地址。而CallGate也是提供选择子的,并且调用门是写在GDT里的,如果IDT的选择子选到了GDT里的调用门,那么CS寄存器最终会加载哪个选择子?
这个很好试嘛,然后开搞。
思路很简单,GDT找个空位置,写个调用门进去。然后改IDT 3号中断描述符的选择子,Index为调用门在GDT中的偏移除8
//调用门描述符 参考
typedef struct _CALLGATE_DESC
{
	SHORT	OFFSETL;
	SHORT	SELECTOR;
	CHAR	DCOUNT;
	CHAR	GTYPE;
	SHORT	OFFSETH;
}CALLGATE_DESC;
写调用门跟前面写段描述符一样。
ULONG32 AddCallGate(ULONG32 FuncAddr)
{
	INT32 i = 0;
	_asm
	{
		; push ebp
		; mov ebp, esp
		sub esp, 20h
		pushfd
		pushad
		push esi
		; esp - 2里写入的是低16位的limit,esp里写入的是32位的Base
		sgdt[esp - 2]
		; 得到ptr GDT
		pop esi
		; 用eax遍历GDT
		mov eax, 8
		mov i, eax
LoopStart :
		lea edx, [esi + eax]
		; edx: ptr CALLGATE
		xor ecx, ecx
		mov cl, [edx + 5]
		;0x80 Attr == 0x80 P == 1,其它标志位为0
		test cl, 80h
		jnz notZero
		mov g_eax, eax
		mov ebx, FuncAddr
		mov[edx], bx
		;selector == 08		1 0 00
		mov[edx + 2], 08h
		mov[edx + 4], 0
		mov[edx + 5], GATE_TYPE
		;将高16位放在结构体的高16位里
		shr ebx, 16
		mov[edx + 6], bx
		popad
		popfd
		mov esp, ebp
		pop ebp
		ret
notZero :
		add eax, 8
		test eax, GDT_LIMIT
		jnz LoopStart
		mov eax, 0
		popad
		popfd
		ret
	}
}
然后把WriteTrap这里的两个分号放开,08h改成bl就成。
结果崩了0.0,老哥们可以测测看,下周考试,先不搞了。
近期再碰3号中断我就一掌震死自己,为什么要搞3号中断呢?为什么要跟自己过不去,一整天调的眼睛都瞎了(ー̀дー́)
新人刚上车,大佬们轻拍,赶上元旦,祝老哥们新的一年功力大进(逃


_declspec(naked)
VOID NewTrap()
{
	_asm
	{
		push    0; ErrorCode
		push    ebp
		push    ebx
		push    esi
		push    edi
		push    fs
		;Ring0下 fs:[0]指向的是_KPCR的地址
		mov     ebx, 30h
		mov     fs, bx
		mov     ebx, dword ptr fs : [0]
		push    ebx
		sub     esp, 4
		push    eax
		push    ecx
		push    edx
		push    ds
		push    es
		push    gs
		sub     esp, 30h    //对齐TrapFrame首地址
		
		call   TrapCall   
		add     esp, 0x30
		pop        gs
		pop        es
		pop        ds
		pop        edx
		pop        ecx
		pop        eax
		add        esp, 4
		pop        ebx
		pop        fs
		pop        edi
		pop        esi
		pop        ebx
		pop        ebp
		add     esp, 0x4

		//跳回原例程入口
		//push 08h
		//push g_RealTrap
		//retf
		jmp g_RealTrap
	}
}

VOID TrapCall()
{
	DbgPrint("TrapCall\r\n");
}
我们的目的是将3号中断钩到以上代码中。
现在就要找IDT然后写东西进去。找IDT可以用sidt将IDT地址保存到栈里。有了地址就可以直接写了。
图四、中断描述符:

根据这个我们可以定义出中段描述符结构体。
//中断门描述符 仅做参考
typedef struct _INTER_DESC
{
	short OffsetLow;
	short selector;
	char IST : 3;
	char dumb : 5;
	char Attr0 : 1;
	char Attr1 : 1;
	char Attr2 : 1;
	char Attr3 : 1;
	char Attrzero : 1;
	char AttrDPL : 2;
	char Attrp : 1;
	short OffsetHigh;
}INTER_DESC, *PINTER_DESC;


//中断门描述符 仅做参考
typedef struct _INTER_DESC
{
	short OffsetLow;
	short selector;
	char IST : 3;
	char dumb : 5;
	char Attr0 : 1;
	char Attr1 : 1;
	char Attr2 : 1;
	char Attr3 : 1;
	char Attrzero : 1;
	char AttrDPL : 2;
	char Attrp : 1;
	short OffsetHigh;
}INTER_DESC, *PINTER_DESC;

只要改offset其他的依旧照葫芦画瓢,先不研究。
VOID WriteTrap(ULONG32 FuncAddr)
{
//参数传NewTrap
	_asm
	{
		;写调用门用到的索引
		mov ebx,g_eax
	}
	_asm
	{
		
		sub esp, 20h
		push esi
		; mov esi, ptr IDT
		sidt[esp - 2]
		pop esi

		; 找到第三个中断描述符的位置
		add esi, 18h
		mov ebx, [esi]
		mov g_DescLow, ebx
		mov ebx, [esi + 4]
		;用的时候取高16位
		mov g_DescHigh, ebx
		mov ebx, FuncAddr
		mov[esi + INTERGATE_OFFSETLOW], bx
		shr ebx, 16
		mov[esi + INTERGATE_OFFSETHIGH], bx
		mov[esi + INTERGATE_ATTR], 0xee; ? ?
		mov[esi + INTERGATE_DUMB], 00
		; mov ebx, g_eax
		; shr ebx, 3
		; 1 0 00   Index == 1,RPL == 0
		mov [esi + INTERGATE_SELECTOR], 08h
		
		//保存真实例程地址
		lea eax, g_RealTrap
		mov ebx, g_DescLow
		mov[eax], bx
		mov ebx, g_DescHigh
		shr ebx, 16
		mov[eax + 2], bx

	/*	
		Hook INT 3的时候不好调试,在这先调试下恢复
		mov ebx, g_DescLow
		mov[esi + INTERGATE_OFFSETLOW], bx
		mov ebx, g_DescHigh
		shr ebx, 16
		mov[esi + INTERGATE_OFFSETHIGH], bx
		*/

		add esp,20h
	}
}
WriteTrap后再在windbg单击单步中断就能看到DbgPrint("TrapCall\r\n")了

图五、 成功钩出。

VOID WriteTrap(ULONG32 FuncAddr)
{
//参数传NewTrap
	_asm
	{
		;写调用门用到的索引
		mov ebx,g_eax
	}
	_asm
	{
		
		sub esp, 20h
		push esi
		; mov esi, ptr IDT
		sidt[esp - 2]
		pop esi

		; 找到第三个中断描述符的位置
		add esi, 18h
		mov ebx, [esi]
		mov g_DescLow, ebx
		mov ebx, [esi + 4]
		;用的时候取高16位
		mov g_DescHigh, ebx
		mov ebx, FuncAddr
		mov[esi + INTERGATE_OFFSETLOW], bx
		shr ebx, 16
		mov[esi + INTERGATE_OFFSETHIGH], bx
		mov[esi + INTERGATE_ATTR], 0xee; ? ?
		mov[esi + INTERGATE_DUMB], 00
		; mov ebx, g_eax
		; shr ebx, 3
		; 1 0 00   Index == 1,RPL == 0
		mov [esi + INTERGATE_SELECTOR], 08h
		
		//保存真实例程地址
		lea eax, g_RealTrap
		mov ebx, g_DescLow
		mov[eax], bx
		mov ebx, g_DescHigh
		shr ebx, 16
		mov[eax + 2], bx

	/*	
		Hook INT 3的时候不好调试,在这先调试下恢复
		mov ebx, g_DescLow
		mov[esi + INTERGATE_OFFSETLOW], bx
		mov ebx, g_DescHigh
		shr ebx, 16
		mov[esi + INTERGATE_OFFSETHIGH], bx
		*/

		add esp,20h
	}
}
WriteTrap后再在windbg单击单步中断就能看到DbgPrint("TrapCall\r\n")了

图五、 成功钩出。
然后我们来看第二种方法,Path GDT:
用windbg输入! idt -a可以查看各INT中断处理例程入口。
db idtr可以看到很多描述符的selector字段是0x0008。
都是找GDT表索引为1号的描述符项,那么一号描述符不让写也很容易理解。
但是空的表项很多,找到第一个空的写进去便是(见图二)。
图六、GDT

[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

上传的附件:
收藏
免费 1
支持
分享
最新回复 (12)
雪    币: 300
活跃值: (2532)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
mark
2018-1-1 20:24
0
雪    币: 12857
活跃值: (9172)
能力值: ( LV9,RANK:280 )
在线值:
发帖
回帖
粉丝
3
楼主PATCH  IDT的时候没碰到写保护?

PKIDTENTRY_EX  idt_entry  =  (PKIDTENTRY_EX)KiGetInterruptBase(idt_info);
               KIRQL  oldIrql  =  WPOFF();
               KiSetInterruptTrap(idt_entry[ctx->Index],  ctx->TrapHandler);
               WPON(oldIrql);

我都是这样才修改成功了,不WPOFF直接蓝了
2018-1-2 08:55
0
雪    币: 7
活跃值: (250)
能力值: ( LV3,RANK:25 )
在线值:
发帖
回帖
粉丝
4
我把保护屏蔽了发现可以直接写IDT
2018-1-2 09:28
0
雪    币: 405
活跃值: (2355)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
5
想起了20年前,CIH流行,天天整这些GDT,IDT,LDT  CALLGATE  INTGATE  TASKGATE  TSS==,不过自从XP  某个SP以后禁止R3访问PHy  mem  后,不能再装B,R3进R0了,都成了过往云烟。
2018-1-2 10:27
0
雪    币: 7
活跃值: (250)
能力值: ( LV3,RANK:25 )
在线值:
发帖
回帖
粉丝
6
wowocock 想起了20年前,CIH流行,天天整这些GDT,IDT,LDT CALLGATE INTGATE TASKGATE TSS==,不过自从XP 某个SP以后禁止R3访问PHy mem 后,不能再装B,R3 ...
之前看combojiqng的Ring3进Ring0调用门方法以为可以无驱进,然而还不是要驱动,话说都能加载驱动了还纠结Ring3干嘛...
2018-1-2 10:42
0
雪    币: 5039
活跃值: (2646)
能力值: ( LV12,RANK:290 )
在线值:
发帖
回帖
粉丝
7
mark.  LZ好牛逼,蒂花之秀
2018-1-2 20:11
0
雪    币: 7
活跃值: (250)
能力值: ( LV3,RANK:25 )
在线值:
发帖
回帖
粉丝
8
FaEry mark. LZ好牛逼,蒂花之秀
大佬式疯狂嘲讽,我当真了(~ ̄▽ ̄)~ 
2018-1-2 20:17
0
雪    币: 6818
活跃值: (153)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
不错!
2018-1-2 22:43
0
雪    币: 441
活跃值: (1105)
能力值: ( LV2,RANK:15 )
在线值:
发帖
回帖
粉丝
10
陈独秀!收藏了
2018-1-17 15:03
0
雪    币: 1485
活跃值: (1135)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
11
实测XP系统调用门可以。。WIN7X86的调用门    R3程序直接崩溃
2018-3-7 15:10
0
雪    币: 1485
活跃值: (1135)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
12
异常机制应该处理
2018-3-7 15:14
0
雪    币: 1485
活跃值: (1135)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
13
大神,有测试过win7系统?r3直接使用调用门后r3崩溃
2018-3-20 12:17
0
游客
登录 | 注册 方可回帖
返回
//