两种HOOK IDT方法

IDT :就是系统中断表,是存放Windows的中断和异常的处理函数的表

IDT的数据结构:

 
  1. typedef struct _IDTENTRY

  2. {

  3. unsigned short LowOffset; //处理函数的低2字节

  4. unsigned short selector; //处理函数所在的段选择子

  5. unsigned char retention : 5;

  6. unsigned char zero1 : 3;

  7. unsigned char gate_type : 1;

  8. unsigned char zero2 : 1;

  9. unsigned char interrupt_gate_size : 1;

  10. unsigned char zero3 : 1;

  11. unsigned char zero4 : 1;

  12. unsigned char DPL : 2; //descriptor privilege level

  13. unsigned char P : 1;

  14. unsigned short HiOffset; //处理函数的高2字节

  15. } IDTENTRY, *PIDTENTRY;

 

 

何为段选择子参照上一篇博客

处理函数的地址可以通过一个MAKELONG(LoOffset,HiOffset)宏来获取

 

寻找IDT表:IDT表的长度与地址是由CPU的IDTR寄存器来描述的

 

 

IDTR的高32位为IDT表的地址,IDTR的低16位为IDT表的长度(以字节为单位)

求IDT表元素个数则需要用IDTR.IDT_limit / 8获取

 
  1. typedef struct _IDTR {

  2. USHORT IDT_limit;

  3. USHORT IDT_LOWbase;

  4. USHORT IDT_HIGbase;

  5. }IDTR, *PIDTR;

 

获取IDTR可以用SIDT指令获取

 

 
  1. IDTR idtr;

  2. __asm SIDT idtr;

 

 

获取IDT的地址可以通过IDTR的高32位地址获取,也可以通过遍历KiProcessorBlock数组;

为何遍历KiProcessorBlock:因为每个CPU都有自己的IDT表,所以对于多核CPU时候要获取每一个CPU对应的IDT结构。

怎么通过KiProcessorBlock来获取IDT:

KiProcessorBlock数组的每一个非0项都是指向一个_kprcb结构,而_kprcb结构正是再_kpcr结构的+0x120处,_kpcr结构的+0x38就是_IDT结构地址。

每一个IDT的成员项为8字节的_KIDTENTRY:

typedef struct _IDTENTRY
{
unsigned short LowOffset;                      //段基址低2字节
unsigned short selector; //段选择子
unsigned char retention : 5;
unsigned char zero1 : 3;
unsigned char gate_type : 1;
unsigned char zero2 : 1;
unsigned char interrupt_gate_size : 1;
unsigned char zero3 : 1;
unsigned char zero4 : 1;
unsigned char DPL : 2;
unsigned char P : 1;
unsigned short HiOffset;                           //段基址高两字节
} IDTENTRY, *PIDTENTRY;

 

可以通过windbg查看这_kpcr结构:

 
  1. lkd> dt _kpcr

  2. nt!_KPCR

  3. +0x000 NtTib : _NT_TIB

  4. +0x000 Used_ExceptionList : Ptr32 _EXCEPTION_REGISTRATION_RECORD

  5. +0x004 Used_StackBase : Ptr32 Void

  6. +0x008 Spare2 : Ptr32 Void

  7. +0x00c TssCopy : Ptr32 Void

  8. +0x010 ContextSwitches : Uint4B

  9. +0x014 SetMemberCopy : Uint4B

  10. +0x018 Used_Self : Ptr32 Void

  11. +0x01c SelfPcr : Ptr32 _KPCR

  12. +0x020 Prcb : Ptr32 _KPRCB

  13. +0x024 Irql : UChar

  14. +0x028 IRR : Uint4B

  15. +0x02c IrrActive : Uint4B

  16. +0x030 IDR : Uint4B

  17. +0x034 KdVersionBlock : Ptr32 Void

  18. +0x038 IDT : Ptr32 _KIDTENTRY

  19. +0x03c GDT : Ptr32 _KGDTENTRY

  20. +0x040 TSS : Ptr32 _KTSS

  21. +0x044 MajorVersion : Uint2B

  22. +0x046 MinorVersion : Uint2B

  23. +0x048 SetMember : Uint4B

  24. +0x04c StallScaleFactor : Uint4B

  25. +0x050 SpareUnused : UChar

  26. +0x051 Number : UChar

  27. +0x052 Spare0 : UChar

  28. +0x053 SecondLevelCacheAssociativity : UChar

  29. +0x054 VdmAlert : Uint4B

  30. +0x058 KernelReserved : [14] Uint4B

  31. +0x090 SecondLevelCacheSize : Uint4B

  32. +0x094 HalReserved : [16] Uint4B

  33. +0x0d4 InterruptMode : Uint4B

  34. +0x0d8 Spare1 : UChar

  35. +0x0dc KernelReserved2 : [17] Uint4B

  36. +0x120 PrcbData : _KPRCB

 

 

那么获取IDT的方法知道了,如何获取KiProcessorBlock:

用IDA分析内核模块ntkrnlpa模块,加载符号链接后(将windbg的symsrv.yes拷贝到IDA目录下),然后搜索KiProcessorBlock,一般ntkrnlpa导出的内核函数会有使用这个全局变量的,这里选取KeSetTimeIncrementAPI,这里直接通过硬编码定位到这个KiProcessorBlock:

 
  1. ULONG GetKiProcessorBlock()

  2. {

  3. UNICODE_STRING usFuncName;

  4. ULONG uFuncAddrKeSetTimeIncrement;

  5. RtlInitUnicodeString(&usFuncName,L"KeSetTimeIncrement");

  6. uFuncAddrKeSetTimeIncrement = (ULONG)MmGetSystemRoutineAddress(&usFuncName);

  7. if (!MmIsAddressValid((PVOID)uFuncAddrKeSetTimeIncrement))

  8. return 0;

  9.  
  10. return *(ULONG *)(uFuncAddrKeSetTimeIncrement + 44); //通过IDA搜索KiProcessorBlock来获取到偏移

  11. }

 

获取到IDT表的地址后就能够进行HOOK操作:

比如要HOOK IDT的3 号中断函数:

一法:直接修改IDT中成员的地址为Hook函数(要初始化fs寄存器,因为这是内核环境下,并且要还原fs寄存器,供原IDT函数使用):

 
  1. _declspec(naked)

  2. void Fake_InterruptFun()

  3. {

  4. _asm {

  5. pushad

  6. pushfd

  7.  
  8. push fs

  9. push 0x30

  10. pop fs //初始化fs寄存器

  11.  
  12. call FilterInterruptFun;

  13. pop fs

  14.  
  15. popfd

  16. popad

  17.  
  18. jmp g_Interrupt3

  19. }

  20. };

FilterInterruptFun这个就是自己写的过滤函数,具体想怎么实现过滤或者其他操作自己写。

后面再跳转回原来的IDT函数地址。

关键代码:

 

二法:

通过修改段选择子实现

 

段选择子结构

段选择子就是一个数字,一共有16位,结构如下:

 
  1. | 1 | 0 | 字节

  2. |7654321076543 2 10| 比特

  3. |-------------|-|--| 占位

  4. | INDEX |T|R | 含义

  5. | |I|P |

  6. | | |L |

  • INDEX:在GDT数组或LDT数组的索引号
  • TI:Table Indicator,这个值为0表示查找GDT,1则查找LDT
  • RPL:请求特权级。以什么样的权限去访问段。

段描述符结构

 
  1. | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | 字节

  2. |76543210|7 6 5 4 3210 |7 65 4 3210|76543210|76543210|76543210|76543210|76543210| 比特

  3. |--------|-|-|-|-|---- |-|--|-|----|--------|--------|--------|--------|--------| 占位

  4. | BASE |G|D|0|A|LIMIT|P|D |S|TYPE|<------- BASE 23-0 ------>|<-- LIMIT 15-0 ->| 含义

  5. | 31-24 | |/| |V|19-16| |P |

  6. |B| |L| | |L |

  • BASE: 段基址,由上图中的两部分(BASE 31-24 和 BASE 23-0)组成
  • G:LIMIT的单位,该位 0 表示单位是字节,1表示单位是 4KB
  • D/B: 该位为 0 表示这是一个 16 位的段,1 表示这是一个 32 位段
  • AVL: 该位是用户位,可以被用户自由使用
  • LIMIT: 段的界限,单位由 G 位决定。数值上(经过单位换算后的值)等于段的长度(字节)- 1。
  • P: 段存在位,该位为 0 表示该段不存在,为 1 表示存在。
  • DPL:段权限
  • S: 该位为 1 表示这是一个数据段或者代码段。为 0 表示这是一个系统段(比如调用门,中断门等)
  • TYPE: 根据 S 位的结果,再次对段类型进行细分。

 

在保护模式下不像在实模式下通过cs:xx来访问,而是通过段选择子和段描述符表来得到段基址 再加上IDTENTRY中的虚拟偏移得到真正的地址

所以可以修改IDT表指定中断IDTENTRY的段选择子(SELECTOR)为我们再GDT表中自己创建的GDTENTRY,再修改我们修改的GDTENTRY的offset来实现跳转到我们的Fake_InterruptFunction,实现Hook。

注意:

1.只修改想要修改的GDTENTRY的OFFSET其他的属性都不去改变

2.只修改IDTENTRY的段选择子部分

3.在Fake_InterruptFunction中要跳转回原来的段内再实现过滤或者其他的操作,Fake_InterruptFunction只是实现跳转回远啦的段内的功能。(保护模式下段间跳转使用jmp fword ptr [address],address为6位大小,前四位为跳转的函数虚拟偏移地址,后两位为所在的段选择子)

4.在实现过滤操作时候一定要保存下fs的,因为在返回用户层时候,会恢复原来的fs寄存器。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值