Переполнение буфера
Ошибки переполнения буфера характеризуются перезаписью фрагментов памяти процесса, которые никогда не должны были изменяться намеренно или непреднамеренно. Перезапись значений IP (указатель команд), BP (базовый указатель) и других регистров приводит к возникновению исключений, сбоев сегментации и других ошибок. Обычно эти ошибки заканчивают выполнение приложения неожиданным образом. Ошибки переполнения буфера возникают, когда мы работаем с буферами типа char.
Переполнение буфера может состоять из переполнения стека или переполнения кучи. Я не привожу различия между этими двумя в этой статье, чтобы избежать путаницы. Приведенные ниже примеры написаны на языке C в системе GNU/Linux на архитектуре x86.
Пример 1
#include <stdio.h> int main(int argc, char **argv) { char buf[8]; // buffer for eight characters gets(buf); // read from stdio (sensitive function!) printf("%s\n", buf); // print out data stored in buf return 0; // 0 as return value }Это очень простое приложение считывает из стандартного ввода массив символов и копирует его в буфер типа char. Размер этого буфера составляет восемь символов. После этого отображается содержимое буфера и приложение завершает работу.
Компиляция программы:
user@spin ~/inzyeria $ gcc bo-simple.c -o bo-simple /tmp/cCXQAX.o: In function `main': bo-simple.c:(.text+0x17): warning: the `gets' function is dangerous and should not be used.На этом этапе даже компилятор предупреждает, что функция gets() небезопасна. Пример использования:
user@spin ~/inzynieria $ ./bo-simple // program start 1234 // we eneter "1234" string from the keyboard 1234 // program prints out the conent of the buffer user@spin ~/inzynieria $ ./bo-simple // start 123456789012 // we eneter "123456789012" 123456789012 // content of the buffer "buf" ?!?! Segmentation fault // information about memory segmenatation faultК счастью, нам удается (не) выполнить ошибочную операцию с помощью программы и спровоцировать ее аварийный выход.
Анализ проблемы:
Программа вызывает функцию, которая работает с буфером типа char и не проверяет переполнение размера, назначенного этому буферу. В результате можно намеренно или непреднамеренно хранить больше данных в буфере, что приведет к ошибке. Возникает следующий вопрос: в буфере хранится только восемь символов, так почему функция printf() отобразила двенадцать?. Ответ приходит из организации памяти процесса. Четыре символа, которые заполнили буфер, также перезаписывают значение, хранящееся в одном из регистров, что было необходимо для правильного возврата функции. Непрерывность памяти привела к выводу данных, хранящихся в этой области памяти.
Пример 2
#include <stdio.h> #include <string.h> void doit(void) { char buf[8]; gets(buf); printf("%s\n", buf); } int main(void) { printf("So... The End...\n"); doit(); printf("or... maybe not?\n"); return 0; }Этот пример аналогичен первому. Кроме того, до и после функции doit() у нас есть два вызова функции printf(). Программа между двумя определенными вызовами printf() отображает содержимое буфера, которое заполнено данными, введенными пользователем. Поскольку размер буфера был определен (char buf[8]) и он был заполнен тринадцатью символами типа char, буфер был переполнен.
Если наше двоичное приложение в формате ELF, то мы можем использовать программу objdump для его анализа и поиска необходимой информации для использования ошибки переполнения буфера.
Такие ошибки можно использовать например для переупорядочивания выполнение функций выполнения кода (если мы сможем ввести шелл-код, описанный в отдельном документе).
Как возникают ошибки переполнения буфера?
Такого рода ошибки очень легко совершить. В течение многих лет они были кошмаром программиста. Проблема заключается в собственных функциях языка Си, которые не заботятся о выполнении соответствующих проверок длины буфера. Ниже приведен список таких функций и, если они существуют, их безопасные эквиваленты:
- gets() -\> fgets() - считывает символы
- strcpy() -\> strncpy() - копирует содержимое буфера
- strcat() -\> strncat() - объединение буферов
- sprintf() -\> snprintf() - заполняет буфер данными разных типов
- (f)scanf() - считывание из STDIN
- getwd() - возвращает рабочий каталог
- realpath() - возвращает абсолютный (полный) путь
- gets() -\> fgets()
- strcpy() -\> strncpy()
- strcat() -\> strncat()
- sprintf() -\> snprintf()
Используйте компиляторы, которые способны идентифицировать небезопасные функции, логические ошибки и проверять, перезаписана ли память, когда и где ее не должно быть.
Автор этого материала - я - Пахолков Юрий. Я оказываю услуги по написанию программ на языках Java, C++, C# (а также консультирую по ним) и созданию сайтов. Работаю с сайтами на CMS OpenCart, WordPress, ModX и самописными. Кроме этого, работаю напрямую с JavaScript, PHP, CSS, HTML - то есть могу доработать ваш сайт или помочь с веб-программированием. Пишите сюда.
Отправляя сообщение я подтверждаю, что ознакомлен и согласен с политикой конфиденциальности данного сайта.