Откуда берутся шелл-коды Степан Ильин (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. В небольших программах, имеющих простую структуру, эти инструкции абсолютно равноправны, однако машинный код во втором случае не содержит нулевых байтов. А именно это нам и надо. |