端口访问监控原理

端口访问监控原理

 

NetRoc

http://www.DbgTech.net/

本来不打算写文章的,呵呵。既然AhnLab敢用,我当然也敢写咯,哈哈

安博士的反外挂系统最近添加了一个功能,可以检查出来按键精灵、简单游这些用增强版WinIo直接进行端口读写的程序。后来拿来看了一下,其原理就是自己前段时间实现过的那种。通过Hook int 1,设置IO断点进行监控的方法。

原理如下:

Intel兼容CPU都内置了调试功能。可以设置的断点类型包括执行断点、内存访问断点和IO断点。通过操作DrX寄存器和CR4 寄存器,可以在发生特定端口的读写操作时触发断点。AhnLab的这种检测技术就是基于CPU的这种功能。以下的介绍都基于32位处理器。

CPU调试寄存器简介:

DR0DR3寄存器:它们是32位调试地址寄存器。根据DR7中设置的不同,它们可以包含内存地址,也可以包含IO端口号。很多调试器的硬件断点也是通过这几个寄存器实现的,所以一般硬件断点只能设置4个。

DR4DR5:这两个寄存器是被系统保留的,当CR4中的DE被设置时,访问这两个寄存器会产生非法指令错误#UD;当CR4中的DE被清空时,这两个寄存器和DR6DR7关联,即访问它们和访问DR6DR7一样。

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;

l  B0B3用于指示哪个断点被触发。它们分别对应于DR0DR3中的地址或端口。

l  BD表示触发断点的下一条指令是对调试寄存器的访问。当DR7GD标志被设置时,对调试寄存器进行访问的指令会触发调试事件,并且DR6BD被设置。

l  BS表示是由於单步执行触发的调试事件。当EFLAGSTF标志被设置时,这种断点会被触发。

l  BT指示是由于任务切换触发的调试事件。当TSS中的T标志被设置时会产生这种事件。

DR7:调试控制寄存器。对断点是否启用、断点类型等的控制。设置断点需要配合DR0DR3DR7寄存器。定义如下:

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;

l  L0L3:设置时为当前任务启用相应的断点条件。每次任务切换时CPU都会自动清除Lx位,所以这几位只控制当前任务的断点。

l  G0G3:为所有任务启用相应的断点条件。这是针对整个机器的。

l  LEGEP6 family和之后的IA32处理器都不支持这两位。当设置时,使得处理器会检测触发数据断点的精确的指令。为了兼容性,Intel建议使用精确断点时把LEGE都设置为1

l  GD:设置GD位时启用对调试寄存器的保护,这时对这些寄存器的访问都会触发调试中断。进入中断处理函数前,CPU会清掉GD位,使得中断处理函数能够访问DRx寄存器。

l  R/W0R/W3:指定各个断点的触发条件。它们对应于DR0DR3中的地址以及DR6中的4个断点条件标志。这几位的意义会受到CR4中的DE位的影响。

DE位为1时,它们的意义如下:

00 仅在指令执行时中断

01 仅数据写入时中断

10 IO输入输出时中断

11 数据读取或写入时中断,但是不受指令预取的影响

DE位为0时,它们的意义如下:

00 仅在指令执行时中断

01 仅数据写入时中断

10 —未定义

11 数据读取或写入时中断,但是不受指令预取的影响

l  LEN0LEN3:指定在调试地址寄存器DR0DR3中指定的地址位置的大小。如果R/Wx位为0,则LENx位也必须为0,否则会产生不确定的行为。这几位的意义如下:

00 1字节长度

01 2字节长度

10 未定义

11 四字节长度

IO监控的实现

介绍了上面这些内容,那么IO监控的实现方法就很简单了,键盘IO的端口是6064,比如要监控60端口,就可以这样进行:

l  HookTrap01,自己接管调试中断

l  设置CR4DE,以及DR7中的LEGE

l  DR0DR3中选一个来设置端口号,比如选择DR0设置为0x60

l  设置DR7中的R/WxLENx位,这里应该设置RW010LEN000

l  Hook的中端函数中,检查DR6中的B0B3,如果是B0的话,表明发生了对0x60端口的读写操作。

由于IO断点是Trap,即在事件发生后才能触发中断,所以这种方法不能阻止对端口的读写,而仅能够进行监控。判断读写的数据以及要精确的判断是读还是写需要更进一步的操作,也是有一些办法可以实现的,这里就不说完了,呵呵。

实现的关键代码

HookIDT

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标志的话,清调试器的办法要麻烦一些。这里也不赘述了,呵呵。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章