Переполнение буфера


Ошибки переполнения буфера характеризуются перезаписью фрагментов памяти процесса, которые никогда не должны были изменяться намеренно или непреднамеренно. Перезапись значений 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 - то есть могу доработать ваш сайт или помочь с веб-программированием. Пишите сюда.

тегизаметки, си плюс плюс, ошибки




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




Украина и Яндекс
Роман "Работа во сне": Глава №2
Урок 19. Наследование и конструкторы C#