Переполнение буфера кучи. Часть 3



Дата публикации: 2020-05-15
Автор: Анонимный переводчик, ред. @N3M351D4
Теги: ,

Источник: https://www.fuzzysecurity.com/tutorials/mr_me/4.html

Привет, ребята. Некоторое время назад я рассказывал о древней, но важной технике переполнения буфера кучи в Windows XP SP3. Сегодня я расскажу вам о еще одной. Вдобавок, я познакомлю вас с моим плагином для Immunity Debugger под названием !heaper.

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

Поэтому ОГРОМНОЕ спасибо Бретту Муру (Brett Moore), Николасу Вайсману (Nicolas Waisman) и Крису Валасик (Chris Valasek).

В декабре 2005 Бретт Мур опубликовал очень интересное исследование: “Эксплуатация freelist[0] в XP SP2”. В нем были рассмотрены два полезных метода, с помощью которых можно атаковать freelist[0]. Мы рассмотрим только одну из них – вставку во freelist[0], в связи с её практичностью.

Вам потребуется:

  • Windows XP с установленным SP2/SP3
  • Immunity Debugger
  • pyparser
  • graphviz
  • heaper.py – плагин к immunity debugger
  • Компилятор C/C++ (Dev C++, lcc-32, MS visual C++ 6.0 (если вы сможете его достать)).
  • Удобный для вас скриптовый язык (я использую python, вы можете пользоваться perl)
  • Мозги (и/или настойчивость)
  • Некоторые знания Ассемблера, C и умение использовать плагин HideDbg для Olly или !hidedebug в Immunity debugger
  • Время.

Вставка во freelist[0]

Идея этой атаки заключается в перезаписи указателя blink в чанке freelist[0] и последующей вставке другого чанка перед перезаписанным. blink не проверяется перед обновлением указателей flink/blink. Проверка указателей списка (safe unlinking) происходит только в манипулируемых чанках и его соседях, но не в тех, связи которых изменяются.

Чтобы исправить эту проблему, в Windows 7 программисты Microsoft добавили проверку, похожую на следующий сниппет псевдокода:

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

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

Скомпилируйте его или скачайте готовый бинарник (b33f: бинарника нет). Открыв его в отладчике, вы увидите:

Спускайтесь ниже, пока не увидите вызовы API из main: HeapCreate, HeapAlloc, HeapFree. Установите точку останова на втором HeapFree и последующих HeapAlloc’ах.

Теперь нужно скрыть факт того, что программа запущена под отладчиком: используем команду ‘!hidedebug all_debug’, чтобы пропатчить все API вызовы.

При запуске приложение попросит ввести какие-нибудь данные. Так как данные подаются на стандартный поток ввода (STDIN), у нас не получится просто скопировать туда бинарные данные. Чтобы было понятнее, мы скормим программе ASCII текст, а затем исправим его в памяти.

Внимательный читатель заметит, что чанк, в который мы пишем, имеет размер 1024 байта. Любые данные длинной больше этого размера переполнят буфер и дадут нам контроль над выполнением. Чтобы получить полный контроль, потребуется перезаписать 16 байт: в первых 8-ми хранится заголовок следующего чанка, в остальных — указатели flink/blink. Давайте сгенерируем данные:

Скопируем их на вход приложения. Отладчик остановится на вызове HeapFree:

Проанализируем происходящее. Для начала взглянем на freelist и осмотрим его структуру командой !heaper ab -g.

Эту информацию можно визуально представить командой !heaper ab 490000 -g. Она создаст картинку с изображением графа и сохранит её в ‘C:\Program Files\Immunity Inc\Immunity Debugger\’ (по умолчанию имя картинки ‘freelist_graph.png’).

Несложно заметить, что flink/blink были перезаписаны нашими данными. Теперь мы хотим перезаписать blink, хранящийся в элементе lookaside[3]. На данном этапе массив lookaside пока пуст, но мы подделаем несколько элементов. Заменим указатель:

Элемент freelist[0] с изменённым указателем:

Когда мы сделаем один шаг отладчиком после вызова HeapFree, мы заметим серьёзные изменения. В lookaside[3] появятся 3 элемента, а наш flink станет flink’ом подделанного чанка в lookaside. Использовав ‘!heaper af [heap]’ или ‘!heaper analysefrontend [heap]’, вы увидите следующее:

Напомню, что для получения графа можно использовать команду !heaper af 490000 -g. В этом случае, имя по умолчанию будет lal_graph.png. Его можно изменить флагом -f.

Продолжим выполнение до следующего вызова HeapAlloc. Мы увидим, что flink возвращается из любого элемента lookaside.

Разумеется, 0x43434343 не является указателем на существующий чанк, а нам нужно, чтобы flink указывал на доступную для чтения/записи память. Для этого мы возьмём из PEB указатель на функцию FastPEBLockRoutine, описанный в предыдущей статье. Там сказано, что указатели в PEB рандомизированы, но так как я всего лишь демонстрирую метод, мне нужен любой доступный для записи указатель. Чтобы получить его значение, воспользуемся командой ‘!heaper dp -m’. Она покажет нам содержимое управляющей структуры PEB. По смещению 0x20 находится указатель на FastPEBLockRoutine.

Пропатчим 0x43434343 в элементе lookaside[3]:

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

  • мы добились ситуации, в которой возможна запись данных/шеллкода в любые 4 байта в памяти.
  • при попытке вызова из RtlAcquirePebLock+0x28 FastPEBLockRoutine(). Поскольку FastPEBLockRoutine() использует текущий PEB с контролируемым нами указателем, мы можем заменить его на адрес нашего шеллкода.

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

В конечном итоге должно получиться нечто похожее на:

Таким образом, указатель будет восстановлен, и шеллкод не будет вызываться бесконечно. Конечно, в Windows XP SP3 это должно производиться динамически в том случае, если адрес PEB был подобран с помощью перебора. Я оставлю читателю возможность расширения этой ассемблерной заготовки до работы с динамическим PEB и правкой смещения 0x20. Вы можете использовать fs:[0x30].

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

python -c "\x41" * 1024 + "\x42" * 8 + "\x20\xf0\xfd\x7f" + "\x18\x07\x49" | nc -v <target> <ip>

NULL байт будет добавлен в конце строки.

Требования и ограничения:

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

Heaper

Когда я пытался разобраться в методах переполнения кучи, мне часто требовалось визуализировать структуры кучи. Для этого я использовал Immunity Debugger (вывод !heap в windbg совершенно безумен). Тогда мне не удалось найти инструмент в Immunity Debugger, который мог бы проанализировать кучу и определить возможность эксплуатации. С учётом того, что Immunity — отладчик, ориентированный на “эксплойты”, я решил написать плагин, который не только визуализирует структуры кучи, но и определяет возможность эксплуатации, используя несколько эвристических методов.

В настоящий момент, плагин не использует эвристические методы, однако они появятся в ближайшем будущем. Пока он лишь проверяет возможность перезаписи указателей flink/blink, а также было ли перезаписано поле заголовка, содержащее размер чанка. В течение ближайших месяцев появится поддержка Windows 7. Пока я провожу анализ возможностей эксплуатации и требуемых для них условий.

Убедитесь, что у вас установлены pydot, pyparser и graphviz и сохраните код плагина в ‘C:\Program Files\Immunity Inc\Immunity Debugger\PyCommands\’. Вызвать справку можно командой !heaper.

Я уже отмечал, что не так-то просто найти указатель, который можно будет перезаписать. Более того, вам нужно будет убедиться, что найденный указатель будет вызван после нашего переполнения. !heaper поможет нам решить эту проблему.

С его помощью вы можете сдампить все указатели на функции в секции .data командой ‘!heaper dumpfunctionpointers’ или ‘!heaper dfp’.

Чтобы пропатчить указатель на функцию значением по умолчанию 0x41414141, введите команду ‘!heaper dfp -p <указатель на функцию>’. Чтобы пропатчить все указатели — ‘!heaper dfp -p all’.

Можно также восстановить исходное значение командами ‘!heaper -r <указатель на функцию>’ и ‘!heaper -r all’.

Восстановление указателя, пропатченного выше:

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

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

Примеры некоторых предложений:

  • добавить перехват хуков для некоторых вызовов кучи, таких как VirtuallAlloc, HeapAlloc, HeapFree, HeapCreate и так далее, с выводом их аргументов (по возможности, с возвращаемым значением).
  • выводить статистику по heap spray. Например, количество используемых в атаке блоков, их размер, смещение в блоке, с которого начинается нагрузка.
  • добавить поддержку Windows 7 с LFH и убедиться, что работает графический вывод.
  • добавить эвристические методы и убедиться, что их оценки достаточно точны.

В текущий момент начата работа над четырьмя видами атак: инверсия битов (bitmap flipping), вставка во freelist[0], поиск по freelist[0] и перезапись чанка в lookaside.

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