0x00 前言 IDT(Interrupt Descriptor Table)中断描述符表,中断就是停下现在的活动,去完成新的任务。一个中断可以起源于软件或硬件。比如,出现页错误,调用IDT中的0x0E。或用户进程请求系统服务(SSDT)时,调用IDT中的0x2E。而系统服务的调用是经常的,这个中断就能触发。我们现在就想办法,先在系统中找到IDT,然后确定0x2E在IDT中的地址,最后用我们的函数地址去取代它,这样以来,用户的进程(可以特定设置)一调用系统服务,我们的hook函数即被激发。
0x01 需要解决的问题
1.IDT如何获取呢?SIDT指令可以办到,它可以在内存中找到IDT,返回一个IDTINFO结构的地址。这个结构中就含有IDT的高半地址和低半地址。为了方便把这两个半地址合在一起,我们可以用一个数据结构:IDTINFO,和宏的结构如下:
typedef struct
{
WORD IDTLimit;
WORD LowIDTbase; //IDT的低半地址
WORD HiIDTbase; //IDT的高半地址
} IDTINFO;
方便获取地址存取的宏
#define MAKELONG(a, b)((LONG)(((WORD)(a))|((DWORD)((WORD)(b)))<< 16))
2.IDT有最多256个入口,我们现在要的是其中的0x2E,这个中断号的入口地址如何获取呢?
#pragma pack(1)
typedef struct
{
WORD LowOffset; //入口的低半地址
WORD selector;
BYTE unused_lo;
unsigned char unused_hi:5; // stored TYPE ?
unsigned char DPL:2;
unsigned char P:1; // vector is present
WORD HiOffset; //入口地址的低半地址
} IDTENTRY;
#pragma pack()
知道了这个入口结构,就相当于知道了每间房(可以把IDT看作是一排有256间房组成的线性结构)的长度,我们先获取所有的入口idt_entrys,那么第0x2E个房间的地址也就可以确定了,即idt_entrys[0x2E]。
3.如果得到了0x2e的地址,如何用我们的hook地址改写原中断地址呢?
见以下核心代码:
DWORD KiRealSystemServiceISR_Ptr; // 真正的2E句柄,保存以便恢复hook
#define NT_SYSTEM_SERVICE_INT 0x2e
//我们的hook函数
int HookInterrupts() { IDTINFO idt_info; //SIDT将返回的结构 IDTENTRY* idt_entries; //IDT的所有入口 IDTENTRY* int2e_entry; //我们目标的入口 __asm{ sidt idt_info; //获取IDTINFO } //获取所有的入口 idt_entries = (IDTENTRY*)MAKELONG(idt_info.LowIDTbase,idt_info.HiIDTbase); //保存真实的2e地址 KiRealSystemServiceISR_Ptr = MAKELONG(idt_entries[NT_SYSTEM_SERVICE_INT].LowOffset, idt_entries[NT_SYSTEM_SERVICE_INT].HiOffset); //获取0x2E的入口地址 int2e_entry = &(idt_entries[NT_SYSTEM_SERVICE_INT]); __asm{ cli; // 屏蔽中断,防止被打扰 lea eax,MyKiSystemService; // 获得我们hook函数的地址,保存在eax mov ebx, int2e_entry; // 0x2E在IDT中的地址,ebx中分地高两个半地址 mov [ebx],ax; // 把我们hook函数的地半地址写入真是第半地址 shr eax,16 //eax右移16,得到高半地址 mov [ebx+6],ax; // 写入高半地址 sti; //开中断 } return 0; }