При записи 16 байт в область буфера программы, представленной на рис. 8.1, он становится полным. При записи 17 байт один байт записывается в область стека, предназначенную для хранения переменной int2. Произошло искажение, или порча, данных (data corruption). При всех последующих обращениях к переменной int2 будет получено ее неверное значение. Продолжая в том же духе, при записи в буфер 28 байт будет затерто ранее сохраненное в стеке значение регистра EBP, а при записи 32 байт – значение регистра EIP. В результате при выполнении команды возврата из функции ret в регистр EIP будет записано значение из стека (затертое при записи в буфер) и, интерпретируя записанное значение как адрес следующей выполняемой команды, будет передано управление по содержимому регистра EIP. Если в регистр EIP будет помещен указатель на программу, то она будет выполнена.
Языку С может быть приписано следующее высказывание: «Мы предлагаем достаточно веревки, чтобы повеситься». Другими словами, язык С предлагает мощные средства управления компьютером, которыми программист должен разумно пользоваться, избегая потенциальных проблем. C – язык, в котором чрезмерно строго не контролируются типы обрабатываемых данных (loosely typed language), поэтому в нем не предусмотрено каких-либо мер безопасности при обработке разнотипных данных. Часто в написанных на языке C программах происходит переполнение буфера из-за допущенных ошибок при обработке строк. В таблице 8.1 приведены некоторые из небезопасных функций обработки строк языка C. Эта таблица ни в коем случае не претендует на полное освещение всех проблематичных функций, но дает хорошее представление относительно наиболее часто используемых.
Таблица 8.1.
Примеры проблематичных функций языка С
В следующей секции приведен пример программы, в которой при обработке входных данных большого размера происходит переполнение буфера. Позднее будут внимательно рассмотрены варианты использования уязвимых к переполнению буфера программ для выполнения нужного программного кода.
Простое неуправляемое переполнение: программа-пример
На рисунке 8.10 приведен простой пример неуправляемого переполнения. Он не годится для использования на практике, но полезен для изучения. Программа демонстрирует наиболее типичную ошибку программирования, которая отражается на работоспособности программы. В программе вызывается специально написанная для примера функция bof(), которая записывает двадцатисимвольную строку в буфер, предназначенный для хранения 8 байт. В результате происходит переполнение буфера. Заметьте, что в главной функции main функция printf() никогда не будет вызвана, поскольку в результате переполнения буфера при завершении функции bof () управление будет передано по неверному адресу возврата. Приведенная на рис. 8.10 программа была скомпилирована как консольное приложение Windows в режиме построения окончательной версии Release.
Рис. 8.10. Простое неуправляемое переполнение в стеке
Дизассемблирование
Хорошо раскрывают суть предыдущей программы результаты ее дизассемблирования, приведенные на рис. 8.11. Обратите внимание на то, что в функции main() стековые переменные не создаются, а переменная буфера buffer в функции bof() используется без предварительной инициализации. Определение переменной без инициализации уже может стать источником проблем и потенциальной возможности переполнения буфера. Это целиком зависит от состояния стека в момент создания переменной и ее использования в дальнейшем. Для инициализации переменных рекомендуется использовать функции memset() или bzero().
Рис. 8.11. Дизассемблированный вид программы-примера неуправляемого переполнения
Дампы стека
Приведенные дампы стека прослеживают изменения в стеке программы вплоть до возникновения переполнения буфера. Хотя в этой секции не рассматривается вопрос использования содержимого регистра EIP в личных целях, представленной на рис. 8.12 информации достаточно для того, чтобы позднее выполнить его.
Рис. 8.12. Дамп стека до вызова функции
bof()в программе
main()
Перед началом работы программы main() в стеке сохранены только значения регистров EBP и EIP, поскольку в программе main() нет локальных переменных.
На рисунке 8.13 показан дамп стека после начала работы функции bof(), но до инициализации переменной buffer функцией strcpy(). Поскольку буфер еще не проинициализирован, то в отведенной для него области памяти находятся случайные значения, которые ранее хранились в стеке. Дамп стека функции bof() после обращения к функции strcpy(), но до инициализации переменной buffer показан на рис. 8.14.
Читать дальше
Конец ознакомительного отрывка
Купить книгу