原文地址:https://mozhe.cn/news/detail/285
漏洞说明
系统内核通过Mov到SS(stack segment)或Pop到SS指令进行堆栈切换操作后处理异常情况时发现了一个缺陷.在堆栈切换操作期间,处理器没有传送中断和异常,而是在堆栈切换后面的第一条指令执行完传送.一个没有特权的系统用户可以利用这个漏洞来破坏系统内核,导致拒绝服务.实际上MOV到SS会延迟一些调试异常(例如一个硬件断点)直到下条指令完成;如果跟随MOV到SS或者POP到SS后面的指令是SYSCALL、SYSENTER、INT 3之类的指令,调试异常被捕获之后将转移到Ring0执行.
样本环境
首先从https://github.com/can1357/CVE-2018-8897下载POC源代码,用vs2015新建立一个win32控制台工程;POC里面有个64位汇编的asm文件,使用VC++自带的汇编编译器编译Native.asm为一个obj文件[ml64.exe/FlNative /c /Zi Native.asm],再把obj添加到工程里面[右击工程名字,点击属性;点击配置属性→链接器→输入→附加依赖项;输入obj名字],然后编译得到一个POC,编译POC一步算完了.
样本是Win7x64位的,接下来搭建一个Win7x64的VMWar环境,运行poc.exe尝试触发一次,POC显示失败了.
POC失败问题先暂停.关于WinDBG的内核调试,WinDBG与VMWare双机内核调试工具的配置请使用VirtualKD.如何安装使用请自行搜索.
修复样本
回看一下上面的问题,样本为什么会失败?看下前面错误提示之前的代码,这段代码与KeBugCheckEx与PsGetCurrentProcess函数有关.
在IDA中打开目标系统的ntoskrnl.exe文件,看KeBugCheckEx的汇编代码,代码应该搜寻0x53E0这个偏移,gs:20h是一_KPRCB结构的变量_KPCR→CurrentPrcb,这些结构一般是公开的,利用搜索引擎很容易获得.0x53EO偏移处保存的一个CONTEXT结构,也就是Prcb.Context.
PsGetCurrentProcess的汇编代码,代码应该是搜寻0x70值.GS寄存器等于32位系统FS寄存器,其实这里指向内核结构_KPCR结构.0x188的偏移位置是_KTHREAD结构,0x70的偏移位置是_KTHREAD结构,也就是KThread.ApcStateFill.Process
PsGetCurrentProcess函数的有关实现.
现在说下修复方法,找到自己系统上这两个函数数据结构的偏移,如果发现没有这两个数据结构的偏移,那么请找一个函数修复这个偏移.测试系统上Prcb.Context对应不上,需要修复偏移.
打开WinDBG进行双机内核调试,输入命令dt _KPRCB查找_CONTEXT的偏移地址,测试系统偏移为0x4bd8.
修改POC里面的偏移地址,然后运行POC测试
在vmware里面运行到漏洞触发那里还是崩溃了?为什么呢?原因是读取GS的基址失败了.原因很有可能就是vmware不支持rdgsbase之类的指令,尝试了二进制翻译功能,仍然提示错误.怎么解决呢?请看下面.
如果本机打了补丁的请卸载名字为KB4103712 与KB4103718的补丁,卸载补丁后再次运行.运行结果是直接重启.WHAT?说明已经触发了漏洞,但是例如ROP与Shellcode之类的可能还需要修复.先看一下攻击过程./p>
攻击过程
源码中首先检测内核页表隔离(KPTI,kernel page table isolation, Windows内核中又叫KvaShadow)是否被启用,这好像与一个INTEL漏洞相关.
使用VirtualLock函数把两个函数的代码锁定在物理页面,供内核使用.
得到内核基址并加载(LoadLibrary)内核ntoskrnl.exe到自身进程.
分配4个内核对象(_KPCR,_KTHREAD,_KPROCESS,_KPRCB)供内核使用.
内核结构_KPCR的偏移0x180处是_KPRCB结构.
解析内核模块,查找一些用于ROP技术的小组件.
找到内核结构_KPRCB成员Context的偏移地址,找到进程_EPROCESS的偏移地址.前面讲过,不重复讲.
填充_KPCR,_KPRCB,_KTHREAD结构体,相关成员的偏移地址在不同的系统可能会一样,请自己修复.
读取堆栈段寄存器保存到变量SavedSS.
创建一个线程,获得填充_KPRCB对象的成员_CONTEXT的偏移地址,并等待RtlCaptureContext函数调用获得泄漏的RSP,漏洞触发前会一直等待,实际上异常才会进入这个函数.等待第二次RtlCaptureContext被调是计算两次RSP的差异值来预测下一次RSP的值,并预测返回指针的位置,并建立RtlCaptureContext需要的上下文.第一条ROP的RETN位置放在XMM13寄存器.
设置辅助线程的优先级为最高并分离当前线程与创建的辅助线程,让它们在不同的CPU上执行,接着获取了一些内核函数的地址.
给线程设置两个异常,一个在SS段寄存器为读写异常,另一个写异常位于结构_KPRCB→ProcessorState(_KPROCESSOR_STATE)→SpecialRegisters(_KSPECIAL_REGISTERS)→Cr8.
WinDBG输入u KeBugCheckEx,红色部分,如果偏移不一样则需要修复这个结构.
开始填充rop到XMM寄存器.
最后读取gs段寄存器基址,重设基址指向自己填充的_KPCR结构,然后触发漏洞,恢复GS段寄存器,并使用SS段寄存器去触发漏洞.
先使用mov ss指令切换堆栈,并且会产生前面的ss调试异常,但mov ss切换后不会立即发送异常要等下一条指令执行完成后才发送;紧接着是一个int 3指令,由于前面会改变了gs段寄存器的基址,进入内核之后要使用SWAPGS指令交换gs段寄存器.
可能需要修复的一些数据偏移,根据不同的系统偏移不同.
小结
由于无法使用WinDBG对本机进行内核调试验证,修复POC里面的数据偏移,如果数据偏移没有修改正确就测试POC,可能会造成系统重启.快点自己动手试下吧.
网友评论