CVE-2021-26411 漏洞利用样本分析

概述
该样本是利用 CVE-2021-26411 漏洞进行攻击的 html 文件,攻击目标是 ie 和 edge 浏览器,其最终的目的是执行 shellcode 启动 powershell 进程进行下载行为。关于 CVE-2021-26411 漏洞的原因参考文章 【1】已经讲的比较精细,这里就不在赘述,简要描述是由于 Internet Explorer 的 mshtml 组件中存在一个释放后使用的缺陷。当用户访问了一个恶意页面时,会触发属性对象 nodeValue 的 valueOf 回调。在回调期间,手动调用 clearAttributes(),导致 nodeValue 保存的 BSTR 被提前释放。这样就会造成内存破坏和远程代码执行。关于利用方式参考文章【1】【2】讲的略微精简。样本分析主要包括漏洞利用部分和 shellcode 部分。
样本信息
MD5:56B8C44BB0B2B7EE31A766E1A77BAEDA
SHA1:95D643B3C7DA6AE209940A6F3722074A800282C0
分析环境
Windows 10 x64 1607
IE11,Windbg x86,IDA 7.5
mshtml.dll(11.0.14393.0),jscript9.dll(11.0.14393.0)
样本分析
样本的初始内容是一段混淆&加密的 js。

加密算法使用的是 AES(CBC)。

js 解密后的内容除去 shellcode 基本与参考文章【3】中公布的漏洞利用代码一致,只是做了一些精简。

漏洞利用分析
为了便于对利用原理的理解,我使用了参考文章【3】中的较规范的 js 代码进行分析。
利用过程
利用 CVE-2021-26411 的 UAF 造成类型混淆
利用类型混淆泄露对象元数据,使用泄露对象的元数据伪造一个起始地址为 0,大小为 0xffffffff 的 ArrayBuffer 对象
利用伪造的 ArrayBuffer 对象实现任意读写原语
使用任意读原语实现任意对象地址泄露原语
伪造 RPC_MESSAGE 为任意函数调用做准备
bypass CFG
执行 shellcode
造成类型混淆
执行 valueOf 的调用栈,执行重写 valueOf 的原因似乎和 CVE-2016-0189 一样,均是需要进行类型转换。

element.removeAttributeNode(attr1) 开始时的 element,其中 attr2.nodeValue 是长度为 0x2000a 的 BSTR,为什么它占用的空间是 0x20010,是因为 BSTR 还包括字符串前长度为 4 字节的长度域和尾部 2字节的 \x00。

element.clearAttributes() 结束后,AttributeArray 中的无效元素将被其最后一个元素 attr2.nodeValue 覆盖,而 attr2.nodeValue 所占空间也被释放,被释放的缘由是参考文章【1】【3】中提到的极长 BSTR (大于 0x8000)。

hd2 = hd1.cloneNode() 结束后,原 attr2.nodeValue 所占空间将被重新占用,重新占用的目的有两个:
为了避免在第一次 CBase::DeleteAt 删除 [2] attr1 时 CAttrValue::Free 释放无效内存而崩溃
为了在 CAttrValue::Free 将其释放后继续持有这块内存的地址从而形成悬垂指针

element.setAttribute('yyy', 1337) 结束后,attr1.nodeValue 被重新设置,重新设置的目的是为了避免在第二次 CBase::DeleteAt 删除 [1] attr1.nodeValue 时对象解引用失败而崩溃。

element.removeAttributeNode(attr1) 结束后虽然 attr2.nodeValue(0874035c) 被释放,但是 hd2.nodeValue (BSTR)仍然持有这块内存的地址。

hd0.nodeValue = alloc 结束后 attr2.nodeValue 将被 hd0.nodeValue 重新占用并且与 hd2.nodeValue 形成类型混淆。
hd0.nodeValue 类型值为 0xc safeArray。

hd2.nodeValue 类型值为 0x8 BSTR。

泄露对象元数据,伪造 ArrayBuffer
dump 函数以 hd2.nodeValue 为参数,使用 string 对象方法 charCodeAt 获取 hd2.nodeValue(0874035c) 处的数据,然后再以 uint32 视图泄露 fake 对象和 arr 对象的地址。

setData 函数将 fake 对象的元数据的地址填充到 abf ArrayBuffer 中。

abf ArrayBuffer。

flush 函数再将 abf ArrayBuffer 中的数据刷新到 hd2.nodeValue(0874035c)。

使用 hd0.nodeValue(safeArray) 泄露 fake 对象的元数据。

继续使用 hd0.nodeValue(safeArray) 泄露 fake.ArrayBuffer 的元数据。

使用泄露的 fake 对象的元数据在 fake.ArrayBuffer.buffer 中伪造对象,伪造的对象是一个起始地址为 0,大小为 0xffffffff 的 ArrayBuffer 对象。

实现任意读写原语
使用伪造的 ArrayBuffer 对象实现任意读写对象 god。

以 god 对象实现任意读。
以 god 对象实现任意写。
任意对象地址泄露原语
addrOf 将对象地址存储在 arr[0],然后读取值。

伪造 RPC_MESSAGE
伪造 RPC_MESSAGE 之前需要先调用 rpcrt4!I_RpcTransServerNewConnection 以获得 OSF_SCALL_Vftable,OSF_SCALL_Vftable 最终将被设置到 RPC_MESSAGE->Handle 中。而 I_RpcTransServerNewConnection 和后续 rpcrt4!NdrServerCall2 的调用都是通过伪造 Attribute 进行的。
创建 xyz 作为伪造 Attribute 的目标对象。

伪造 Attribute,这里伪造的 Attribute 只用在 I_RpcTransServerNewConnection 调用,NdrServerCall2 的调用将会重新构造。
伪造的 Attribute 与原 Attribute 只有虚表指针不同。

使用目的函数地址替换假虚表中 normalize 函数的地址,这样调用 xyz.normalize() 函数便可以执行目的函数,normalize 函数地址对比。

调用方式是先将 xyz 的 Attribute 指针修改为伪造 Attribute 的地址,然后调用 xyz.normalize(),调用完再恢复 xyz 的 Attribute 指针。

initRpc() 的内容比较庞大这里就不展开说明,这里用一张图说明其构建的 RPC_MESSAGE 主要结构,其中 RPC_MESSAGE 也是伪造的 Attribute,其虚表的 0x28c 处是 xyz.normalize() 执行的 NdrServerCall2 函数的地址。

bypass CFG
构建完 RPC_MESSAGE 后只需将想要调用的函数的地址放在上图中的 Target Func 处并将函数参数放在 ArgementBuffer 处,然后使用 xyz.normalize() 即可执行目的函数。但是由于 rpcrt4!Invoke 在执行目标函数之前会进行 CFG Check,这样只能调用在 CFGBitmap 中的函数,想调用位于任意位置的 shellcode 就需要 bypass CFG。

bypass 的方法是将 RPCRT4!__guard_check_icall_fptr 中保存的负责进行 CFG Check 的函数指针由 ntdll!LdrpValidateUserCallTarget 替换为 ntdll!KiFastSystemCallRet。

执行 shellcode
加载 msi.dll 模块到进程中,将 shellcode 写入距 msi.dll 基址 0x5000 的位置,设置内存属性后执行之。
shellcode 分析
shellcode 通过在 kernel32.dll 模块导出表中查找 WinExec 函数的地址,然后使用其执行了命令行。


最终执行了一段 powershell 脚本执行继续执行下载动作。


直达360沙箱云:https://ata.360.net/
参考文章
【1】https://iamelli0t.github.io/2021/03/12/CVE-2021-26411.html
【2】https://iamelli0t.github.io/2021/04/10/RPC-Bypass-CFG.html
【3】https://medium.com/enki-techblog/internet-explorer-0day-%EB%B6%84%EC%84%9D-f14bf5db771e