这期内容当中小编将会给大家带来有关在野样本中如何利用RPC绕过CFG缓解技术的CVE-2021-26411研究,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。
浏览器渲染进程漏洞利用的一般思路是:在利用漏洞获得用户态任意地址读写权限后,通过篡改DOM、js等对象的虚表函数指针劫持程序执行流,通过ROP链调用VirtualProtect等Win32 API,修改保存shellcode buffer的内存属性为PAGE_EXECUTE_READWRITE,最终由ROP链跳转到shellcode执行。Windows 8.1后,Microsoft引入了CFG(Control Flow Guard)缓解技术[1],对间接调用的函数指针进行验证,从而缓解了通过篡改虚表函数指针劫持程序执行流这种利用技术。
然而对抗不会因此终止,随后出现了一些绕过CFG缓解技术的新方法,比如chakra/jscript9中通过篡改栈上函数返回地址劫持程序执行流[2],v8中利用具有可执行内存属性的WebAssembly对象执行shellcode[3]等。 2020年12月,Microsoft在Windows 10 20H1中基于Intel Tiger Lake CPU加入了CET缓解技术[4],防护了通过篡改栈上函数返回地址劫持程序执行流的利用方法。因此,如何在有CET防护的环境中绕过CFG再次成为漏洞利用的难题。
在分析CVE-2021-26411在野利用样本时[5],我们发现了一种利用Windows RPC(Remote Procedure Call)[5]绕过CFG的新方法,这种方法无需依赖ROP链,通过构造RPC_MESSAGE并调用rpcrt4!NdrServerCall2即可实现任意代码执行。
1. CVE-2021-26411回顾
《IE浏览器在野0day:CVE-2021-26411分析》[5] 一文中介绍了该漏洞的根因:removeAttributeNode()触发属性对象nodeValue的valueOf回调,回调期间手动调用clearAttributes(),导致nodeValue保存的BSTR被提前释放。回调返回后,没有检查nodeValue是否存在继续使用该对象,最终导致UAF。
3月份Windows补丁中该漏洞的修复方法为,在CAttrArray::Destroy函数中删除对象操作前增加索引检查:
对于这样一个大小可控的UAF漏洞,利用思路为:利用两个不同类型的指针(BSTR和Dictionary.items)指向该空洞内存,通过类型混淆实现指针泄露和指针解引用:
2. RPC原理及利用方法
Windows RPC用来解决分布式客户端/服务端函数调用问题。基于RPC,客户端可以像调用本地函数一样调用服务端函数,RPC基本架构如下图:
客户端/服务端程序将调用参数/返回值等传给下层Stub函数,Stub函数负责封装数据成NDR(Network Data Representation)格式,最后通过rpcrt4.dll提供的runtime库进行通信。
下面给出一示例idl:
当客户端调用add函数后,服务端由rpcrt4.dll接受处理请求并调用rpcrt4!NdrServerCall2:
rpcrt4!NdrServerCall2只有一个参数PRPC_MESSAGE,其中包含了客户端调用的函数索引、传参等重要数据,服务端RPC_MESSAGE结构及主要子数据结构如下图(32位):
如上图所示,RPC_MESSAGE结构中,函数调用关键的两个变量为Buffer和RpcInterfaceInformation。其中Buffer存放了函数的传参,RpcInterfaceInformation指向RPC_SERVER_INTERFACE结构。RPC_SERVER_INTERFACE结构保存了服务端程序接口信息,其中+0x2c DispatchTable保存了runtime库和Stub函数的接口函数指针,+0x3c InterpreterInfo指向MIDL_SERVER_INFO结构。MIDL_SERVER_INFO结构保存了服务端IDL接口信息,其中DispatchTable保存了服务端提供的远程调用例程的函数指针数组。
下面以一个实例介绍RPC_MESSAGE的结构:
根据上面给出的idl,当客户端调用add(0x111, 0x222),服务端程序断在rpcrt4!NdrServerCall2:
可以看到,动态调试的内存dump与RPC_MESSAGE结构分析一致,其中add函数就存放在MIDL_SERVER_INFO. DispatchTable中。
接下来分析rpcrt4!NdrServerCall2是如何根据RPC_MESSAGE调用add函数的:
rpcrt4!NdrServerCall2内部调用rpcrt4!NdrStubCall2,rpcrt4!NdrStubCall2内部根据MIDL_SERVER_INFO. DispatchTable的基地址和RPC_MESSAGE. ProcNum计算调用的函数指针地址,将函数指针、函数参数和参数长度传给rpcrt4!Invoke:
rpcrt4!Invoke内部最终调用服务端例程函数:
通过上面的分析可以知道,在获得任意地址读写权限后,可以构造一个RPC_MESSAGE数据结构,传入想要调用的函数指针和函数参数,最后手动调用rpcrt4!NdrServerCall2,即可实现任意函数执行。
接下来需要解决两个问题:
1)如何通过js脚本调用rpcrt4! NdrServerCall2
2)观察rpcrt4!Invoke最后的服务端例程函数调用:
可以看到这里是一处间接调用,且有CFG检查。因此需要考虑替换MIDL_SERVER_INFO. DispatchTable函数指针后如何绕过这里的CFG防护。
首先解决问题1: 如何通过js脚本调用rpcrt4! NdrServerCall2
这里可以复用替换DOM对象虚表函数指针劫持程序执行流的方法,因为rpcrt4!NdrServerCall2是记录在CFGBitmap里的合法指针,所以替换后依然可以通过CFG检查。样本里通过篡改MSHTML!CAttribute::normalize,最终由“xyz.normalize()”调用rpcrt4!NdrServerCall2。
接着解决问题2: 如何绕过rpcrt4!NdrServerCall2中的CFG防护
样本里的思路是:
1) 利用伪造的RPC_MESSAGE和rpcrt4!NdrServerCall2调用VirtualProtect修改RPCRT4!__guard_check_icall_fptr内存属性为PAGE_EXECUTE_READWRITE
2)替换rpcrt4!__guard_check_icall_fptr里保存的指针ntdll!LdrpValidateUserCallTarget为ntdll!KiFastSystemCallRet,从而关闭rpcrt4的CFG检查
3) 恢复RPCRT4!__guard_check_icall_fptr内存属性
解决了问题1,2后,后续即可利用伪造的RPC_MESSAGE实现任意函数的调用。样本中将shellcode写入msi.dll + 0x5000的位置,最终通过rpcrt4!NdrServerCall2调用shellcode:
最终完整的利用演示:
一些思考
CVE-2021-26411在野样本中出现了利用RPC绕过CFG缓解技术的这一创新方法。这种利用方法无需构造ROP链,直接通过伪造RPC_MESSAGE即可实现任意代码执行,利用简单且稳定,有理由相信该方法会成为绕过CFG缓解措施的一种新的有效利用技术。
上述就是小编为大家分享的在野样本中如何利用RPC绕过CFG缓解技术的CVE-2021-26411研究了,如果刚好有类似的疑惑,不妨参照上述分析进行理解。如果想知道更多相关知识,欢迎关注亿速云行业资讯频道。
原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/tech/safety/219972.html