Когда нужно защитить передаваемые данные, мы прибегаем к помощи 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
стоит из семи полей.
Поле |
Описание, значение |
Flags |
Битовое поле, заполняю значением |
Version |
Версия, сейчас всегда |
DCID len |
Длина DCID в байтах, для init header значение |
DCID |
Номер получения |
SCID len |
Длина SCID в байтах, для init header значение |
Token len |
Длина SCID в байтах, для init header значение |
Data len |
Состоит из двух частей, первые два бита, первого байта это показатель общей длины Data len, в моем случае 0b01(0x4), а к 0x4 прибавляем длину |
Если смотреть в WireShark, то QUIC response header
стоит из семи полей.
Поле |
Описание, значение |
Flags |
Битовое поле, заполняю значением |
Version |
Версия, сейчас всегда |
DCID len |
Длина DCID в байтах, для response header значение |
SCID len |
Длина SCID в байтах, для response header значение |
SCID |
Номер отправителя |
Token len |
Длина SCID в байтах, для response header значение |
Data len |
Состоит из двух частей, первые два бита, первого байта это показатель общей длины Data len, в моем случае 0b01(0x4), а к 0x4 прибавляем длину |
Соответственно, если спереди к 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 интерпретирует рукопожатие WireGuard. Wireshark определяет соединение как QUIC. Мы достигли желаемого результата.