Использование того, что у вас есть
Даже простые программы часто загружают в память больше программных модулей, чем им действительно нужно. При установке связи с динамически подключаемой библиотекой программа определяет, когда загружать библиотеку: при запуске программы или во время ее выполнения. К сожалению, при использовании динамически подключаемой библиотеки DLL или совместно используемой библиотеки в системе UNIX в память загружается программный код всей библиотеки, а не только необходимые функции. Это означает, что в программу включается не только необходимый программный код, но и масса дополнительных функций. Современные операционные системы и мощные компьютеры не видят в этом ничего плохого, поскольку лишний программный код никогда не будет выполнен и, следовательно, он не окажет никакого воздействия на работу программы.
Но у злоумышленника другое отношение к дополнительному никогда не выполняющемуся коду. Для него он может оказаться чрезвычайно полезным. Его можно использовать для поиска не только точек перехода, но и уже загруженных в память полезных битов и фрагментов программного кода. При условии частой загрузки динамически подключаемых библиотек можно использовать загруженные неиспользуемые функции.
Статическая компоновка библиотек может уменьшить количество добавляемого в программу выполнимого кода до минимума, но на практике это часто не делается. Подобно библиотекам динамической связи, статические библиотеки обычно содержат большой объем программного кода на все случаи жизни, увеличивая непроизводительные издержки. Поэтому компоновка программы с использованием статических библиотек в большинстве случаев также приводит к избыточному программному коду.
Например, если библиотека kernel32.dll загружена, то можно использовать любую ее функцию, даже не используемую явно программой. Функцию можно использовать, потому что она, как и все другие компоненты библиотеки, уже загружена в память. Другими словами, при установлении связи с любой динамически подключаемой библиотекой DLL загружается гораздо больше программного кода, чем это кажется на первый взгляд.
Другой пример использования имеющегося под рукой кода относится к UNIX-системам. Речь идет о трюке, который использовался исследователями безопасности для преодоления защиты ранних патчей ядра Linux и модификаций ядра в рамках проекта PAX. Впервые этот трюк применила Solar Designer. Он заключался в записи в стек сначала параметров функции execve, а затем подмены хранимого в стеке содержимого регистра EIP на адрес функции execve. Стек оказывался настроен таким же образом, как и при вызове функции execve. По завершении функции команда ret восстанавливала подмененное содержимое регистра EIP и передавала управление на функцию execve. Следовательно, при подозрении взлома защиты выполнения программ из стека можно заблокировать выполнение программ из стека.
Загрузка новых динамически подключаемых библиотек
Наиболее современные операционные системы поддерживают концепцию совместно используемых библиотек. Они предназначены для уменьшения расхода памяти и многократного использования кода. Уже упоминалось о возможности использования в своих интересах программного кода, загруженного в память, но иногда может потребоваться то, что еще не загружено.
Аналогично обычной программе, программный код полезной нагрузки может при необходимости загрузить динамическую библиотеку и использовать ее функции, как это было показано в примере программы переполнения буфера для Windows NT.
В Windows NT есть пара функций, которыми всегда может воспользоваться программа: LoadLibrary() и GetProcAddress(). Они позволяют загрузить любую динамически подключаемую библиотеку DLL и вызвать функцию. В системе UNIX для этих целей служат функции dlopen() и dlsym().
Перечисленные функции делятся на две группы: функции загрузки библиотеки и функции определения адреса экспортируемой функции. Краткое пояснение каждой функции позволит лучше понять их предназначение.
Функции загрузки библиотеки LoadLibrary() или dlopen() загружают совместно используемую часть кода в доступную программе память. Совсем не обязательно, что загружаемый код будет выполняться, но после загрузки он доступен для использования. В основном загружаемый код впоследствии выполняется.
Функции GetProcAddress() и dlsym() определяют в таблице функций динамически подключаемой библиотеки адрес экспортируемой функции. Для поиска в таблице функций используются символические имена и, возможно, необязательные порядковые целые числа – индексы. Входным параметром этих функций является имя искомой функции или ее индекс, а выходным – адрес искомой функции.
Читать дальше
Конец ознакомительного отрывка
Купить книгу