Как правило, перечисленные функции загружают в память всякую динамически подключаемую библиотеку DLL. После загрузки библиотеки можно получить адрес любой из ее функций по имени. Поэтому пока доступна динамически подключаемая библиотека, программисту предоставляется очень гибкий и удобный инструмент написания программ.
Известны два основных способа поиска функции при использовании динамических библиотек. Можно или жестко запрограммировать адреса функций, или найти их в таблице импортируемых символов (таблице перехода) атакованного процесса во время его выполнения.
Программа с жестко запрограммированными адресами функций работает быстро и безошибочно, но, как правило, непереносима с одной платформы на другую. Для Windows NT это означает ограничение работоспособности программы переполнения буфера рамками единственного служебного пакета service pack и составом операционной системы OS combo. В зависимости от используемой платформы и библиотек в UNIX она может вообще не заработать.
Второй способ основан на определении адреса функции по таблице импортируемых символов атакованного процесса во время его выполнения. В этом случае программа работает лучше и переносима на другие платформы, но больше по размеру. В условиях недостатка памяти это серьезный минус, который может привести к непригодности способа. Для поиска адреса функции в управляющем коде должны быть предусмотрены возможности поиска. Лучше найти уже загруженную в память функцию определения адреса функции и использовать ее. Конечно, этот способ предполагает, что функция загружена в память. Часто так и бывает, но вообще это дело случая. Для успешного применения способа необходимо ясно представлять используемый в операционной системе механизм редактирования связей. Для Windows NT он реализован в виде переносимого выполнимого формата PE (portable executable format). Для большинства систем UNIX это выполнимый формат редактирования связей ELF (executable and linking format).
Эти форматы настолько интересны, что наверняка захочется познакомиться с ними подробнее. В них содержатся лаконичные сведения о загруженных процессом компонентах во время редактирования связей. Они позволяют понять, что может выполнимая или совместно используемая библиотека.
Вложенный программный код полезной нагрузки
Один из самых интересных типов программного кода полезной нагрузки известен под названием вложенного программного кода полезной нагрузки (eggshellpayload). Под вложенностью понимается использование одного кода полезной нагрузки внутри другого. Цель подобных манипуляций заключается в использовании программы с незначительными правами и внедренным кодом полезной нагрузки для атаки на привилегированную программу.
Этот способ позволяет с помощью простой программы переполнения буфера сначала переступить одной ногой через порог, а затем с бандой вломиться в дом. Благодаря нему результат достигается с меньшими усилиями и за меньшее время, поскольку нападение комплексное: удаленная атака на непривилегированный процесс объединяется с локальным нападением на привилегированный процесс, образуя разрушительную комбинацию.
Вложенный программный код полезной нагрузки использован в программе IISHACK1.5, которая компрометирует Windows NT Server с установленным информационным сервером Интернет IIS 4. Подробный анализ программы и ее код можно найти в документе www.eeye.com/html/Research/Advisories/AD20001003.html. Для внедрения программой asp-файла на сервер был использован непривилегированный код, реализующий атаку «Unicode». Атака «Unicode» выполняется в пространстве процесса IUSR_MACHINE, который обычно является непривилегированным процессом.
Атака «Unicode» была объединена с атакой переполнения буфера на неназванный синтаксический анализатор. ASP, который выполнялся в контексте LOCAL_SYSTEM. Их комбинация позволила добиться полной компрометации системы.
Переполнение буфера – реальная опасность современных программ. На нем основаны многие из наиболее опасных уязвимостей, которые когда-либо были обнаружены. В главе рассказано о стеке и показано, каким образом его используют современные компиляторы и программы при вызове функций. Далее были исследованы способы построения программ переполнения буфера и выделены их составные части. И наконец, были освещены некоторые современные способы реализации переполнения буфера, позволяющие адаптировать программный код к конкретной ситуации и сделать его более переносимым и работоспособным.
Читать дальше
Конец ознакомительного отрывка
Купить книгу