A、反断点的一些原理
B、认识IDT表
C、IDT表相关结构
D、读出IDT表-sidt指令
E、测试
课时:44
一、反断点的一些原理
1、让他不能正常下断点 (0xCC断点int 3)
2、正常下断点后,让他不能断下(IDT HOOK)
二、IDT表
大小 00-0xFF ,0-255
返回 iretd ;
中断INT n
举个简单的例子
我们常会在驱动代码里写一行_asm int 3
这样我们就手动设置了一个断点,当代码执行到这里 就会使CPU的执行暂停,并跳到中断处理函数中去执行。
同样的和SSDT与SSDT Shadow一样 IDT也有一张IDT表(俗称中断描述符表)
1、怎么样获取IDT表基址?
2、怎么样读取IDT表 中的中断函数地址?
__asm sidt idt_info
#pragma pack(push)
#pragma pack(1) // 1字节方式对齐
typedef struct _IDTR //IDT基址
{
USHORT limit; //范围 占8位
ULONG base; //基地址 占32位 PIDT_ENTRY类型指针
}IDTR,*PIDTR;
256*8
typedef struct _IDT_ENTRY
{
USHORT offset_low; //中断处理函数地址低16位
USHORT selector;
UCHAR reserved;
UCHAR type:4; //4位
UCHAR always0:1; //1位(半字节)
UCHAR dpl:2; //2位
UCHAR present:1; //1位
USHORT offset_high;//中断处理函数地址高16位
}IDT_ENTRY,*PIDT_ENTRY;//+3.offset_high<<16+offset_low //int 3 中断处理函数地址
#pragma pack(pop) //#pragma pack(pop)
========================================================
笔记:
又是理论课,讲反断点的IDT表,用一个小程序OD载入,按钮事件会弹出消息框,在OD里下断后按钮会被断下来,其实就是CC代码INT3指令,用CE看一下地址就发现了,再改成原有的8B后OD就断不下来了,实际上写入指令是通过WritProcessMemory,再开启另外一个OD来附加这个OD下断bp WritProcessMemory,当第一个OD再设置断点时就被第二个OD断下来了.
下断的原理知道了那么如何反断点呢,比如我们来到函数MessageBoxA…错了,要在第二个OD里函数WriteProcessMemory,这里,将开始的代码改成RETN 14,也就是在后面找到的返回代码,
此时再用第一个OD断MessageBoxA就断不下来了,因为第一个OD的WriteProcessMemory函数被修改了.
不能下断点不是我们的重点,我们需要的主要功能是下断后的清除,打开虚拟机进行测试.先用XueTr看一下IDT表,在钩子->系统中断表里是00—FF个列表项,再看一下KernelDetective工具里中断描述符表,发现中断表与CPU有关,双核的就是两个表.那么我们INT3中断的时候是会转到03这里,也就是0x804DFAA1执行,那么我们修改一下,中断列表的返回是iretd(无参数),将第一句push 0 改成iretd,当再次下断的时候就没有被执行,程序出错了.因为返回后执行了1字节的指令,解决的方法就是将指令长度进行对齐.在我们修改了INT3中断列表之后,发现OD的很多功能都失效了,比如不能读取模块等.理论上我们需要加个判断,如果是我们需要保护的进行就在中断列表这里给它过滤掉,如果不是我们要保护的进程就恢复正常的中断.
这种方法只能反INT3断点,并不能够反硬件断点和内存断点.读取IDT表的typedef struct _IDTR函数.还有typedef struct _IDT_ENTRY函数的参数是要将低16位和高16位合起来才是.
接起来老师将写好的代码进行讲解
#include <ntddk.h>
#pragma pack(push)
#pragma pack(1) // 1字节对齐
typedef struct _IDTR //IDT基址
{
USHORT limit; //范围占位
ULONG base; //基地址占位_IDT_ENTRY类型指针
}IDTR,*PIDTR;
typedef struct _IDT_ENTRY
{
USHORT offset_low; //中断处理函数地址低位
USHORT selector;
UCHAR reserved;
UCHAR type:4; //4位
UCHAR always0:1; //1位
UCHAR dpl:2; //2位
UCHAR present:1; //1位
USHORT offset_high;//中断处理函数地址低位
}IDT_ENTRY,*PIDT_ENTRY;//获取基址实际上是这个类型
#pragma pack(pop) //#pragma pack(pop)
ULONG ReadIdt(ULONG CPUNUM)//多核需要ULONG CPUNUM参数
{
IDTR idtr; //定义结构用于获取表基址
PIDT_ENTRY Aidt;
KdPrint(("IDT_ENTRY size=%d \n",sizeof(IDT_ENTRY)));
__asm sidt idtr; //获取表基址信息
KdPrint(("IDT BASE=%x \n",idtr.base));
Aidt=PIDT_ENTRY(idtr.base);//为获取基址转换类型
for (int i=0;i<0xff;i++) //进行遍历
{
ULONG cur_idt= Aidt->offset_high;
cur_idt=cur_idt<<16; //得到高位地址
cur_idt=cur_idt+ULONG(Aidt->offset_low);//合成中断处理函数地址
KdPrint(("high=%x,low=%x,IDT %d=%0000x \n",Aidt->offset_high,Aidt->offset_low,i,cur_idt));//打印出高低位信息
Aidt++;
}
return idtr.base;
}
写好代码后在虚拟机中测试,在WINDBG中显示出一大堆信息,有高位和低位的信息.然后用工具查看一下所读出的表地址都已经正确了,只是在WINDBG中显示的是high=804d,low=f50e 而在工具中显示的是0x804DF50E,也就是说需要合在一起.另外说一下比如多核的CPU的中断表一般情况下都是相同的,所以真正HOOK的时候在多核情况下还是需要考虑的,具体HOOK代码下节课再讲. |