Издательский дом ООО "Гейм Лэнд"СПЕЦВЫПУСК ЖУРНАЛА ХАКЕР #45, АВГУСТ 2004 г.

Живучий код

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

Спецвыпуск Xakep, номер #045, стр. 045-064-2


При условии, что shell-код расположен в стеке (при переполнении автоматических буферов он оказывается именно там), мы можем использовать регистр ESP в качестве базы, однако текущее значение ESP должно быть известно, а известно оно далеко не всегда. Для определения текущего значения регистра указателя команд достаточно сделать near call и вытащить адрес возврата командой pop. Обычно это выглядит так:

Определение расположения shell-кода в памяти

00000000: E800000000 call 000000005 ; закинуть EIP+sizeof(call) в стек

00000005: 5D pop ebp ; теперь в регистре ebp текущий eip

Приведенный код не свободен от нулей (а нули в shell-коде в большинстве случаев недопустимы), и, чтобы от них избавиться, call необходимо перенаправить "назад":

Освобождение shell-кода от паразитных нулевых символов

00000000: EB04 jmps 000000006 ; короткий прыжок на call

00000002: 5D pop ebp ; ebp содержит адрес, следующий за call

00000003: 90 nop ; \

00000004: 90 nop ; +- актуальный shell-код

00000005: 90 nop ; /

00000006: E8F7FFFFFF call 000000002 ; закинуть адрес следующей команды в стек

Жесткая привязка

Нет ничего проще вызова API-функции по абсолютным адресам. Выбрав функцию (пусть это будет GetCurrentThreadId, экспортируемая KERNEL32.DLL), мы пропускам ее через утилиту dumpbin, входящую в комплект поставки практически любого компилятора. Узнав RVA (Relative Virtual Address – относительный виртуальный адрес) нашей подопечной, мы складываем его с базовым адресом загрузки, сообщаемым тем же dumpbin'ом, получая в результате абсолютный адрес функции.

Полный сеанс работы с утилитой выглядит так:

На моей машине абсолютный адрес функции GetCurrentThreadId равен 77E876A1h, но в других версиях Windows NT он наверняка будет иным. Зато ее вызов свободно укладывается всего в две строки, соответствующие следующим семи байтам:

Прямой вызов API-функции по абсолютному адресу

00000000: B8A1867E07 mov eax,0077E86A1

00000005: FFD0 call eax

Теперь попробуем вызвать функцию connect, экспортируемую ws2_32.dll. Пропускаем ws2_32.dll через dumpbin и... Стоп! А кто нам вообще обещал, что эта динамическая библиотека окажется в памяти? А если даже и окажется, то не факт, что базовый адрес, прописанный в ее заголовке, совпадает с реальным базовым адресом загрузки. Ведь динамических библиотек много, и, если этот адрес уже занят, операционная система загрузит библиотеку в другой регион памяти.

Лишь две динамические библиотеки гарантируют свое присутствие в адресном пространстве любого процесса, всегда загружаясь по одним и тем же адресам (базовый адрес загрузки этих динамических библиотек постоянен для данной версии операционной системы). Это KERNEL32.DLL и NTDLL.DLL. Функции, экспортируемые остальными библиотеками, правильно вызывать так:

Назад на стр. 045-064-1  Содержание  Вперед на стр. 045-064-3