Skip to content

Commit

Permalink
quic: ja3 computation and logging and detection
Browse files Browse the repository at this point in the history
Ticket: 5143
  • Loading branch information
catenacyber committed Mar 2, 2022
1 parent f9a89f5 commit 065f1ae
Show file tree
Hide file tree
Showing 9 changed files with 326 additions and 9 deletions.
15 changes: 15 additions & 0 deletions rust/src/quic/detect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,21 @@ pub unsafe extern "C" fn rs_quic_tx_get_sni(
}
}

#[no_mangle]
pub unsafe extern "C" fn rs_quic_tx_get_ja3(
tx: &QuicTransaction, buffer: *mut *const u8, buffer_len: *mut u32,
) -> u8 {
if let Some(ja3) = &tx.ja3 {
*buffer = ja3.as_ptr();
*buffer_len = ja3.len() as u32;
1
} else {
*buffer = ptr::null();
*buffer_len = 0;
0
}
}

#[no_mangle]
pub unsafe extern "C" fn rs_quic_tx_get_version(
tx: &QuicTransaction, buffer: *mut *const u8, buffer_len: *mut u32,
Expand Down
103 changes: 98 additions & 5 deletions rust/src/quic/frames.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ use nom::sequence::pair;
use nom::IResult;
use num::FromPrimitive;
use std::fmt;
use std::fmt::Write;
use tls_parser::TlsMessage::Handshake;
use tls_parser::TlsMessageHandshake::{ClientHello, ServerHello};
use tls_parser::{
Expand Down Expand Up @@ -137,6 +138,7 @@ pub(crate) struct Crypto {
//does not work, because of lifetime due to references to the slice used for parsing
//and cannot manage to create a stucture with the Vector the slices refer to...
//pub exts: Vec<TlsExtension>,
pub ja3: String,
}

#[derive(Debug, PartialEq)]
Expand Down Expand Up @@ -177,12 +179,25 @@ pub struct QuicTlsExtension {
}

// get interesting stuff out of parsed tls extensions
fn quic_get_tls_extensions(input: Option<&[u8]>) -> Vec<QuicTlsExtension> {
fn quic_get_tls_extensions(
input: Option<&[u8]>, ja3: &mut String, client: bool,
) -> Vec<QuicTlsExtension> {
let mut extv = Vec::new();
if let Some(extr) = input {
if let Ok((_, exts)) = parse_tls_extensions(extr) {
let mut dash = false;
for e in &exts {
let etype = TlsExtensionType::from(e);
if dash {
match write!(ja3, "-") {
_ => {}
}
} else {
dash = true;
}
match write!(ja3, "{}", u16::from(etype)) {
_ => {}
}
let mut values = Vec::new();
match e {
TlsExtension::SNI(x) => {
Expand All @@ -203,6 +218,54 @@ fn quic_get_tls_extensions(input: Option<&[u8]>) -> Vec<QuicTlsExtension> {
}
extv.push(QuicTlsExtension { etype, values })
}
if client {
match write!(ja3, ",") {
_ => {}
}
dash = false;
for e in &exts {
match e {
TlsExtension::EllipticCurves(x) => {
for ec in x {
if dash {
match write!(ja3, "-") {
_ => {}
}
} else {
dash = true;
}
match write!(ja3, "{}", ec.0) {
_ => {}
}
}
}
_ => {}
}
}
match write!(ja3, ",") {
_ => {}
}
dash = false;
for e in &exts {
match e {
TlsExtension::EcPointFormats(x) => {
for ec in *x {
if dash {
match write!(ja3, "-") {
_ => {}
}
} else {
dash = true;
}
match write!(ja3, "{}", ec) {
_ => {}
}
}
}
_ => {}
}
}
}
}
}
return extv;
Expand All @@ -218,14 +281,44 @@ fn parse_crypto_frame(input: &[u8]) -> IResult<&[u8], Frame, QuicError> {
if let Handshake(hs) = msg {
match hs {
ClientHello(ch) => {
let mut ja3 = String::with_capacity(256);
match write!(&mut ja3, "{},", u16::from(ch.version)) {
_ => {}
}
let mut dash = false;
for c in &ch.ciphers {
if dash {
match write!(&mut ja3, "-") {
_ => {}
}
} else {
dash = true;
}
match write!(&mut ja3, "{}", u16::from(*c)) {
_ => {}
}
}
match write!(&mut ja3, ",") {
_ => {}
}
let ciphers = ch.ciphers;
let extv = quic_get_tls_extensions(ch.ext);
return Ok((rest, Frame::Crypto(Crypto { ciphers, extv })));
let extv = quic_get_tls_extensions(ch.ext, &mut ja3, true);
return Ok((rest, Frame::Crypto(Crypto { ciphers, extv, ja3 })));
}
ServerHello(sh) => {
let mut ja3 = String::with_capacity(256);
match write!(&mut ja3, "{},", u16::from(sh.version)) {
_ => {}
}
match write!(&mut ja3, "{}", u16::from(sh.cipher)) {
_ => {}
}
match write!(&mut ja3, ",") {
_ => {}
}
let ciphers = vec![sh.cipher];
let extv = quic_get_tls_extensions(sh.ext);
return Ok((rest, Frame::Crypto(Crypto { ciphers, extv })));
let extv = quic_get_tls_extensions(sh.ext, &mut ja3, false);
return Ok((rest, Frame::Crypto(Crypto { ciphers, extv, ja3 })));
}
_ => {}
}
Expand Down
3 changes: 3 additions & 0 deletions rust/src/quic/logger.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ fn log_template(tx: &QuicTransaction, js: &mut JsonBuilder) -> Result<(), JsonEr
js.close()?;
}

if let Some(ja3) = &tx.ja3 {
js.set_string("ja3", ja3)?;
}
if tx.extv.len() > 0 {
js.open_array("extensions")?;
for e in &tx.extv {
Expand Down
12 changes: 8 additions & 4 deletions rust/src/quic/quic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,14 @@ pub struct QuicTransaction {
pub sni: Option<Vec<u8>>,
pub ua: Option<Vec<u8>>,
pub extv: Vec<QuicTlsExtension>,
pub ja3: Option<String>,
tx_data: AppLayerTxData,
}

impl QuicTransaction {
fn new(
header: QuicHeader, data: QuicData, sni: Option<Vec<u8>>, ua: Option<Vec<u8>>,
extv: Vec<QuicTlsExtension>,
extv: Vec<QuicTlsExtension>, ja3: Option<String>,
) -> Self {
let cyu = Cyu::generate(&header, &data.frames);
QuicTransaction {
Expand All @@ -53,6 +54,7 @@ impl QuicTransaction {
sni,
ua,
extv,
ja3,
tx_data: AppLayerTxData::new(),
}
}
Expand Down Expand Up @@ -100,9 +102,9 @@ impl QuicState {

fn new_tx(
&mut self, header: QuicHeader, data: QuicData, sni: Option<Vec<u8>>, ua: Option<Vec<u8>>,
extb: Vec<QuicTlsExtension>,
extb: Vec<QuicTlsExtension>, ja3: Option<String>,
) {
let mut tx = QuicTransaction::new(header, data, sni, ua, extb);
let mut tx = QuicTransaction::new(header, data, sni, ua, extb, ja3);
self.max_tx_id += 1;
tx.tx_id = self.max_tx_id;
self.transactions.push(tx);
Expand Down Expand Up @@ -215,6 +217,7 @@ impl QuicState {
if header.ty != QuicType::Short {
let mut sni: Option<Vec<u8>> = None;
let mut ua: Option<Vec<u8>> = None;
let mut ja3: Option<String> = None;
let mut extv: Vec<QuicTlsExtension> = Vec::new();
for frame in &data.frames {
match frame {
Expand All @@ -233,6 +236,7 @@ impl QuicState {
}
}
Frame::Crypto(c) => {
ja3 = Some(c.ja3.clone());
for e in &c.extv {
if e.etype == TlsExtensionType::ServerName
&& e.values.len() > 0
Expand All @@ -251,7 +255,7 @@ impl QuicState {
}
}

self.new_tx(header, data, sni, ua, extv);
self.new_tx(header, data, sni, ua, extv, ja3);
}
}
Err(_e) => {
Expand Down
2 changes: 2 additions & 0 deletions src/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,7 @@ noinst_HEADERS = \
detect-mark.h \
detect-metadata.h \
detect-modbus.h \
detect-quic-ja3.h \
detect-quic-sni.h \
detect-quic-ua.h \
detect-quic-version.h \
Expand Down Expand Up @@ -835,6 +836,7 @@ libsuricata_c_a_SOURCES = \
detect-mark.c \
detect-metadata.c \
detect-modbus.c \
detect-quic-ja3.c \
detect-quic-sni.c \
detect-quic-ua.c \
detect-quic-version.c \
Expand Down
2 changes: 2 additions & 0 deletions src/detect-engine-register.c
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,7 @@
#include "detect-mqtt-publish-message.h"
#include "detect-mqtt-subscribe-topic.h"
#include "detect-mqtt-unsubscribe-topic.h"
#include "detect-quic-ja3.h"
#include "detect-quic-sni.h"
#include "detect-quic-ua.h"
#include "detect-quic-version.h"
Expand Down Expand Up @@ -652,6 +653,7 @@ void SigTableSetup(void)
DetectMQTTPublishMessageRegister();
DetectMQTTSubscribeTopicRegister();
DetectMQTTUnsubscribeTopicRegister();
DetectQuicJa3Register();
DetectQuicSniRegister();
DetectQuicUaRegister();
DetectQuicVersionRegister();
Expand Down
1 change: 1 addition & 0 deletions src/detect-engine-register.h
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,7 @@ enum DetectKeywordId {
DETECT_AL_QUIC_UA,
DETECT_AL_QUIC_CYU_HASH,
DETECT_AL_QUIC_CYU_STRING,
DETECT_AL_QUIC_JA3,
DETECT_AL_TEMPLATE_BUFFER,

DETECT_BYPASS,
Expand Down
Loading

0 comments on commit 065f1ae

Please sign in to comment.