Что такое переполнение буфера и как его вызвать?

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

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

11 ответов


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

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

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

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)

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


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

// 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 " (потому что gets прекратите читать здесь). Для других способов, которые мешают опасным строковым функциям, двоичный ноль проблематичен, потому что строковые функции перестают копировать туда в буфер. Люди использовали xor с двумя одинаковыми значениями для получения нуля тоже, без записи нулевого байта явно.

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

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

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

, а не получает использовать fgets. И вместо sprintf использовать snprintf где подходит и доступен (или просто стиль c++ вещи, такие как istream и прочее)

в современной ОС 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 опасен без протектора стека, потому что функция без проверки, сколько байтов мы будем вводить. компилировать с помощью extra вариант - fno-stack-protector dan-mpreferred-stack-boundary=2 для взлета стека protector в вашей программе 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

buffer overflow c program with SUID root access scenatio теперь у нас есть сделать это. теперь давайте искать, сколько байтов нам нужно поместить в буфер, чтобы сделать ошибку сегментации программы

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@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)

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


переполнение буфера - это просто запись за конец буфера:

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

в дополнение к тому, что уже было сказано, имейте в виду, что программа you'R может или не может "сбой" при переполнении буфера происходит. Это должны сбой, и вы должны надеяться, что это произойдет, но если переполнение буфера "переполняет" другой адрес, который также выделил ваше приложение, ваше приложение может работать нормально в течение более длительного периода времени.

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


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

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

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

    char accessViolation = foo[0];
}

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

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

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


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


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

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.


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

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


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

(по ссылке вы найдете архив с несколькими версиями размера, а также транскрипт, если вы довольно визуально ориентированы). Аудио не является идеальный среда для этого но Стив творит чудеса, чтобы справиться с этим.