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

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

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

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


Теперь рассмотрим второй пример.

section .data

name db '/bin/sh', 0

section .text

global _start

_start:

; setreuid(0, 0)

mov eax, 70

mov ebx, 0

mov ecx, 0

int 0x80

; execve("/bin/sh",["/bin/sh", NULL], NULL)

mov eax, 11

mov ebx, name

push 0

push name

mov ecx, esp

mov edx, 0

int 0x80

Если не брать в расчет вызов функции execve(), то большая часть кода аналогична предыдущему примеру. Те же сегменты, тот же принцип вызова функции setreuid(). Проблема лишь в том, что в качестве второго параметра подпрограммы execve() передается массив, состоящий из двух элементов. Такой параметр уместно передавать через стек, в который сначала помещается ноль (push 0) , а потом адрес начала строки name (push name). Параметры передаются в обратном порядке неслучайно. Стек использует принцип LIFO (последним пришел - первым вышел), поэтому при извлечении данных из стека, сначала будет получен адрес переменной name и лишь потом ноль, что нам и надо. Необходимо позаботиться о том, чтобы подпрограмма знала, где эти параметры искать: и в этом неоценимую помощь окажет специальный регистр ESP (сокращение от Stack Pointer), который всегда указывает на адрес вершины стека. Все, что от нас требуется, - это скопировать содержимое регистра ESP в регистр ECX, который при запуске 0x80 прерывания будет обработан, как второй параметр функции.

[почему нет?]

Полученный код на ассемблере работоспособен на все 100% процентов. Его без труда можно откомпилировать с помощью nasm'а, запустить и даже посмотреть бинарник в шестнадцатеричном редакторе, то есть получить готовый шелл-код. Одна лишь проблема: толку от такого шелл-кода будет немного. Обе программы используют собственные сегменты данных, что полностью лишает возможности выполнить их внутри другого приложения. Это значит, что эксплойт не сможет инжектировать требуемый код в стек и выполнить его.

Именно поэтому следующим нашим шагом будет оптимизация программы на работу без использования сегмента данных. Для этого мы немного схитрим и воспользуемся одним приемом, основанным на инструкциях ассемблера jmp (перейти на метку) и call (вызвать процедуру). Обе инструкции осуществляют переход в нужное место программы, но call, помимо всего прочего, заносит в стек адрес возврата. Это необходимо для того, чтобы после завершения процедуры вернуться в исходное место программы и продолжить ее нормальное выполнение. Рассмотрим суть приема на примере:

jmp two

one:

pop ebx

[текст программы]

two:

call one

db 'строка'

В самом начале программы осуществляется переход на метку two, за которой расположен вызов процедуры one. На самом деле никакой процедуры нет, однако с таким именем есть еще одна метка, которой и передается управления. В этот момент в стек заносится адрес возврата, то есть адрес следующей за call инструкцией. В нашем случае это строка байт - db 'string'. Получается, что к моменту, когда начинается обработка метки one, в стек уже будет помещен адрес начала строки. Нам остается лишь извлечь ее и использовать, как заблагорассудиться. Попробуем этим воспользоваться в реальной программе: для этого модифицируем наш второй пример. Для удобства назовем его shell.asm, так как далее будем работать только с ним.

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