Издательский дом ООО "Гейм Лэнд"ЖУРНАЛ ХАКЕР #88, АПРЕЛЬ 2006 г.

Системный шпионаж в *nix часть 2

Крис Касперски ака мыщъх

Хакер, номер #088, стр. 088-098-3


Программировать в машинных кодах очень неудобно, и возникает естественное желание задействовать Си и другие языки высокого уровня. И это возможно! Поскольку thunk-код вызывается в контексте вызывавшего его процесса, он может загружать свои собственные динамические библиотеки, вызывая dlopen/dlsym. На машинном коде пишется лишь крохотный загрузчик, а основной код перехватчика сосредотачивается в динамической библиотеке, которую можно написать и на Си.

Кстати говоря, отказываться от функции gets совершенно необязательно, и мы можем перенести ее функционал в нашу динамическую библиотеку! Только переносить необходимо именно функционал (то есть переписывать функцию заново), а не пытаться копировать код, ведь gets вызывает "свои" подфункции по относительным адресам. При перемещении ее тела на другое место они изменятся и... здравствуй, segmentation fault!

[перехват функций не во сне, а наяву]

Самое сложное в перехвате — это определить границы машинных инструкций, поверх которых записывается команда перехода на перехватчик (он же thunk, расположенный в нашем случае в теле функции gets). По-хорошему, для решения этой задачи требуется написать мини-дизассемблер, но... это же сколько всего писать придется! А можно ли без него обойтись? Можно!

В начале большинства библиотечных функций расположен стандартный пролог вида PUSH EBP/MOV EBP,ESP/SUB ESP,XXXh (55h/89h E5h/ 83h ECh XXh), дающий нам пять байт — необходимый минимум для внедрения! Встречаются и другие, слегка видоизмененные прологи, например: PUSH EBP/MOV EBP,ESP/PUSH EDI/PUSH ESI (55h/89h E5h/ 57h/ 56h); PUSH EBP/MOV EAX, 0FFFFFFFFh/MOV EBP, ESP (55h/B8h FFh FFh FFh FFh/89h E5h); PUSH EBP/XOR EAX, EAX/MOV EBP,ESP (55h/31h C0h/89h E5h). Хороший перехватчик должен их учитывать.

Таким образом, наш перехватчик должен проверить первые 5 байт перехватываемой функции и, если они совпадают со стандартным (или слегка оптимизированным) прологом, скопировать этот пролог в свое тело и выполнить его перед передачей управления оригинальной функции. А куда его можно скопировать? Сегмент данных, как уже говорилось, нам недоступен, стек трогать нельзя (перед передачей управления на функции он должен быть восстановлен), а сегмент кода запрещен для модификации.

Существует по меньше мере три решения: во-первых, мы можем вызывать функцию mprotect, присвоив кодовой странице атрибут writable (но это некрасиво), во-вторых, трогать стек все-таки можно: забрасываем пролог на верхушку, забрасываем туда же копию всех аргументов (а сколько у функции аргументов? неизвестно, вот и приходится копировать с запасом) и передаем ей управление как ни в чем не бывало. В-третьих, мы можем поступить так:

Фрагмент программы-инсталлятора, анализирующей пролог перехватываемой функции и устанавливающей обработчик с соответствующим прологом:

// "коллекция" разнообразных прологов для сравнения

Назад на стр. 088-098-2  Содержание  Вперед на стр. 088-098-4