Теперь, когда вы получили представление о том, как система взаимодействует со стеком, разберем, как атакующий пользуется этой возможностью. Рассмотрим, например, переполнение буфера - эта процедура похожа на попытку налить десять литров воды в ведро, которое рассчитано только на пять. Понятно, что часть воды просто выльется. Проанализируем пример программы, который предлагает Алеф Уан в своей статье «Разрушение стека: для удовольствия или извлечения прибыли».
Основой программы является создание массива, содержащего 255 копий символа «А», затем этот массив передается функции sample_function. В функции массив big_buffег обрабатывается как символьная строка, создается переменная buffer, в которую можно поместить 16 символов. Потом встречается процедура strcpy, в процессе выполнения которой информация копируется из одной строки символов в другую.
В программе примера strcpy переместит символы из строки в переменную buffer. К сожалению, процедура strcpy небрежно выполняет свою работу: так, перед началом копирования она не проверяет длину символьной строки, а просто копирует символы из одной строки в другую до тех пор, пока не дойдет до нуль-символа исходной строки. Нуль-символ представляет собой байт с нулевым кодом, что обычно означает конец строки.
Такое ограничение strcpy хорошо известно и присуще многим функциям обычной библиотеки языка С, в особенности функциям по обработке символов. При создании big_buffer в конец не был помещен нуль-символ, также была создана строка более длинная, чем может выдержать переменная buffer (256 символов по сравнению с 16 символами).
Плохо! Ведь система разрешает процедуре strcpy записывать данные до тех пор, пока не встретится нуль-символ, а в нашем примере он не встретится! Это одна из серьезных проблем компьютеров: они делают в точности то, что им говорят, ни больше, ни меньше.
Что же произойдет со стеком при выполнении такой партнерской программы? Возникнет путаница. Символ «А» выйдет за пределы места, выделенного под переменную buffer, заместит сохраненный указатель фрейма и даже указатель возврата. То есть указатель возврата будет заменен набором символов «А».
Когда программа завершит выполнение функции, она удалит из стековой памяти локальные переменные и сохраненный указатель фрейма, а также указатель возврата (вместе со всеми символами «А»), указатель возврата будет скопирован в указатель команд процессора, компьютер попытается извлечь из памяти следующую команду, причем станет искать ее на том месте, которое соответствует двоичному представлению символа «А». Скорее всего, такого места в памяти просто не окажется, и работа программы будет нарушена.
Итак, теперь вы знаете, как написать программу, которая завершится аварийно. Вы можете подумать: «Хе-хе, во многих создаваемых мною программах и без того часто происходят сбои». По крайней мере, так случается с моими программами.
Но посмотрим на это более пристально. Хотя загрузка символов «А» в буфер может привести к аварийному отказу программы, что случится, если заполнить буфер более полезной информацией? Допустимо поместить в буфер машинный код команды, которые вы хотите выполнить. Но как заставить систему выполнить их?
Помните, что когда заполняется место, отведенное под локальные переменные, замещается указатель возврата. Итак, переполнив буфер, легко изменить указатель возврата, чтобы теперь он содержал адрес, где начинаются нужные вам команды. В итоге получится ситуация - атака переполнения стековой памяти сети, которая позволит выполнять произвольные команды в системе.
Рассмотрим, как работает разрушенный стек. Атакующий заставляет программу расположить в локальных переменных данные, размер которых больше, чем там может находиться. Данные представляют собой машинный код. Но этим дело не заканчивается. Система продолжает замещать данными указатель возврата, на его место приходят данные, которые указывают на команды, загруженные в стек.
Когда функция завершается, локальные переменные, расположенные в стеке, перестают использоваться, но не удаляются. Затем процессору сообщается значение указателя возврата, и система запускает те команды, которые адресует указатель. Следовательно, процессор будет выполнять команды, которые атакующий загрузит в стековую память буфера. Вот так атакующий заставляет программу реализовывать команды из стека.
Вся проблема состоит в том, что функция не проверяет размер информации, которую хотят поместить в локальную переменную. Без тщательной проверки функция может выйти за пределы дозволенного, то есть за пределы отведенного ей пространства. По существу, атаки переполнения стековой памяти - результат некачественного программирования, когда не проверяется объем данных, помещаемых в локальные переменные, либо когда используются шаблонные функции, разработанные столь же небрежным программистом.
Теперь, понимая, как атакующий располагает команды в стековой памяти и каким образом заставляет программу их выполнять, рассмотрим, какие именно команды будут помещены в буфер. В UNIX, вероятно, самый лучший вариант - заставить компьютер запустить командную оболочку, поскольку командная оболочка (например, /bin/sh) позволяет выполнять любые другие команды.
Для этого в стек лучше внести машинный код, при котором будет активизирована командная оболочка /bin/sh (с помощью системного вызова execve). После ее запуска атакующий может автоматически выполнить некоторые специальные команды, инициировать любую программу или сделать системный вызов. Атакующие с помощью переполнения буфера часто стараются активизировать динамически подсоединяемую библиотеку (Dynamic Link Library - DLL), чтобы достичь своей цели. DLL - просто небольшая программа, с помощью которой различные приложения выполняют определенные задачи.
Наиболее эффективно при переполнении буфера в старой Windows NT/2000 вызывал wininet.dll: она позволяет атакующему отправлять запросы и получать ответы из сети, чтобы загрузить дополнительный код или отыскать команды, которые нужно выполнить.
Атаки переполнения буфера сильно зависят от процессора и операционной системы, так как «сырой» машинный код будет обрабатываться только конкретным процессором, а методы выполнения команд в разных операционных системах отличаются. Стратегия атаки должна соответствовать процессору и типу операционной системы на атакуемом компьютере.
Методы переполнения стековой памяти
Все это звучит великолепно, но как с помощью описанного метода действительно проникнуть в систему? Запомните, что большая часть полезных, современных программ написаны с использованием вызова функций, при этом некоторые программы не осуществляют надлежащей проверки объема информации при обработке локальных переменных.
Пользователь просто вводит данные в программу либо посредством графического пользовательского интерфейса, либо с помощью интерфейса командной строки, либо просто вписывая аргументы в командную строку. Для программ, к которым подключаются по сети, данные мобильного трафика вводятся через открытые порты; эти данные обычно отформатированы надлежащим образом, в них включены специальные поля, которые ищет программа.
Чтобы вызвать переполнение буфера, атакующий будет вносить данные в программу посредством графического интерфейса или командной строки либо отправит пакеты в специальном формате, причем машинный код и указатель возврата содержатся в отдельном пакете. Если атакующий передаст правильный код с правильно отформатированным указателем возврата и переполнит буфер, то функция в программе скопирует данные в стек, а затем выполнит код атакующего.
Поскольку для программы все должно быть отформатировано надлежащим образом, создание новых программ по переполнению буфера отнюдь не легкий процесс.
Автор: pimka21
Еще советуем: