ТехноСаратов → Блог

Переполнение буфера. Часть вторая.БлогПрограммирование

В этой статье названы основные цели, которые преследуют злоумышленники при переполнении буферов и рассматривается реализация и противостояние первой из них – изменение значений локальных переменных.r/>
Цели

Переполнение буфера можно использовать в следующих целях:

1.Изменение значения локальных переменных функции
2.Чтение памяти из адресного пространства процесса
3.Управление ходом программы (вызов любых функций программы)
4.Внедрение собственного кода и исполнение его от имени программы

Изменение значения локальных переменных

Проще всего, при переполнении буфера, изменить локальные переменные функции находящиеся выше верхнего предела буфера. Простейший пример:

Функция gets с легкостью переполняет массив buf, если пользователь вводит более 10 символов. Если стек выровнен для большей скорости выборки, то от его последнего элемента до переменной int x будет n-ное количество байт, которое можно определить эмпирически:

$./a.exe
1234567890123456
int x = 11
.
.
.
$./a.exe
1234567890123456789012345678
int x = 0

Запас прочности буфера истек 😉 Такая программа будет работать нестабильно, а намеренное изменение значения переменной int x на столько легко, что превращается просто в рутину.

А ведь локальные переменные могут иметь разные назначения. Например, они могут хранить пароли:

$ ./a.exe
enter password: mmm
(pass: MYXMY)
false

$ ./a.exe
enter password: MYXMY
(pass: MYXMY)
true

$ ./a.exe
enter password: mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm
(pass: mmmmmmmmmmmmmmmm)
false

Ага! Вот мы и поймали, выравнивание на 16 байт. То есть через 16 байт от начала вводимого пароля лежит настоящий пароль. Значит мы должны передать на вход программы строку:

mmmmmmmmmmmmmmm(тут бинарный ноль)mmmmmmmmmmmmmmm(и тут тоже)

Для создания такого файла достаем свой HEX-редактор, чтобы вставлять 0x00 в нужные места. Сохраняем его, скажем, в файл in.txt и запускаем нашу подопытную программу так:

$ ./a.exe enter password: (pass: mmmmmmmmmmmmmmm)
true

Что и требовалось доказать.

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

Как бороться?

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

Соответственно при копировании из одного буфера в другой следует использовать strncpy вместо strcpy.

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

Менее очевидное, но более корректное решение – вводить строки с помощью функции getline:

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

Присутствие этой функции гарантировано только в glibc, и если она вам недоступна, то ее можно реализовать самостоятельно, или содрать у дяди Столмена.

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