Windows内核逆向——<中断处理 从硬件机制到用户驱动接管>
2022-2-5 17:59:0 Author: mp.weixin.qq.com(查看原文) 阅读量:57 收藏


看雪论坛作者ID:小白养的菜鸡

本文主要描述 win10_x64 + IA32-E 的中断处理流程,从I/O APIC开始,至调用用户注册的 ISR 函数被调用结束。
 
会用到的东西:Win10_x64 + Windbg + IDA + Xp源代码 + Intel手册

CPU相关知识:

APIC(Advanced Programmable Interrupt Controller)

用于在多核心环境下处理中断而引入。
 
由两个组件构成 : APIC = I/O APIC + Local APIC
 
I/O APIC : 收集设备发来的中断,在此处建立中断请求于向量号的对应关系,分发中断请求至 Local APIC。
 
Local APIC : 每个核心存在一个,接收 I/O APIC 发来中断(此时已确定了中断向量号)并在合适的时机(优先级机制)将其提交给CPU,提供IPI(Inter-Processor Interrupt)核心间中断功能。
 
架构图如下:

配置 local APIC

根据intel手册描述 存在两种方案: (win10_x64 采用第二种方案)

(1)通过 写IA32_APIC_BASE MSR寄存器,将其寄存器空间映射至内存,访问内存,进行配置。

(2)通过读写MSR寄存器的方式对APIC提供的功能进行配置(所有配置均通过写MSR寄存器完成配置)。

优先级机制(Task and Processor Priorities)

local APIC 中 的 TPR(Task-Priority Register)寄存器 用于描述此local APIC 所属核心的当前的中断优先级,windows 内核中的 IRQL 机制的主要 功能 在 x86架构下依赖读写此寄存器实现,为了加快访问速度,在IA32-E模式下,TPR[7:4]被映射为Cr8寄存器,软件通过设置当前中断优先级(通过读写Cr8)来阻塞中断。


PPR(Processor-Priority Register)为只读寄存器 PPR[7:4] 等价于TPR[7:4] ,在local APIC中将中断发送给处理器前会通过读此寄存器的值决定是否打断处理器的执行流(投递中断),这也是系统在高IRQL不会被第IRQL中断的原因。

IDT 表 由硬件到软件:

在 local APIC 向处理器 提交一个中断请求后,处理器会以其中断向量号为下标在中断向量表(IDT)中索引对应的中断处理例程,在栈中压入部分当前线程上下文后,跳转至系统的中断例程进行处理。下文描述相关设施。
(为了叙述简单忽略由IST 位引起的自动栈切换)

(1)IDTR 寄存器

指出中断向量表的位置 (PG使用重新加载IDT表的方式使内核调试器无法处理调试相关的中断)

(2)LIDT 指令

用于设置 IDTR寄存器的值

(3)中断描述符

用于给出一个中断处理例程的地址,和其栈地址(间接指出),
每个可用的中断号对应一个,由操作系统构造并写入IDT表中说明了,一个由local APIC 投递的中断,被CPU收到时,应该跳转到的中断处理程序的位置,以及栈的位置。

(4)中断向量号的分配

0-21 号中断 :用于对CPU中产生的异常的处理

22-31 号中断 :被intel保留,无法被使用

31-255 号中断 :由系统自行分配

(5)如何根据一个中断向量号找到一个中断描述符的细节

(6)中断后的栈

在CPU收到local APIC 投递的中断后会自动(无需OS代码参与)在栈中压入以下内容,其意义为保存当正在执行的线程的部分上下文,操作系统接管后,会保存所有的线程上下文。

(7)软件接管

压入部分线程上下文后,CPU 的 RIP 寄存器被设置为中断描述符中所指出的中断处理例程的值,至此由 I/O APIC 发出的中断请求被软件接管。
I/O APIC -> Local APIC -> IDT -> 系统的中断处理例程
在CPU接收到local APIC 发来的中断请求后,在栈中自动压入必要参数并跳转至中断描述符中所指地址后,win10_x64 内核代码开始接管中断处理流程。
从两个方向来分析 NT 内核的中断处理:

(1)收到中断后的处理流程

(2)驱动程序注册ISR(Interrupt Service Routines)的过程

收到中断后的处理流程

在windbg 中使用 !idt 命令 列出相关信息
可以明显看出:
0-0x15(21)号中断 :用于对CPU中产生的异常的处理
22-31(1f) 号中断 :未被使用
31-255 号中断 :由系统自行分配

以0x35号向量为例开始分析 使用 u ffff803801dee38 指令对IDT描述符所指中断处理例程进行反汇编,发现其存在明显特征 即在栈中压入其中断向量号0x35 和 rbp 寄存器地址后,进行跳转且 下文存在同样结构,在压入0x36和rbp后进行跳转,且跳转地址一致,我们猜测0x36号中断的中断处理例程指向push 0x36 首地址,通过分析证明此猜测正确。



在IDA中加载内核文件,并重定位基址使其于windbg中一致,发现大量一致性结构 ,证明 对所有中断处理的第一步 是首先执行 KiIsrThunkShadow 中对应的代码,用于在栈中压入对应的中断向量,和保存rbp。

开启页表隔离后应为,可以看出少压入了rbp。

少压入的rbp参数会在进入KiIsrLinkage 前被补充,所以页表隔离并不影响中断处理的主要逻辑。

分析KxIsrLinkageShadow函数(开启页表隔离的情况下的中断处理流程)

分析KxIsrLinkage的主要部分



_guard_dispatch_icall 函数用作指令流保护,意在利用编译时产生的cfgbitmap,检测出 被跳转位置是否为合法地址,已阻止 shellocde 的执行此处等价于 jmp rax。
总结上文所述 NT内核 在接管中断后动作如下:

① 在栈中压入中断号

② 处理页表隔离

③ 保存好中断前的线程上下文

④ 根据中断向量号 在此核心的KPCR中找到对应的中断对象

⑤ 调用中断对象的 DispatchAddress函数

下文开始分析驱动程序注册ISR(Interrupt Service Routines)的过程。

中断处理过程的最后调用了_KINTERRUPT.DispatchAddress中所指函数,通过分析ISR注册过程得到DispatchAddress所对应的函数。
 
msdn中对IoConnectInterrupt 给出的解释:
The IoConnectInterrupt routine registers a device driver's InterruptService routine (ISR), so that it will be called when a device interrupts on any of a specified set of processors.
NTSTATUS IoConnectInterrupt(  [out]          PKINTERRUPT       *InterruptObject,  [in]           PKSERVICE_ROUTINE ServiceRoutine,  [in, optional] PVOID             ServiceContext,  [in, optional] PKSPIN_LOCK       SpinLock,  [in]           ULONG             Vector,  [in]           KIRQL             Irql,  [in]           KIRQL             SynchronizeIrql,  [in]           KINTERRUPT_MODE   InterruptMode,  [in]           BOOLEAN           ShareVector,  [in]           KAFFINITY         ProcessorEnableMask,  [in]           BOOLEAN           FloatingSave);

我们只分析四个参数:

[out] InterruptObject 输出参数,返回被初始化好的中断对象

[in] ServiceRoutine 由用户驱动实现的中断响应函数

[in, optional] ServiceContext 调用用户中断响应函数时传递的参数

[in] Vector 中断响应函数对应的中断向量

IoConnectInterruptEx -> IopConnectInterrupt -> KeInitializeInterruptEx
KeInitializeInterruptEx 用于初始化一个中断对象:

IoConnectInterruptEx -> IopConnectInterrupt -> KeConnectInterrupt—>KiConnectInterrupt
KiConnectInterrupt用于将一个被初始化好的中断对象插入目标核心的KPCR中。

由此得出_KINTERRUPT.DispatchAddress = KiChainedDispatch
总结上文:
(1)NT内核将用户注册的中断例程抽象为一个中断对象
(2)NT内核将此对象初始化完毕后挂入KPCR.InterruptObject
(3) 在发生中断后调用 _KINTERRUPT.DispatchAddress 函数
即KiChainedDispatch
下文开始分析 KiChainedDispatch
KiChainedDispatch->KiScanInterruptObjectList
->KiCallInterruptServiceRoutine
KiScanInterruptObjectList 扫描此KPCR(核心)相关联的所有中断对象。



KiCallInterruptServiceRoutine 调用由用户注册的ISR函数

总结一下上文:
由用户设定的ISR 由 KeInitializeInterruptEx 函数写入中断对象中 _KINTERRUPT.ServiceRoutine = isr_callback //用户注册的isr
从一个中断到达I/O APIC 至用户注册的ISR被调用的过程:

1)I/O APIC

2)local APIC

3)IDT

3)KiIsrThunkShadow

4)KxIsrLinkageShadow

5)KiChainedDispatch

6)KiCallInterruptServiceRoutine

7)_KINTERRUPT.ServiceRoutine

 
先写到这里,难免有疏漏,请各位斧正。

 

看雪ID:小白养的菜鸡

https://bbs.pediy.com/user-home-882390.htm

*本文由看雪论坛 小白养的菜鸡 原创,转载请注明来自看雪社区

# 往期推荐

1.Chrome v8 issue 1234770(CVE-2021-30599)漏洞分析

2.新姿势绕过应用的ROOT检测

3.关于unicorn去搞VMP的iat那点事

4.【符号执行】KLEE安装与KLEE论文阅读笔记

5.一次Web探测服务器技术学习总结

6.解锁一辆车的非“优雅”方式

球分享

球点赞

球在看

点击“阅读原文”,了解更多!


文章来源: http://mp.weixin.qq.com/s?__biz=MjM5NTc2MDYxMw==&mid=2458428139&idx=1&sn=ac793292c14fe8126588dc7a612b7de3&chksm=b18f926186f81b77104d546de4b803829c3218d51c5d2c8116be9d4a3c3379091bc67ed941d0#rd
如有侵权请联系:admin#unsafe.sh