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

Пишем shell-код!

Коваленко Дмитрий aka Ingrem

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


(ingrem@list.ru)

Принципы создания shell-кода и связанные с этим проблемы

Вступление

Привет! Сегодня я расскажу о том, как грамотно писать shell-код. Допустим, ты нашел уязвимость. Устроил DoS? Весело, но настоящий хакер стремится не к разрушению, а к контролю, который можно получить только с помощью добротного shell-кода. Однако сделать это не так-то просто. Давай поговорим об этом подробнее.

Основные проблемы при написании shell-кода

Как ты уже знаешь, shell-код – это набор машинных инструкций, который переполняет буфер атакуемого процесса. При этом shell-код прикидывается чем-то безобидным, например, слишком длинной строкой. Он затирает память, расположенную сразу за буфером, изменяя данные, которые там находятся. Это меняет логику программы и позволяет shell-коду получить управление. Shell-коды обычно классифицируют в зависимости от того, где находится переполняемый буфер – в стековой памяти, в куче или в секции данных. Иногда ещё shell-коды различают по данным, которые затираются (адрес возврата из функции, указатель на функцию, указатель на класс и т.п.). Несмотря на это основные принципы написания shell-кодов всегда одинаковы. Shell-код чем-то похож на вирус: в большинстве случаев неизвестно, по какому адресу в памяти он окажется. Поэтому хакер, который пишет shell-код, обычно сталкивается с несколькими проблемами. Первая заключается в том, что для безусловных переходов внутри shell-кода нельзя использовать "дальних" вариантов инструкций jmp и call, потому что в качестве аргумента в них выступает абсолютный адрес. Но это не так уж страшно, shell-код обычно маленький и редко требует "дальних" переходов. Вторая проблема – локальные переменные. Но она тоже решается довольно просто. Если shell-код переполняет буфер в стеке, то на его голову обычно указывает esp, так что все переменные можно адресовать с помощью смещения:

Листинг

shell_code_start:

; занесем в eax лок. переменную var_1

mov eax, [esp+var_1-shell_code_start]

......

var_1 dd 0

......

Если же shell-код находится в куче, то, получив управление, он может провернуть старый вирусный фокус:

Листинг

shell_code_start:

call $+5

pop ebp

sub ebp, 5 ; теперь в ebp - адрес shell_code_start

......

После этого он может обращаться к локальным переменным по смещению относительно ebp. Третья проблема – самая сложная. Как вызывать из shell-кода API? Ведь сначала нужно как-то узнать их адреса. Но каким образом? "Законно" shell-код эти адреса получить не может (хотя бы потому, что у него нет таблицы импорта). Поэтому нам остаются только способы "незаконные". Из них наиболее распространены два. Первый: запомнить адреса API и вызвать их напрямую; это очень просто, хотя не слишком удачно. Адреса API могут отличаться в разных версиях Windows и даже в разных сервиспаках одной и той же версии. Это сильно снижает шансы на успешную атаку удаленной системы. Второй способ: найти адреса API, используя какую-то вирусную технику. Это намного универсальнее, но сложнее, и код в результате получается больше. Вирусных техник существует около десятка. Прежде чем за них браться, нужно изучить формат Portable EXE (PE) и неплохо разбираться в некоторых особенностях архитектуры Windows. Для примера мы реализуем наиболее распространенную технику – нахождение адреса API путем анализа библиотеки kernel32.dll в памяти. Чтобы не лезть в теоретические дебри, давай напишем процедуру, которая находит адрес API-функции по ее имени. Эта процедура будет адресно-независимой, рабочей на всех версиях Windows, и ее можно будет использовать без каких-либо дополнительных корректив. Так что если ты не горишь желанием ковыряться в формате PE, просто бери ее и используй (INC-файл ты найдёшь на диске). Если же ты прочтешь комментарии и не поленишься во всем разобраться – большой тебе респект! :-)

Содержание  Вперед на стр. 045-014-2