当前位置:编程学习 > 网站相关 >>

简析利用调试寄存器实现内核函数的HOOK

某些RK,木马会经常HOOK一些关键函数从而达到隐藏等目的,而相应的ARK检测软件也会通常会先恢复这些关键函数的HOOK(譬如利用硬盘文件恢复),然后再调用来检测RK,这样就可以检测出某些隐藏.下面就介绍利用调试器实现某些内核函数的HOOK.


Intel386以后的系列CPU增加了8个32位的调试寄存器,从Dr0到Dr7,方便调试使用.如果设置了相应的调试信息,在条件满足的情况下将会发生 1 号(DB例外)中断,CPU就会陷入中断例程,执行中断代码,我们的HOOK目的就可以通过这个实现.

首先看下面百度出来的对寄存器组的使用方法的解释:

这八个寄存器中由四个用于断点,两个用于控制,另两个保留未用。对这八个寄存器的访问,只能在0级特权级进行。在其它任何特权级对这八个寄存器中的任意一个寄存器进行读或写访问,都将产生无效操作码异常。此外,这八个寄存器还可用DR6及DR7中的BD位和GD位进行进一步的保护,使其即使是在0级也不能进行读出或写入。 

对这些寄存器的访问使用通常的MOV指令: 
MOV reg Dri 该指令将调试寄存器i中的内容读至通用寄存器reg中; 
MOV Dri reg 该指令将通用寄存器reg中的内容写至调试寄存器i中。此处i的取值可以为0至7中的任意值。 

这些寄存器的功能如下: 
DR0—DR3 寄存器DR0—DR3包含有与四个断点条件的每一个相联系的线性地址(断点条件则在DR7中)。因为这里使用的是线性地址,所以,断点设施的操作,无论分页机制是否启用,都是相同的。 
DR4—DR5 保留。 
DR6是调试状态寄存器。当一个调试异常产生时,处理器设置DR6的相应位,用于指示调试异常发生的原因,帮助调试异常处理程序分析、判断,以及作出相应处理。 
DR7是调试控制寄存器。分别对应四个断点寄存器的控制位,对断点的启用及断点类型的选择进行控制。所有断点寄存器的保护也在此寄存器中规定。(下面这图怎么也改不好,大家还是去百度吧)

    |---------------|----------------|
Dr6 |                               |BBB                     BBB B |
    |                               TSD                     3 2 1 0 |
    | --------------|----------------|
Dr7 |RWE LEN   ...     RWE LEN   | G               GLGLGLGLGL |
    | 3   3   ...           0   0   | D               EE33221100 |
    |---------------|----------------|
  31                               15                       0


DR6各位的功能
B0—B3(对应0-3位) 当断点线性地址寄存器规定的条件被检测到时,将对应的B0—B3位置1。置位B0—B3与断点条件是否被启用无关。即B0—B3的某位被置1,并不表示要进行对应的断点异常处理。 
BD(13位) 如下一条指令要对八个调试寄存器之一进行读或写时,则在指令的边界BD位置1。在一条指令内,每当即将读写调试寄存器时,也BD位置1。BD位置1与DR7中GD位启用与否无关。 
BS(14位) 如果单步异常发生时,BS位被置1。单步条件由EFLAGS寄存器中的TF位启用。如果程序由于单步条件进入调试处理程序,则BS位被置1。与DR6中的其它位不同的是,BS位只在单步陷阱实际发生时才置位,而不是检测到单步条件就置位。 
BT(15位) BT位对任务切换导致TSS中的调试陷阱位被启用而造成的调试异常,指示其原因。对这一条件,在DR7中没有启用位。 

DR6中的各个标志位,在处理机的各种清除操作中不受影响,因此,调试异常处理程序在运行以前,应清除DR6,以避免下一次检测到异常条件时,受到原来的DR6中状态位的影响。 

DR7各位的功能 
LEN LEN为一个两位的字段,用以指示断点的长度。每一断点寄存器对应一个这样的字段,所以共有四个这样的字段分别对应四个断点寄存器。LEN的四种译码状态对应的断点长度如下 

LEN 说明 
0 0 断点为一字节 
0 1 断点为两字节 
1 0 保留 
1 1 断点为四字节 

这里,如果断点是多字节长度,则必须按对应多字节边界进行对齐。如果对应断点是一个指令地址,则LEN必须为00 
RWE RWE也是两位的字段,用以指示引起断点异常的访问类型。共有四个RWE字段分别对应四个断点寄存器,RWE的四种译码状态对应的访问类型如下 

RWE 说明 
0 0 指令 
0 1 数据写 
1 0 保留 
1 1 数据读和写 

GE/LE GE/LE为分别指示准确的全局/局部数据断点。如果GE或LE被置位,则处理器将放慢执行速度,使得数据断点准确地把产生断点的指令报告出来。如果这些位没有置位,则处理器在执行数据写的指令接近执行结束稍前一点报告断点条件。建议读者每当启用数据断点时,启用LE或GE。降低处理机执行速度除稍微降低一点性能以外,不会引起别的问题。但是,对速度要求严格的代码区域除外。这时,必须禁用GE及LE,并且必须容许某些不太精确的调试异常报告。 
L0—L3/G0—G3 L0—L3及G0—G3位 
分别为四个断点寄存器的局部及全局启用信号。如果有任一个局部或全局启用位被置位,则由对应断点寄存器DRi规定的断点被启用。 
GD GD位启用调试寄存器保护条件。注意,处理程序在每次转入调试异常处理程序入口处清除GD位,从而使处理程序可以不受限制地访问调试寄存器。 

前述的各个L位(即LE,L0—L3)是有关任务的局部位,使调试条件只在特定的任务启用。而各个G位(即GD,G0—G3)是全局的,调试条件对系统中的所有任务皆有效。在每次任务切换时,处理器都要清除L位。 

 

  如果你耐心把上面的信息看完了,基本上也就应该明白了.其实我们可以利用调试寄存器做的不只是函数的HOOK,也可以进行I/O的HOOK,下面要说的是在指定的内核函数上下指令执行断点,然后挂接1号中断实现HOOK(有很多种方法可以实现,看你自己喜欢哪种了).

  首先把DR0寄存器设置为要挂接的内核函数的地址(譬如ZwCreateFile),然后修改 Dr7的L0和G0(第0和第1位)都为1, Dr7的 R/W0(16.17位) 为00, LEN0(18.19位)位为00,这样当CPU执行到ZwCreateFile地址的时候,就会进入1号中断例程.
  然后我们应该去修改IDT表,将1号中断指向我们的处理程序.
  接着需要考虑在中断例程我们要做的事情,关于中断时CPU具体做了什么,大家可以去搜索.下面只介绍我们所关心的在内核空间发生DB例外时的情况:中断发生时,依次将 EFLAGS,CS,EIP压入堆栈,然后进入中断程序(由于中断地址本来就在内核空间,所以不需要切换堆栈).在中断处理程序中作出相应的处理,然后使用 iretd 指令退出中断.( iretd 指令: 依次将堆栈弹出到 EIP,CS,EFLAGS),我们可以通过修改堆栈中EIP的值,在中断返回时跳转实现HOOK.

  上面就是主要的内容,但是还有点问题.Windows在KiFastCall和线程切换时会修改Drx的值,为了防止我们的断点被清除,可以利用Dr7:GD位保护寄存器,这样任何对调试寄存器的操作(读和写)都会产生DB例外然后进入1号中断例程.这样,我们在中断例程中又需要利用Dr6的标识位处理那些因为操作调试寄存器产生的例外(关于这个我只是简单的跳过了那些对DRX操作的代码,并没有详细分析).

下面可以看详细的实现代码:

简单实现HOOK下ZwCreateFile,XP下,其他系统慎用

/*
drxhook.h
Written By yykingking@126.com
*/

#ifndef _DRX_HOOK
#define _DRX_HOOK

#include <ntddk.h>

typedef unsigned long DWORD;
typedef unsigned char BOOL;

#pragma pack(push,1)
typedef struct _idtr
{
  //定义中断描述符表的限制,长度两字节;
  short     IDTLimit;
  //定义中断描述服表的基址,长度四字节;
  unsigned int   IDTBase;
}IDTR,*PIDTR;

typedef struct _IDTENTRY
{
  unsigned short LowOffset;
  unsigned short selector;
  unsigned char unused_lo;
  unsigned char segment_type:4;   //0x0E is an interrupt gate
 

补充:综合编程 , 安全编程 ,
CopyRight © 2012 站长网 编程知识问答 www.zzzyk.com All Rights Reserved
部份技术文章来自网络,