ZipaCna
06.05.2008, 00:03
Kids Buffer Overflow Paper.
Автор: darkeagle
Бродя по многочисленным форумам, смотря рассылки и т.д. Я наткнулся на один очень частный
вопрос. Звучит он примерно так: "Я не пойму технику переполнения буфера, объясните,
пожалуйста!". В данном материале я бы хотел рассмотреть технику полностью. Весь материал
будет рассчитан для ОС Linux. Я постараюсь затронуть тему локального и удаленного переполнения буфера. Постараюсь внятно объяснить все. Я думаю, этот материал будет понятен даже новичку.
Итак, пора приступить к изучению.
Переполнение буфера это, пожалуй, самая распространенная ошибка как в больших приложениях, так
и в маленьких утилитах. Впервые техника переполнения буфера была предпринята в нашумевшем черве
конца 80-х годов - черве Роберта Морриса. С тех пор, данная уязвимость стала такой популярной,
что на данный момент число эксплоитов, которые написаны на основе данной уязвимости, перевалило
уже за отметку более 2-х тысяч. Из всех уязвимостей, которые на данный момент известны миру, переполнение буфера занимает 1-ое место. Ежедневно обнаруживается огромное количество ошибок на основе переполнения буфера. Для примера, подпишитесь на рассылку новостей bugtraq, и составьте процентное соотношение обнаруженных уязвимостей. У меня вышло примерно 35-40 % уязвимостей основанных на переполнении буфера. А ведь это только публичные данные! Представьте, что находится в закрытых источниках, там примерно такое же соотношение.
Что-то я уж заговорился :) Давайте перейдем к обсуждение данной ошибки.
Скажу, что для изучения данного материала, у Вас должны быть хотя бы начальные знания языка Си под Linux.
Для дальнейшей работы нам понадобятся следующие инструменты:
gcc, gdb, gedit (но можно и другой редактор).
Теперь перейдем к непосредственному объяснению техники переполнения.
Допустим, Вы написали утилиту, которая принимает входную строку (первый аргумент). Далее она вызывает системный вызов утилиты "ls" и ищет файл/директорию. В случае если файл/директория найдены, то программа оповещает пользователя о том, что такой файл/директория существуют в системе. Давайте посмотрим на пример такой программки.
[========================================CODE#1 util.c=================================]
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[])
{
char filename[255], cmd[600];
if ( argc != 2 )
{
printf("\n\nusage: %s <filename/dir>\n\n", argv[0]);
exit(0);
} //usage rulezz :)
strcpy(filename, argv[1]); //copy into filename 1st argument
snprintf(cmd, 600, "ls |grep %s", filename); //copy cmd-line to execute via system();
system(cmd); //execute
return 0;
}
[========================================CODE#1 util.c=================================]
Давайте откомпилируем программу и попытаемся запустить:
[root@localhost boft]# gcc util.c -o util
[root@localhost boft]# ./util aaaaaaaaaaaaaaaaaa
[root@localhost boft]#
Итак, программа не нашла файла/директории в текущем каталоге. Теперь попробуем создать файл в текущем каталоге.
[root@localhost boft]# touch something.file
[root@localhost boft]# ./util something.file
something.file
[root@localhost boft]#
Так, мы создали файл с помощью стандартной утилиты touch в системе Linux, и программа оповестила нас о том, что такой файл существует в системе. Вроде ничего подозрительного и нет. Никакого переполнения нет в системе. Согласен, программе ведет себя вполне стандартно.
Теперь давайте попробуем ввести название файла более 267 символов. Потом объясню, почему именно более 267 символов.
Итак:
[root@localhost boft]# ./util `perl -e 'print "A"x268'`
Segmentation fault (core dumped)
[root@localhost boft]#
Опа... Что мы видим :) Сейчас мы использовали синтаксис языка perl. Строка `perl -e 'print "A"x268'` говорит о том, что в качестве первого аргумента будет значение "A" общей суммой символов равной 268. Т.е. программа в качестве первого аргумента получит такое: A = 268 символам.
Идем далее... Программа ничего нам не вывела, а выскочило странное сообщение "Segmentation fault (core dumped)". Чтобы оно могло значить??? А значит оно одно. Наша программа повела себя нестандартно и что-то там произошло. А что именно я попытаюсь сейчас объяснить.
В системе Unix (как и в Win32) для хранения данных используется "стек". Именно в нем хранятся различные значения переменных (да и они сами там хранятся) в момент запуска программы. После закрытия программы все данные выгружаются из "стека". Стек можно сравнить со складом :) Конечно это довольно грубое объяснение, но все же. Так вот, в нашей программе мы используем несколько буферов. А именно буфер для хранения значения переменной "filename" и буфер переменной "cmd". Буфер "cmd" нас не интересует. А вот буфер переменной "filename" нам более интересен. А все потому, что именно данная переменная используется как имя файла/директории, истоки которого берутся из первого входного аргумента при запуске программы. Копирование строки происходит путем стандартной в языке Си функции strcpy();
Синтаксис ее таков:
strcpy(строка_в_которую_нужно_ опировать_данные, строка_из_которой_следует_к опировать);
Так вот строка в коде:
strcpy(filename, argv[1]);
Говорит о том, что в переменную filename нужно копировать первый входной аргумент программы.
Но взглянем выше, и мы увидим следующее:
char filename[255];
Вышеприведенная строка является объявлением переменной filename как типа char (символьного), который состоит из 255 массивов. То есть данная переменная имеет входной буфер на 255 символов. Получается, мы туда можем поместить 255 символов из входного аргумента нашей программы. Итак... Я думаю, вы уже догадались о том странном сообщении. Если нет, то оно значит то, что мы ввели более 255 символов во входной буфер, и программа вызвала ошибку т.к. размер, введенный в аргументе, превышает отведенный размер буфера переменной. Именно это и называется переполнение буфера. А теперь попробуйте сформулировать определение...
Переполнение буфера - это буфер, значение которого определяется ранее в программе и в последующий момент выходит за рамки определяемости (т.е. переполняется).
Несколько запутанно и некорректно. Но каждый может для себя составить определение этому словосочетанию :) Для меня же понятнее мое определение.
Двигаемся дальше.
Я думаю все линуксоиды знают очень хорошую и нужную утилиту gdb. Это утилита является встроенным
отладчиком в системах Unix. gdb расшифровывается как GNU Debugger.
Теперь давайте запустим нашу утилиту в этом отладчике и попробуем ввести длинное имя файла/директории.
[root@localhost boft]# gdb util
GNU gdb 6.0-2mdk (Mandrake Linux)
Copyright 2003 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i586-mandrake-linux-gnu"...Using host libthread_db library "/lib/tls/libthread_db.so.1".
(gdb) r `perl -e 'print "A"x1000'`
Starting program: /home/boft/util `perl -e 'print "A"x1000'`
Detaching after fork from child process 2861.
Program received signal SIGSEGV, Segmentation fault.
0x41414141 in ?? ()
(gdb)
Итак, мы ввели 1000 символов "A" в первый аргумент программы. И что мы видим? Программа приняла 1000 символов "A" и попыталась осуществить поиск. Но не тут то было :) Буфер переменной-файла равен всего 255 символов. И поэтому произошло переполнение. Строка 0x41414141 in ?? () говорит о том, что наша утилита попыталась обратится по адресу 0x41414141, но там ничего и нет :) Почему именно 0x41414141, так это потому что значение "A" в шестнадцаричном hex формате равно 41. А адрес у нас состоит из 8 символов. Поэтому последние четыре символа "A" будут адресом, к которому после переполнения обратится наша программа. Для более детального закрепления давайте рассмотрим следующий пример. Как я говорил ранее для переполнения нам нужно более 268 символов.
Смотрим пример:
(gdb) r `perl -e 'print "A"x268'`BBBB
Starting program: /home/boft/util `perl -e 'print "A"x268'`BBBB
Detaching after fork from child process 2898.
Program received signal SIGSEGV, Segmentation fault.
0x42424242 in ?? ()
(gdb)
И что мы видим :) А видим мы следующие. Для переполнения буфера нам нужно 268 символов. Это значение, при котором буфер полностью заполняется до краев :). Т.е. в вышеприведенном примере мы записали 268 символов "A" и четыре символа "B". Получается, что буфер заполняется до краев значением "A", а далее мы указываем по какому адресу ему следует обращаться после переполнение.
Мы указываем ему 4 символа "B", поэтому строка 0x42424242 in ?? () означает что после полного переполнения адрес по которому обратится функция будет указывать на адрес "B" в шестнадцаричном -hex формате.
А теперь взглянем на следующий пример:
(gdb) r `perl -e 'print "A"x268'`BBBA
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/boft/util `perl -e 'print "A"x268'`BBBA
Detaching after fork from child process 2920.
Program received signal SIGSEGV, Segmentation fault.
0x41424242 in ?? ()
(gdb)
Взглянем на адрес, по которому после переполнения обращается функция. Он равен: 0x41424242. А теперь взглянем на запуск программы:
(gdb) r `perl -e 'print "A"x268'`BBBA
Starting program: /home/boft/util `perl -e 'print "A"x268'`BBBA
В аргументе присутствуют 268 символов "A" и адрес равный BBBA. А теперь переведите его в hex формат. У меня получилось вот что: 0x42424241, а у компьютера вот: 0x41424242. Из этого можно судить, что компьютер читает значения как арабы или китайцы. Т.е. справа налево. Ну и конечно сверху вниз. Поэтому в системе Unix (да и в Win32) стек растет сверху вниз. Получается, что самый большой адрес будет наверху, а далее стек будет убывать. Примерный вариант стека в стандартной программе таков:
стек (адрес вершины стека = 0xbfffffff)
__
|| ДАННЫЕ
||
||
\/ АДРЕС
Т.е. в случае с нашим переполнением программа себя ведет в стеке так:
Идут данные... Если все в порядке, то программа обычно завершает свою работу и выгружается из стека. В случае переполнения ДАННЫЕ превышают норму и уже АДРЕС будет указывать не на выход из функции ( в нашем случает это return в main() ), а на что-то другое ( в нашем случае это последние 4 символа в аргументе. )
Давайте взглянем на следующее. Я думаю, вы еще не закрыли gdb.
Введите следующую команду.
(gdb) i r
eax 0x0 0
ecx 0xbffff20c -1073745396
edx 0x0 0
ebx 0x4016e800 1075243008
esp 0xbffff6b0 0xbffff6b0
ebp 0x41414141 0x41414141
esi 0x40016640 1073833536
edi 0x8048510 134513936
eip 0x41424242 0x41424242
<остальное опущено>
(gdb)
Мы видим регистры слева, а справа их значения. Помните, я Вам говорил, что для переполнения нужно ввести 268 символов, а не 255 как определено. Теперь взгляните на это:
ebp 0x41414141 0x41414141
Так вот 268 символов это и есть переполнение при котором значение регистра ebp затирается на значение входного аргумента в hex формате ( в нашем случае на "A" в hex формате ).
Т.е. попробуйте ввести такое в нашу утилиту:
`perl -e 'print "A"x267'`
(gdb) r `perl -e 'print "A"x267'`
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/boft/util `perl -e 'print "A"x267'`
Detaching after fork from child process 2942.
Program exited normally.
(gdb)
Мы видим, что программа завершилась нормально без каких-либо ошибок и переполнений.
Взглянем на значения регистров:
(gdb) i r
The program has no registers now.
(gdb)
А их и нет :) Программа завершилась нормально и выгрузилась из памяти.
А попробуйте ввести такое значение:
`perl -e 'print "A"x268'`
(gdb) r `perl -e 'print "A"x268'`
Starting program: /home/boft/util `perl -e 'print "A"x268'`
Detaching after fork from child process 2948.
Program received signal SIGSEGV, Segmentation fault.
0x4003f900 in __libc_start_main () from /lib/tls/libc.so.6
(gdb)
Видно что программа завершилась с ошибкой и в качестве адреса по которому она обратится (адресом возврата) является сама функция main() из библиотеки libc. И поэтому для того чтобы указать свой адрес мы использовали 4 дополнительных символа. Они переводились в hex формат и указывали на адрес возврата. При просмотре регистров мы увидим, что регистр ebp затерся значение "A" в hex.
Теперь давайте взглянем на другой регистр. Название ему EIP.
eip 0x41424242 0x41424242
Мы видим, что его адрес перезаписался на тот адрес, который мы указали. Т.е. на BBBA в hex формате. Я теперь хочу немного отклониться и рассказать вам об этих самых регистрах процессора.
РЕГИСТРЫ.
Вообще регистры это некое подобие строителей внутри процессора. Они как бы получают данные и складывают их в компьютере. Т.е. в случае со строителями они строят дом/гараж и т.д. Они получают данные и складывают их, а далее некая программа пытается прочесть информацию из этих регистров. Количество регистров в архитектуре процессора x86 большое. И с каждым разом все увеличивается и увеличивается. Они бывают как 16-ти разрядные, так и 32-х. Сейчас я хочу рассказать более детально об основных регистрах процесорра.
регистр EIP - это регистр содержит в себе адрес функции, на который должна перепрыгнуть программа в какое-либо действие. В нашем случае адрес eip был равен BBBA = 0x41424242. А что расположено по этому адресу? А ничего. В дальнейшем мы разберем эту тему.
регистр ESP - это регистр, с помощью которого можно бегать по стеку. Т.е. обращаться к какому либо адресу в стеке.
регистр EBP - это регистр, который дает нам возможность прямого обращения к данным, находящимся в стеке.
Эти три регистра считаются основными. Понимание их значений является, по сути, основным фундаментом в понимании техники переполнения буфера. Итак, пора двигаться далее...
Автор: darkeagle
Бродя по многочисленным форумам, смотря рассылки и т.д. Я наткнулся на один очень частный
вопрос. Звучит он примерно так: "Я не пойму технику переполнения буфера, объясните,
пожалуйста!". В данном материале я бы хотел рассмотреть технику полностью. Весь материал
будет рассчитан для ОС Linux. Я постараюсь затронуть тему локального и удаленного переполнения буфера. Постараюсь внятно объяснить все. Я думаю, этот материал будет понятен даже новичку.
Итак, пора приступить к изучению.
Переполнение буфера это, пожалуй, самая распространенная ошибка как в больших приложениях, так
и в маленьких утилитах. Впервые техника переполнения буфера была предпринята в нашумевшем черве
конца 80-х годов - черве Роберта Морриса. С тех пор, данная уязвимость стала такой популярной,
что на данный момент число эксплоитов, которые написаны на основе данной уязвимости, перевалило
уже за отметку более 2-х тысяч. Из всех уязвимостей, которые на данный момент известны миру, переполнение буфера занимает 1-ое место. Ежедневно обнаруживается огромное количество ошибок на основе переполнения буфера. Для примера, подпишитесь на рассылку новостей bugtraq, и составьте процентное соотношение обнаруженных уязвимостей. У меня вышло примерно 35-40 % уязвимостей основанных на переполнении буфера. А ведь это только публичные данные! Представьте, что находится в закрытых источниках, там примерно такое же соотношение.
Что-то я уж заговорился :) Давайте перейдем к обсуждение данной ошибки.
Скажу, что для изучения данного материала, у Вас должны быть хотя бы начальные знания языка Си под Linux.
Для дальнейшей работы нам понадобятся следующие инструменты:
gcc, gdb, gedit (но можно и другой редактор).
Теперь перейдем к непосредственному объяснению техники переполнения.
Допустим, Вы написали утилиту, которая принимает входную строку (первый аргумент). Далее она вызывает системный вызов утилиты "ls" и ищет файл/директорию. В случае если файл/директория найдены, то программа оповещает пользователя о том, что такой файл/директория существуют в системе. Давайте посмотрим на пример такой программки.
[========================================CODE#1 util.c=================================]
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[])
{
char filename[255], cmd[600];
if ( argc != 2 )
{
printf("\n\nusage: %s <filename/dir>\n\n", argv[0]);
exit(0);
} //usage rulezz :)
strcpy(filename, argv[1]); //copy into filename 1st argument
snprintf(cmd, 600, "ls |grep %s", filename); //copy cmd-line to execute via system();
system(cmd); //execute
return 0;
}
[========================================CODE#1 util.c=================================]
Давайте откомпилируем программу и попытаемся запустить:
[root@localhost boft]# gcc util.c -o util
[root@localhost boft]# ./util aaaaaaaaaaaaaaaaaa
[root@localhost boft]#
Итак, программа не нашла файла/директории в текущем каталоге. Теперь попробуем создать файл в текущем каталоге.
[root@localhost boft]# touch something.file
[root@localhost boft]# ./util something.file
something.file
[root@localhost boft]#
Так, мы создали файл с помощью стандартной утилиты touch в системе Linux, и программа оповестила нас о том, что такой файл существует в системе. Вроде ничего подозрительного и нет. Никакого переполнения нет в системе. Согласен, программе ведет себя вполне стандартно.
Теперь давайте попробуем ввести название файла более 267 символов. Потом объясню, почему именно более 267 символов.
Итак:
[root@localhost boft]# ./util `perl -e 'print "A"x268'`
Segmentation fault (core dumped)
[root@localhost boft]#
Опа... Что мы видим :) Сейчас мы использовали синтаксис языка perl. Строка `perl -e 'print "A"x268'` говорит о том, что в качестве первого аргумента будет значение "A" общей суммой символов равной 268. Т.е. программа в качестве первого аргумента получит такое: A = 268 символам.
Идем далее... Программа ничего нам не вывела, а выскочило странное сообщение "Segmentation fault (core dumped)". Чтобы оно могло значить??? А значит оно одно. Наша программа повела себя нестандартно и что-то там произошло. А что именно я попытаюсь сейчас объяснить.
В системе Unix (как и в Win32) для хранения данных используется "стек". Именно в нем хранятся различные значения переменных (да и они сами там хранятся) в момент запуска программы. После закрытия программы все данные выгружаются из "стека". Стек можно сравнить со складом :) Конечно это довольно грубое объяснение, но все же. Так вот, в нашей программе мы используем несколько буферов. А именно буфер для хранения значения переменной "filename" и буфер переменной "cmd". Буфер "cmd" нас не интересует. А вот буфер переменной "filename" нам более интересен. А все потому, что именно данная переменная используется как имя файла/директории, истоки которого берутся из первого входного аргумента при запуске программы. Копирование строки происходит путем стандартной в языке Си функции strcpy();
Синтаксис ее таков:
strcpy(строка_в_которую_нужно_ опировать_данные, строка_из_которой_следует_к опировать);
Так вот строка в коде:
strcpy(filename, argv[1]);
Говорит о том, что в переменную filename нужно копировать первый входной аргумент программы.
Но взглянем выше, и мы увидим следующее:
char filename[255];
Вышеприведенная строка является объявлением переменной filename как типа char (символьного), который состоит из 255 массивов. То есть данная переменная имеет входной буфер на 255 символов. Получается, мы туда можем поместить 255 символов из входного аргумента нашей программы. Итак... Я думаю, вы уже догадались о том странном сообщении. Если нет, то оно значит то, что мы ввели более 255 символов во входной буфер, и программа вызвала ошибку т.к. размер, введенный в аргументе, превышает отведенный размер буфера переменной. Именно это и называется переполнение буфера. А теперь попробуйте сформулировать определение...
Переполнение буфера - это буфер, значение которого определяется ранее в программе и в последующий момент выходит за рамки определяемости (т.е. переполняется).
Несколько запутанно и некорректно. Но каждый может для себя составить определение этому словосочетанию :) Для меня же понятнее мое определение.
Двигаемся дальше.
Я думаю все линуксоиды знают очень хорошую и нужную утилиту gdb. Это утилита является встроенным
отладчиком в системах Unix. gdb расшифровывается как GNU Debugger.
Теперь давайте запустим нашу утилиту в этом отладчике и попробуем ввести длинное имя файла/директории.
[root@localhost boft]# gdb util
GNU gdb 6.0-2mdk (Mandrake Linux)
Copyright 2003 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i586-mandrake-linux-gnu"...Using host libthread_db library "/lib/tls/libthread_db.so.1".
(gdb) r `perl -e 'print "A"x1000'`
Starting program: /home/boft/util `perl -e 'print "A"x1000'`
Detaching after fork from child process 2861.
Program received signal SIGSEGV, Segmentation fault.
0x41414141 in ?? ()
(gdb)
Итак, мы ввели 1000 символов "A" в первый аргумент программы. И что мы видим? Программа приняла 1000 символов "A" и попыталась осуществить поиск. Но не тут то было :) Буфер переменной-файла равен всего 255 символов. И поэтому произошло переполнение. Строка 0x41414141 in ?? () говорит о том, что наша утилита попыталась обратится по адресу 0x41414141, но там ничего и нет :) Почему именно 0x41414141, так это потому что значение "A" в шестнадцаричном hex формате равно 41. А адрес у нас состоит из 8 символов. Поэтому последние четыре символа "A" будут адресом, к которому после переполнения обратится наша программа. Для более детального закрепления давайте рассмотрим следующий пример. Как я говорил ранее для переполнения нам нужно более 268 символов.
Смотрим пример:
(gdb) r `perl -e 'print "A"x268'`BBBB
Starting program: /home/boft/util `perl -e 'print "A"x268'`BBBB
Detaching after fork from child process 2898.
Program received signal SIGSEGV, Segmentation fault.
0x42424242 in ?? ()
(gdb)
И что мы видим :) А видим мы следующие. Для переполнения буфера нам нужно 268 символов. Это значение, при котором буфер полностью заполняется до краев :). Т.е. в вышеприведенном примере мы записали 268 символов "A" и четыре символа "B". Получается, что буфер заполняется до краев значением "A", а далее мы указываем по какому адресу ему следует обращаться после переполнение.
Мы указываем ему 4 символа "B", поэтому строка 0x42424242 in ?? () означает что после полного переполнения адрес по которому обратится функция будет указывать на адрес "B" в шестнадцаричном -hex формате.
А теперь взглянем на следующий пример:
(gdb) r `perl -e 'print "A"x268'`BBBA
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/boft/util `perl -e 'print "A"x268'`BBBA
Detaching after fork from child process 2920.
Program received signal SIGSEGV, Segmentation fault.
0x41424242 in ?? ()
(gdb)
Взглянем на адрес, по которому после переполнения обращается функция. Он равен: 0x41424242. А теперь взглянем на запуск программы:
(gdb) r `perl -e 'print "A"x268'`BBBA
Starting program: /home/boft/util `perl -e 'print "A"x268'`BBBA
В аргументе присутствуют 268 символов "A" и адрес равный BBBA. А теперь переведите его в hex формат. У меня получилось вот что: 0x42424241, а у компьютера вот: 0x41424242. Из этого можно судить, что компьютер читает значения как арабы или китайцы. Т.е. справа налево. Ну и конечно сверху вниз. Поэтому в системе Unix (да и в Win32) стек растет сверху вниз. Получается, что самый большой адрес будет наверху, а далее стек будет убывать. Примерный вариант стека в стандартной программе таков:
стек (адрес вершины стека = 0xbfffffff)
__
|| ДАННЫЕ
||
||
\/ АДРЕС
Т.е. в случае с нашим переполнением программа себя ведет в стеке так:
Идут данные... Если все в порядке, то программа обычно завершает свою работу и выгружается из стека. В случае переполнения ДАННЫЕ превышают норму и уже АДРЕС будет указывать не на выход из функции ( в нашем случает это return в main() ), а на что-то другое ( в нашем случае это последние 4 символа в аргументе. )
Давайте взглянем на следующее. Я думаю, вы еще не закрыли gdb.
Введите следующую команду.
(gdb) i r
eax 0x0 0
ecx 0xbffff20c -1073745396
edx 0x0 0
ebx 0x4016e800 1075243008
esp 0xbffff6b0 0xbffff6b0
ebp 0x41414141 0x41414141
esi 0x40016640 1073833536
edi 0x8048510 134513936
eip 0x41424242 0x41424242
<остальное опущено>
(gdb)
Мы видим регистры слева, а справа их значения. Помните, я Вам говорил, что для переполнения нужно ввести 268 символов, а не 255 как определено. Теперь взгляните на это:
ebp 0x41414141 0x41414141
Так вот 268 символов это и есть переполнение при котором значение регистра ebp затирается на значение входного аргумента в hex формате ( в нашем случае на "A" в hex формате ).
Т.е. попробуйте ввести такое в нашу утилиту:
`perl -e 'print "A"x267'`
(gdb) r `perl -e 'print "A"x267'`
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/boft/util `perl -e 'print "A"x267'`
Detaching after fork from child process 2942.
Program exited normally.
(gdb)
Мы видим, что программа завершилась нормально без каких-либо ошибок и переполнений.
Взглянем на значения регистров:
(gdb) i r
The program has no registers now.
(gdb)
А их и нет :) Программа завершилась нормально и выгрузилась из памяти.
А попробуйте ввести такое значение:
`perl -e 'print "A"x268'`
(gdb) r `perl -e 'print "A"x268'`
Starting program: /home/boft/util `perl -e 'print "A"x268'`
Detaching after fork from child process 2948.
Program received signal SIGSEGV, Segmentation fault.
0x4003f900 in __libc_start_main () from /lib/tls/libc.so.6
(gdb)
Видно что программа завершилась с ошибкой и в качестве адреса по которому она обратится (адресом возврата) является сама функция main() из библиотеки libc. И поэтому для того чтобы указать свой адрес мы использовали 4 дополнительных символа. Они переводились в hex формат и указывали на адрес возврата. При просмотре регистров мы увидим, что регистр ebp затерся значение "A" в hex.
Теперь давайте взглянем на другой регистр. Название ему EIP.
eip 0x41424242 0x41424242
Мы видим, что его адрес перезаписался на тот адрес, который мы указали. Т.е. на BBBA в hex формате. Я теперь хочу немного отклониться и рассказать вам об этих самых регистрах процессора.
РЕГИСТРЫ.
Вообще регистры это некое подобие строителей внутри процессора. Они как бы получают данные и складывают их в компьютере. Т.е. в случае со строителями они строят дом/гараж и т.д. Они получают данные и складывают их, а далее некая программа пытается прочесть информацию из этих регистров. Количество регистров в архитектуре процессора x86 большое. И с каждым разом все увеличивается и увеличивается. Они бывают как 16-ти разрядные, так и 32-х. Сейчас я хочу рассказать более детально об основных регистрах процесорра.
регистр EIP - это регистр содержит в себе адрес функции, на который должна перепрыгнуть программа в какое-либо действие. В нашем случае адрес eip был равен BBBA = 0x41424242. А что расположено по этому адресу? А ничего. В дальнейшем мы разберем эту тему.
регистр ESP - это регистр, с помощью которого можно бегать по стеку. Т.е. обращаться к какому либо адресу в стеке.
регистр EBP - это регистр, который дает нам возможность прямого обращения к данным, находящимся в стеке.
Эти три регистра считаются основными. Понимание их значений является, по сути, основным фундаментом в понимании техники переполнения буфера. Итак, пора двигаться далее...