Что такое переполнение буфера и как его вызвать? - PullRequest
30 голосов
/ 22 февраля 2009

Я слышал о переполнении буфера и хотел бы знать, как его вызвать.

Может кто-нибудь показать мне небольшой пример переполнения буфера? Новый (А для чего они используются?)

Ответы [ 12 ]

32 голосов
/ 22 февраля 2009

Классический пример переполнения буфера:

// noone will ever have the time to type more than 64 characters...
char buf[64];
gets(buf); // let user put his name

Переполнение буфера чаще всего не происходит преднамеренно. Чаще всего это происходит из-за так называемой ошибки "off-by-one". Это означает, что вы неправильно рассчитали размер массива на единицу - возможно, потому, что вы забыли учесть завершающий нулевой символ, или потому, что некоторые другие вещи.

Но это также может быть использовано для некоторых злых дел. Действительно, пользователь долго знал эту дыру, а затем вставляет, скажем, 70 символов, причем последние содержат некоторые специальные байты, которые перезаписывают некоторый слот стека - если пользователь действительно хитр, он / она попадет в слот обратного адреса в стеке и перезаписывает его так, что он переходит вперед в только что вставленный буфер: потому что пользователь ввел не его имя, а его шелл-код, который он ранее скомпилировал и выгрузил. Тот будет просто казнен. Есть некоторые проблемы. Например, вы должны договориться о том, чтобы в этом двоичном коде не было "\ n" (потому что get перестанет читать). Для других способов, которые связываются с опасными строковыми функциями, двоичный ноль проблематичен, потому что строковые функции перестают копировать туда в буфер. Люди использовали xor с двумя значениями, одинаковыми для получения нуля, без явной записи нулевого байта.

Это классический способ сделать это. Но есть некоторые блоки безопасности, которые могут сказать, что такие вещи произошли, и другие вещи, которые делают стек неисполняемым. Но я предполагаю, что есть намного лучшие трюки, чем я только что объяснил. Какой-то ассемблер, вероятно, теперь мог бы рассказать вам длинные истории об этом:)

Как этого избежать

Всегда используйте функции, которые также принимают аргумент максимальной длины, если вы не 100% уверены, что буфер действительно достаточно велик. Не играйте в такие игры, как «о, число не будет превышать 5 символов» - однажды это провалится. Помните, что одна ракета, где ученые говорили, что число не будет превышать какую-то величину, потому что ракета никогда не будет такой быстрой. Но однажды, это было на самом деле быстрее, и в результате было переполнение целого числа и сбой ракеты (это об ошибке в Ariane 5 , одной из самых дорогих компьютерных ошибок в истории ).

Например, вместо get используется fgets. И вместо sprintf используйте snprintf, где это уместно и доступно (или только такие вещи в стиле C ++, как istream и прочее)

26 голосов
/ 22 февраля 2009

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

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

С точки зрения того, как вы могли бы запрограммировать себя, было бы просто:

char a[4];
strcpy(a,"a string longer than 4 characters"); // write past end of buffer (buffer overflow)
printf("%s\n",a[6]); // read past end of buffer (also not a good idea)

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

12 голосов
/ 07 декабря 2010

В современной ОС Linux вы не можете использовать переполнение буфера без какого-либо дополнительного эксперимента. Зачем ? потому что вы будете заблокированы ASLR (рандомизация уровня стека адресов) и защитник стека в этом современном компиляторе GNU C. Вы не сможете легко найти память, потому что она попадет в случайную память, вызванную ASLR . и вы будете заблокированы стековым протектором , если попытаетесь переполнить программу.

Для начала вам нужно установить ASLR равным 0 значение по умолчанию 2

root@bt:~# cat /proc/sys/kernel/randomize_va_space
2
root@bt:~# echo 0 > /proc/sys/kernel/randomize_va_space
root@bt:~# cat /proc/sys/kernel/randomize_va_space
0
root@bt:~#

в данном случае это не учебник по переполнению буфера в старом стиле, который вы можете получить из интернета. или aleph one tutorial больше не будет работать в вашей системе.

теперь позволяет сделать уязвимость программы в сценарии переполнения буфера

---------------------bof.c--------------------------
#include <stdio.h>
#include <string.h>

int main(int argc, char** argv)
{
        char buffer[400];
        strcpy(buffer, argv[1]);

        return 0;
}
---------------------EOF-----------------------------

считает, что функция strcpy опасна без защитника стека, потому что функция без проверки количества байтов, которые мы введем. скомпилировать с дополнительной опцией -fno-stack-protector dan -mpreferred-stack-border = 2 для снятия защиты стека в вашей C-программе

root@bt:~# gcc -g -o bof -fno-stack-protector -mpreferred-stack-boundary=2 bof.c
root@bt:~# chown root:root bof
root@bt:~# chmod 4755 bof

Переполнение буфера в C-программе со сценарием корневого доступа SUID. Теперь давайте посмотрим, сколько байтов нам нужно поместить в буфер, чтобы сделать ошибку сегментации программы

root@bt:~# ./bof `perl -e 'print "A" x 400'`
root@bt:~# ./bof `perl -e 'print "A" x 403'`
root@bt:~# ./bof `perl -e 'print "A" x 404'`
Segmentation fault
root@bt:~#

вы видите, что нам нужно 404 байта для ошибки сегментации программы (сбой), сколько байт нам нужно перезаписать EIP ? EIP инструкция будет выполнена после. так что хакер перезаписывает EIP злым инструкциям, что они хотят в двоичном SUID программы. если программа в корне SUID, инструкция будет запущена в режиме root.

root@bt:~# gdb -q bof
(gdb) list
1       #include <stdio.h>
2       #include <string.h>
3
4       int main(int argc, char** argv)
5       {
6               char buffer[400];
7               strcpy(buffer, argv[1]);
8
9               return 0;
10      }
(gdb) run `perl -e 'print "A" x 404'`
Starting program: /root/bof `perl -e 'print "A" x 404'`

Program received signal SIGSEGV, Segmentation fault.
0xb7e86606 in __libc_start_main () from /lib/tls/i686/cmov/libc.so.6
(gdb) run `perl -e 'print "A" x 405'`
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /root/bof `perl -e 'print "A" x 405'`

Program received signal SIGSEGV, Segmentation fault.
0xb7e800a9 in ?? () from /lib/tls/i686/cmov/libc.so.6
(gdb)

программа ПОЛУЧИЛА код возврата ошибки сегментации. давайте введем больше байтов и посмотрим на регистр EIP.

(gdb) run `perl -e 'print "A" x 406'`
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /root/bof `perl -e 'print "A" x 406'`

Program received signal SIGSEGV, Segmentation fault.
0xb7004141 in ?? ()
(gdb)

(gdb) run `perl -e 'print "A" x 407'`
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /root/bof `perl -e 'print "A" x 407'`

Program received signal SIGSEGV, Segmentation fault.
0x00414141 in ?? ()
(gdb)

немного больше

(gdb) run `perl -e 'print "A" x 408'`
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /root/bof `perl -e 'print "A" x 408'`

Program received signal SIGSEGV, Segmentation fault.
0x41414141 in ?? ()
(gdb)

(gdb) i r
eax            0x0      0
ecx            0xbffff0b7       -1073745737
edx            0x199    409
ebx            0xb7fc9ff4       -1208180748
esp            0xbffff250       0xbffff250
ebp            0x41414141       0x41414141
esi            0x8048400        134513664
edi            0x8048310        134513424
eip            0x41414141       0x41414141 <-- overwriten !!
eflags         0x210246 [ PF ZF IF RF ID ]
cs             0x73     115
ss             0x7b     123
ds             0x7b     123
es             0x7b     123
fs             0x0      0
gs             0x33     51
(gdb)

теперь вы можете сделать следующий шаг ...

10 голосов
/ 22 февраля 2009

Переполнение буфера просто записывает после конца буфера:

int main(int argc, const char* argv[])
{
    char buf[10];
    memset(buf, 0, 11);
    return 0;
}
5 голосов
/ 22 февраля 2009

В дополнение к тому, что уже было сказано, имейте в виду, что ваша программа может или не может "зависнуть" при переполнении буфера. Он должен аварийно завершить работу, и вы должны надеяться, что это произойдет - но если переполнение буфера «переполнится» другим адресом, который также выделено вашим приложением - ваше приложение может работать нормально в течение более длительного периода времени.

Если вы используете более позднюю версию Microsoft Visual Studio - я бы предложил использовать новые безопасные аналоги в stdlib, такие как sprintf_s insted из sprintf, ect ...

1 голос
/ 12 сентября 2014

Это общий комментарий о полученных вами ответах. Например:

int main(int argc, char *argv[])
{
    char buffer[10];
    strcpy(buffer, argv[1]);
}

И

int main(int argc, const char* argv[])
{
    char buf[10];
    memset(buf, 0, 11);
    return 0;
}

На современных платформах Linux это может работать не так, как ожидалось или предполагалось. Может не работать из-за функции безопасности FORTIFY_SOURCE.

FORTIFY_SOURCE использует «более безопасные» варианты функций высокого риска, такие как memcpy и strcpy. Компилятор использует более безопасные варианты, когда он может определить размер буфера назначения. Если размер копии превысит размер буфера назначения, программа вызывает abort().

Чтобы отключить FORTIFY_SOURCE для тестирования, вы должны скомпилировать программу с помощью -U_FORTIFY_SOURCE или -D_FORTIFY_SOURCE=0.

1 голос
/ 22 февраля 2009

Если вы хотите проверить вашу программу на переполнение буфера, вы можете запустить ее с помощью таких инструментов, как Valgrind . Они найдут для вас ошибки управления памятью.

1 голос
/ 22 февраля 2009

«Классический» пример переполнения буфера:

int main(int argc, char *argv[])
{
    char buffer[10];
    strcpy(buffer, argv[1]);
}

Это позволяет вам играть с параметрами переполнения буфера и подстраивать их под ваши сердца. Книга " Взлом - Искусство Эксплуатации " (ссылка идет на Amazon) подробно описывает, как обходиться переполнением буфера (очевидно, чисто как интеллектуальное упражнение).

1 голос
/ 22 февраля 2009

Это должно быть достаточно, чтобы воспроизвести это:

void buffer_overflow() 
{
    char * foo = "foo";
    char buffer[10];

    for(int it = 0; it < 1000; it++) {
        buffer[it] = '*';
    }

    char accessViolation = foo[0];
}
0 голосов
/ 05 мая 2019

Переполнение буфера - это вставка символов, превышающая то, что может содержать выделенная память.

...