
Когда атакующий уже полностью просканировал сеть, он знает обо всех составляющих сети и потенциальных уязвимых местах компьютеров. Теперь требуется получить доступ к системе.
Подход к данной задаче напрямую зависит от знаний атакующего: простые «сценаристы» будут искать ранее разработанные методы взлома, а опытные станут действовать более прагматично.
«Сценарист» подбирает методы взлома
Для того чтобы получить доступ к системе, обычный «сценарист» возьмет результаты работы сканера уязвимых мест и затем зайдет на сайт, где имеются различные программы взлома. Некоторые организации предлагают огромные базы способов взлома, при этом поддерживается поиск, с помощью которого можно найти методы, подходящие для конкретного приложения, операционной системы или обнаруженного уязвимого места.
Существуют различные мнения относительно организаций, распространяющих программы взлома. Некоторые такие компании придерживаются концепции полного раскрытия информации: если атакующие знают об этих программах взлома, то о них должны знать и все остальные, чтобы иметь возможность защититься.
Поставщики информации о методах взлома систем декларируют, что они всего лишь оказывают услугу интернет сообществу. Другие же придерживаются мнения, что подобные методы упрощают атаки, отчего те получают все большее распространение.
В то время как «сценаристы» с помощью поисковых систем находят определенные программы взлома, хотя и не понимают их функций, опытный атакующий воспользуется более сложными методами проникновения в систему. Проанализируем всеохватывающие методы взлома и концепции, лежащие в основе большинства программ.
Третий этап (получение доступа) - огромное поле для творчества опытных атакующих. В то время как другие этапы атаки (исследование, сканирование, сохранение доступа и «заметание следов») обычно весьма систематизированы, методы проникновения в систему практически полностью зависят от архитектуры и конфигурации сети, от проделанной атакующим работы и от его пристрастий, а также от уровня доступа, который он изначально получает. Из-за перечисленных условий более опытные атакующие весьма прагматичны, и на этапе получения доступа они выбирают определенный метод среди множества способов, применимых к особенностям интересующей их сети.
Выше этапы исследования и сканирования были описаны в хронологическом порядке, о каждой тактике рассказывалось в той последовательности, в какой она применяется типичным атакующим. Однако, поскольку проникновение в систему зависит от прагматизма, опыта и навыков хакера, становится понятно: на данном этапе ни о каком порядке и речи быть не может.
Существуют десятки популярных операционных систем и сотни или тысячи различных приложений; как показала практика, у каждой операционной системы и у большинства приложений есть свои недостатки. Однако большая часть этих уязвимых мест может быть атакована при помощи множества популярных схем и методов.
Атаки переполнения стековой памяти
На сегодняшний день атаки переполнения стековой памяти - обычное дело, они предоставляют атакующему возможность проникнуть в систему и получить определенную степень контроля над ней и мобильным трафиком.
У любого плохо разработанного приложения или элемента операционной системы вероятно переполнение стековой памяти. Атакуя уязвимое приложение или операционную систему, хакер способен выполнить на атакуемой машине произвольные команды или получить полный контроль над ней.
Атакующие просто обожают систему, на которой могут выполнить команды. Чтобы понять, как переполнение стековой памяти предоставляет такой вид доступа, необходимо изучить важный элемент в архитектуре большинства современных компьютерных систем - стек.
Что такое стек?
Стек - структура данных, где содержится важная информация для активных процессов. Стек работает наподобие сверхоперативной памяти системы. Система делает необходимые ей пометки, которые нужно сохранить, и помещает их в стек - специальную зарезервированную область памяти.
Стеки похожи на стопку посуды, так как работают по принципу «последним вошел, первым вышел» (Last-In, Firts-Out - LIFO). Когда вы собираете стопку посуды, вы кладете одну тарелку на другую. Когда же требуется убрать тарелку из стопки, сначала вы возьмете верхнюю тарелку - ту, которую вы положили последней. Аналогично, если система помещает данные в стек, элементы стека один за другим перемещаются вниз. При необходимости получить данные из стека система возьмет сначала последний помещенный ею в стек элемент.
Итак, какую же информацию хранит компьютер в стеке? Помимо всего прочего, в стеке содержится информация, связанная с вызовами функций сети. Вызовы функций используются программистами, чтобы разбить программный код на более мелкие части.
Когда начинается работа программы, выполняется процедура main. Первое, что делает эта процедура, - вызывает функцию sample_function. Теперь реализация программы представляет собой взаимодействие процедуры main и функции sample_function. Система должна запомнить, когда приостановилось выполнение процедуры main, потому что после завершения sample_function программа к ней вернется. Стек помогает организовать переход к функции и возврат к процедуре.
Система помещает в стек различные элементы данных, связанные с вызовом функции. Сначала система сохранит аргументы вызова функции - любые данные, передаваемые из процедуры main функции. Для простоты в нашем примере нет никаких аргументов. Затем система отправит в стек указатель возврата, который определяет место памяти, где содержится продолжение процедуры main.
Программа представляет собой набор битов в памяти компьютера, группу команд для процессора. У процессора есть регистр (небольшая часть быстродействующей памяти процессора), который называется указателем команды (instruction pointer): он обозначает команду, которую должен выполнить процессор.
Во время выполнения программы значение этого указателя постепенно возрастает, переходя от одной команды программы к другой, а при вызове функции увеличивается на определенное число. Для вызова процедуры система должна запомнить величину указателя команды, чтобы знать, к какой части процедуры main возвращаться после завершения функции. Указатель команды записывается в стек в качестве указателя возврата.
Затем система отправляет в стек указатель фрейма. Данная величина позволяет обращаться к различным элементам стека. И наконец, в стеке освобождается место для переменных, которые могут использоваться функцией. В нашем примере в стек была помещена локальная переменная buffer. Такие локальные переменные применяются только функцией, которая хранит в них данные и обрабатывает их значения.
После завершения работы функции (в нашем примере на печать будет выведено радостное сообщение) контроль возвращается к основной программе. При этом из стека убираются локальные переменные (в примере - переменная buffer). Для большей эффективности часть памяти, выделенная под переменные, не стирается. Данные убираются из стека, при этом значение указателя стека изменяется - теперь оно становится равным значению, которое было до вызова функции.
Сохраненный указатель фрейма также удаляется из стека и передается процессору. Затем из стека извлекается указатель возврата, который помещается в регистр указателя команды процессора. И наконец, удаляются аргументы вызова функции, стек возвращается в исходное положение. С этого момента система продолжает выполнение процедуры main - так требует указатель команды.
Что такое переполнение стековой памяти?
Теперь, когда вы получили представление о том, как система взаимодействует со стеком, разберем, как атакующий пользуется этой возможностью. Рассмотрим, например, переполнение буфера - эта процедура похожа на попытку налить десять литров воды в ведро, которое рассчитано только на пять. Понятно, что часть воды просто выльется. Проанализируем пример программы.
Основой программы является создание массива, содержащего 255 копий символа «А», затем этот массив передается функции sample_function. В функции массив big_buf f ег обрабатывается как символьная строка, создается переменная 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 - просто небольшая программа, с помощью которой различные приложения выполняют определенные задачи. Наиболее эффективно при переполнении буфера вызвать wininet.dll: она позволяет атакующему отправлять запросы и получать ответы из сети, чтобы загрузить дополнительный код или отыскать команды, которые нужно выполнить.
Атаки переполнения буфера сильно зависят от процессора и операционной системы, так как «сырой» машинный код будет обрабатываться только конкретным процессором, а методы выполнения команд в разных операционных системах отличаются.
Методы переполнения стековой памяти
Все это звучит великолепно, но как с помощью описанного метода действительно проникнуть в систему? Запомните, что большаая часть полезных, современных программ написаны с использованием вызова функций, при этом некоторые программы не осуществляют надлежащей проверки объема информации при обработке локальных переменных.
Пользователь просто вводит данные в программу либо посредством графического пользовательского интерфейса, либо с помощью интерфейса командной строки, либо просто вписывая аргументы в командную строку. Для программ, к которым подключаются по сети, данные вводятся через открытые порты; эти данные обычно отформатированы надлежащим образом, в них включены специальные поля, которые ищет программа.
Чтобы вызвать переполнение буфера, атакующий будет вносить данные в программу посредством графического интерфейса или командной строки либо отправит пакеты в специальном формате, причем машинный код и указатель возврата содержатся в отдельном пакете.
Если атакующий передаст правильный код с правильно отформатированным указателем возврата и переполнит буфер, то функция в программе скопирует данные в стек, а затем выполнит код атакующего. Поскольку для программы все должно быть отформатировано надлежащим образом, создание новых программ по переполнению буфера отнюдь не легкий процесс.
Автор: pimka21
Еще советуем: