源代码中的注释部分,使用了 sidt 指令来获取 IDT 地址。然而,可以发现在部分虚拟
机环境中,sidt 不能返回正确的结果。这是因为sidt并不是特权指令,虚拟机不借助硬件
虚拟化等功能时,模拟sidt的指令比较困难。因此,本文使用KPCR结构来获取IDT的地址。
使用 windbg工具,可以看到KPCR 结构的详细内容。
kd> dt _KPCR
nt!_KPCR
+0x000 NtTib : _NT_TIB
+0x01c SelfPcr : Ptr32 _KPCR
+0x020 Prcb : Ptr32 _KPRCB
+0x024 Irql : UChar
+0x028 IRR : Uint4B
+0x02c IrrActive : Uint4B
+0x030 IDR : Uint4B
+0x034 KdVersionBlock : Ptr32 Void
+0x038 IDT : Ptr32 _KIDTENTRY
+0x03c GDT : Ptr32 _KGDTENTRY
+0x040 TSS : Ptr32 _KTSS
+0x044 MajorVersion : Uint2B
+0x046 MinorVersion : Uint2B
+0x048 SetMember : Uint4B
+0x04c StallScaleFactor : Uint4B
+0x050 DebugActive : UChar
+0x051 Number : UChar
+0x052 Spare0 : UChar
+0x053 SecondLevelCacheAssociativity : UChar
+0x054 VdmAlert : Uint4B
+0x058 KernelReserved : [14] Uint4B
+0x090 SecondLevelCacheSize : Uint4B
+0x094 HalReserved : [16] Uint4B
+0x0d4 InterruptMode : Uint4B
+0x0d8 Spare1 : UChar
+0x0dc KernelReserved2 : [17] Uint4B
+0x120 PrcbData : _KPRCB
KPCR 结构的 0x38 偏移处即为 IDT 的地址,由于当前处理器的 KPCR 总对应 fs:[0],所
以 IDT的地址可以通过fs:[0x38]取得。
获取到IDT的地址后,便可以修改中断处理函数的地址了。需要注意的是,IDT中存储
处理函数的地址的方式比较特别,低16 位和高 16位是分开保存的,下面两个函数可以读取
或者设置指定中断向量的处理例程的地址。
PVOID int_get(ULONG n)
{
return
(PVOID)((ULONG)idtentries[n].LowOffset|((ULONG)idtentries[n].HiOffset <<
16));
}
VOID int_set(ULONG n,PVOID ptr)
{
USHORT lo,hi;
lo = (USHORT)((ULONG)ptr & 0x0000ffff);
hi = (USHORT)((ULONG)ptr >> 16);
ihook_wpoff();
idtentries[n].LowOffset = lo;
idtentries[n].HiOffset = hi;
ihook_wpon();
}
IDT通常是不可写的,修改CR0寄存器的WP位可以绕过此保护,写入后也需要还原。
VOID ihook_wpoff() //关闭 wp位
{
__asm
{
cli;
push eax;
mov eax, cr0;
and eax, 0FFFEFFFFh;
mov cr0, eax;
pop eax;
}
}
VOID ihook_wpon() //设置wp位
{
__asm
{
push eax;
mov eax, cr0;
or eax, 10000h;
mov cr0, eax;
pop eax;
sti; |