На рисунке 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.
Рис. 8.13. Дамп стека после вызова функции bof, но до выполнения функции strcpy Рис. 8.14. Дамп стека функции bof
после обращения к функции strcpy, но до инициализации переменной buffer Теперь в дампе стека видны два параметра функции strcpy.
Первый параметр указывает на область буфера, размещенного в стеке, а второй – на статический буфер, вмещающий 20 символов «А».
Рис. 8.15. Дамп стека функции bof после инициализации переменной buffer функцией strcpy (сравните с рис. 8.13)Из дампа стека видно, что функция strcpy,
проинициализировав буфер, уничтожила ранее записанные в стеке данные. В эпилоге функции bof программа, попытавшись восстановить из стека содержимое регистра EBP, загрузит в регистр значение 0x414141. После этого команда ret восстановит из стека содержимое регистра EIP и попытается передать управление по восстановленному адресу. В результате возникнет ошибка нарушения доступа при попытке выполнить неразрешенную операцию с памятью, поскольку команда ret загрузит в регистр EIP значение 0x41414141, указывающее на недействительную область памяти (см. рис. 8.16).
Рис. 8.16. Диагностика аварийного завершения программы из-за неверного содержимого регистров EIP и EBP