We detected you are likely not from a Russian-speaking region. Would you like to switch to the international version of the site?

  Назад к списку статей

Разработка кастомных C2‑серверов на Python в Termux с использованием asyncio и TLS‑шифрования

Тема «кастомных C2‑серверов» часто ассоциируется с несанкционированным управлением. В рамках закона РФ и этики ИБ я рассматриваю только легитимные задачи: построение защищённого управляющего канала для собственных стендов, лабораторных сред, удалённого администрирования устройств, а также отладочных контроллеров. Используйте получаемые знания только там, где у вас есть явное разрешение на доступ и взаимодействие.

Ниже — концептуальная и техническая основа того, как проектировать защищённый управляющий сервер на Python в Termux: асинхронная модель, TLS, протокол поверх TCP/HTTP‑совместимых сокетов и практики безопасной эксплуатации.

Требования и обзор архитектуры

Для разработки в Termux вам пригодятся:

  • Android‑устройство с Termux (желательно актуальная версия).
  • Python 3, пакеты для TLS/сокетов (встроенные возможности Python обычно достаточны).
  • Права для входящих подключений (в лабораторной сети это может быть ограничено политиками Android и сетевыми настройками).
  • Планирование безопасности: ключи/сертификаты, валидация клиента, ограничение доступа по IP/сегментам сети.

Архитектурно целесообразно разделить проект на компоненты:

  • Transport: TCP‑подключение, обёртка TLS.
  • Protocol: формат сообщений (например, JSON по строкам) и обработчики команд.
  • Session/State: привязка соединения к идентификатору клиента, таймауты, heartbeats.
  • Security: управление сертификатами, проверка хоста, минимизация поверхности атаки.

Подготовка Termux и окружения

Обновите пакеты и установите нужные компоненты. Терминальные команды приведены как ориентир.

pkg update -y
pkg upgrade -y
pkg install -y python

Создайте рабочую директорию проекта:

mkdir -p ~/c2lab
cd ~/c2lab

Генерация ключей и сертификатов для TLS

Для лабораторной установки TLS проще всего начать с самоподписанного сертификата. В реальном контуре предпочтительнее собственный CA и выпуск клиентских/серверных сертификатов, а также строгая проверка доверия.

Если у вас есть OpenSSL в Termux, можно сделать так:

pkg install -y openssl
mkdir -p tls
openssl req -x509 -newkey rsa:2048 -nodes -keyout tls/server.key -out tls/server.crt -days 365 -subj "/CN=localhost"

Совет: при развёртывании «не на localhost» в поле CN/SAN укажите корректное имя или используйте проверку по отпечатку (pinning) на клиентской стороне — это лучше, чем «глушить» проверку сертификата.

Коммуникационный протокол: JSON по строкам

Для простоты и расширяемости удобен протокол «1 сообщение = 1 строка JSON». Тогда сервер может читать из потока до перевода строки. Пример структуры сообщения:

  • type: тип сообщения (например, hello, command, result).
  • client_id: идентификатор клиента.
  • payload: данные команды/ответа.
  • ts: timestamp (опционально).

Важно: обязательно валидируйте входные данные (схема, типы, лимиты по размеру, таймауты).

Асинхронный TLS‑сервер на Python (asyncio)

Ниже показан пример защищённого серверного процесса, который:

  • поднимает TCP‑слушатель;
  • оборачивает соединение в TLS;
  • обрабатывает сообщения JSON построчно;
  • реализует базовые проверки и heartbeats на уровне протокола.

Этот код — каркас для легитимного управляющего канала в собственной среде. Логика «команд» должна быть ограничена разрешёнными действиями.

import asyncio
import json
import ssl
import time

HOST = "0.0.0.0"
PORT = 8443

CERT_PATH = "tls/server.crt"
KEY_PATH = "tls/server.key"

MAX_LINE = 8192  # ограничение размера входящего сообщения
READ_TIMEOUT = 30
WRITE_TIMEOUT = 30

async def safe_send(writer: asyncio.StreamWriter, obj: dict):
    data = (json.dumps(obj, ensure_ascii=False) + "
").encode("utf-8")
    writer.write(data)
    await asyncio.wait_for(writer.drain(), timeout=WRITE_TIMEOUT)

async def handle_client(reader: asyncio.StreamReader, writer: asyncio.StreamWriter):
    peer = writer.get_extra_info("peername")
    sslobj = writer.get_extra_info("ssl_object")

    # Минимальная идентификация на старте (в реальных проектах добавляют мTLS или подписи)
    client_id = None

    try:
        # Приветствие/ожидание hello
        writer_task_deadline = time.time() + READ_TIMEOUT
        while client_id is None and time.time() < writer_task_deadline:
            line = await asyncio.wait_for(reader.readline(), timeout=READ_TIMEOUT)
            if not line:
                break
            if len(line) > MAX_LINE:
                await safe_send(writer, {"type": "error", "error": "message_too_large"})
                return

            msg = json.loads(line.decode("utf-8").strip())
            if msg.get("type") == "hello":
                client_id = msg.get("client_id")
                await safe_send(writer, {"type": "hello_ack", "server_time": int(time.time())})
            else:
                await safe_send(writer, {"type": "error", "error": "expected_hello"})

        if client_id is None:
            await safe_send(writer, {"type": "error", "error": "no_hello"})
            return

        # Основной цикл обмена
        while True:
            line = await asyncio.wait_for(reader.readline(), timeout=READ_TIMEOUT)
            if not line:
                break

            if len(line) > MAX_LINE:
                await safe_send(writer, {"type": "error", "error": "message_too_large"})
                continue

            msg = json.loads(line.decode("utf-8").strip())
            mtype = msg.get("type")

            # Heartbeat пример
            if mtype == "ping":
                await safe_send(writer, {"type": "pong", "ts": int(time.time())})
                continue

            # Ограниченный обработчик «команд» (пример)
            if mtype == "command":
                cmd = msg.get("payload", {}).get("name")

                # Важно: не выполняйте произвольный код.
                # Разрешайте только белый список безопасных действий.
                if cmd == "echo":
                    echo_text = msg.get("payload", {}).get("text", "")
                    result = {"echo": echo_text}
                    await safe_send(writer, {"type": "result", "payload": result})
                else:
                    await safe_send(writer, {"type": "error", "error": "unknown_or_unauthorized_command"})
                continue

            await safe_send(writer, {"type": "error", "error": "unknown_message_type"})

    except asyncio.TimeoutError:
        # клиент не отправил ничего в пределах READ_TIMEOUT
        pass
    except (json.JSONDecodeError, UnicodeDecodeError):
        try:
            await safe_send(writer, {"type": "error", "error": "invalid_json"})
        except Exception:
            pass
    finally:
        writer.close()
        try:
            await writer.wait_closed()
        except Exception:
            pass

async def main():
    ssl_context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
    ssl_context.load_cert_chain(certfile=CERT_PATH, keyfile=KEY_PATH)

    # Сервер не требует клиентского сертификата в этом каркасе.
    # Для усиления безопасности в лабораториях обычно используют mTLS.

    server = await asyncio.start_server(
        client_connected_cb=handle_client,
        host=HOST,
        port=PORT,
        ssl=ssl_context,
        backlog=100,
        # В asyncio Python это передаётся как is; ограничения по keepalive/таймингам можно добавить дополнительно.
    )

    addrs = ", ".join(str(sock.getsockname()) for sock in (server.sockets or []))
    print(f"[+] TLS server listening on {addrs}")

    async with server:
        await server.serve_forever()

if name == "main":
    asyncio.run(main())

Запуск:

python server.py

Клиент для теста (опционально)

Чтобы проверить сервер в локальной сети, можно написать минимальный клиент, который:

  • подключается к TLS порту;
  • отправляет hello и потом command (например, echo);
  • обрабатывает ответы.

Обратите внимание: в тестовой среде можно временно отключать проверку сертификата, но в прод‑подходе это нельзя делать. Ниже — только для удобного старта в лаборатории.

import asyncio
import json
import ssl

HOST = "192.168.1.10"  # замените на IP вашего устройства/серверного хоста
PORT = 8443

async def main():
    ssl_context = ssl.create_default_context()
    # Лабораторный тест: доверие самоподписанному сертификату.
    # Правильнее — загрузить CA/сертификат сервера в доверенный store.
    ssl_context.check_hostname = False
    ssl_context.verify_mode = ssl.CERT_NONE

    reader, writer = await asyncio.open_connection(HOST, PORT, ssl=ssl_context)

    def send(obj):
        writer.write((json.dumps(obj, ensure_ascii=False) + "
").encode("utf-8"))

    send({"type": "hello", "client_id": "client-001"})
    await writer.drain()

    line = await reader.readline()
    print("server:", line.decode().strip())

    send({"type": "command", "payload": {"name": "echo", "text": "hello from client"}})
    await writer.drain()

    line = await reader.readline()
    print("server:", line.decode().strip())

    send({"type": "ping"})
    await writer.drain()

    line = await reader.readline()
    print("server:", line.decode().strip())

    writer.close()
    await writer.wait_closed()

if name == "main":
    asyncio.run(main())

Практики безопасности при разработке управляющего сервера

Даже для легитимного управляющего канала важно придерживаться базовых правил:

  • mTLS/аутентификация: лучше требовать клиентские сертификаты или использовать подпись сообщений.
  • Белый список действий: команды должны быть ограничены предсказуемыми и безопасными операциями.
  • Валидация и лимиты: размер сообщений, таймауты, контроль частоты запросов.
  • Логирование: не логируйте секреты; фиксируйте события аутентификации и ошибок.
  • Сегментация сети: используйте локальную сеть/лабораторный контур без доступа из «чужих» сегментов. При необходимости можно настроить локальную сеть через VPN для удобства тестов, но не для обхода блокировок.

Эксплуатация в Termux: устойчивость и настройки

Termux — не серверная ОС, поэтому учитывайте ограничения:

  • Перевод устройства в сон может обрывать соединения. Решение — настройка энергосбережения и keepalive на уровне приложения/сети.
  • Ограничения по фоновой работе Android. Для длительных сессий используйте механизмы, которые не мешают процессу.
  • Мониторинг и перезапуск: продумайте сценарий автоперезапуска при падениях (на уровне вашего окружения).

Внутри сервера полезно добавить метрики: число активных сессий, частоту команд, среднее время обработки, количество ошибок протокола.

Расширение протокола: версионирование и схематизация

Чтобы сервер и клиенты развивались без поломок:

  • Добавляйте поле version.
  • Фиксируйте контракт сообщений (например, JSON schema в тестах).
  • Разделяйте «команды» и «события».

Пример идеи для сообщения:

{
  "version": 1,
  "type": "command",
  "client_id": "client-001",
  "payload": {"name": "echo", "text": "..."}
}

Заключение

Мы рассмотрели легитимный подход к созданию защищённого управляющего сервиса в Termux: асинхронный сервер на asyncio, транспорт поверх TLS, каркас протокола и практики безопасного проектирования (лимиты, таймауты, белый список действий). Такой фундамент подходит для собственных лабораторий, стендов и задач удалённого администрирования в рамках разрешённых сценариев.

Если вам нужно быстрее собрать прототип под ваш контур, настроить корректную выдачу сертификатов (включая CA/mTLS), разработать схему сообщений и внедрить безопасную политику команд — команда РыбинскЛАБ поможет на практике: от проектирования до развёртывания и тестирования.

* Текст статьи подготовлен и структурирован с использованием технологий искусственного интеллекта. Проверен и доработан перед публикацией.

Нужна помощь с настройкой Termux, Linux и серверов?

Я оказываю ИТ-услуги: настройка серверов, автоматизация, безопасность, помощь с Linux и инфраструктурой. Материалы сайта — только в ознакомительных и образовательных целях.

Связаться со мной
Поддержать проект