Я слышал о переполнении буфера и хотел бы знать, как его вызвать.
Может ли кто-нибудь показать мне небольшой пример переполнения буфера? Новые(А для чего они?)
Я слышал о переполнении буфера и хотел бы знать, как его вызвать.
Может ли кто-нибудь показать мне небольшой пример переполнения буфера? Новые(А для чего они?)
Переполнение буфера в основном происходит, когда созданный раздел (или буфер) памяти записывается за пределы его предполагаемых границ. Если злоумышленнику удастся сделать это извне программы, это может вызвать проблемы с безопасностью, поскольку потенциально может позволить ему манипулировать произвольными ячейками памяти, хотя многие современные операционные системы защищают от наихудших случаев этого.
Хотя как чтение, так и запись за пределами предполагаемых границ обычно считаются плохой идеей, термин "переполнение буфера" обычно используется для записи за пределами границ, так как это может привести к тому, что злоумышленник может легко изменить способ ваш код работает. В Википедии есть хорошая статья о переполнении буфера и различных способах их использования для эксплойтов. .
С точки зрения того, как вы можете запрограммировать его самостоятельно, это будет простой вопрос:
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 символов" - когда-нибудь это не удастся. Помните ту ракету, где ученые сказали, что число не превысит какой-то величины, потому что ракета никогда не будет такой быстрой. Но в один прекрасный день она была на самом деле быстрее, что привело к целочисленному переполнению и падению ракеты (ошибка в Ariane 5, одна из самых дорогих компьютерных ошибок в истории).
Например, вместо gets используйте 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 опасно без защиты стека, потому что функция без проверки того, сколько байт мы будем вводить. скомпилируйте с дополнительной опцией -fno-stack-protector dan -mpreferred-stack-boundary=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 доступе.
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)
запрограммировать код возврата ошибки сегментации GOT. давайте введем больше байтов и посмотрим на регистр 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;
}
В дополнение к тому, что уже было сказано, имейте в виду, что ваша программа может или не может "вылететь" при переполнении буфера. Он должен дать сбой, и вы должны надеяться, что это произойдет, но если переполнение буфера "переполняет" другой адрес, который также выделил ваше приложение, ваше приложение может работать нормально в течение более длительного периода времени.
Если вы используете более позднюю версию Microsoft Visual Studio, я бы предложил использовать новые безопасные аналоги в stdlib, такие как sprintf_s вместо sprintf и т. д.
snprintf
, преимущество которого заключается в том, что он стандартизирован (ISO C 99). Также есть asprintf
(GNU и BSD libc), g_strdup_printf
(Glib).
- person sleske; 07.07.2010
Этого должно быть достаточно, чтобы воспроизвести его:
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) очень подробно рассказывает о том, как играть с переполнением буфера (очевидно, чисто как интеллектуальное упражнение).
Если вы хотите проверить свою программу на переполнение буфера, вы можете запустить ее с помощью таких инструментов, как Valgrind. Они найдут для вас некоторые ошибки управления памятью.
Это общий комментарий к полученным вами ответам. Например:
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
.
В этом контексте буфер — это часть памяти, отведенная для определенной цели, а переполнение буфера — это то, что происходит, когда операция записи в буфер продолжается до конца (запись в память, которая имеет другую цель). Это всегда ошибка.
Атака переполнения буфера — это атака, в которой эта ошибка используется для выполнения того, что автор программы не предполагал возможным.
С правильными ответами: Чтобы больше узнать об этой теме, вы можете послушать подкаст Security Now. В Эпизоде 39 (некоторое время назад) они подробно обсуждали это. Это быстрый способ получить более глубокое понимание, не требуя переваривания всей книги.
(По ссылке вы найдете архив с версиями в разных размерах, а также расшифровку, если вы ориентируетесь на визуальное восприятие). Аудио не является идеальным средством для этой темы, но Стив творит чудеса, чтобы справиться с этим.
Переполнение буфера — это вставка символов сверх того, что может вместить выделенная память.