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

Живучий код

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

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


Техника написания переносимого shell-кода

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

Условимся называть переносимым shell-кодом машинный код, поддерживающий заданную линейку операционных систем (например, Windows NT, Windows 2000 и Windows XP). Как показывает практика, для решения подавляющего большинства задач такой степени переносимости вполне достаточно. В конце концов, гораздо проще написать десяток узкоспециализированных shell-кодов, чем один универсальный. Что поделаешь, переносимость требует жертв и, в первую очередь, увеличения объема shell-кода, а потому она оправдывает себя только в исключительных случаях.

Требования, предъявляемые к переносимому shell-коду

Переносимый shell-код должен полностью сохранять работоспособность при любом расположении в памяти и использовать минимум системно-зависимых служебных структур, закладываясь лишь на наименее изменчивые и наиболее документированные из них.

Отталкиваться от содержимого регистров ЦП на момент возникновения переполнения категорически недопустимо, поскольку их значения в общем случае неопределенны, и решиться на такой шаг можно только с голодухи, когда shell-код упрямо не желает вмещаться в отведенное ему количество байт и приходится импровизировать, принося в жертву переносимость.

Не стоит злоупотреблять хитрыми трюками ("хаками") и недокументированными возможностями – это негативно сказывается на переносимости и фактически ничего не дает взамен.

Пути достижения мобильности

Техника создания перемещаемого кода тесно связана с архитектурой конкретного микропроцессора. В частности, линейка x86 поддерживает следующие относительные команды: PUSH/POP, CALL и Jx. Старушка PDP-11 в этом отношении была намного богаче и, что самое приятное, позволяла использовать регистр указателя команд в адресных выражениях, существенно упрощая нашу задачу. Но, к сожалению, мы не располагаем возможностью выбора процессора.

Команды условного перехода Jxx всегда относительны, то есть операнд команды задает отнюдь не целевой адрес, а разницу между целевым адресом и адресом следующей команды, благодаря чему переход полностью перемещаем. Поддерживаются два типа операндов: byte и word/dword, оба знаковые – переход может быть направлен как "вперед", так и "назад" (в последнем случае операнд становится отрицательным).

Команды безусловного перехода JMP бывают и абсолютными, и относительными. Относительные начинаются с опкода EBh (операнд типа byte) или E9h (операнд типа word/dword), а абсолютные – с EAh, при этом операнд записывается в форме сегмент: смещение. Существуют еще и косвенные команды, передающие управление по указателю, лежащему по абсолютному адресу или регистру. Последнее наиболее удобно и осуществляется приблизительно так: mov eax, абсолютный адрес/jmp eax.

Команда вызова подпрограммы CALL ведет себя аналогично JMP, за тем лишь исключением что кодируется другими опкодами (E8h – относительный операнд типа word/dword, FFh /2 – косвенный вызов) и перед передачей управления на целевой адрес забрасывает на верхушку стека адрес возврата, представляющий собой адрес команды, следующей за call.

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