0X00 漏洞描述
CVE-2013-1347是IE8打开指定页面后会在本地加载mshtml.dll和jscript两个动态链接库来解析 触发恶意脚本
0X01 分析环境
目标系统:Windows 7 32位
调试器:WinDbg
反汇编器:IDA Pro
漏洞软件:IE8
0X02 基本信息
WinDbg调试
首先看看poc代码脚本干了什么:第一步按顺序创建了3个span元素并添加到body元素尾部 第二步在f1和f2的尾部分别创建并添加datalist和table元素 第三步f0的offsetParent属性设置为null 最后一步将f1和f2的innerHTML属性变为变为空并创建了一个hr元素添加到f0的尾部 在后面的调试中可以知道在做完这些步骤并使用CollectGarbage进行垃圾回收后 JS脚本中的HTML元素会被重新渲染 导致触发异常
然后在IE8打开poc文件 先不要点“允许阻止。。。” 打开windbg附加进程 然后再点击“允许阻止。。。” 发现windbg断下来
kv查看栈回溯
查看崩溃时栈顶的返回地址0x68cfb694前面的指令
看到了虚函数调用指令 在mshtml!celment::Doc中调用此虚函数 但不能下断点 会不停的断下 所以我们可以开启堆页保护辅助分析
重新附加poc运行后 windbg断下
输出堆的信息可以断定是引用已释放的堆地址才导致的崩溃 通过栈回溯来看 引用了已被删除的CGenericElement对象 是在JavaScript垃圾回收时被释放内存的 因此 该漏洞正是由于CGenericElement对象被释放重引用导致的
接下来逆向分析js代码 从第一句开始 主要是用于创建span元素:
使用windbg的x命令查询document.createElement 可以看到CDocument::createElement CDocument::CreateElementHelper可能和解析js代码中的document.createElement有关 所以先分析CDocument::createElement函数
在IDA中找到该函数 发现他调用CDocument::CreateElementHelper函数
跟进CDocument::CreateElementHelper函数发现调用很多函数 我们这里只看Cmarkup::CreateElement函数
跟进Cmarkup::CreateElement函数 发现会调用CreateElement函数
进入CreateElement函数 该函数会从一个特定的函数数组中寻找相应的元素创建函数 比如此处poc创建的是span元素 那么它就调用的CSpanElemennt::CreateElement 所以在call eax指令处下断点看调用的函数
1 | bu mshtml!CreateElement+0x41 "ln eax;gc" |
发现有两处 这里我们使用第二个
继续分析CSpanElemennt::CreateElement 他首先会HeapAlloc分配一块大小为28字节的内存 接着调用CElement::CElement创建元素 将元素内容写入前面分配的内存地址
因此 为了查看创建元素所在的内存数据 可以在CElement::CElement下断点
1 | bu mshtml!CElement::CElement+0x1e ".echo '===CElement===';dd edi l(28/4);gc" |
下如下断点
运行poc可以看到各个元素的创建过程 创建顺序跟poc中的各标签一致:
1 | '===CElement===' |
分析完元素的创建过程 我们继续看poc中的下一句js代码 他会在DOM树中插入前面已创建的元素:
查看包含appendChild符号
从poc代码中知道它在网页中添加f0元素 因此先针对CElement::appendChild函数进行分析 该函数直接调用CElement::insertBefore函数进行处理 然后再调用CElement::insertBeforeHelper
CElement::appendChild函数 调用CElement::insertBefore函数
CElement::insertBefore函数 调用CElement::insertBeforeHelper函数
接下来跟踪CElement::insertBeforeHelper函数 发现它会调用CElement::GetDOMInsertPosition获取元素插入的DOM树位置
此处 Poc试在body主要体内插入span元素f0
找到元素插入的位置后 接下来就是执行插入动作 我们继续跟踪调试下去
接下来会调用 程序会调用UnicodeCharacterCount函数 跟踪进入函数发现调用mshtml!CElement::Doc获取CDoc对象 代表网页中的HTML Document文档
接下来调用CDoc::InsertElement函数 是插入元素的关键函数
跟进CDoc::InsertElement函数 发现在尾部会调用CMarkup::insertElementInternal函数 还是用IDA看吧
CMarkup::insertElementInternal函数里面会在DOM结构树里面搜索准备插入的分支节点 此处是body节点 找到后会分配0x4C大小的内存 然后调用CTreeNode::CTreeNode构建所添加元素的DOM树数据结构信息
接下来看看CTreeNode::CTreeNode函数 我们先在IDA中找到该函数 然后看到偏移是B0AA
然后在Windbg中下断点运行到此处 单步执行后得到span元素的CTreeNode对象地址 里面就指向span元素
为了知道所创建元素的CTreeNode 我们可以在上面的CTreeNode::CTreeNode的吓一跳指令下断点 获得CTreeNode对象地址
1 | bu mshtml!CMarkup::InsertElementInternal+1ec ".echo '===CtreeNode===';dd eax l1;dps poi(eax) l1;gc" |
执行效果如下
结合前面创建元素的断点设置方法 我们可以实时的列出所添加元素的CElement和CTreeNode对象地址及内容
1 | ######## 创建f0(span)元素####### |
根据上面的日志信息 可以知道漏洞对象CGenericElement就是在f2添加datalist时创建的 相当于poc中的代码
我们分析CGenericElement的创建过程对CMarkup::InsertElementInternal 在IDA中找到该函数 可以看到分配0x4c大小的内存正是用于构造DOM树数据结构的CTreeNode 里面存放将创建的元素对象的地址
IE调试
打开Poc 然后F12打开开发人员工具 在脚本前单击一下即可下断点
然后启动调试 断在了下断点的地方 此时f0.offsetParent指向了Body
单步运行后发现offsetParent变为null了
搜索可能相关的函数 发现最有可能的就是CElement::get_offsetParent函数与CElement::get_offsetParentHelper函数 其实get_offsetParent最终也是调用get_offsetParentHelper 所以在get_offsetParentHelper上下断点 这里会去查找临近的祖先元素 所以也会区索引DOM树结构 CTreeNode结构数据可能会被进行读写操作
根据上面下了如下几个断点
查看打印了的datalist元素的CTreeNode对象的CtreeNode的值
再次运行后断在了CElement::get_offsetParentHelper结尾处 再次查看datalist的元素的CTreeNode对象的CtreeNode的值 CTreeNode对象+8和+C处的两个数据 这两个数据在一开始均处于未初始化状态 所以为fff 执行完 CElement::GetOffserParentHelper函数之后才被赋值 之后结合旧的IE源码发现CTreeNode对象+C是定义CharFormat的整数值 如果该整数值小于0就不会重新渲染HTML元素 而CElement::GetOffserParentHelper 将值变为了2导致还会对HTML做渲染
重新设置断点然后运行
1 | '===CElement===' |
使用!heap -p -a命令查询datalist元素所对应的CTreeNode对象的地址 发现datalist的CTreeNode对象并没有被释放而datalist的对象已经被释放 而CTreeNode的头4个字节却依然指向已释放的datalist对象 这也是释放后所引用导致异常的对象
如果我们去掉了f0.offsetParent=null这条语句 对比之后发现datalist元素对象和datalist元素的CTreeNode对象都会被释放
再次查看栈回溯 最早引用CGenericElement的CTreeNode地址的是ISpanQualifier::GetFancyFormat函数 因此从该函数开始分析 先对其下断点 断下后发现CTreeNode是通过eax传递给下一个函数的
查看该函数 发现CTreeNode是通过eax传递给下一个调用函数的
ISpanQualifier::GetFancyFormat函数的eax可能来自于SLayoutRun::HasInlineMbp函数里的edi 因为在调用SLayouRun::HasInlineMbp函数之前还调用了SRunPoniter::SpanQualifier函数
查看SRunPoniter::SpanQualifier函数发现他修改了eax的值 使eax=[[eax+4]+c]
追踪eax+4的值
1 | bu mshtml!SRunPointer::SpanQualifier ".echo '=== eax + 4 ===';dd eax+4;g" |
整个过程是在设置f0.offsetParent=null时发生的 从中可以看出eax+4指向一个数组列表的地址 每个数组偏移+4的地址是数组的id值 偏移+8即是CTreePos地址,用于标记该元素在DOM树的位置 偏移+c则为元素CTreeNode的地址 为了方便分析 暂时将次数组命名为element_array查看element_array列表所占内存的分配来源 开启ust栈回溯
从栈回溯信息可以推测出element_array列表是在构造CTextBlock时生成的 前面的分析知道了CTreeNode+0x44保存着CTextBlock结构地址
通过设置条件断点记录CTextBlock与element_array列表地址 最后发现CTextBlock+0x58保存着element_array列表地址
综上所属 我们得到的信息如下
CElement+0x14=>CTreeNode
CTreeNode+0x0=>CElement
CTreeNode+0x4=>parentCTreeNode
CTreeNode+0xc=>charformat
CTreeNode+0x44=>CTexBlock
CTexBlock+0x58=>element_array
element_array结构:
+00unkown
+04id
+08CTreePos
+0cCTreeNode
通过分析调试,获得CTexBlock部分结构信息,在源码中为搜到CTexBlock的相关信息,因此采用逆向手段去分析。
CTexBlock结构
+00vftable
+04count
+0cstartpos
+10endpos
+1cprev
+20Next
+58element_array
为了弄清楚导致漏洞的根本原因 我们重新分配垃圾回收后 Body主体元素的CTexBlock结构中的element_array的变化情况 上面几个结构的的追踪方法已经分析过 所以我们设置直接下断点进行分析 断点如下所示
1 | bu jscript!JsAtan2 ".printf \"%mu\",poi(poi(poi(esp+14)+8)+8);.echo" |
在执行垃圾回收后 虽然f0和f1的innerHTML被清空 但是f2 CTextBlock里面的element_array依然时审判嵌套着datalist元素 也就是说 虽然DOM树结构已变 但在里面保存的DOM树关系结构的CTBlock并没有及时更新
此时的CGenericElement对象(CTreeNode地址:08766fb0 CELement地址:05ed8fb8)已经释放
假如把poc中的f0.offsetParent=null 一行代码去掉 会发现垃圾回收后f2(span)已经不存在CTextBlock和element_array结构了 正是该代码导致的漏洞 接下来 程序调用下列语句 在f0添加hr子元素 这会重新改变DOM树结构 就需要重新遍历DOM树结构 其中就包括已经释放的CGenericElement对象 如下所示
f0.appendChild(document.createElement(‘hr’));
执行完JavaScript代码后会重新渲染页面 重新计算节点格式 从而引用到指向已释放对象 CGnericChild的CTreeNode结构 最后触发漏洞
总结
1.代码中对f0.offsetParent进行置空 会设置iCF格式整数值为1 使得它不做节点格式计算 相当于被标记为已渲染的状态 由于设置的是f0的父节点body 会导致其相邻的f1和f2都受到影响 各个CTreeNode中的iCF值均被置1 使得原本未被渲染的它们误以为被渲染了
2.在f0添加hr子元素 会改变DOM树结构进行重绘 此时就需要遍历CTreeNode 就会引用到CGenericElement的CTreeNode结构
3.垃圾回收后 由于第一步的原因 导致CGenericElement的CTreeNode结构未被删除 虽然此时的DOM树已经改变 但用于构建页面的CTextBlock也依然保存着对CGnerivElement的引用 而此时CGnericElement早已因为f2.innerHTML被清空而释放掉 最终导致UAF漏洞
从上面这些信息 其中导致漏洞的原因跟f0、f1、f2元素关系不大 但必须是块元素和内联元素(否则不会进入CLayoutBlock::BuildBlock构建CTextBlock) 比如其中的span、datalist可以替换成u下划线和a标签 此时崩溃对象就不是CGnericElement 而可能是CAnchorElement或者CSpanElement对象 关键在于你poc中所设置的具体元素 而块元素table 也是可以用p或div这样的块元素代替 但是hr不可替换 只有他才会去遍历寻找CTreeNode 进入漏洞触发流程
该漏洞和CGnericElement对象并没有任何关系 只是原漏洞发现着提供的poc刚好使用datalist这个元素 才导致CGnericElement对象被释放重引用
0X03 漏洞利用
**此处换成64位系统就可以劫持 32位不可以
只要能覆盖CGnericElement对象内存 就有机会实现利用,因此可以在垃圾回放之后 分配某个对象 并控制对象的内容及大小 在IE8里面刚好有个t:ANIMATECOLOR标签 通过他就可以实现上述目标
然后用第一个分号前面的字符串覆盖虚表指针,通过分析导致崩溃的mshtml!CElement::Doc函数,可以发现函数在调用虚函数前是通过偏移虚表0x70进行索引的
采用0x70/4控制edx的值
成功控制流程
0X04 漏洞修复
下载补丁后安装
CBlockContainerBlock::buildBlockContainer函数中 会对CLayoutBlock进行重绘 重绘的基本算法是:
1.根据重绘时当时的CTreeNode节点去生成CLayoutBlock
2.如果CTreeNode没有关联的CLayoutBlock 则生成相应的CLayoutBlock 如果存在则将CTreeNode加入到CLayoutBlock中
3.在遍历CTreeNode的同时 也会对CLayoutBlock进行遍历 当前的CTreeNode指向的CLayoutBlock和当前的节点不一致时 需要删去该CTreeNode
4.当CTreeNode遍历完成之后 如果当前的CLayoutBlock的指针pLastLayoutBlock不为空 则需要进行的操作
用IDA对比 未打补丁时会对参数传递进来的CLayoutBlock+0x08处第11useflag位置进行判断(一般是CRootElement对应的CLayoutBlock) 如果未置位 则不会进入到RemoveChild的流程中
打过补丁后 可以看到 RemoveChild删除的操作 仅仅在pLastLayoutBlock指向的CLayoutBlock不为空时就执行 不会在判断参数传递进来的CLayoutBlock+0x08处的第11位是否置位
0X05 经验总结
这次IE调试算是比较失败吧 调试时间较长之后导致忘记了一样 连不起来 前面分析还算是顺利 后面就不太顺利了 感觉这次并没有从中获取很多知识 第一次的UAF漏洞复现 也算是比较长了 从头到尾没一点连贯性 有些地方不能复现一致(指书上)导致自己无法理解部分问题 复现就断了 算是 比较失败了吧这次