- Network
- A
WireGuard and QUIC
When we want to protect transmitted data, we use a VPN. But sometimes we want the fact of using a VPN to be hidden as well. We care so much about our data:)
When it is necessary to protect transmitted data, we resort to the help of a VPN. However, sometimes there is a desire to hide even the fact of its use. We care so much about our security!😊
Since WireGuard is now at the peak of popularity, and it works based on the UDP protocol, the idea arose to disguise its traffic as another popular protocol based on UDP — QUIC. QUIC combines the functions of TCP and TLS, so it was logical to try to make WireGuard traffic look like QUIC traffic. To implement this idea, it was necessary to study the source code of WireGuard in the Linux kernel in detail. The patch for the Linux kernel is available on QUICWireGuard.
Port selection
QUIC traffic uses the same port as HTTPS — 443. Therefore, WireGuard will also be raised on this port. However, since this solution is intended exclusively for operation between two devices on Linux, we leave the possibility of connecting other devices on alternative ports. Mimicry will be activated only when using port 443 for WireGuard or when an incoming connection to this port. In all other cases, WireGuard will remain unchanged.
Installation on Arch, Ubuntu, OpenWRT
Two scripts were made for assembly and installation:
linux_auto_install.sh - suitable for building the wireguard.ko module for this system and installing it.
openwrt_auto_install.sh - suitable for building the wireguard.ko module for OpenWRT and installing it, as an argument to the script you need to specify either the ssh name of the router or [email protected]
Both scripts do not remove the previous wireguard.ko module from the system. Removing the old module, loading the updated one, and subsequently rebooting the WireGuard device must be done manually. To make sure that the new version is loaded, check the output of the command 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 . All Rights Reserved.
[ 8.975695] wireguard: Copyright (C) 2024 Karen.
You should see the message QUICWireGuard
. If you only have access to the machine through WireGuard, be especially careful when checking: there is always a risk of kernel failure or incorrect installation. However, in my practice, such cases have not occurred.
For systems other than Arch, Ubuntu, and OpenWRT, the script is also applicable, but you will need to add the installation of packages required to build the kernel module for your specific distribution.
Forming the WireGuard package
If you open the code of WireGuard in the Linux kernel. There is a file messages.h, which describes two structures:
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;
};
The variables of these structures are sent during the WireGuard handshake in the file send.c in the functions wg_packet_send_handshake_initiation
and wg_packet_send_handshake_response
. Therefore, if you send not only the packet
but also the QUIC header
, the WireGuard handshake will resemble the 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);
Description of the QUIC header
If you look in WireShark, the QUIC init header
consists of seven fields.
Field | Description, value |
Flags | Bit field, fill with value |
Version | Version, now always |
DCID len | DCID length in bytes, for init header value |
DCID | Receiving number |
SCID len | SCID length in bytes, for init header value |
Token len | SCID length in bytes, for init header value |
Data len | Consists of two parts, the first two bits of the first byte are the indicator of the total length of Data len, in my case 0b01(0x4), and to 0x4 we add the length of |
If you look at WireShark, the QUIC response header
consists of seven fields.
Field | Description, value |
Flags | Bit field, fill with value |
Version | Version, now always |
DCID len | DCID length in bytes, for response header value |
SCID len | SCID length in bytes, for response header value |
SCID | Sender number |
Token len | SCID length in bytes, for response header value |
Data len | Consists of two parts, the first two bits of the first byte are the indicator of the total length of Data len, in my case 0b01(0x4), and to 0x4 we add the length of |
Accordingly, if you add QUIC init header
in front of struct message_handshake_initiation packet
, the start of the WireGuard session will look like the start of the QUIC session. Similarly, if you add QUIC response header
in front of struct message_handshake_response packet
, the start of the WireGuard session will look like the start of the QUIC session.
Receiving WireGuard Packet
Opening the WireGuard code in the Linux kernel, you will find the file receive.c, where the reception of incoming packets is processed. In the prepare_skb_header
function, the UDP header is removed from the skb buffer. In some cases, we will also need to remove the QUIC header. We will add a port number check and also take into account that all WireGuard packets start with a number not exceeding four, whereas the QUIC header starts with 0xC0
. Thus, by adding a condition to check the first byte of the packet, we will be able to distinguish between initiation response
and data
packets.
Result
Here is an example of how Wireshark interprets the WireGuard handshake. Wireshark identifies the connection as QUIC. We have achieved the desired result.
Write comment