Skip to content

Commit

Permalink
Adding JA4 for Zeek
Browse files Browse the repository at this point in the history
The rest of JA4+ is still in progress
  • Loading branch information
john-althouse committed Nov 15, 2023
1 parent 6d4e5eb commit df38585
Show file tree
Hide file tree
Showing 5 changed files with 423 additions and 0 deletions.
39 changes: 39 additions & 0 deletions zeek/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# JA4+ for Zeek
This will add JA4+ fingerprints to zeek logs.

Included fingerprints:
JA4 - ```ssl.log```

Fingerprints still in development for Zeek:
JA4S - ```ssl.log```
JA4H - ```http.log```
JA4L - ```conn.log```
JA4X - ```x509.log```
JA4SSH - ```ssh.log```

See https://github.com/FoxIO-LLC/ja4 for more detail on JA4+ and other implmentations.

## Install
(in progress) If using the [Zeek Package Manager](https://docs.zeek.org/projects/package-manager/en/stable/), run:
```
zkg install ja4
```

Otherwise, download the zeek files to zeek/share/zeek/site/ja4 and add this line to either __load__.zeek or local.zeek:
```
@load ja4
```

## Configuration
By default this will add ja4 to ssl.log. To add the raw, original, or raw+original fingerprints, uncomment the applicable line in ja4.zeek:
```
# ja4_r: string &optional &log;
# ja4_o: string &optional &log;
# ja4_ro: string &optional &log;
```
...
```
# c$ssl$ja4_r = c$fp$ja4$r; # JA4_r (raw fingerprint)
# c$ssl$ja4_o = c$fp$ja4$o; # JA4_o (Original Ordering, not sorted, fingerprint)
# c$ssl$ja4_ro = c$fp$ja4$ro; # JA4_ro (raw fingerprint with original ordering, closest to what was seen on the wire)
```
2 changes: 2 additions & 0 deletions zeek/__load__.zeek
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
@load ./helper.zeek
@load ./ja4.zeek
163 changes: 163 additions & 0 deletions zeek/helper.zeek
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
# Copyright (c) 2023, FoxIO, LLC.
# All rights reserved.
# JA4 TLS Client Fingerprinting is Open-Source, Licensed under BSD 3-Clause
# For full license text and more details, see the repo root https://github.com/FoxIO-LLC/ja4
# JA4 by John Althouse
# Zeek script by Anthony Kasza and Caleb Yu

module FINGERPRINT;

export {
type Info: record {};
option delimiter: string = "_";
global TLS_VERSION_MAPPER: table[count] of string = {
[256] = "s1",
[512] = "s2",
[0x0300] = "s3",
[0x0301] = "10",
[0x0302] = "11",
[0x0303] = "12",
[0x0304] = "13"
};

global TLS_GREASE_TYPES: set[count] = {
0x0a0a, 0x1a1a, 0x2a2a, 0x3a3a,
0x4a4a, 0x5a5a, 0x6a6a, 0x7a7a,
0x8a8a, 0x9a9a, 0xaaaa, 0xbaba,
0xcaca, 0xdada, 0xeaea, 0xfafa
};
}
redef record connection += { fp: FINGERPRINT::Info &optional; };

module FINGERPRINT::JA4;

export {
type ClientHello: record {

# The highest TLS version found in the supported versions extension
# or the TLS record
version: count &optional;

# The cipher suites offered by the client, GREASE removed
cipher_suites: vector of count &default=vector();

# The compression methods offered by the client, GREASE removed
compression_methods: vector of count &default=vector();

# The extensions present in the ClientHello, GREASE removed
extension_codes: vector of count &default=vector();

# The alpn values present in the ClientHello
alpns: vector of string &optional;

# The signature and hashing algorithms offered by the client
signature_algos: vector of count &default=vector();

# The sni values present in the ClientHello
sni: vector of string &optional;
};
}

redef record FINGERPRINT::Info += {
client_hello: ClientHello &default=[];
};

# Format the signature and hashing algorithm codes into a single value
function make_quadword(byte1: count, byte2: count): count {
local t: table[string] of count = [
["0"] = 0, ["1"] = 1, ["2"] = 2, ["3"] = 3,
["4"] = 4, ["5"] = 5, ["6"] = 6, ["7"] = 7,
["8"] = 8, ["9"] = 9, ["a"] = 10, ["b"] = 11,
["c"] = 12, ["d"] = 13, ["e"] = 14, ["f"] = 15,
];
local b1 = to_lower(fmt("%02x", byte1));
local b2 = to_lower(fmt("%02x", byte2));
local byte1_total: count = (t[b1[0]] * 16*16*16) + (t[b1[1]] * 16*16);
local byte2_total: count = (t[b2[0]] * 16) + (t[b2[1]] * 1);
return byte1_total + byte2_total;
}

# This event is processed at the end of the hello, after all the extension-specific events occur
event ssl_client_hello(c: connection, version: count, record_version: count, possible_ts: time,
client_random: string, session_id: string, ciphers: index_vec, comp_methods: index_vec) {
if(!c?$fp) { c$fp = []; }
local no_grease_ciphers: index_vec = vector();
for (idx, val in ciphers) {
if (val !in FINGERPRINT::TLS_GREASE_TYPES) {
no_grease_ciphers += val;
}
}

local no_grease_comp_methods: index_vec = vector();
for (idx, val in no_grease_comp_methods) {
if (val !in FINGERPRINT::TLS_GREASE_TYPES) {
no_grease_comp_methods += val;
}
}

if (!c$fp$client_hello?$version) {
c$fp$client_hello$version = version;
}
c$fp$client_hello$cipher_suites = no_grease_ciphers;
c$fp$client_hello$compression_methods = no_grease_comp_methods;
}

# For each extension, ignoring GREASE, build up an array of code in the order they appear
event ssl_extension(c: connection, is_client: bool, code: count, val: string) {
if(!c?$fp) { c$fp = []; }
if (code in FINGERPRINT::TLS_GREASE_TYPES) { return; }
if (is_client) {
if (!c$fp?$client_hello) {
c$fp$client_hello = [];
}
c$fp$client_hello$extension_codes += code;
}
}

# For each alpn build up an array protocol strings
event ssl_extension_application_layer_protocol_negotiation(c: connection, is_client: bool, protocols: string_vec) {
if(!c?$fp) { c$fp = []; }
if (is_client) {
if (!c$fp$client_hello?$alpns) {
c$fp$client_hello$alpns = vector();
}
c$fp$client_hello$alpns += protocols;
}
}

# If the supported versions extension is present, find the largest offered version and store it
event ssl_extension_supported_versions(c: connection, is_client: bool, versions: index_vec) {
if(!c?$fp) { c$fp = []; }
if (is_client) {
local largest: count = 0;
for (idx, val in versions) {
if (val in FINGERPRINT::TLS_GREASE_TYPES) {
next;
}
if (val > largest) {
largest = val;
}
}
c$fp$client_hello$version = largest;
}
}

# Build up a list of hash and signature algorithms in the order they appear
event ssl_extension_signature_algorithm(c: connection, is_client: bool, signature_algorithms: signature_and_hashalgorithm_vec) {
if(!c?$fp) { c$fp = []; }
if (is_client) {
for (idx, val in signature_algorithms) {
local ha: count = val$HashAlgorithm;
local sa: count = val$SignatureAlgorithm;
c$fp$client_hello$signature_algos += make_quadword(ha, sa);
}
}
}

# Store the array of SNIs in the connection context
event ssl_extension_server_name(c: connection, is_client: bool, names: string_vec) {
if(!c?$fp) { c$fp = []; }
if (is_client) {
c$fp$client_hello$sni = names;
}
}
Loading

0 comments on commit df38585

Please sign in to comment.