Настольная книга компьютерного исследователя


Грядущему поколению: Атака на переполнение буфера

Увы, сегодня далеко не каждый хотябы отдаленно понимает, как происходит переполнение буффера, как этого достигнуть «искусственным» путем и что с этого мы можем поиметь. Основная масса ошибок в софте последних лет как раз и использует такого рода глюки. Раньше считалось, что это реально только на unix/linux системмах, но опыт последних месяцев показал, что переполнением буффера под win9можно тоже не только ее повесить (hint: IIS bug ;).

Более точным было бы название не переполнение буффера, а переполнение стека, ведь буффер — просто неупорядоченная куча дерьма, а стек — неупорядоченная, но индексированная операционной системой. Стек работает по достаточно интересному принципу — добавлять мы можем только на «верх» нашей кучи, а вытаскивать лишь снизу. Для добавления на крышу стека мы используем команду PUSH, для вытаскива- ния снизу — POP. Для програмирования стека также имеется такая полезная штука, как SP — stack pointer. Это регистр, в котором содержиться адрес верхней грани- цы стека. Есть также еще один важный регистр — FP (frame pointer). Он отвечает за «разбивку» стека на фреймы. При помощи только SP и FP мы уже вполне реально сможем найти нужную нам переменную в стеке. Затем идет адрес возврата, к которому мы обратимся после выполнения всех инструкций. Вот наш первый «переполнятель», давайте посмотрим, что он делает:

 $cat a1.c
 #include <всякий хрен>
 char buffer[4028];
 void main() {
    int i;
    for (i=0; i<=4028; i)
        buffer[i]='A';
    syslog(LOG_ERR, buffer);
 }
 $gcc -g a1.c -o a
 $a
 Segmentation fault (core dumped)

Опааа… Что-то пошло криво? У линуксных сишников это сообщение не вызовет испуга -  обычная ошибка адресации, ща исправим… Но нам ее не нужно исправлять! И теперь мы сделаем мааленькое свинство. Если посмотреть в gdb на нашу игрушку, то мы увидим, что регистр eip (instruction pointer, указатель на следующую инструкцию) забит мусором. Ура :)

Так вот, как же мы можем использовать это удовольствие? В принципе на многих машинах (имееться ввиду на многих операционных системах) eip идет сразу после ebp, так что все, что нам нужно — это просто в нормальном буфере расположить наш код, а выше — на него обращение. Однако тут встает небольшой, но неприятный вопрос — как реально ограничить код? Набили мы свой хак, а дальше? Пихаем NOP, что еще.

Для тех, кто сумел впитать вышенаписанное приведу более конкретный пример использования этой техники. Мы хотим например запустить /bin/sh… Напишем код, который бы только это и делал:

 $cat ex.c
 #include <всякий хрен>
 main()
 {
    char *name[2];
    name[0] = "sh";
    name[1] = NULL;
    execve("/bin/sh",name,NULL);
 }
 $gcc -g ex.c -o ex
 $ex
 #

Вот. Работает. Теперь дизассемблируем функцию execve:

 leal   0x3b,%eax
 lcall  0x7,0x0
 jb     0x10b0
 ret

Что же она делает? Просто вызывает системный вызов ядра для выполнения кода. В качетсве  параметра наш любимый /bin/sh. Что же теперь делать? Пишем программу, вызывающую вышеописанный код и поверх ее кладем jmp 0xAAA, где AAA — ее адрес. Все наверное, дальше любой состоятельный програмист на C будет в состоянии написать такую глупость.



©2013 Журнал Хакера Entries (RSS) and Comments (RSS)