Когда нужно защитить передаваемые данные, мы прибегаем к помощи VPN. Однако порой возникает желание скрыть даже сам факт его использования. Так сильно мы заботимся о своей безопасности!?

Поскольку WireGuard сейчас на пике популярности, а он работает на основе протокола UDP, возникла идея замаскировать его трафик под другой популярный протокол на базе UDP — QUIC. QUIC объединяет в себе функции TCP и TLS, поэтому было логично попытаться сделать так, чтобы трафик WireGuard выглядел как трафик QUIC. Для реализации этой идеи потребовалось детально изучить исходный код WireGuard в ядре Linux. Патч для ядра Linux доступен на QUICWireGuard.

Выбор порта

Трафик QUIC использует тот же порт, что и HTTPS — 443. Поэтому WireGuard также будет подниматься на этом порту. Однако, поскольку данное решение предназначено исключительно для работы между двумя устройствами на Linux, мы оставляем возможность подключения других устройств на альтернативных портах. Мимикрия будет активироваться только при использовании порта 443 для WireGuard либо при входящем соединении на этот порт. Во всех остальных случаях WireGuard останется без изменений.

Установка на Arch, Ubuntu, OpenWRT

Для сборки и установки были сделаны два скрипта:

linux_auto_install.sh - подходит для сборки модуля wireguard.ko для данной системы и установки его.

openwrt_auto_install.sh - подходит для сборки модуля wireguard.ko для OpenWRT и установки, как аргумент скрипта надо указать или ssh имя роутера или xxxx@x.x.x.x

Оба скрипта не удаляют из системы предыдущий модуль wireguard.ko. Удаление старого модуля, загрузка обновленного и последующая перезагрузка устройства WireGuard должны выполняться вручную. Чтобы убедиться, что загружена именно новая версия, проверьте вывод команды dmesg | grep -i wireguard.

[    8.975692] wireguard: QUICWireGuard 1.0.0 loaded. See https://github.com/karen07/QUICWireGuard for information.
[    8.975694] wireguard: Copyright (C) 2015-2019 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
[    8.975695] wireguard: Copyright (C) 2024 Karen.

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

Для систем, отличных от Arch, Ubuntu и OpenWRT, скрипт также применим, однако вам потребуется добавить в него установку пакетов, необходимых для сборки модуля ядра под ваш конкретный дистрибутив.

Формирование пакета WireGuard

Если открыть код WireGuard в ядре Linux. Там есть файл messages.h, в котором описаны две структуры:

struct message_handshake_initiation {
	struct message_header header;
	__le32 sender_index;
	u8 unencrypted_ephemeral[NOISE_PUBLIC_KEY_LEN];
	u8 encrypted_static[noise_encrypted_len(NOISE_PUBLIC_KEY_LEN)];
	u8 encrypted_timestamp[noise_encrypted_len(NOISE_TIMESTAMP_LEN)];
	struct message_macs macs;
};
struct message_handshake_response {
	struct message_header header;
	__le32 sender_index;
	__le32 receiver_index;
	u8 unencrypted_ephemeral[NOISE_PUBLIC_KEY_LEN];
	u8 encrypted_nothing[noise_encrypted_len(0)];
	struct message_macs macs;
};

Переменных этих структур, отправляются при WireGuard handshake в файле send.c в функциях wg_packet_send_handshake_initiation и wg_packet_send_handshake_response. Поэтому если отправлять не только packet, а еще и QUIC header, то WireGuard handshake будет похож на QUIC handshake.

struct message_handshake_initiation packet;
wg_socket_send_buffer_to_peer(peer, &packet, sizeof(packet),
					      HANDSHAKE_DSCP);
struct message_handshake_response packet;
wg_socket_send_buffer_to_peer(peer, &packet,
						      sizeof(packet),
						      HANDSHAKE_DSCP);

Описание QUIC header

Если смотреть в WireShark, то QUIC init header стоит из семи полей.

Поля init header
Поля init header

Поле

Описание, значение

Flags

Битовое поле, заполняю значением 0xC0

Version

Версия, сейчас всегда 0x1 в BE порядке

DCID len

Длина DCID в байтах, для init header значение 0x8

DCID

Номер получения

SCID len

Длина SCID в байтах, для init header значение 0x0

Token len

Длина SCID в байтах, для init header значение 0x0

Data len

Состоит из двух частей, первые два бита, первого байта это показатель общей длины Data len, в моем случае 0b01(0x4), а к 0x4 прибавляем длину init header

Если смотреть в WireShark, то QUIC response header стоит из семи полей.

Поля response header
Поля response header

Поле

Описание, значение

Flags

Битовое поле, заполняю значением 0xC0

Version

Версия, сейчас всегда 0x1 в BE порядке

DCID len

Длина DCID в байтах, для response header значение 0x0

SCID len

Длина SCID в байтах, для response header значение 0x8

SCID

Номер отправителя

Token len

Длина SCID в байтах, для response header значение 0x0

Data len

Состоит из двух частей, первые два бита, первого байта это показатель общей длины Data len, в моем случае 0b01(0x4), а к 0x4 прибавляем длину response header

Соответственно, если спереди к struct message_handshake_initiation packet добавить QUIC init header, то начало сессии WireGuard будет похоже на начало сессии QUIC. Тоже самое, если спереди к struct message_handshake_response packet добавить QUIC response header, то начало сессии WireGuard будет похоже на начало сессии QUIC.

Прием пакета WireGuard

Открыв код WireGuard в ядре Linux, вы найдете файл receive.c, где обрабатывается прием входящих пакетов. В функции prepare_skb_header производится удаление заголовка UDP из буфера skb. Нам же понадобится в некоторых случаях удалять также заголовок QUIC. Добавим проверку на номер порта, а также учтем, что все пакеты WireGuard начинаются с числа, не превышающего четыре, тогда как заголовок QUIC начинается с 0xC0. Таким образом, добавив условие проверки первого байта пакета, мы сможем различать пакеты типа initiation response и data.

Результат

Wireshark
Wireshark

Вот пример того, как Wireshark интерпретирует рукопожатие WireGuard. Wireshark определяет соединение как QUIC. Мы достигли желаемого результата.

Комментарии (0)