Пишем 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-файл ты найдёшь на диске). Если же ты прочтешь комментарии и не поленишься во всем разобраться – большой тебе респект! :-) |