查看文章
 
Analysis Of CVE-2011-2371 Array.reduceRight Integer Overflow
2011-10-14 23:59

 CVE-2011-2371 是Firefox(3.6.18以前版本)js3250.dll中的一处整数溢出漏洞,该漏洞由Matasano Security的两位安全研究员Chris Rohlf and Yan Ivnitskiy发现。这二位在今年Blackhat USA 2011上作了题为《Attacking Clientside JIT Compilers》[1]的演讲,在演示案例中粗略介绍了这个漏洞的成因以及利用方法。Mozila的中也对这个漏洞的成因作了一定介绍。昨天在EXPLOIT-DB上看到了Matteo Memelli给出的一份bypass ASLR+DEP的POC。本文且算作是自己通过调试这份POC以及翻阅相关源码对这个漏洞成因的一点肤浅的理解吧。

 

漏洞触发的方式非常简单——创建一个Array对象,并给其length属性赋以一个非常大的整数

hola = new Array; 

hola.length = 2197815302;

 

w00t = function ph33r(prev, myobj, indx, array) {  

       alert(myobj[0]); // trigger getProperty  

}  

hola.reduceRight(w00t,1,2,3);  


随后在调用Array对象的reduceRight方法时就会触发漏洞

reduceRight方法对应jsarry.cpp文件(文件路径:firefox-3.6.17.source\mozilla-1.9.2\js\src\jsarry.cpp)中的函数array_reduceRight。这个函数会直接调用同文件中的array_extra——

 

static JSBool
array_reduceRight(JSContext *cx, uintN argc, jsval *vp)
{
    return array_extra(cx, REDUCE_RIGHT, argc, vp);
}

在array_extra中:

 

3087行调用js_GetLengthProperty获取数组长度。数组长度保存在局部变量length中,注意此处length的类型为jsuint

 

这里将length-1 赋给了变量start,这就埋下了祸根!因为start,end,step这几个变量的类型都是jint型的

 

这个Array对象的length属性被赋值为2197815302,减一后对应的16进
制为0x83000005。但是把这个值赋给一个jint型变量start时,start的值将会变成一个负数-2097151995!

随后会进入一个循环:

 

在第一轮循环中,调用GetArrayElement时,i的值为-2097151995;

在GetArrayElement函数中:

 

虽然JS_ASSERT会验证index是否大于0,但是根据jsutil.h文件(文件路径:firefox-3.6.17.source\mozilla-1.9.2\js\src\jsutil.h)中的定义此处的断言只有在DEBUG版本中有效——


所以毫无压力!并且满足index < js_DenseArrayCapacity(obj)的条件,所以将会继续执行

*vp = obj->dslots[jsuint(index)]。此时index是一个负数,更重要的是它是一个攻击者可控的数!其对应汇编代码如下:

 

(js3250.dll base address:0x4E0000)

.text:004EE074                 mov     edx, [esp+10h]                       ;[esp+10h]=0x83000005
.text:004EE078                 mov     ecx, [ecx+edx*4] ; point out
.text:004EE07B                 cmp     ecx, 36h
.text:004EE07E                 mov     [ebx], ecx

 

此时访问内存的地址为ecx+edx*4=0x8300005*4+0,0x8300005*4将发生第二次整数溢出——计算结果为0x0c000014,而这个地址的内存页没有被映射那么此时将发生内存访问异常。但攻击者可以通过Heap-Spray强制在这个地址上布置上自己可控的数据——0x0c000048

使用命令s -d 01000000 10000000 0x0c000048,可以查看0x0c000048被喷洒到的内存地址——

 

随后这个0x0c000048将会被写入到ebx=0x064e504c所指向的内存单元中——


对应源码——*vp = obj->dslots[jsuint(index)] (vp=0x064e504c)

回顾GetArrayElement的函数原型以及调用GetArrayElement时传入的参数——

static JSBool
GetArrayElement(JSContext *cx, JSObject *obj, jsdouble index, JSBool *hole,jsval *vp)

GetArrayElement(cx, obj, i, &hole, elemroot);

所以此时*element==0x0c000048(elemroot类型为jsval *)

GetArrayElement函数返回后将继续执行array_extra中的循环——

此处变量sp和invokevp的类型均为jsval*

*sp++ = *elemroot;将0x0c000048写入到当前sp所指向的内存单元中——

 

随后将会把变量invokevp压榨调用函数js_Invoke。此处调用js_Invoke就是去回调javascript中的回调函数

w00t = function ph33r(prev, myobj, indx, array) {  

       alert(myobj[0]); // trigger getProperty  

}  



invokevp==0x064e5050


 

之后为了寻找0x0c000048这个地址在何处被引用,对地址0x064e50c下内存访问断点后F5;当断点断下后——


 

 

可以看到此时控制流已经到了函数js_Interpret内部,js_Interpret是从js_Invoke调用而来。此时EAX=0x0c000048,而这个值从何而来却

不是很清楚。对这条指令附近的代码做反汇编,可以看到——

.text:00518F77                 mov     ecx, [esp+40h]  ; jumptable 10038F70 cases 84,217
.text:00518F7B                 mov     edx, [ecx+28h]
.text:00518F7E                 mov     ecx, [esp+2Ch]
.text:00518F82                 xor      eax, eax
.text:00518F84                 mov     ah, [edi+1]
.text:00518F87                 mov     al, [edi+2]
.text:00518F8A                 mov     eax, [edx+eax*4]
.text:00518F8D                 mov     [ecx], eax

根据调试符号显示信息,这段汇编代码对应源码为jsops.cpp文件(文件路径:firefox-3.6.17.source\mozilla-1.9.2\js\src\jsops.cpp)中2752行的

 

在jsinterp.cpp(文件路径:firefox-3.6.17.source\mozilla-1.9.2\js\src\jsinterp.cpp)中可以看到这个宏的定义:

#define PUSH(v)              (*regs.sp++ = (v))
#define PUSH_OPND(v)    PUSH(v)

至于程序的控制流如何会走到这里,我并没有仔细进行分析,所以对此处的操作并没有完全理解。于是只好继续在这里对ECX中保存的内存地址(0x064e5070)下内存访问断点。

 

mov esi,dword ptr[ebx-8](ebx=0x064e5078)从地址0x0x064e5070将之前写入的0x0c000048保存在ESI中。同样根据符号信息,可以确定

0x00519542起始的基本块对应的源码位于jsops.cpp(文件路径:firefox-3.6.17.source\mozilla-1.9.2\js\src\jsops.cpp)1866行的

FETCH_OPND的定义位于jsinterp.cpp文件:

#define FETCH_OPND(n)   (regs.sp[n]) 这和之前的PUSH_OPND可以对应起来。

随后,程序执行至以下基本块:

00519f8f            mov     edx,dword ptr [esi]                                    ;edx=[0x0c000048]=0x0c00007c
00519f91            mov     ecx,dword ptr [edx]                                  ;ecx=[0x0c00007c]=0x0c00002e
00519f93            mov     ecx,dword ptr [ecx+0Ch]                           ;ecx=[0x0c00002e+0xc]=0x7c370eef
00519f96            cmp     ecx,offset js3250!js_GetProperty (00518e40)
00519f9c            lea     edx,[esp+34h]
00519fa0            push    edx
00519fa1            jne     js3250!js_Interpret+0x48c8 (0051d728)

0051d728           push    eax
0051d729           mov     eax,dword ptr [esp+38h]
0051d72d           push    esi
0051d72e           push    eax
0051d72f            call    ecx                                                               ;ecx=0x7c370eef

 

由此可见0x0c00002e是一个由攻击者精心构造的某个对象的指针,程序在调用这个对象的某个方法时将会使程序的控制流将会被劫持到攻击者指定的位置——0x7c370eef


0x7c370eef位于可执行模块MSVCR71.dll。这个模块由于对ASLR具有先天免疫的特性,所以成为Exploit Firefox时ROP Gadgets的主要来源(结合mona.py效果更佳!具体参见Corelan Team荣誉出品的《Universal DEP/ASLR bypass with msvcr71.dll and mona.py》[2])


7c370eef       lea     esp,[esi-3]                  ;ESI=0x0c000048 ESP=0x0c000045

7c370ef2      dec     dword ptr [ebx]

7c370ef4      ret      1C75h

 

执行过lea esp,[esi-3],栈指针就指向了堆上之前被Heap-Spray的攻击者可控的数据。


执行ret 1c75后,程序的控制流转向0x7c341024并且栈指针向高地址调整0x1c75h字节——跨过用于填充的数据。当执行0x7c341024地址上的ret指令时,ESP所指向的已经是非常整齐美观的一段ROP Gadgets了



总的来说这个漏洞的成因就是程序在循环遍历Array对象中的元素时,由于循环变量i的类型(jint)和Array的length属性的类型(juint)不符,导致将一个超大的juint赋给一个jint时发生整数溢出。在用这个超大的juint计算访问obj->dslot数组的数组下标时发生第二次整数溢出,导致数组越界。如果越界访问的内存恰好被攻击者通过Heap-Spray布置了精心构造的对象指针,那么在随后在利用该指针回调javascript中的回调函数时程序的控制流将被劫持。


P.S:再说几句


Q:对于这种bypass ASLR+DEP的Exploit,调试的时候怎样找程序的控制流被劫持的地方?

A: 要想bypass ASLR+DEP,攻击者要么能够通过栈溢出直接将ROP Gadgets写在栈上,当函数返回或者调用SEH Handler时就直接从栈上取ROP Gadget执行了。这种情况分析调试起来和调试一般的栈溢出漏洞没有什么区别。或者,攻击者先通过Heap-Spray将ROP Gadgets喷洒在堆上,然后想办法(构造fake object或者use-after-free改写被释放对象的虚函数表)执行一个能够修改栈指针的ROP Gadget(lea esp,xxx;push xxx,pop esp;mov esp,xxx)。像这种情况在改写栈指针的指令上下断点即可,断下后查看栈顶上保存的返回地址就可以知道程序的控制流是在什么地方被劫持的了。比如本文分析的这份Exploit,在改写栈指针的lea esp,[esi-3](地址:0x7c370eef)上下断。断下后查看返回地址就可以得知是控制流是0x0051d72f 处调用call ecx转过来的。


Q:Firefox有调试符号吗?

A:有的,

   调试符号服务器:http://symbols.mozilla.org/firefox

   在windbg中设置命令:SRV*[调试符号保存路径]\*http://symbols.mozilla.org/firefox

   参看 https://developer.mozilla.org/en/Using_the_Mozilla_symbol_server


Q:Firefox源码在什么地方下载?

A:参看 https://developer.mozilla.org/En/Developer_Guide/Source_Code


[1].Attacking Clientside JIT Compilers 

     https://media.blackhat.com/bh-us-11/Rohlf/BH_US_11_RohlfIvnitskiy_Attacking_Client_Side_JIT_Compilers_WP.pdf

[2].Universal DEP/ASLR bypass with msvcr71.dll and mona.py

    https://www.corelan.be/index.php/2011/07/03/universal-depaslr-bypass-with-msvcr71-dll-and-mona-py/



 

 

 

 

 

 

 

 

 

 

 

 

 


类别:软件安全研究中心| |分享到i贴吧|浏览(250)|评论 (0)
 
网友评论:
发表评论:
姓 名:
网址或邮箱: (选填)
内 容:
     

   
帮助中心 | 空间客服 | 投诉中心 | 空间协议
©2012 Baidu