Shellcode своими руками

Xakep, номер #044, стр. 044-038-2


Как писать эксплоиты?

В нашем примере сервер пытается записать наши "AAAA..." в буфер, но места там не хватает, и, как и описано, адрес возврата функции затирается значением 0x41414141, соответствующим четырем байтам 'A'. После завершения процедуры управление передается по этому адресу. Естественно, что там нет ничего интересного, и программа-сервер просто завершается с ошибкой General protection fault: "программа обратилась к адресу 0x41414141 и будет закрыта". А нам бы хотелось совершать более интересные действия. Вернемся к изображению стека и посмотрим, как нужно сконструировать данные.

Стек передаваемые данные

...

<буфер - 1024 байта> <наш код - 1024 байта>

<переменная 1 - 4 байта> <новое значение переменной 1>

<адрес возврата - 4 байта> <адрес нашего кода в стеке>

...

Итог - после переполнения буфера переменная 1 примет новое значение (что иногда помогает нам, иногда мешает, а иногда на это наплевать), а после выхода из процедуры начнут выполняться 1024 байта нашего кода, которые находятся сейчас в стеке. Эти 1024 байта могут, например, открыть порт 5000, ждать подключения к нему и запускать командный интерпретатор /bin/sh (для *никса) или cmd.exe (для NT, Win2K и XP), организовав некое подобие сервиса telnet. За любовь к вызову OS Shell этот тип кода называют shellcode, шеллкод.

Создание шеллкода

Шеллкод - низкоуровневый код, специфичный для операционной системы. Вряд ли в рамках этой статьи удастся научить тебя низкоуровневому программированию под все ОС, так что придется понадеяться, что ты уже научился этому раннее. Здесь будут затронуты только специфичные для шеллкода проблемы.

1. Ограничение на набор символов. Программа может считать шеллкод чем угодно, только не шеллкодом. Например, если программа работает с данными как со строкой символов (функциями strcpy и т.п.), то шеллкод не должен содержать символ с кодом 0, иначе часть шеллкода после этого символа будет проигнорирована. Возможны и другие ограничения на набор символов. Кроме того, программа может обрабатывать данные, заменяя один символ на другой - это тоже надо учитывать. Чтобы получить шеллкод без нулей, придется пофантазировать. Однако неправильно считать, что шеллкод никогда не может содержать символ с кодом 0 - все зависит от конкретной программы.

2. Мобильность кода. При эксплуатации переполнения буфера не всегда известно, какой точный адрес будет у кода в памяти. Что делать, если нужно обращаться к данным, находящимся внутри шеллкода? Вот типичный способ определения адреса текущей инструкции:

call $+0

pop eax

В регистре eax окажется адрес инструкции, следующей за командой call. Однако команда call $+0 содержит "нехорошие" символы - два байта с кодом 0. Вот способ обхода.

jmp beforedata

afterjump:

pop eax

...

beforedata:

call afterjump

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