From: Philippe Antoine Date: Sun, 31 Jul 2022 18:22:59 +0000 (+0200) Subject: quic: reassemble crypto frames and parse it X-Git-Tag: suricata-7.0.0-beta1~327 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=95125811b82155c6e184b3d4109ad0c43622a353;p=thirdparty%2Fsuricata.git quic: reassemble crypto frames and parse it --- diff --git a/rust/src/quic/frames.rs b/rust/src/quic/frames.rs index e1dedba5af..c115725ace 100644 --- a/rust/src/quic/frames.rs +++ b/rust/src/quic/frames.rs @@ -29,7 +29,7 @@ use tls_parser::TlsMessage::Handshake; use tls_parser::TlsMessageHandshake::{ClientHello, ServerHello}; use tls_parser::{ parse_tls_extensions, parse_tls_message_handshake, TlsCipherSuiteID, TlsExtension, - TlsExtensionType, + TlsExtensionType, TlsMessage, }; /// Tuple of StreamTag and offset @@ -139,12 +139,23 @@ pub(crate) struct Crypto { pub ja3: String, } +#[derive(Debug, PartialEq)] +pub(crate) struct CryptoFrag { + pub offset: u64, + pub length: u64, + pub data: Vec, +} + #[derive(Debug, PartialEq)] pub(crate) enum Frame { Padding, Ping, Ack(Ack), + // this is more than a crypto frame : it contains a fully parsed tls hello Crypto(Crypto), + // this is a regular quic crypto frame : they can be reassembled + // in order to parse a tls hello + CryptoFrag(CryptoFrag), Stream(Stream), Unknown(Vec), } @@ -272,45 +283,77 @@ fn quic_get_tls_extensions( return extv; } +fn parse_quic_handshake(msg: TlsMessage) -> Option { + if let Handshake(hs) = msg { + match hs { + ClientHello(ch) => { + let mut ja3 = String::with_capacity(256); + ja3.push_str(&u16::from(ch.version).to_string()); + ja3.push_str(","); + let mut dash = false; + for c in &ch.ciphers { + if dash { + ja3.push_str("-"); + } else { + dash = true; + } + ja3.push_str(&u16::from(*c).to_string()); + } + ja3.push_str(","); + let ciphers = ch.ciphers; + let extv = quic_get_tls_extensions(ch.ext, &mut ja3, true); + return Some(Frame::Crypto(Crypto { ciphers, extv, ja3 })); + } + ServerHello(sh) => { + let mut ja3 = String::with_capacity(256); + ja3.push_str(&u16::from(sh.version).to_string()); + ja3.push_str(","); + ja3.push_str(&u16::from(sh.cipher).to_string()); + ja3.push_str(","); + let ciphers = vec![sh.cipher]; + let extv = quic_get_tls_extensions(sh.ext, &mut ja3, false); + return Some(Frame::Crypto(Crypto { ciphers, extv, ja3 })); + } + _ => {} + } + } + return None; +} + fn parse_crypto_frame(input: &[u8]) -> IResult<&[u8], Frame, QuicError> { - let (rest, _offset) = quic_var_uint(input)?; + let (rest, offset) = quic_var_uint(input)?; let (rest, length) = quic_var_uint(rest)?; let (rest, data) = take(length as usize)(rest)?; - if let Ok((rest, msg)) = parse_tls_message_handshake(data) { - if let Handshake(hs) = msg { - match hs { - ClientHello(ch) => { - let mut ja3 = String::with_capacity(256); - ja3.push_str(&u16::from(ch.version).to_string()); - ja3.push_str(","); - let mut dash = false; - for c in &ch.ciphers { - if dash { - ja3.push_str("-"); - } else { - dash = true; - } - ja3.push_str(&u16::from(*c).to_string()); - } - ja3.push_str(","); - let ciphers = ch.ciphers; - 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); - ja3.push_str(&u16::from(sh.version).to_string()); - ja3.push_str(","); - ja3.push_str(&u16::from(sh.cipher).to_string()); - ja3.push_str(","); - let ciphers = vec![sh.cipher]; - let extv = quic_get_tls_extensions(sh.ext, &mut ja3, false); - return Ok((rest, Frame::Crypto(Crypto { ciphers, extv, ja3 }))); - } - _ => {} + if offset > 0 { + return Ok(( + rest, + Frame::CryptoFrag(CryptoFrag { + offset, + length, + data: data.to_vec(), + }), + )); + } + // if we have offset 0, try quick path : parse directly + match parse_tls_message_handshake(data) { + Ok((_, msg)) => { + if let Some(c) = parse_quic_handshake(msg) { + return Ok((rest, c)); } } + Err(nom7::Err::Incomplete(_)) => { + // offset 0 but incomplete : save it as a fragment for later reassembly + return Ok(( + rest, + Frame::CryptoFrag(CryptoFrag { + offset, + length, + data: data.to_vec(), + }), + )); + } + _ => {} } return Err(nom::Err::Error(QuicError::InvalidPacket)); } @@ -449,7 +492,44 @@ impl Frame { } pub(crate) fn decode_frames(input: &[u8]) -> IResult<&[u8], Vec, QuicError> { - let (rest, frames) = many0(complete(Frame::decode_frame))(input)?; + let (rest, mut frames) = many0(complete(Frame::decode_frame))(input)?; + + // reassemble crypto fragments : first find total size + let mut crypto_max_size = 0; + let mut crypto_total_size = 0; + for f in &frames { + match f { + Frame::CryptoFrag(c) => { + if crypto_max_size < c.offset + c.length { + crypto_max_size = c.offset + c.length; + } + crypto_total_size += c.length; + } + _ => {} + } + } + if crypto_max_size > 0 && crypto_total_size == crypto_max_size { + // we have some, and no gaps from offset 0 + let mut d = vec![0; crypto_max_size as usize]; + for f in &frames { + match f { + Frame::CryptoFrag(c) => { + d[c.offset as usize..(c.offset + c.length) as usize] + .clone_from_slice(&c.data); + } + _ => {} + } + } + match parse_tls_message_handshake(&d) { + Ok((_, msg)) => { + if let Some(c) = parse_quic_handshake(msg) { + // add a parsed crypto frame + frames.push(c); + } + } + _ => {} + } + } Ok((rest, frames)) }