win10 1909逆向之APIC中断和实验
2022-9-9 18:24:13 Author: 看雪学苑(查看原文) 阅读量:10 收藏


本文为看雪论坛优秀文章

看雪论坛作者ID:学技术打豆豆

         

由于工作项目时间很紧,勉强断断续续逆向了一部分加自己做些实验来验证,目前先将这部分分享出来,带和我一样的新手入个门,以后中断虚拟化应该能用到。

建议先看下面推荐的三本书里的APIC章节,文章下面关于基础知识的介绍,来自网络各个大佬的笔墨,笔者就搬抄过来。建议先看书,文章纯粹是辅助。以下的步骤就是APIC的初始化流程。AMD和INTEL内部实现会有部分区别,我的是INTEL的,AMD将就看看,区别不大。

参考书籍:《Intel手册》、《x86/x64体系探索及编程》、《一个64位操作系统的设计与实现》


APIC介绍(简单介绍)

APIC (Advanced Programmable Interrupt Controller)是90年代Intel为了应对将来的多核趋势提出的一整套中断处理方案,用于取代老旧的8259A PIC。这套方案适用于多核(Multi-Processor)机器,每个CPU拥有一个Local APIC,整个机器拥有一个或多个IOAPIC,设备的中断信号先经由IOAPIC汇总,再分发给一个或多个CPU的Local APIC。为了配合APIC,还推出了MPSpec (Multiprocessor Specification),为BIOS向OS提供中断配置信息的方式提供了规范。

自90年代以来,PCI总线发展出了MSI (Message Signalled Interrupt),目前的机器中是以MSI为主要的中断机制,IOAPIC作为辅助,但CPU处仍使用Local APIC接收和处理中断。当时提出的MPSpec经过演化目前已成为了ACPI规范的一部分,BIOS可以通过ACPI表向OS报告中断配置情况(e.g. IOAPIC的引脚连接到哪个设备)。

初代奔腾(Pentium)上初次引入Local APIC时,它是外置的Intel 82489DX芯片。在一些奔腾型号以及P6 family(即从奔腾Pro到奔腾3)上则将其改为了内置,但功能保持不变。自奔腾4及至强(Xeon)开始取消了APIC Bus以及一部分相关设置,于是改称xAPIC,目前Intel CPU的默认模式就是xAPIC。后来又增加了x2APIC模式作为xAPIC的扩展。


ACPI表

通过上面【BIOS可以通过ACPI表向OS报告中断配置情况】这段话,我们知道windows内核是通过ACPI表知道中断硬件的配置情况。

没办法,简单介绍一下啥是ACPI,简单来说:就是固件(BIOS/UEFI)向系统传递硬件架构信息的机制。

那传递哪些硬件架构信息了?

MADT/"APIC":每个处理器的Local APIC信息(包括ID以及NMI等信息)以及系统的I/O APIC信息(包括与8259A相比的板载基础设备的中断重定向信息)。

"HPET":系统的HPET信息。

"MCFG":系统的PCIe的MCFG信息。

"DSDT"、"SSDT":其它信息,包括板载基础设备的配置信息(包括内存、I/O、中断等信息),PCI/PCIe设备在8259A和APIC状态下的中断路由信息,以及 ACPI的一些特定的电源配置信息等。

好了,正主来了,MADT(多APIC描述表),它描述了APIC的工作原理,windows通过以下API来得到,内部实现并不复杂,就是通过关键字去查找你需要的是那个硬件配置信息,有兴趣可以自行逆向研究,当然整个ACPI机制还是较为复杂的,有兴趣可以带小弟一起学习。     

pMapic=HalSocGetAcpiTable('CIPA')

不同的版本,有不同的结构体,APICTABLES内部不同,为了方便,我们通过RW来查看。

内部各个结构字段的意义,还请各位看官自己动动手BAIDU/GOOGLE吧!


APIC注册函数

前面的基础介绍里面,介绍了APIC->xAPIC->x2APIC的由来,知道从Apic总线变成了SystemBus,当然变化的不只这一点,CPU的个数变了,更重要的是访问方式也改变了,LAPIC的寄存器是通过MMIO访问的(即xAPIC模式),后来添加的x2APIC模式则通过MSR来访问其寄存器(可以向前兼容),windows需要根据你是哪个模式,采用哪种方式访问,所以用了下面这个API。

status = HalpApicSetupRegisterAccess();

主要实现了:

如果是xAPIC:

HalpApicRead=HalpApic1ReadRegister

HalpApicWrite=HalpApic1WriteRegister

HalpApicWriteCommand=HalpApic1WriteIcr

HalpApicWaitForCommand=HalpApic1WaitForIcr

HalpApicEndOfInterrupt=HalpApic1EndOfInterrupt

如果是x2APIC

HalpApicRead=HalpApicX2ReadRegister

HalpApicWrite=HalpApicX2WriteRegister

HalpApicWriteCommand=HalpApicX2WriteCommand

HalpApicWaitForCommand=HalAcquireDisplayOwnership

HalpApicEndOfInterrupt==HalpApicX2EndOfInterrupt


APIC注册Io单元

实际上就是生成一个中断控制器对象,然后放进HalpRegisteredInterruptControllers链表里,(不好理解?你就理解类似EPROCESS的创建,后续只要调用APIC的函数,就需要这个对象)

HalpApicRegisterIoUnit(__int64 IoapicPhyAddr, int IoApicId, int GsiBase)

至于这个GsiBase是啥,百度吧,简单介绍一下就是:GSI是ACPI引入的概念,它为系统中每个中断源指定一个唯一的中断号,如果你只是想简单的在windows上了解APIC的工作机制,这个可以不用去深究,如果你想了解windows上APIC的管理,这个你得去深究了,windows把LocalApi和IoApic的引脚弄了一套中断线(INTERRUPT_LINE)的概念,一个Interrupt Line可以管理对应1/多个引脚,对中断控制器进行抽象 ,类似于对象管理器(object/object_header等等),Apic / Gic / Bcm2836ic / pic就是实例,实现很复杂,目前我没有逆完,就不在这里误人子弟了。 


LocalApic的初始化(HalpInterruptInitializeLocalUnit)

1.调用HalpApicInitializeLocalUnit(_IO_APIC_DATA *ApicData, __int64 CpuNumber, int SvrVector, int LvtCmciVector, unsigned int LvtErrorVector, unsigned int *pApicId)

好了,我要从书上截图了,没错,就是开始初始化LVT表:

里面的各个参数,各位看官看书吧,我再这么解释,不如书上的一目了然。

那内部干了啥了:

(1)LocalApic的物理地址映射一个虚拟地址。

HalpLocalApic= HalMapIoSpace(HalpLocalApicPhysical, 0x1000, 0);

(2)我截图吧,注释都写明白,这个CMCI的设置很复杂,这里是简单的初始化了一下,Intel白皮书第15章,可有整整一个章节介绍,这里各位看官结合书来理解吧,应该没什么难度。

APIC可以硬件禁用/启用,禁用再启用后相当于断电重启。此外还可以软件禁用/启用(这里就是伪中断寄存器干的活)。需要注意的是,APIC在通电后默认是处于软件禁用的状态的。

注意:这里面的Vector对应的中断函数,在HalpInitializeInterrupts会提前注册,后续我们实验的时候会模拟自己创建这种中断流程,并且LocalApic的初始化并不会有很大的纠葛,所以省略,但可以截图上来。

2.调用HalpApicSetLogicalId(_IO_APIC_DATA *ApicData, _INTERRUPT_TARGET *InterruptTarget) 设置LocalAPIC寄存器里逻辑APIC ID值(逻辑目标寄存器LDR 和 目标格式寄存器DFR)。

备注:逻辑APIC ID值和LocalApic ID值不是一个东西哦。

LocalAPIC ID寄存器:当物理平台上电后,硬件设备会为系统总线上的每一个LocalAPIC分配唯一的初始APIC ID值,并将其保存在APIC ID寄存器内,你会发现在上面的HalpApicInitializeLocalUnit这个API里,并没有设置APIC ID的值,只有得到,这个地址在LocalApic寄存器地址映射表+0x20的偏移。

逻辑APIC ID,这玩意就有点复杂了,懒得废话,直接截图。

来看看windows是怎么设置的(我的CPU总共核没有超过8个,也就在逻辑平坦模式混混,哪位超过的,可以分享一下自己的,应该会进入HalpApicConvertId,进行ID转换,这个你们自己去验证咯)。

这里直观感受一下二者的不同,+0xD0是逻辑APIC ID,+0x20是APIC ID。

备注:每个核会创建一个_INTERRUPT_TAGRET CPU核目标模式的结构,放进HalpInterruptTargets数组里。


IoApic的初始化(HalpApicInitializeIoUnit)

IoApic的初始化就相当简单了,调用HalpApicInitializeIoUnit(_IO_APIC_DATA *IoApicData)。

好了,我又要从书上截图了,没错,就是开始初始化RET重定向表:

这个就没有难度了,直接给两个结构,自己去倒腾一下。


结语

初始化先写到这里吧,后面会另开一个帖子,写实验,windows居然写了一个bug,和Intel手册的处理居然写反了,不过那里无关轻重,不会产生特别大的影响,后续的代码注释会写的比较详细,上个图。

看雪ID:学技术打豆豆

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

*本文由看雪论坛 学技术打豆豆 原创,转载请注明来自看雪社区

# 往期推荐

1.四级分页下的页表自映射与基址随机化原理介绍

2.Android 10属性系统原理,检测与定制源码反检测

3.WhatsApp私信协议实现记录

4.Android4.4和8.0 DexClassLoader加载流程分析之寻找脱壳点

5.实战DLL注入

6.某车联网APP加固分析

球分享

球点赞

球在看

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


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