Издательский дом ООО "Гейм Лэнд"ЖУРНАЛ ХАКЕР #85, ЯНВАРЬ 2006 г.

Откуда берутся шелл-коды

Степан Ильин (step@gameland.ru)

Хакер, номер #085, стр. 085-050-6


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

Идея простая: найти те участки кода, которые порождают нулевые байты и видоизменить их таким образом, чтобы комбинации \x00 в шелл-коде больше не встречалось. Опытный программист в большинстве случаев легко может определить из-за чего в машинном коде появляются нули, но нам, как новичкам, необходима помощью дизассемблера. Воспользуемся ndisasm:

$ nasm shell.asm

$ ndisasm shell.asm

После выполнения этой команды на экран будет выведен дизассемблированный код программы. Первый столбец - адрес инструкции, он для нас не особенно важен. Во втором столбце расположены машинные инструкции, точно такие же, как и при просмотре бинарника hexdump'ом. В третьем столбце для каждой машинной инструкции дан эквивалент на ассемблере. Только с помощью ассемблерного кода мы можем судить о том, откуда взяли нулевые байты в шелл-коде.

После беглого осмотра дампа становится ясно, что большинство NULL-байтов связаны с инструкциями, которые управляют содержимым регистра и стека. Этого следовало ожидать, поскольку мы работаем в 32-битном режиме и соответственно для каждого числа выделяется 4 байта памяти. Мы же оперируем числами, для которых хватает и одного байта. Например, в самом начале программы shell мы имеем следующую конструкцию «mov eax, 70» (занести в регистр eax число 70). В дампе и шелл-коде эта инструкция представлена как «B8 46 00 00 00». Причем B8 - это машинный эквивалент ассемблерной команды mov ax, а B8 00 00 00 - число 70 в шестнадцатеричной системе счисления, дополненное нулями до размера 4 байт. Аналогично возникает еще множество NULL-байтов.

К счастью, решить эту проблему будет проще простого. Достаточно вспомнить, что 32-битный регистры (EAX, EBX и другие, начинающиеся с буквы e) можно разложить на регистры меньшей размерности. Посмотри на иллюстрацию и тебе сразу станет ясно, что никто не мешает нам работать с двухбайтным регистром AX, а также с его младшей и старшей частями AL и AH. Последние имеют размер всего в один байт - как раз то, что нам надо. Нужно лишь заменить «mov eax, 70» на «mov al, 70» и т.д. Важно позаботиться о том, чтобы остальная часть регистра не содержала мусора. Быстро и эффективно обнулить значение всего регистра можно, воспользовавшись логической функцией «исключающее или». Так, «xor eax,eax» обнулит значение регистра EAX.

[проблемы не кончаются]

Даже после внесенных изменений шелл-код все равно содержит нулевые байты. Отладчик показывает, что источник всех бед - инструкция jmp:

E91500 jmp 0x29

0000 add [bx+si],al

Тут есть одна хитрость: вместо обычной команды jmp нужно использовать инструкцию близкого перехода - jmp short. В небольших программах, имеющих простую структуру, эти инструкции абсолютно равноправны, однако машинный код во втором случае не содержит нулевых байтов. А именно это нам и надо.

Назад на стр. 085-050-5  Содержание  Вперед на стр. 085-050-7