Skip to content

Commit

Permalink
Added Tailscale portion.
Browse files Browse the repository at this point in the history
  • Loading branch information
keithjjones committed Nov 24, 2021
1 parent 1098469 commit 35fb052
Show file tree
Hide file tree
Showing 11 changed files with 354 additions and 44 deletions.
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
build
.idea/
cmake-build-debug/
.DS_Store
*.swp
*.tmp
tests/.btest*
29 changes: 29 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
Copyright (c) 2021, Corelight, Inc. All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:

(1) Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.

(2) Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the
distribution.

(3) Neither the name of Corelight nor the names of any contributors
may be used to endorse or promote products derived from this
software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
62 changes: 62 additions & 0 deletions Readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# zeek-spicy-wireguard

This package provides a Spicy based Wireguard protocol analyzer
for Zeek.

You must install [Spicy](https://docs.zeek.org/projects/spicy/en/latest/)
to use this package.

This is a straightforward implementation following https://www.wireguard.com/protocol/

## Example Logs:

```
#separator \x09
#set_separator ,
#empty_field (empty)
#unset_field -
#path conn
#open 2021-11-24-18-10-11
#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p proto service duration orig_bytes resp_bytes conn_state local_orig local_resp missed_bytes history orig_pkts orig_ip_bytes resp_pkts resp_ip_bytes tunnel_parents
#types time string addr port addr port enum string interval count count string bool bool count string count count count count set[string]
1611583877.627931 CHhAvVGS1DHFjwGM9 188.166.170.114 45965 188.166.170.115 51194 udp spicy_wireguard 35.595192 13516 14924 SF - - 0 Dd 90 16036 82 17220 -
#close 2021-11-24-18-10-11
```

```
#separator \x09
#set_separator ,
#empty_field (empty)
#unset_field -
#path wireguard
#open 2021-11-24-18-10-11
#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p established initiations responses
#types time string addr port addr port bool count count
1611583877.627931 CHhAvVGS1DHFjwGM9 188.166.170.114 45965 188.166.170.115 51194 T 1 1
#close 2021-11-24-18-10-11
```

This package also detects...

# Tailscale

[Tailscale](https://tailscale.com/) is a VPN that modifies the Wireguard protocol
slightly by adding Tailscale discovery messages. While the generic Wireguard protocol
analyzer in this repo will not support this, this protocol analyzer will.

Relevant code section: <https://github.com/tailscale/tailscale/blob/main/disco/disco.go#L32>

## Example Log:

```
#separator \x09
#set_separator ,
#empty_field (empty)
#unset_field -
#path conn
#open 2021-11-24-18-11-40
#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p proto service duration orig_bytes resp_bytes conn_state local_orig local_resp missed_bytes history orig_pkts orig_ip_bytes resp_pkts resp_ip_bytes tunnel_parents
#types time string addr port addr port enum string interval count count string bool bool count string count count count count set[string]
1623328901.893092 CHhAvVGS1DHFjwGM9 192.168.88.3 41641 18.196.71.179 41641 udp spicy_tailscale 31.882638 5700 6322 SF - - 0 Dd 51 7128 56 7890 -
#close 2021-11-24-18-11-40
```
35 changes: 14 additions & 21 deletions analyzer/analyzer.evt
Original file line number Diff line number Diff line change
@@ -1,25 +1,18 @@
# TODO: Adjust here whether this is a file, tcp or ucp analyzer,
# and the ports the analyzers work on. See
# https://docs.zeek.org/projects/spicy/en/latest/zeek.html#interface-definitions-evt-files
# for the DSL used here. Below, the active analyzer declaration defines a protocol analyzer.
#
# A file analyzer would be define like this:
#
# file analyzer spicy::ZIP:
# parse with ZIP::Archive,
# mime-type application/zip;
#
# A packet analyzer would look like this:
import zeek_spicy_wireguard;
import Zeek_zeek_spicy_wireguard;

# packet analyzer spicy::RawLayer:
# parse with Raw Layer::Packet;
protocol analyzer spicy::Wireguard over UDP:
parse with zeek_spicy_wireguard::WireGuardPacket;

protocol analyzer spicy::Wireguard over TCP:
parse with zeek_spicy_wireguard::Wireguard,
port 8080/tcp;
protocol analyzer spicy::Tailscale over UDP:
parse with zeek_spicy_wireguard::TailscalePacket;

import zeek_spicy_wireguard;
import Zeek_zeek_spicy_wireguard;
on zeek_spicy_wireguard::HandshakeInitiation -> event Wireguard::handshake_initiation($conn, $is_orig, self.sender_index, self.unencrypted_ephemeral, self.encrypted_static, self.encrypted_timestamp, self.mac1, self.mac2);

on zeek_spicy_wireguard::HandshakeResponse -> event Wireguard::handshake_response($conn, $is_orig, self.sender_index, self.receiver_index, self.unencrypted_ephemeral, self.encrypted_nothing, self.mac1, self.mac2);

on zeek_spicy_wireguard::PacketCookieReply -> event Wireguard::packet_cookie_reply($conn, $is_orig, self.receiver_index, self.nonce, self.encrypted_cookie);

on zeek_spicy_wireguard::PacketData -> event Wireguard::packet_data($conn, $is_orig, self.receiver_index, self.counter, cast<uint64>(|self.encrypted_encapsulated_packet|));

# TODO: Connect Spicy-side events with Zeek-side events.
on zeek_spicy_wireguard::Wireguard -> event Wireguard::message($conn, $is_orig, self.payload);
on zeek_spicy_wireguard::TSDiscoveryPacket -> event Wireguard::tailscale_discovery_message($conn, $is_orig, self.senderDiscoPub);
95 changes: 92 additions & 3 deletions analyzer/analyzer.spicy
Original file line number Diff line number Diff line change
@@ -1,7 +1,96 @@
# TODO: Define your analyzers here.
# This is pretty much a straightforward implementation following https://www.wireguard.com/protocol/

module zeek_spicy_wireguard;

public type Wireguard = unit {
payload: bytes &eod;
import spicy;

%byte-order = spicy::ByteOrder::Little;

type MessageType = enum {
handshake_initiation = 1,
handshake_response = 2,
packet_cookie_reply = 3,
packet_data = 4
};

function AEAD_LEN(num: uint64) : uint64 {
return num + 16;
}

public type WireGuardPacket = unit {
message_type: uint8;
reserved_zero: bytes &size=3 &requires=($$ == b"\x00\x00\x00");
switch ( MessageType(self.message_type) ) {
MessageType::handshake_initiation -> handshake_initiation: HandshakeInitiation;
MessageType::handshake_response -> handshake_response: HandshakeResponse;
MessageType::packet_cookie_reply -> packet_cookie_reply: PacketCookieReply;
MessageType::packet_data -> packet_data: PacketData;
};
};

type HandshakeInitiation = unit {
sender_index: uint32;
unencrypted_ephemeral: bytes &size=32;
encrypted_static: bytes &size=AEAD_LEN(32);
encrypted_timestamp: bytes &size=AEAD_LEN(12);
mac1: bytes &size=16;
mac2: bytes &size=16;
nothing: bytes &eod &requires=(|$$| == 0);
};

type HandshakeResponse = unit {
sender_index: uint32;
receiver_index: uint32;
unencrypted_ephemeral: bytes &size=32;
encrypted_nothing: bytes &size=AEAD_LEN(0);
mac1: bytes &size=16;
mac2: bytes &size=16;
nothing: bytes &eod &requires=(|$$| == 0);
};

type PacketCookieReply = unit {
receiver_index: uint32;
nonce: bytes &size=24;
encrypted_cookie: bytes &size=AEAD_LEN(16);
nothing: bytes &eod &requires=(|$$| == 0);
};

type PacketData = unit {
receiver_index: uint32;
counter: uint64;
encrypted_encapsulated_packet: bytes &eod;
};

%byte-order = spicy::ByteOrder::Big;

public type TailscalePacket = unit {
magic: bytes &size=1 {
local _magic = $$.to_uint(spicy::ByteOrder::Big);
self.has_discovery = _magic == 0x54;
self.has_wireguard = _magic == 0x01 || _magic == 0x02 || _magic == 0x03 || _magic == 0x04;

if (self.has_wireguard) {
self.data.connect(new WireGuardPacket);
self.data.write(self.magic);
}
}

var has_wireguard: bool;
var has_discovery: bool;

disc: TSDiscoveryPacket if (self.has_discovery);

: bytes &eod &chunked if (self.has_wireguard) {
self.data.write($$);
}

sink data;
};

type TSDiscoveryPacket = unit {
# We already ate a byte, so we only need the next 5 bytes here.
magic: bytes &size=5 &requires=($$ == b"\x53\xf0\x9f\x92\xac");
senderDiscoPub: bytes &size=32;
nonce: bytes &size=24;
payload: bytes &eod;
} &byte-order = spicy::ByteOrder::Big;
18 changes: 11 additions & 7 deletions analyzer/dpd.sig
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
# TODO: Use this file to optionally declare signatures which can be used to activate your analyzers.
#
# signature dpd_Wireguard {
# ip-proto == tcp
# payload /^\x11\x22\x33\x44/
# enable "spicy_Wireguard"
# }
signature wireguard_packet {
ip-proto == udp
payload /^(\x01|\x02|\x03)\x00\x00\x00/
enable "spicy_Wireguard"
}

signature tailscale_packet {
ip-proto == udp
payload /^\x54\x53\xf0\x9f\x92\xac/
enable "spicy_Tailscale"
}
99 changes: 97 additions & 2 deletions analyzer/main.zeek
Original file line number Diff line number Diff line change
@@ -1,3 +1,98 @@
# TODO: Define Zeek-side records or functions you want to provide with your plugin.

module Wireguard;

export {
redef enum Log::ID += { LOG };

## The record type which contains the fields of the Wireguard log.
## Wireguard purposefully contains only very limited information. As such, the only
## things that we record in the log are wireguard handshakes - since the frequency of handshakes
## (as well as the successes of them) might be of some interest
type Info: record {
### Time the first packet was encountered
ts: time &log &default=network_time();
### Unique ID for the connection
uid: string &log;
## The connection's 4-tuple of endpoint addresses/ports
id: conn_id &log;
### 32 bit identifier chosen by the sender. Not logged.
sender_index: count &optional;
### Set to true if we think that a handshake was succesfully performed.
### Please note that this flag is a bit speculative and should not be 100% relied on.
established: bool &log &default=F;
## Number of handshake initiation packets we have encountered during the connection
initiations: count &log &default=0;
## Number of handshake response packets we have encountered during the connection
responses: count &log &default=0;
};

## Event that can be handled to access the Wireguard
## record as it is sent on to the logging framework.
global log_wireguard: event(rec: Info);

## Event raised for the wireguard handshake_initiation packet
global Wireguard::handshake_initiation: event(c: connection, is_orig: bool, sender_index: count, unencrypted_ephemeral: string, encrypted_static: string, encrypted_timestamp: string, mac1: string, mac2: string);

## Event raised for the wireguard handshake_response packet
global Wireguard::handshake_response: event(c: connection, is_orig: bool, sender_index: count, receiver_index: count, unencrypted_ephemeral: string, encrypted_nothing: string, mac1: string, mac2: string);

## Event raised for the wireguard packet_cookie_reply packet
global Wireguard::packet_cookie_reply:event(c: connection, is_orig: bool, receiver_index: count, nonce: string, encrypted_cookie: string);

## Event raised for the wireguard packet_data packet
global Wireguard::packet_data: event(c: connection, is_orig: bool, receiver_index: count, key_counter: count, encapsulated_packet_length: count);

## Event raised for the Tailscale discovery packet
global Wireguard::tailscale_discovery_message: event(c: connection, is_orig: bool, senderDiscoPub: string);

@ifdef ( Conn::register_removal_hook )
## Wireguard finalization hook; wireguard information is logged in it
global finalize_wireguard: Conn::RemovalHook;
@endif
}

redef record connection += {
wireguard: Info &optional;
};

function set_session(c: connection)
{
if ( ! c?$wireguard )
{
c$wireguard = Info($uid=c$uid, $id=c$id);
@ifdef ( Conn::register_removal_hook )
Conn::register_removal_hook(c, finalize_wireguard);
@endif
}
}

event zeek_init() &priority=5
{
Log::create_stream(Wireguard::LOG, [$columns=Info, $ev=log_wireguard, $path="wireguard"]);
}

event Wireguard::handshake_initiation(c: connection, is_orig: bool, sender_index: count, unencrypted_ephemeral: string, encrypted_static: string, encrypted_timestamp: string, mac1: string, mac2: string)
{
set_session(c);
c$wireguard$initiations += 1;
c$wireguard$sender_index = sender_index;
}

event Wireguard::handshake_response(c: connection, is_orig: bool, sender_index: count, receiver_index: count, unencrypted_ephemeral: string, encrypted_nothing: string, mac1: string, mac2: string)
{
set_session(c);
c$wireguard$responses += 1;
if ( c$wireguard?$sender_index && c$wireguard$sender_index == receiver_index )
c$wireguard$established = T;
}

@ifdef ( Conn::register_removal_hook )
hook finalize_wireguard(c: connection)
@else
event connection_state_remove(c: connection)
@endif
{
if (c?$wireguard)
{
Log::write(LOG, c$wireguard);
}
}
31 changes: 23 additions & 8 deletions analyzer/zeek_analyzer.spicy
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,26 @@ module Zeek_zeek_spicy_wireguard;
import zeek_spicy_wireguard;
import zeek;

# TODO: For DPD, protocol analyzers should confirm or reject a protocol.
# on zeek_spicy_wireguard::Wireguard::%done {
# zeek::confirm_protocol();
# }
#
# on zeek_spicy_wireguard::Wireguard::%error {
# zeek::reject_protocol("error while parsing Wireguard record");
# }
on zeek_spicy_wireguard::HandshakeInitiation::%done {
zeek::confirm_protocol();
}

on zeek_spicy_wireguard::HandshakeResponse::%done {
zeek::confirm_protocol();
}

on zeek_spicy_wireguard::PacketCookieReply::%done {
zeek::confirm_protocol();
}

on zeek_spicy_wireguard::WireGuardPacket::%error {
zeek::reject_protocol("error while parsing Wireguard packet");
}

on zeek_spicy_wireguard::TailscalePacket::%done {
zeek::confirm_protocol();
}

on zeek_spicy_wireguard::TailscalePacket::%error {
zeek::reject_protocol("error while parsing Tailscale packet");
}
Loading

0 comments on commit 35fb052

Please sign in to comment.