0x01 概述
将中断告知系统CPU ,中断的典型示例是硬件中断,例如鼠标按钮或键盘按键按下,网络数据包活动以及硬件生成的异常,例如除零或断点-分别中断号0x00和0x03。
一旦CPU被中断,它将停止执行其正在执行的操作并响应新的中断 。
CPU通过查找在中断描述符表(IDT)中找到的中断服务例程(ISR),知道如何响应以及对新接收到的中断执行哪些内核例程。
IDT是IDT描述符条目的列表,取决于具体的体系结构,其大小为8或16个字节 。
IDT的指针存储在每个物理处理器的IDTR寄存器中,换句话说,每个处理器都有自己的IDTR寄存器,指向自己的中断描述符表。
0x02 IDT的位置
我们可以通过读取IDTR寄存器来检查中断描述符表在内核中的位置:
r idtr
如稍后所述,该命令!idt允许我们dump中断描述符表的内容,并且还确认IDT位于如下所示:fffff803536dda00`
idtr寄存器包含与!idt dump IDT时看到的相同值
0x03 Dump IDT
我们可以转储IDT并查看给定中断的中断服务程序的地址。以下是中断描述符表的摘要:
kd> !idt Dumping IDT: fffff80091456000 00: fffff8008f37e100 nt!KiDivideErrorFaultShadow 01: fffff8008f37e180 nt!KiDebugTrapOrFaultShadow Stack = 0xFFFFF8009145A9E0 02: fffff8008f37e200 nt!KiNmiInterruptShadow Stack = 0xFFFFF8009145A7E0 03: fffff8008f37e280 nt!KiBreakpointTrapShadow ... 90: fffff8008f37f680 i8042prt!I8042MouseInterruptService (KINTERRUPT ffffd4816353e8c0) a0: fffff8008f37f700 i8042prt!I8042KeyboardInterruptService (KINTERRUPT ffffd4816353ea00)
下面显示了实际的IDT转储和ISR代码执行:
· IDT表与 !idt
· 中断a0的IRS入口点位于fffff8008f37f700
- 这是在操作系统上注册键盘事件(例如按键)时首先在内核内部执行的例程
- 最终,代码在fffff8008f37f700执行完成,就会响应例程(在实际的键盘驱动程序内部)i8042prt!I8042KeyboardInterruptService
· 设置断点
i8042prt!I8042KeyboardInterruptService
· 设置断点后,在操作系统登录提示上按一个键,然后击中断点,确认
i8042prt!I8042KeyboardInterruptService 确实可以处理键盘中断
下面是一个高度简化的示意图,说明了上述所有事件的发生:
· 键盘中断0xa0发生
· 查找使用索引的IDT表(0x0aIDT地址+ 0xa0 * 0x10),并解析ISR入口点,并将代码跳转到该表
· 经过一番努力之后,代码最终被重定向到键盘驱动程序,在 i8042prt!I8042KeyboardInterruptService中处理中断
0x04 IDT条目
IDT由IDT条目_KIDTENTRY64组成,IDT条目是内核内存结构,其定义如下:
kd> dt nt!_KIDTENTRY64 +0x000 OffsetLow : Uint2B +0x002 Selector : Uint2B +0x004 IstIndex : Pos 0, 3 Bits +0x004 Reserved0 : Pos 3, 5 Bits +0x004 Type : Pos 8, 5 Bits +0x004 Dpl : Pos 13, 2 Bits +0x004 Present : Pos 15, 1 Bit +0x006 OffsetMiddle : Uint2B +0x008 OffsetHigh : Uint4B +0x00c Reserved1 : Uint4B +0x000 Alignment : Uint8B
序号OffsetLow``OffsetMiddle``OffsetHigh在偏移量0x000,0x006和0x008的内核虚拟地址上,它是在代码执行将由CPU一旦特定中断被转移到发生-换句话说-这是中断服务程序的(ISR ) 入口点。
0x05 IDT的键盘中断
检查一下IDT条目中的键盘中断,该中断位于IDT表中的索引a0处:
!idt a0
IDT位于:fffff803536dd000
kd > r idtr idtr = fffff803536dd000
可以得到a0的位置,通过0xa0*0x10增加IDT入口(中断索引a0,0x10一个描述符条目长度为16个字节)的IDT表的地址fffff803536dd000,得到:fffff803536dda00`
kd > dq idtr + (0 xa0 * 0 x10 )L2 fffff803 ` 536 dda00 51568e00 ` 0010e700 00000000 ` fffff803
利用以上信息,可以将a0中断描述符条目与_KIDTENTRY64重叠并检查IDT条目a0的内容:
kd> dt _kidtentry64 (idtr + (0xa0*0x10)) ntdll!_KIDTENTRY64 +0x000 OffsetLow : 0xe700 +0x002 Selector : 0x10 +0x004 IstIndex : 0y000 +0x004 Reserved0 : 0y00000 (0) +0x004 Type : 0y01110 (0xe) +0x004 Dpl : 0y00 +0x004 Present : 0y1 +0x006 OffsetMiddle : 0x5156 +0x008 OffsetHigh : 0xfffff803 +0x00c Reserved1 : 0 +0x000 Alignment : 0x51568e00`0010e700
0x06 键盘中断a0的ISR
根据对键盘中断所述,IDT入口偏移的组合形成的中断服务程序(ISR)入口点的虚拟地址将在执行的代码a0中断由键盘触发:
下面显示了一旦触发a0中断,CPU将在fffff8035156e700`(ISR入口点)执行指令:
· FFFFFFFFFFFFFF A0将被压入堆栈
· 跳到fffff8035156ea40`
最后,i8042prt!I8042KeyboardInterruptService将被命中,在fffff8035156e700命中了断点,立即命中:i8042prt!I8042KeyboardInterruptService`
0x07 _KINTERRUPT
_KINTERRUPT是一种内核内存结构,其中包含有关中断的信息。文章中此结构的关键成员是位于offset 0x18的成员,它是指向ServiceRoutine例程的指针(在关联的驱动程序内部),该例程负责实际处理中断:
dt nt!_KINTERRUPT +0x000 Type : Int2B +0x002 Size : Int2B +0x008 InterruptListEntry : _LIST_ENTRY +0x018 ServiceRoutine : Ptr64 unsigned char ... +0x0f8 Padding : [8] UChar
作为示例,从较早的时候开始,我们就知道键盘中断的ISR位于ffffd4816353ea00,因此我们可以通过在_KINTERRUPT``ffffd4816353ea00中断处覆盖内存内容来检查该中断的结构:
dt nt !_KINTERRUPT ffffd4816353ea00
这使我们可以确认再次正确指向键盘驱动程序内部:ServiceRoutine``i8042prt!I8042KeyboardInterruptService
0x08 Finding _KINTERRUPT
为了手动找到给定中断_KINTERRUPT的位置,我们需要利用以下存储器位置和结构。
进程控制区域或PCR(_KPCR内核中的内存结构)存储有关给定处理器的信息:
kd> dt _KPCR ntdll!_KPCR +0x000 NtTib : _NT_TIB +0x000 GdtBase : Ptr64 _KGDTENTRY64 +0x008 TssBase : Ptr64 _KTSS64 +0x010 UserRsp : Uint8B +0x018 Self : Ptr64 _KPCR +0x020 CurrentPrcb : Ptr64 _KPRCB +0x028 LockArray : Ptr64 _KSPIN_LOCK_QUEUE +0x030 Used_Self : Ptr64 Void +0x038 IdtBase : Ptr64 _KIDTENTRY64 +0x040 Unused : [2] Uint8B +0x050 Irql : UChar +0x051 SecondLevelCacheAssociativity : UChar +0x052 ObsoleteNumber : UChar +0x053 Fill0 : UChar +0x054 Unused0 : [3] Uint4B +0x060 MajorVersion : Uint2B +0x062 MinorVersion : Uint2B +0x064 StallScaleFactor : Uint4B +0x068 Unused1 : [3] Ptr64 Void +0x080 KernelReserved : [15] Uint4B +0x0bc SecondLevelCacheSize : Uint4B +0x0c0 HalReserved : [16] Uint4B +0x100 Unused2 : Uint4B +0x108 KdVersionBlock : Ptr64 Void +0x110 Unused3 : Ptr64 Void +0x118 PcrAlign1 : [24] Uint4B +0x180 Prcb : _KPRCB
_KPCR的位置可以这样找到:
kd> ? @$pcr Evaluate expression: -8781847822336 = fffff803`51148000 kd> !pcr KPCR for Processor 0 at fffff80351148000: Major 1 Minor 1 NtTib.ExceptionList: fffff803536dffb0 NtTib.StackBase: fffff803536de000 NtTib.StackLimit: 0000000000000000 ...snip...
_KPCR内,偏移量处0x180有一个成员指向过程控制块内存结构_KPRCB,该结构包含有关处理器状态的信息。
尝试查找给定中断的_KINTERRUPT内存位置时,我们感兴趣的关键成员是InterruptObject,_KINTERRUPT包含指向对象列表的指针列表。 InterrupObject 位于0x2e80偏移处,如下所示:
kd> dt _KPRCB ntdll!_KPRCB +0x000 MxCsr : Uint4B +0x004 LegacyNumber : UChar +0x005 ReservedMustBeZero : UChar .... +0x2e80 InterruptObject : [256] Ptr64 Void //256 pointers max as noted earlier ....
有了以上知识,我们现在可以找到_KINTERRUPT键盘中断a0的位置:
dt @$pcr nt!_KPCR Prcb.InterruptObject[a0]
下面是_KINTERRUPT找到的中断a0的手动匹配命令!idt给出的中断:
0x09 参考资料
https://nagareshwar.securityxploded.com/2014/03/20/code-injection-and-api-hooking-techniques/
https://en.wikipedia.org/wiki/Interrupt_handler
https://www.linux.com/tutorials/kernel-interrupt-overview/
https://relearex.wordpress.com/2017/12/27/hooking-series-part-ii-interrupt-descriptor-table-hooking/
https://resources.infosecinstitute.com/hooking-idt/
0x10 学习总结
可以通过读取IDTR寄存器来检查中断描述符表在内核中的位置。
我们可以通过!idt转储IDT并查看给定中断的中断服务程序的地址。
还没有评论,来说两句吧...