首页
社区
课程
招聘
[原创]粗略了解Intel处理器的CET技术原理以及目前Win10操作系统的支持情况
发表于: 3天前 642

[原创]粗略了解Intel处理器的CET技术原理以及目前Win10操作系统的支持情况

3天前
642

前言:


    在一次调试自己写的VT框架时,猛然发现CR4控制寄存器的第23位被置位了,觉得事情不对,赶紧查阅intel白皮书,发现CR4的第23位为CET位,此位被置位说明CPU启用了Control-flow Enforcement Technology (bit 23 of CR4),翻译成人话就是——控制流强制技术。


1.什么是控制流强制技术?

    这个技术是属于比较新的技术,从Intel 11酷睿处理器才开始加入这项技术。目的为了应对栈溢出攻击而诞生的技术,和CFG控制流防护的作用差不多,只不过CFG是属于编译器层面的技术,而这个所谓的CET技术是CPU硬件层面的技术。

    这个技术对程序控制流的保护分为两部分,一部分是就是前文所说的为应对ROP(栈溢出)攻击,而诞生的名为“影子堆栈”保护机制;另一部分名为间接分支跟踪机制,是为了应对间COP/JOP攻击。本文只对影子堆栈机原理制作粗略的解析。


2.如何启用影子堆栈技术?

要启用影子堆栈强制技术主要有以下两个步骤:

  1. 将CR4.CET位(第23位)置位

  2. 将CPU特权级对应的MSR寄存器的SH_STK_EN位置位

  • 0环,1环,2环对应的MSR寄存器名为IA32_S_CET,编码为0x6a2

  • 3环对应的MSR寄存器名为IA32_U_CET,编码为0x6a0

3.影子堆栈强制保护原理:

    启用影子堆栈的程序除了有一个正常的堆栈外,还额外增加了名为"影子堆栈"的堆栈,并且CPU还多出来有一个名为SSP寄存器(影子堆栈指针寄存器),这个SSP寄存器和RSP寄存器作用类似,用于程序寻址影子堆栈,并且无法使用常规的汇编指令修改或读取它的值。影子堆栈不用于存储程序的一般数据,程序执行CALL指令(包括远调用)时,除了在普通堆栈里PUSH一份返回地址外,同时还会在影子堆栈中PUSH一份返回地址,SSP寄存器里的值和RSP寄存器一样减4(32位)或者减8(64位),在程序执行完例程返回执行RET指令时CPU会将普通堆栈的返回地址和影子堆栈中的返回地址进行对比,如果两份返回地址相同,则程序正常执行,SSP寄存器加4(32位)或者加8(64位);如果发现两份返回地址不一致则立刻触发#CP故障(中断描述符表中的第21号中断),应用层触发#CP程序崩溃,内核层触发#CP故障操作系统直接蓝屏GG!

    有同学看到这里肯定会想到,既然是在程序执行RET指令时对比普通栈和影子栈中的返回地址,那我可不可以在栈溢出攻击接管CPU执行流例程中把影子堆栈中返回地址改成和普通堆栈中的返回地址一样不就破解了这个保护吗?那我只能说你想多了,影子堆栈所使用的内存页面相比普通堆栈所使用的内存页面在硬件层面做了特殊的保护(详细内容请看下文),常规的那些读写内存 指令MOV PUSH····等等这些指令全部都无法更改影子堆栈中的数据。要修改影子堆栈中的数据必须使用Intel CPU提供一套全新的指令,具体请参阅intel白皮书第一卷第17章。  


4.影子堆栈切换的简单剖析 

    要加载一个影子堆栈必须在影子堆栈中底部写入一个影子堆栈恢复令牌,影子堆栈恢复令牌是一个64位的值,结构如下图所示:


Intel提供了一对全新的指令用于影子堆栈的加载——RSTORSSPSAVEPREVSSP,RSTORSSP指令用于验证影子恢复令牌的有效性,当令牌有效,会将SSP寄存器的值修改为指向影子堆栈恢复令牌所在的线性地址,切换到新的影子栈后,CPU会自动在新影子栈中的恢复令牌处创建一个影子栈还原令牌,可以使用以便切换回旧的影子栈,影子栈还原令牌格式如下图所示:



从旧影子栈切换到新影子栈堆栈SSP寄存详细过程如下图:



要新影子栈切换回旧的影子栈,需要执行SAVEPREVSSP指令,SAVEPREVSSP 指令将在新影子栈存储还源令牌中的SSP指针减8处创建一个影子栈恢复令牌,随后如同上文切换到新影子栈的操作一样使用RSTORSSP指令恢复到 到旧的影子栈。



5.影子堆栈所使用的内存和普通栈所使用的内存的区别


    前面说过,常用的用于读写内存的那些汇编指令全都无法用来修改影子堆栈中的数据,主要原因是CPU在内存分页层面对影子堆栈所使用的内存作了特殊的保护。

    只有满足以下两点的内存页面才可以作为影子堆栈来使用:

  1. 用于线性地址到物理地址转换的最终分页结构(4kb分页对应PTE,2MB分页对应PDE,1GB分页对应的PDPTE)的read/write位必须为0,脏位必须为1;

  2. 用于线性地址到物理地址转换的其余分页结构的的read/write位必须为1;

    只有严格满足以上两点的内存页面才可以用于影子堆栈,不满足以上两点的内存页面加载到SSP影子堆栈指针寄存器或者执行CALL类RET类指令的时候会触发#CP,看到这里你应该明白了为啥常规的汇编指令不能修改影子堆栈里的数据了。

    当然,这种内存页面也不是完全没办法通过编程修改内存数据,不然之前说的影子栈切第一个影子栈时影子栈恢复令牌完全没办法的设置了,要对影子堆栈内存页面进行写访问要使用专门的指令,例如WRSSD/WRSSQ,WRUSSD/WRUSSQ,关于这些指令的详细信息以及使用细节请参阅白皮书,这里就不多做赘述了


6.操作系统层面对影子堆栈技术的支持

    笔者使用的操作系统是WIN10 22H2,OS内部版本为19045.6466,在这个系统下并没有将内核以及应用层所有进程都整个的开启影子堆栈保护功能,内核层面是完全没有启用这个功能,应用层只有部分进程才启用了这个功能,例如Microsoft Edge浏览器以及以及一些系统服务进程。

    启用影子堆栈保护功能的进程在内核对应EPROCESS结构体中会有一个名称为CetUserShadowStacks的标志,如下图:

    启用用户层影子堆栈保护的进程这个标志位是1,没启用是0。

    另外,操作系统肯定还会有专门用于分配影子堆栈内存的API,毕竟前面说过影子堆栈所使用的内存页面属性很特殊,用常见的内存分配函数是分配不到一个属性是只读并且脏位还置位的内存页面的,哪怕你先申请一个可写的内存,对它写入数据使得它的脏位为1,只要你后面把内存属性改为只读,脏位也必定会被同步清0的,笔者比较愚钝,还没有找到这个API函数,感兴趣的大佬可以挖掘一下。


修改MSR寄存器,写入影子堆栈令牌的这些代码当然也是必不可少的,笔者在VT框架下利用MSR监控功能找到了这些代码。


7.编译器对影子堆栈的支持

    笔者使用的是VS2019,依次点项目属性——链接器——高级,在右边的选项里会有一个叫做“与CET影子堆栈兼容”的选项,如下图:

这个选项改成是你编写的程序便开启了影子堆栈保护功能,打开这个选项后编译器便会在生成的PE可执行文件设置特殊的标志,只不过我在网上各种找资料包括问人工智能都没有找到这个标志具体在哪里,人工智能说是在PE文件的加载配置目录表里有一个GuardFlags标志,这个标志的某一位为1代表这个程序启用了CET影子堆栈功能,但是我对比两份一模一样的PE文件,其中一份开启CET影子堆栈功能,另一份没开启,并没有发现这两份PE文件的这个GuardFlags的值有什么不同。这个感兴趣的大佬也可以发掘一下


8.调试器对影子堆栈的支持

目前没看到有哪个调试器能看到SSP寄存器的值,无论是内核调试器还是3环调试器,新版内核调试器WinDbg View 多了一个命令dps ssp,可以简单的看下影子堆栈中的内容,未来的调试器估计会增加更多对影子堆栈功能的支持


结语:

    关于程序在不同特权级切换时时如何切换影子堆栈的我还没有深究,另外,CET的另一个机制为了应对COP/JOP攻击原理很简单,cpu内部维护了一个状态机,就是在执行CALL或者JMP指令时状态机会进入一种名为WAIT_FOR_ENDBR状态,接下来执行的第一条指令只能是ENDBR32/ENDBR64,否则就会#CP,这个功能目前的操作系统完全没有使用。





冰与火的战歌:Windows内核攻防实战高级班!从零到实战,融合AI与Windows内核攻防全技术栈,打造具备自动化能力的内核开发高手。

最后于 3天前 被Mr.hack编辑 ,原因:
收藏
免费 1
打赏
分享
最新回复 (3)
雪    币: 0
活跃值: (530)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
666
3天前
0
雪    币: 5203
活跃值: (5967)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
3
感谢分享。
3天前
0
雪    币: 108
活跃值: (2070)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
感谢分
12小时前
0
游客
登录 | 注册 方可回帖
返回