Системный шпионаж в *nix часть 2 Крис Касперски ака мыщъх Хакер, номер #088, стр. 088-098-1 Шприц для *nix или функции на игле Внедриться в адресное пространство чужого процесса — проще простого! В первой части статьи мы показали, как сконструировать универсальный шприц. Теперь остается только замутить тот магический раствор, который будет введен внутрь чужеродных клеток машинного кода. Навстречу нашей вакцине тут же устремится батальон иммунных тел, готовых сожрать ее в один момент (и ведь сожрут же!). Но мыщъх знает один хитрый рецепт... [введение] Классический алгоритм внедрения shell-кода выглядит так: сохраняем несколько байт перехватываемой функции и ставим jump на свой thunk, который делает, что задумано, выполняет сохраненные байты и передает управление оригинальной функции, которая может вызываться как по jump, так и по call (подробнее этот вопрос рассмотрен в статье «Капитуляция защитных механизмов», опубликованной в Хакере). Самое сложное — выбрать место для размещения thunk'а. Это должна быть память, доступная всем процессам, а такой памяти в нашем распоряжении нет! Мы знаем, что «подопытная» библиотека претендует на адресное пространство каждого процесса, но это пространство уже занято! Наскрести пару десятков байт, отведенных под выравнивание, вполне реально, только нам этого не хватает! Приходится хитрить. Прежде всего мы можем разместить код перехватчика в какой-нибудь «ненужной» функции, например gets, а в начало всех перехватываемых функций внедрить... нет, не jump (в этом случае перехватчик не сможет определить, откуда пришел вызов), а call gets! Внутри gets перехватчик выталкивает из стека адрес возврата, уменьшает его на длину команды call (в 32-разрядном режиме — 5 байт) и получает искомый указатель на функцию. Зная указатель, можно определить имя функции — в этом нам поможет функция dladdr из GNU Extensions. В POSIX она не входит, но поддерживается практически всеми *nix'ами, так что на этот счет можно не волноваться. Примечание: напоминаем, что при внедрении в gets, равно как и любую другую функцию, мы можем пересекать границы страниц, поскольку за концом текущей страницы наверняка находится совсем посторонняя область памяти! Если же возникает необходимость модифицировать функцию gets целиком, то необходимо найти все принадлежащие ей страницы тем же самым методом, которым мы нашли первую из них. Проблема в том, что dladdr находится в библиотеке libdl.x.so, которой может и не быть в памяти конкретно взятого процесса, а если она там есть, то неизвестно, по какому адресу загружена. Некоторые хакеры утверждают, что в thunk-коде можно использовать только прямые вызовы ядра через интерфейс INT 80h, а все остальные функции недоступны. На самом деле это не так! Как показывает дизассемблер, dladdr - это всего лишь «обертка» вокруг _dl_addr, реализованной в libc.so.x, а она-то доступна наверняка! Вот только на базовый адрес загрузки закладываться ни в коем случае нельзя, и вызов должен быть относительным. Простейшая подпрограмма генерации относительного вызова выглядит так: Подпрограмма генерирует относительный вызов и помещает его в глобальный буфер buf_code, lib_name — имя хакаемой библиотеки, from — имя функции, из которой будет осуществляться вызов (например, gets), to — имя функции, которую нужно вызывать, например write, delta — смещение инструкции call от начала thunk-кода: |