首页
论坛
课程
招聘
[原创]端口访问监控原理
2008-6-2 17:48 22538

[原创]端口访问监控原理

2008-6-2 17:48
22538
NetRoc
http://netroc682.spaces.live.com/
本来不打算写文章的,呵呵。既然AhnLab敢用,我当然也敢写咯,哈哈
安博士的反外挂系统最近添加了一个功能,可以检查出来按键精灵、简单游这些用增强版WinIo直接进行端口读写的程序。后来拿来看了一下,其原理就是自己前段时间实现过的那种。通过Hook int 1,设置IO断点进行监控的方法。
原理如下:
Intel兼容CPU都内置了调试功能。可以设置的断点类型包括执行断点、内存访问断点和IO断点。通过操作DrX寄存器和CR4 寄存器,可以在发生特定端口的读写操作时触发断点。AhnLab的这种检测技术就是基于CPU的这种功能。以下的介绍都基于32位处理器。
CPU调试寄存器简介:
DR0—DR3寄存器:它们是32位调试地址寄存器。根据DR7中设置的不同,它们可以包含内存地址,也可以包含IO端口号。很多调试器的硬件断点也是通过这几个寄存器实现的,所以一般硬件断点只能设置4个。
DR4和DR5:这两个寄存器是被系统保留的,当CR4中的DE被设置时,访问这两个寄存器会产生非法指令错误#UD;当CR4中的DE被清空时,这两个寄存器和DR6、DR7关联,即访问它们和访问DR6、DR7一样。
DR6:调试状态寄存器。这个寄存器用于在调试事件发生时报告状态信息。要判断是哪个断点被触发,触发的原因之类的就是靠它里面的值。DR6的定义如下:
        typedef struct _DR6INFO
{
        unsigned B0        :        1;        //B0
        unsigned B1        :        1;        //B1
        unsigned B2        :        1;        //B2
        unsigned B3        :        1;        //B3
        unsigned Reserved1        :        9;        //reserved
        unsigned BD        :        1;        //BD
        unsigned BS        :        1;        //BS
        unsigned BT        :        1;        //BT
        unsigned Reserved2        :        16;        //Reserved
}DR6INFO, *PDR6INFO;
        B0到B3用于指示哪个断点被触发。它们分别对应于DR0到DR3中的地址或端口。
        BD表示触发断点的下一条指令是对调试寄存器的访问。当DR7的GD标志被设置时,对调试寄存器进行访问的指令会触发调试事件,并且DR6的BD被设置。
        BS表示是由于单步执行触发的调试事件。当EFLAGS的TF标志被设置时,这种断点会被触发。
        BT指示是由于任务切换触发的调试事件。当TSS中的T标志被设置时会产生这种事件。
DR7:调试控制寄存器。对断点是否启用、断点类型等的控制。设置断点需要配合DR0—DR3和DR7寄存器。定义如下:
typedef struct _DR7INFO
{
        unsigned L0        :        1;        //L0
        unsigned G0        :        1;        //G0
        unsigned L1        :        1;        //L1
        unsigned G1        :        1;        //G1
        unsigned L2        :        1;        //L2
        unsigned G2        :        1;        //G2
        unsigned L3        :        1;        //L3
        unsigned G3        :        1;        //G3
        unsigned LE        :        1;        //LE
        unsigned GE        :        1;        //GE
        unsigned Reserved1        :        3;        //reserved
        unsigned GD        :        1;        //GD
        unsigned Reserved2        :        2;        //reserved
        unsigned RW0        :        2;        //R/W0
        unsigned LEN0        :        2;        //LEN0
        unsigned RW1        :        2;        //R/W1
        unsigned LEN1        :        2;        //LEN1
        unsigned RW2        :        2;        //R/W2
        unsigned LEN2        :        2;        //LEN2
        unsigned RW3        :        2;        //R/W3
        unsigned LEN3        :        2;        //LEN3
}DR7INFO, *PDR7INFO;
        L0到L3:设置时为当前任务启用相应的断点条件。每次任务切换时CPU都会自动清除Lx位,所以这几位只控制当前任务的断点。
        G0到G3:为所有任务启用相应的断点条件。这是针对整个机器的。
        LE和GE:P6 family和之后的IA32处理器都不支持这两位。当设置时,使得处理器会检测触发数据断点的精确的指令。为了兼容性,Intel建议使用精确断点时把LE和GE都设置为1。
        GD:设置GD位时启用对调试寄存器的保护,这时对这些寄存器的访问都会触发调试中断。进入中断处理函数前,CPU会清掉GD位,使得中断处理函数能够访问DRx寄存器。
        R/W0到R/W3:指定各个断点的触发条件。它们对应于DR0到DR3中的地址以及DR6中的4个断点条件标志。这几位的意义会受到CR4中的DE位的影响。
当DE位为1时,它们的意义如下:
00 — 仅在指令执行时中断
01 — 仅数据写入时中断
10 — IO输入输出时中断
11 — 数据读取或写入时中断,但是不受指令预取的影响
当DE位为0时,它们的意义如下:
00 — 仅在指令执行时中断
01 — 仅数据写入时中断
10 —未定义
11 — 数据读取或写入时中断,但是不受指令预取的影响
        LEN0到LEN3:指定在调试地址寄存器DR0到DR3中指定的地址位置的大小。如果R/Wx位为0,则LENx位也必须为0,否则会产生不确定的行为。这几位的意义如下:
00 — 1字节长度
01 — 2字节长度
10 — 未定义
11 — 四字节长度
IO监控的实现
介绍了上面这些内容,那么IO监控的实现方法就很简单了,键盘IO的端口是60和64,比如要监控60端口,就可以这样进行:
        Hook掉Trap01,自己接管调试中断
        设置CR4的DE,以及DR7中的LE和GE。
        在DR0到DR3中选一个来设置端口号,比如选择DR0设置为0x60。
        设置DR7中的R/Wx和LENx位,这里应该设置RW0为10、LEN0为00
        在Hook的中端函数中,检查DR6中的B0到B3,如果是B0的话,表明发生了对0x60端口的读写操作。
由于IO断点是Trap,即在事件发生后才能触发中断,所以这种方法不能阻止对端口的读写,而仅能够进行监控。判断读写的数据以及要精确的判断是读还是写需要更进一步的操作,也是有一些办法可以实现的,这里就不说完了,呵呵。
实现的关键代码
Hook掉IDT
NTSTATUS HookIdt(ULONG ulId, PVOID pIntProc, PULONG pOldIntProc, PIDTENTRY pstOldEntry)
{
	CCHAR CpuCount = 0;
	PIDTENTRY    IdtEntry = NULL;
	IDTR stIdtr = {0};

	CpuCount = *KeNumberProcessors;
	while( CpuCount > 0)
	{//处理多CPU
		KeSetAffinityThread( KeGetCurrentThread(), CpuCount);//绑定CPU
		//得到IDTR 中得段界限与基地址
		_asm sidt stIdtr;
		IdtEntry = (PIDTENTRY)stIdtr.Base;
		//保存原有得IDT
		if ( pstOldEntry)
		{
			RtlCopyMemory( pstOldEntry, &IdtEntry[ulId], sizeof( IDTENTRY));
		}
		_asm cli;//禁止中断
		if ( pOldIntProc)
		{
			*pOldIntProc = (ULONG)IdtEntry[ulId].OffsetLow | ((ULONG)IdtEntry[ulId].OffsetHigh<<16);		
		}
		IdtEntry[ulId].OffsetLow   = (unsigned short)pIntProc;                           
		IdtEntry[ulId].OffsetHigh  = (unsigned short)((unsigned int)pIntProc>>16);
		_asm sti;//开中断
		CpuCount--;
	}
	return STATUS_SUCCESS;
}

//卸载钩子
NTSTATUS UnhookIdt(ULONG ulId, ULONG pIntProc)
{
	CCHAR CpuCount = 0;
	PIDTENTRY    IdtEntry = NULL;
	IDTR stIdtr = {0};
	CpuCount = *KeNumberProcessors;
	while( CpuCount > 0)
	{
		KeSetAffinityThread( KeGetCurrentThread(), CpuCount);//绑定CPU
		//得到IDTR 中得段界限与基地址
		_asm sidt stIdtr;
		IdtEntry = (PIDTENTRY)stIdtr.Base;
		_asm cli;//禁止中断	
		IdtEntry[ulId].OffsetLow   = (unsigned short)pIntProc;                           
		IdtEntry[ulId].OffsetHigh  = (unsigned short)((unsigned int)pIntProc>>16);
		_asm sti;//开中断
		CpuCount--;
	}
	return STATUS_SUCCESS;
}

//自己的
#pragma optimize( "", off )
void __declspec (naked) NewTrap01(void)
{
	INT_CONTEXT stContext;
	ULONG ulResult;
	_asm
	{		
		//保存环境;
		push ebp;
		mov ebp, esp;
		sub esp, 100h;

		mov stContext.cs, cs;
		mov stContext.ds, ds;
		mov stContext.eax, eax;
		mov stContext.ebp, ebp;
		mov stContext.ebx, ebx;
		mov stContext.ecx, ecx;
		mov stContext.edi, edi;
		mov stContext.edx, edx;
		mov stContext.es, es;
		mov stContext.esi, esi;
		mov stContext.esp, esp;
		mov stContext.fs, fs;
		mov stContext.ss, ss;
		mov ax, 0x30;
		mov fs, ax;
		mov stContext.gs, gs;
	}
	ulResult = OnTrap01( &stContext);//实际处理
	if ( ulResult == 1)
	{
		_asm
		{		
			mov ax, stContext.ds;
			mov ds, ax;
			mov ebp, stContext.ebp;
			mov ebx, stContext.ebx;
			mov ecx, stContext.ecx;
			mov edi, stContext.edi;
			mov edx, stContext.edx;
			mov esi, stContext.esi;

			mov ax, stContext.es
			mov es, ax;
			mov ax, stContext.fs
			mov fs, ax;
			mov ax, stContext.ss
			mov ss, ax;
			mov ax, stContext.gs;
			mov gs, ax;
			mov eax, stContext.eax;

			mov esp,ebp;
			pop ebp;
			//退出
			iretd;
		}
	}
	else
	{
		_asm
		{		
			mov ax, stContext.ds;
			mov ds, ax;
			mov ebp, stContext.ebp;
			mov ebx, stContext.ebx;
			mov ecx, stContext.ecx;
			mov edi, stContext.edi;
			mov edx, stContext.edx;
			mov esi, stContext.esi;

			mov ax, stContext.es
			mov es, ax;
			mov ax, stContext.fs
			mov fs, ax;
			mov ax, stContext.ss
			mov ss, ax;
			mov ax, stContext.gs;
			mov gs, ax;
			mov eax, stContext.eax;

			mov esp,ebp;
			pop ebp;
			//不是自己需要的事件,调用原来的Trap01;
			jmp g_pOldTrap01;
		}
	}
	
}
#pragma optimize( "", on )

ULONG __stdcall OnTrap01(LPINT_CONTEXT pstContext)
{
	DR6INFO stDr6;
	ULONG ulEip = 0;
	USHORT usCs = 0;
	PUCHAR pucCode = 0;

	ulEip = *((PULONG)(pstContext->ebp + 4));
	usCs = *((PUSHORT)(pstContext->ebp + 8));
	/*DbgPrint( "[ebp]=0x%X, [ebp+4]=0x%X, [ebp+8]=0x%X, [ebp+C]=0x%X\r\n", *((PULONG)(pstContext->ebp)), 
		*((PULONG)(pstContext->ebp + 0x4)), 
		*((PULONG)(pstContext->ebp + 0x8)),
		*((PULONG)(pstContext->ebp + 0xC)));*/
	pucCode = (PUCHAR)ulEip;


	stDr6 = GetDR6();
	/*DbgPrint( "%d:In trap 01.dr6.B0=%d, dr6.B1=%d, dr6.B2=%d,dr6.B3=%d, dr6.BD=%d, dr6.BS=%d, dr6.BT=%d\r\n", __LINE__, 
		stDr6.B0, stDr6.B1, stDr6.B2, stDr6.B3, stDr6.BD, stDr6.BS, stDr6.BT);
	DbgPrint( "%d:Traped EIP=0x%X, CS=0x%X\r\n",  __LINE__, (ULONG)ulEip, (ULONG)usCs);*/
	if ( stDr6.B0 && g_bpInfo[0].blIsSet)
	{
		//
		OnBreak( 0, pstContext, pucCode);
		//DbgPrint("On bp 0\r\n");
		return 1;
	}else if ( stDr6.B1 && g_bpInfo[1].blIsSet)
	{
		//
		OnBreak( 1, pstContext, pucCode);
		//DbgPrint("On bp 1\r\n");
		return 1;
	}else if ( stDr6.B2 && g_bpInfo[2].blIsSet)
	{
		//
		OnBreak( 2, pstContext, pucCode);
		//DbgPrint("On bp 2\r\n");
		return 1;
	}else if ( stDr6.B3 && g_bpInfo[3].blIsSet)
	{
		//
		OnBreak( 3, pstContext, pucCode);
		//DbgPrint("On bp 3\r\n");
		return 1;
	}
	else
	{
		return 0;
	}
}

破解方法
既然知道了原理,那么破解方法也就很明了了。读写端口之前想办法清掉调试寄存器即可。但是如果处理了DR7中的GD标志的话,清调试器的办法要麻烦一些。这里也不赘述了,呵呵。

看雪社区年底排行榜,查查你的排名?

收藏
点赞0
打赏
分享
最新回复 (33)
雪    币: 108
活跃值: 活跃值 (81)
能力值: ( LV13,RANK:1050 )
在线值:
发帖
回帖
粉丝
combojiang 活跃值 26 2008-6-2 17:52
2
0
学习,顶起来。
雪    币: 319
活跃值: 活跃值 (10)
能力值: ( LV8,RANK:120 )
在线值:
发帖
回帖
粉丝
DiYhAcK 活跃值 2 2008-6-2 18:33
3
0
不错,顶起来。。。
雪    币: 203
活跃值: 活跃值 (11)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
wyfe 活跃值 2008-6-2 21:13
4
0
看不懂,以后再看
雪    币: 5535
活跃值: 活跃值 (26)
能力值: (RANK:1060 )
在线值:
发帖
回帖
粉丝
forgot 活跃值 26 2008-6-2 21:31
5
0
那hying的壳怕是跑不起来了
雪    币: 25
活跃值: 活跃值 (10)
能力值: ( LV12,RANK:370 )
在线值:
发帖
回帖
粉丝
xIkUg 活跃值 9 2008-6-2 22:29
6
0
不稳定,安博士在我的电脑上跑不起来
雪    币: 308
活跃值: 活跃值 (55)
能力值: ( LV12,RANK:470 )
在线值:
发帖
回帖
粉丝
zhuwg 活跃值 11 2008-6-3 08:47
7
0
跟着膜拜大牛..学习
雪    币: 326
活跃值: 活跃值 (15)
能力值: ( LV9,RANK:170 )
在线值:
发帖
回帖
粉丝
快雪时晴 活跃值 4 2008-6-3 11:21
8
0
基础不扎实,反调试越来越普遍,我看今后搞破解更难了
雪    币: 25
活跃值: 活跃值 (10)
能力值: ( LV12,RANK:370 )
在线值:
发帖
回帖
粉丝
xIkUg 活跃值 9 2008-6-3 11:36
9
0
//不是自己需要的事件,调用原来的Trap01;

调用原来的Trap01后你设置的端口断点可能失效了,就不是你能完全控制的了。。。
如果重新设置可能会有以下情况:
1. 设置不上
2. 重新设置上了,但会漏网,存在露网的机会即说明如果有人利用这个机会则就可能会导致你想借助端口断点实现的功能完全失效
雪    币: 200
能力值: (RANK:650 )
在线值:
发帖
回帖
粉丝
shoooo 活跃值 16 2008-6-3 11:45
10
0
这不是肯德基

学习膜拜
雪    币: 13
活跃值: 活跃值 (25)
能力值: ( LV9,RANK:490 )
在线值:
发帖
回帖
粉丝
NetRoc 活跃值 12 2008-6-3 11:57
11
0
我的代码纯属测试,呵呵。
实际AhnLab的代码在不断重新设置断点。它的目的在于监控和发现外挂,所以不需要完全抓住,而只要检查出来一部分即可。
这种方法实际漏洞非常大,in、out之前清掉断点就行了。针对不懂的人而已,呵呵。
雪    币: 5535
活跃值: 活跃值 (26)
能力值: (RANK:1060 )
在线值:
发帖
回帖
粉丝
forgot 活跃值 26 2008-6-3 12:28
12
0
只好设GD了
雪    币: 25
活跃值: 活跃值 (10)
能力值: ( LV12,RANK:370 )
在线值:
发帖
回帖
粉丝
xIkUg 活跃值 9 2008-6-3 13:42
13
0
GD没用,漏洞一样存在,只要一进int 1处理程序,GD就失效了,触发int 1很容易,利用起来也很容易
雪    币: 5535
活跃值: 活跃值 (26)
能力值: (RANK:1060 )
在线值:
发帖
回帖
粉丝
forgot 活跃值 26 2008-6-3 14:56
14
0
为什么要进int 1?
雪    币: 0
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
xoojo 活跃值 2008-6-3 15:05
15
0
好文,只能站一旁看着!
雪    币: 200
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
zhucheba 活跃值 2008-6-3 16:25
16
0
不错,顶起来。。。
雪    币: 301
活跃值: 活跃值 (23)
能力值: ( LV13,RANK:330 )
在线值:
发帖
回帖
粉丝
HSQ 活跃值 8 2008-6-4 07:48
17
0
学习了,收藏研究。。。
雪    币: 200
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
大师兄 活跃值 2008-6-4 08:16
18
0
进int1后可以随意操作drx
雪    币: 5535
活跃值: 活跃值 (26)
能力值: (RANK:1060 )
在线值:
发帖
回帖
粉丝
forgot 活跃值 26 2008-6-4 11:38
19
0
进int 123也可以
雪    币: 27
活跃值: 活跃值 (10)
能力值: ( LV7,RANK:100 )
在线值:
发帖
回帖
粉丝
goodcode 活跃值 2 2008-6-4 16:26
20
0
学习....
雪    币: 150
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
cpuArt 活跃值 2008-6-9 23:26
21
0
膜拜中。。。楼主是好人。。。多多交流
雪    币: 337
活跃值: 活跃值 (81)
能力值: ( LV9,RANK:690 )
在线值:
发帖
回帖
粉丝
winndy 活跃值 17 2008-6-10 09:28
22
0
学习
端午玩得可happy,
雪    币: 4385
活跃值: 活跃值 (225)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
RuShi 活跃值 2008-6-12 15:35
23
0
学习+收藏。感谢楼主分享
雪    币: 203
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
fhwclq 活跃值 2008-6-23 16:13
24
0
GoodOK
雪    币: 191
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
glueshred 活跃值 2008-6-23 18:57
25
0
深。。。。。。
游客
登录 | 注册 方可回帖
返回