А теперь посмотрим, что происходит, когда буфер buf переполняется. Сразу вслед за buf находится сохраненное значение регистра EBP (Extended Base Pointer – расширенный указатель на базу). ЕВР содержит указатель кадра стека; при ошибке на одну позицию его значение будет затерто. Если противник сможет получить контроль над областью памяти, начинающейся с адреса 0x0012fe00 (последний байт вследствие ошибки обнулен), то программа перейдет по этому адресу и выполнит помещенный туда противником код.
Если не ограничиваться переполнением на один байт, то следующим будет затерт адрес возврата. Коль скоро противник сумеет получить контроль над этим значением и записать в буфер, адрес которого известен, достаточное число байтов ассемблерного кода, то мы будем иметь классический пример переполнения буфера, допускающего написание эксплойта. Отметим, что ассемблерный код (его обычно называют shell–кодом, потому что чаще всего задача эксплойта – получить доступ к оболочке (shell)) необязательно размещать именно в перезаписываемом буфере. Это типичный случай, но, вообще говоря, код можно внедрить в любое место вашей программы. Не обольщайтесь, полагая, что переполнению подвержен только очень небольшой участок.
После того как адрес возврата переписан, в распоряжении противника оказываются аргументы атакуемой функции. Если функция перед возвратом каким–то образом модифицирует переданные ей аргументы, то открываются новые соблазнительные возможности. Это следует иметь в виду, оценивая эффективность таких средств борьбы с переполнением стека, как программа Stackguard Криспина Коуэена (Crispin Cowan), программа ProPolice, распространяемая IBM, и флаг /GS в компиляторе Microsoft.
Как видите, мы предоставили противнику как минимум три возможности получить контроль над нашим приложением, а это ведь была очень простая функция. Если в стеке объявлен объект класса С++ с виртуальными функциями, то станет доступна таблица указателей на виртуальные функции; такая ошибка тоже легко эксплуатируется. Если одним из аргументов функции является указатель на функцию, что часто бывает в оконных системах (например, в X Window System или Microsoft Windows), то перезапись этого указателя перед использованием–очевидный способ получить контроль над приложением.
Есть множество хитроумных способов перехватить управление программой, гораздо больше, чем способен измыслить наш слабый ум. Существует несоответствие между возможностями и ресурами, доступными разработчику и хакеру. В своей работе вы ограничены сроками, тогда как противник может тратить все свое свободное время на то, чтобы придумать, как заставить вашу программу делать то, что нужно ему. Ваша программа может защищать ресурс, достаточно ценный, чтобы потратить на ее взлом несколько месяцев. Хакер тратит массу времени на то, чтобы быть в курсе последних достижений в области взлома. К его услугам – такие ресурсы, как www.metasploit.com, позволяющие в несколько «кликов» создать shell–код, который будет делать что угодно и при этом включать только символы из ограниченного набора.
Если вы попытаетесь выяснить, можно ли создать эксплойт для какой–то программы, то, скорее всего, полученный ответ будет неполным. В большинстве случае можно лишь доказать, что программа либо уязвима, либо вы недостаточно хитроумны (или потратили на поиск решения недостаточно времени), чтобы написать для нее эксплойт. Очень редко можно с уверенностью утверждать, что для некоторого переполнения эксплойт невозможен.
Мораль, стало быть, в том, что самое правильное – исправить ошибки! Сколько раз случалось, что модификации с целью «повысить качество кода» заодно приводили и к исправлению ошибок, связанных с безопасностью. Автор как–то битых три часа убеждал команду разработчиков исправить некую ошибку. В переписке приняло участие восемь человек, и мы потратили 20 человекочасов (половина рабочей недели одного программиста), споря, нужно ли исправлять ошибку, поскольку разработчики жаждали получить доказательства того, что для нее можно написать эксплойт. Когда эксперты по безопасности доказали, что проблема действительно есть, для исправления потребовался час работы программиста и еще четыре часа на Тестирование. Сколько же времени ушло впустую!
Заниматься анализом надо непосредственно перед поставкой программы. На завершающих стадиях разработки хорошо бы иметь обоснованное предположение о том, достаточно ли велика опасность написания эксплойта для ошибки, чтобы оправдать риск, связанный с переделками и, как следствие, нестабильностью продукта.