JIT SPRAY против IE8: пишем собственный JIT-эксплойтСегодня мы разберемся с новейшей методикой преодоления hardware DEP и ASLR для IE8 — JIT Spray. Разработаем простой прототип эксплойта, а также свой собственный JIT-шеллкод, который, к тому же, окажется первым публично-доступным шеллкодом для таких целей. Как все начиналосьДо недавнего времени считалось, что ASLR+DEP и браузер IE8 — неприступная крепость. Знаменитый эксплойт Aurora, который применялся для атак на Google, использовал узявимости браузера IE, но работал только под IE6/IE7. Однако исследователи не собирались мириться с этим, и в начале февраля 2010 года на конференции BlackHat DC 2010 были представлены техники, которые успешно обходят ASLR, DEP и работают с IE8. К сожалению, исследователи многое скрыли, в частности не открыли ни полноценных примеров шеллкода, ни полных исходных кодов. Но их материала достаточно, чтобы любой продвинутый человек мог собрать свой эксплойт. Так Immunity в своём платном продукте Canvas реализовала эти техники для знаменитого эксплойта Aurora и теперь он успешно работает для IE8 под Windows 7. Уверен, что многие ОченьЧерные-Шляпы, также приняли на вооружение эти методы для своих грязных целей. Так что будем все реализовывать самостоятельно. Previously on ][В прошлых номерах ][, я рассказывал как искать уязвимости, писать свои эксплойты и даже обходить hardware DEP. Эта статья будет заключением трилогии об атаках на браузеры. В качестве примера уязвимости я возьму тот же компонент ActiveX, что и в предыдущих своих статьях — мне кажется, что это будет крайне показательно. Напомню, что мы используем ActiveX emsmtp.dll из пакета QuikSoft EasyMail. В данном объекте есть несколько уязвимых функций — мы используем SubmitToExpress(). Если передать в качестве аргумента строку длиной более 256 байт, то произойдёт переполнение буфера в стеке, при этом мы захватываем регистр ESI, адрес возврата и дескриптор SEH.
IE8 — ХАКЕРЫ НЕ ПРОЙДУТ!Поставим свежий IE8 и включим hardware DEP. Теперь мы должны чувствовать себя в полной безопасности. Попробуем запустить эксплойт из предыдущего номера, который отключает DEP для процесса iexplore. Нас ждет моментальное разочарование — Heap Spray больше не работает. Просто вот так вот. Но не нужно слез, ведь работать с кучей может не только IE. У нас же есть Flash, и он весьма неплохо умеет работать с кучей. Однако, этого мало. Возникает вторая проблема — нельзя отключить DEP для процесса. Дело в том, что IE8 использует permanent DEP. То есть процесс сам устанавливает себе DEP с помощью вызова функции SetProcessDEPPolicy, которая в свою очередь вызывает NtSetInformationProcess. Наш эксплойт аналогично пытается отключить DEP. Но повторный вызов NtSetInformationProcess будет завершен неудачей — Access Denied. Добавим сюда ещё и защиту ASLR, то есть тот факт, что адреса функций в памяти нам неизвестны. Задача усложняется ещё в 256 раз. JIT SPRAY спешит на помощьМир не без добрых людей. Один из них — Дионис Блазакис (Dionysus Blazakis) пришел на конференцию BlackHat DC 2010 и рассказал, как он обошел защиту DEP и ASLR и таки добился выполнения своего шеллкода в контексте IE8. После этого рассказа он выложил на своем веб-сайте документ, который подробно описывает, как этого добиться. Что ж, попробуем разобраться… Итак, вопрос номер один — как обойти permanent DEP? Дион предлагает воспользоваться любым JIT-компилятором, которых в браузере достаточно. Сам он активно пропагандирует JIT-компилятор для байткода ActionScript. Суть идеи следующая — мы пишем AS код, компилируем его в байткод и засовываем в SWF-файл. Flash грузит этот файл, а встроенная виртуальная машина переделывает байткод в исполнимый код и помещает его в памяти процесса IE8. Естественно, область памяти, где лежит код, помечена как исполняемая. Ведь Flash должен выполнить тот код, что программист написал. Если хакер в качестве адреса возврата укажет адрес этой области памяти, то код спокойно выполнится. Однако, код этот безобидный. В ActionScript нет «злых» функций, вроде «открыть сетевой порт и перенаправить ввод с него на «cmd.exe»». А хакера только такие задачи и интересуют. Итак, допустим у нас есть код на ActionScript’e
В памяти он будет выглядеть уже так:
Узнаёшь? 0x35 — это команда XOR EAX, а дальше аргумент — задом наперед. А если адрес возврата будет указывать не на самое начало кода, а, допустим, сдвинется на один байт, тогда исполняемый код станет таким:
0x3C — то, что было у нас аргументом, после сдвига станет CMP AL. При этом, аргумент для сравнения — наш бывший XOR EAX — 0x35. Таким образом, легальный XOR съедается, и идут только операторы NOP’ы и сравнения, что есть тоже семантический NOP в данном контексте. Вот так мы можем писать почти любой код, используя лишь аргументы «исключающего или» в ActionScript’е. Можно сюда просто запихивать шеллкод. Но ведь мы не знаем адрес, где будет этот блок памяти с исполняемым кодом. Откуда нам знать, куда Flash его запихнёт? А если ещё и ASLR, тогда вообще не о чем говорить. Но Дион предложил два варианта. Во-первых, можно открыть очень много файлов с JIT-шеллкодом, тогда получится аналог HeapSpray. Мы заполним память нашим кодом, и тогда угадать адрес станет очень легко. Даже если используется ASLR, остается вероятность угадывания. Этот вариант практически 100% надежен на Windows XP SP3 и малонадежен, но работоспособен на Windows 7. Во-вторых, можно воспользоваться утечками памяти в ActionScript’е для определения реального адреса в памяти. Так как у меня XP SP3, то подробно расскажу лишь о первом способе. Тем не менее, второй способ мы также будем использовать, но немного для других целей. Добавлю ещё, что Дион для работы использовал компилятор Tamarin (входит в состав Flex SDK), я же использовал компилятор из набора SWFTOOLS. ШеллкодОпределим для начала, что мы хотим от шеллкода: чтобы он был универсален, чтобы можно было использовать шеллкоды из MetaSploit, например. Но это не совсем тривиальная задача. В JIT-шеллкоде нужно маскировать XOR EAX. У нас для работы только три байта аргумента. Четвертый байт должен маскировать XOR. Поэтому придётся разрабатывать собственный. В интернете материала об этом не было и нет ни одного примера, да и сам Дионис ничем, кроме теории, не поделился. Будем делать все сами. Логика, описанная Дионом:
Перенос шеллкода из Метасплойта в строку ActionScript. Это просто, действуем по аналогии с HeapSpray. Так, например, последовательность опкодов “\x11\x22\x33\ x44”, в формате строки AS будет выглядеть как: “\u2211\ u4433”. Для удобства я написал небольшой скрипт (ищи на диске), который собирает из обычного шеллкода в формате строки perl шеллкод в формате строки AS. Теперь нам надо вычислить указатель этой строки, чтобы потом JIT-шеллкод сделал память, где лежит эта строка, исполняемой. Дионис нашел способ получения адреса строки в памяти через утечку из объекта класса Dictionary. Класс Dictionary позволяет задавать пару значение-ключ, где в роли ключа может быть любой объект. Например:
О типах объектов и их представлении в памяти (атомах) можно подробно почитать в статье Диониса, я же кратко вынесу суть. В данном примере, key — строка. Строка представляет собой сущность длиной в 32 бита, где первые 3 бита описывают тип, следующие 29 бит — указатель на значение строки. Числа же хранятся не по указателю. Если тип Integer, то 29 бит — значение числа, а первые 3 бита — также тип атома. Так в ActionScript понимаются данные, если упрощенно. При использовании Dictionary, ключ хешируется, и если таблица для значений хеширования достаточно большая, то в результате большая часть исходного значения останется неизменной. Если в качестве ключа используется число, то для хеширования используется его значение, а вот если объект или строка, то адрес. Осталось получить его значение. Соберем два объекта класса Dictionary и заполним первый объект четными числами, второй — нечетными. Затем в каждый из объектов засунем строку с шеллкодом в качестве индекса (значение не имеет значения :)).
Затем переберем каждый объект, сохраняя предыдущий индекс, пока не найдём строку.
Алгоритм работы Dictionary позволяет нам по значению индексов, идущих перед размещением строки, вычислить адрес строки. Использование двух объектов позволяет избежать коллизии, кроме того, эти числа должны отличаться на 17 — это будет говорить нам, что все прошло правильно (все это связано с работой хеширования значения атомов и поиска места в таблице для хранения пары ключ-значение). Это и многое другое ты можешь более детально узнать в той же статье Диониса. 3 бита и делаем сдвиг на 8:
Теперь, если к этому значению добавить ещё 12, то это будет указатель на адрес строки. Время JIT Spray! Для того чтобы это выполнить, достаточно открыть множество SWF файлов. При этом первый файл загрузится, а остальные из кэша браузера пойдут. Тем не менее, надо помнить, что Flash ставит ограничение на время работы скрипта. Обойти это можно, используя таймеры и события или интервал. После этого передаём управление в JavaScript с адресом строки. Теперь надо сформировать переполнение буфера. Суть проста: переписываем адрес возврата на адрес с JIT-шеллкодом, а далее, используя переполнение, добавляем в стек адрес строки с основным шеллкодом. Но тут возможна проблема: если адрес с JIT можно выбрать так, чтобы не было нулевых байтов или байтов не из ASCII-строки, то адрес основного шеллкода уже не выбрать никак. Я придумал костыль, который поможет запихнуть адрес в стек — используем избыточность. Разобьем четырехбайтный адрес на два. К примеру, у нас адрес 0x01FF001A. Второй и третий байт не проходят. Делаем из него два адреса: Переворачиваем последовательность и заносим в строку. Значения [0x60..0x6F] легко передаются. JIT шеллкод восстановит оригинальный адрес по формуле
В таком случае мы учитываем 3 варианта исхода: retn, retn 4 и retn 8. Шеллкод возьмет старшее значение, потом сдвинется в стеке на 12 байт и возьмет младшее. Осталось определить сам адрес возврата. Мои наблюдения показали, что если SWF файл с JIT-шеллкодом будет достаточно объемным, то расстояние между блоками будет слишком большим и есть значительная вероятность не угадать адрес. Чтобы блоки росли с одинаковым инкрементом, который можно предсказать, нужно соблюдать размер выделяемой памяти в пределах 0x1000 байт. Тогда каждый исполняемый блок будет отличаться от предыдущего ровно на 0x010000 байт. Причем размер блока будет 0x1000 байт, после этого блока следуют неисполняемые блоки памяти. Дальнейшее зависит от ASLR, загруженности процесса и количества загружаемых блоков. В нашем примере идеальный адрес возврата есть 0x1A1A0101. Хотя блок начинается с 0x1A1A0000, мы указываем сдвиг, так как сначала идет кусок вводного кода, а затем наш JIT-шеллкод. После шеллкода идет выводной код Flash, остальное забито нулями. В итоге: JIT-SPRAY шеллкодВернемся к собственно JIT-шеллкоду. Когда я начал над ним работать, всплыло как минимум три основных подводных камня. 1. Старший байт не может быть больше 0x7F. Иначе получается слишком большое число и, чтобы обработать его, в нашу XOR строку добавляется ненужный код, который все испортит. Поэтому мы можем использовать значения от 0x00 до 0x7F. 2. Если в нашем шеллкоде будет сравнение, а потом оператор условного перехода (JNE/JE, например), то надо держать Z флаг неизменным после сравнения и до оператора перехода. Но ведь мы можем вводить команды только группой по три байта, а затем маскировать XOR. А XOR мы маскируем оператором CMP, что, естественно, не способствует сохранности Z флага. Чтобы решить эту проблему, нам нужно найти оператор с размером аргумента в один байт (значение XOR), да так, чтобы он не влиял на Z флаг и не переходил границ 0x7F значением опкода. ADD, SUB, XOR, OR, AND и т.д. — все это не подходит. Если в результате выполнения операции регистр AL станет 0, то установится Z флаг. В итоге, единственной подходящей командой оказалась команда PUSH — 0x6A. Тогда пример сравнения и перехода будет выглядеть так: 3. Мы не можем работать полноценно с четырехбайтными значениями. К примеру, сделать PUSH 0xA1B1C3C4. Ведь для этого нам надо 5 контролируемых подряд байт, и ещё 6 байт для маскировки XOR, а у нас их только 3. Но эта проблема решается, если работать с регистром и его частями. Сначала 4 байта в регистр, причем второй младший байт будет XORом — 0x35. Затем меняем AL и AH. Учитывая эти три особенности, можно написать почти любой шеллкод. Пишем тот, что задумали. Полная версия на диске, здесь приведены лишь ключевые моменты.
В комментариях ищи основной смысл, в опкодах же присутствует ещё выравнивание NOP’ами и маскирование XOR’ов. Конечно, данный шеллкод можно вписать в меньшие размеры, но в данном случае значения это не имеет, главное — не набрать кода больше чем 0x1000 = 4000 байт. Наш шеллкод имеет размер около 800 байт, включая NOP’ы, XOR’ы и выравнивания Так что места ещё много. Вот такая история. На диске ты найдешь примеры других шеллкодов и пример эксплойта. К сожалению, мы не успели рассмотреть вариант с точным вычислением адреса JIT шеллкода, тем не менее, и данный вариант будет работать. На диске есть набор для генерации всего что понадобится для обхода защиты DEP в IE8, и вдобавок видео, как этим пользоваться. Вкратце суть следующая:
Пожалуй, на этом трилогия про атаки на браузеры через ActiveX закончена. Надо заметить, что описанный здесь метод применим не только к IE8, но и к другим браузерам. Аналогичный метод может использоваться в PDF файлах. Links
|