diff --git a/rust/Cargo.toml.in b/rust/Cargo.toml.in index 0987651203..7dff99e430 100644 --- a/rust/Cargo.toml.in +++ b/rust/Cargo.toml.in @@ -58,3 +58,4 @@ suricata-derive = { path = "./derive" } [dev-dependencies] test-case = "~1.1.0" +hex = "~0.4.3" diff --git a/rust/cbindgen.toml b/rust/cbindgen.toml index 4f2355fb2f..ac1b8ed1c2 100644 --- a/rust/cbindgen.toml +++ b/rust/cbindgen.toml @@ -78,6 +78,8 @@ include = [ "SIPState", "ModbusState", "CMark", + "QuicState", + "QuicTransaction" ] # A list of items to not include in the generated bindings diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 5008021f0d..7c51b76c7d 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -1,4 +1,4 @@ -/* Copyright (C) 2017 Open Information Security Foundation +/* Copyright (C) 2017-2021 Open Information Security Foundation * * You can copy, redistribute or modify this Program under the terms of * the GNU General Public License version 2 as published by the Free @@ -130,6 +130,7 @@ pub mod asn1; pub mod mime; pub mod ssh; pub mod http2; +pub mod quic; pub mod plugin; pub mod util; pub mod ffi; diff --git a/rust/src/quic/cyu.rs b/rust/src/quic/cyu.rs new file mode 100644 index 0000000000..c3352d0d1c --- /dev/null +++ b/rust/src/quic/cyu.rs @@ -0,0 +1,195 @@ +/* Copyright (C) 2021 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use super::{ + frames::Frame, + parser::{QuicHeader, QuicVersion}, +}; +use md5::{Digest, Md5}; + +#[derive(Debug, PartialEq)] +pub struct Cyu { + pub string: String, + pub hash: String, +} + +impl Cyu { + pub(crate) fn new(string: String, hash: String) -> Self { + Self { string, hash } + } + + pub(crate) fn generate(header: &QuicHeader, frames: &[Frame]) -> Vec { + let version = match header.version { + QuicVersion::Q043 => Some("43"), + QuicVersion::Q044 => Some("44"), + QuicVersion::Q045 => Some("44"), + QuicVersion::Q046 => Some("46"), + _ => { + SCLogDebug!( + "Cannot match QUIC version {:?} to CYU version", + header.version + ); + None + } + }; + + let mut cyu_hashes = Vec::new(); + + if let Some(version) = version { + for frame in frames { + if let Frame::Stream(stream) = frame { + if let Some(tags) = &stream.tags { + let tags = tags + .iter() + .map(|(tag, _value)| tag.to_string()) + .collect::>() + .join("-"); + + let cyu_string = format!("{},{}", version, tags); + + let mut hasher = Md5::new(); + hasher.update(&cyu_string.as_bytes()); + let hash = hasher.finalize(); + + let cyu_hash = format!("{:x}", hash); + + cyu_hashes.push(Cyu::new(cyu_string, cyu_hash)); + } + } + } + } + + cyu_hashes + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::quic::frames::{Frame, Stream, StreamTag}; + use crate::quic::parser::{PublicFlags, QuicType}; + use test_case::test_case; + + macro_rules! mock_header_and_frames { + ($version:expr, $($variants:expr),+) => {{ + let header = QuicHeader::new( + PublicFlags::new(0x80), + QuicType::Initial, + $version, + vec![], + vec![], + ); + + let frames = vec![ + Frame::Stream(Stream { + fin: false, + stream_id: vec![], + offset: vec![], + tags: Some(vec![$(($variants, vec![])),*]) + }) + ]; + + (header, frames) + }}; + } + + // Salesforce tests here: + // https://engineering.salesforce.com/gquic-protocol-analysis-and-fingerprinting-in-zeek-a4178855d75f + #[test_case( + mock_header_and_frames!( + // version + QuicVersion::Q046, + // tags + StreamTag::Pad, StreamTag::Sni, + StreamTag::Stk, StreamTag::Ver, + StreamTag::Ccs, StreamTag::Nonc, + StreamTag::Aead, StreamTag::Uaid, + StreamTag::Scid, StreamTag::Tcid, + StreamTag::Pdmd, StreamTag::Smhl, + StreamTag::Icsl, StreamTag::Nonp, + StreamTag::Pubs, StreamTag::Mids, + StreamTag::Scls, StreamTag::Kexs, + StreamTag::Xlct, StreamTag::Csct, + StreamTag::Copt, StreamTag::Ccrt, + StreamTag::Irtt, StreamTag::Cfcw, + StreamTag::Sfcw + ), + Cyu { + string: "46,PAD-SNI-STK-VER-CCS-NONC-AEAD-UAID-SCID-TCID-PDMD-SMHL-ICSL-NONP-PUBS-MIDS-SCLS-KEXS-XLCT-CSCT-COPT-CCRT-IRTT-CFCW-SFCW".to_string(), + hash: "a46560d4548108cf99308319b3b85346".to_string(), + }; "test cyu 1" + )] + #[test_case( + mock_header_and_frames!( + // version + QuicVersion::Q043, + // tags + StreamTag::Pad, StreamTag::Sni, + StreamTag::Ver, StreamTag::Ccs, + StreamTag::Pdmd, StreamTag::Icsl, + StreamTag::Mids, StreamTag::Cfcw, + StreamTag::Sfcw + ), + Cyu { + string: "43,PAD-SNI-VER-CCS-PDMD-ICSL-MIDS-CFCW-SFCW".to_string(), + hash: "e030dea1f2eea44ac7db5fe4de792acd".to_string(), + }; "test cyu 2" + )] + #[test_case( + mock_header_and_frames!( + // version + QuicVersion::Q043, + // tags + StreamTag::Pad, StreamTag::Sni, + StreamTag::Stk, StreamTag::Ver, + StreamTag::Ccs, StreamTag::Scid, + StreamTag::Pdmd, StreamTag::Icsl, + StreamTag::Mids, StreamTag::Cfcw, + StreamTag::Sfcw + ), + Cyu { + string: "43,PAD-SNI-STK-VER-CCS-SCID-PDMD-ICSL-MIDS-CFCW-SFCW".to_string(), + hash: "0811fab28e41e8c8a33e220a15b964d9".to_string(), + }; "test cyu 3" + )] + #[test_case( + mock_header_and_frames!( + // version + QuicVersion::Q043, + // tags + StreamTag::Pad, StreamTag::Sni, + StreamTag::Stk, StreamTag::Ver, + StreamTag::Ccs, StreamTag::Nonc, + StreamTag::Aead, StreamTag::Scid, + StreamTag::Pdmd, StreamTag::Icsl, + StreamTag::Pubs, StreamTag::Mids, + StreamTag::Kexs, StreamTag::Xlct, + StreamTag::Cfcw, StreamTag::Sfcw + ), + Cyu { + string: "43,PAD-SNI-STK-VER-CCS-NONC-AEAD-SCID-PDMD-ICSL-PUBS-MIDS-KEXS-XLCT-CFCW-SFCW".to_string(), + hash: "d8b208b236d176c89407500dbefb04c2".to_string(), + }; "test cyu 4" + )] + fn test_cyu_generate(input: (QuicHeader, Vec), expected: Cyu) { + let (header, frames) = input; + + let cyu = Cyu::generate(&header, &frames); + assert_eq!(1, cyu.len()); + assert_eq!(expected, cyu[0]); + } +} diff --git a/rust/src/quic/detect.rs b/rust/src/quic/detect.rs new file mode 100644 index 0000000000..c5b1fe18c4 --- /dev/null +++ b/rust/src/quic/detect.rs @@ -0,0 +1,65 @@ +/* Copyright (C) 2021 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use crate::quic::quic::{QuicTransaction}; +use std::ptr; + +#[no_mangle] +pub unsafe extern "C" fn rs_quic_tx_get_cyu_hash( + tx: &QuicTransaction, i: u32, buffer: *mut *const u8, buffer_len: *mut u32, +) -> u8 { + if (i as usize) < tx.cyu.len() { + let cyu = &tx.cyu[i as usize]; + + let p = &cyu.hash; + + *buffer = p.as_ptr(); + *buffer_len = p.len() as u32; + + 1 + } else { + *buffer = ptr::null(); + *buffer_len = 0; + + 0 + } +} + +#[no_mangle] +pub unsafe extern "C" fn rs_quic_tx_get_cyu_string( + tx: &QuicTransaction, i: u32, buffer: *mut *const u8, buffer_len: *mut u32, +) -> u8 { + if (i as usize) < tx.cyu.len() { + let cyu = &tx.cyu[i as usize]; + + let p = &cyu.string; + + *buffer = p.as_ptr(); + *buffer_len = p.len() as u32; + 1 + } else { + *buffer = ptr::null(); + *buffer_len = 0; + + 0 + } +} + +#[no_mangle] +pub extern "C" fn rs_quic_tx_get_version(tx: &QuicTransaction) -> u32 { + tx.header.version.into() +} diff --git a/rust/src/quic/error.rs b/rust/src/quic/error.rs new file mode 100644 index 0000000000..4099fe4cab --- /dev/null +++ b/rust/src/quic/error.rs @@ -0,0 +1,63 @@ +/* Copyright (C) 2021 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use nom::error::{ErrorKind, ParseError}; +use std::error::Error; +use std::fmt; + +#[derive(Debug, PartialEq)] +pub enum QuicError { + StreamTagNoMatch(u32), + InvalidPacket, + Incomplete, + NomError(ErrorKind), +} + +impl ParseError for QuicError { + fn from_error_kind(_input: I, kind: ErrorKind) -> Self { + QuicError::NomError(kind) + } + + fn append(_input: I, kind: ErrorKind, _other: Self) -> Self { + QuicError::NomError(kind) + } +} + +impl fmt::Display for QuicError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + QuicError::StreamTagNoMatch(tag) => { + write!(f, "Could not match stream tag: 0x{:x}", tag) + } + QuicError::Incomplete => write!(f, "Incomplete data"), + QuicError::InvalidPacket => write!(f, "Invalid packet"), + QuicError::NomError(e) => write!(f, "Internal parser error {:?}", e), + } + } +} + +impl Error for QuicError {} + +impl From> for QuicError { + fn from(err: nom::Err) -> Self { + match err { + nom::Err::Incomplete(_) => QuicError::Incomplete, + nom::Err::Error(e) => e, + nom::Err::Failure(e) => e, + } + } +} diff --git a/rust/src/quic/frames.rs b/rust/src/quic/frames.rs new file mode 100644 index 0000000000..3f880dcd1b --- /dev/null +++ b/rust/src/quic/frames.rs @@ -0,0 +1,236 @@ +/* Copyright (C) 2021 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use super::error::QuicError; +use nom::bytes::complete::take; +use nom::combinator::{all_consuming, complete}; +use nom::multi::{count, many0}; +use nom::number::complete::{be_u16, be_u32, be_u8, le_u16, le_u32}; +use nom::sequence::pair; +use nom::IResult; +use num::FromPrimitive; +use std::fmt; + +/// Tuple of StreamTag and offset +type TagOffset = (StreamTag, u32); + +/// Tuple of StreamTag and value +type TagValue = (StreamTag, Vec); + +#[derive(Debug, PartialEq)] +pub(crate) struct Stream { + pub fin: bool, + pub stream_id: Vec, + pub offset: Vec, + pub tags: Option>, +} + +#[repr(u32)] +#[derive(Debug, PartialEq, Clone, Copy, FromPrimitive)] +pub(crate) enum StreamTag { + Aead = 0x41454144, + Ccrt = 0x43435254, + Ccs = 0x43435300, + Cetv = 0x43455456, + Cfcw = 0x43464357, + Chlo = 0x43484c4f, + Copt = 0x434f5054, + Csct = 0x43534354, + Ctim = 0x4354494d, + Icsl = 0x4943534c, + Irtt = 0x49525454, + Kexs = 0x4b455853, + Mids = 0x4d494453, + Mspc = 0x4d535043, + Nonc = 0x4e4f4e43, + Nonp = 0x4e4f4e50, + Pad = 0x50414400, + Pdmd = 0x50444d44, + Pubs = 0x50554253, + Scid = 0x53434944, + Scls = 0x53434c53, + Sfcw = 0x53464357, + Smhl = 0x534d484c, + Sni = 0x534e4900, + Sno = 0x534e4f00, + Stk = 0x53544b00, + Tcid = 0x54434944, + Uaid = 0x55414944, + Ver = 0x56455200, + Xlct = 0x584c4354, +} + +impl fmt::Display for StreamTag { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{}", + match self { + StreamTag::Aead => "AEAD", + StreamTag::Ccrt => "CCRT", + StreamTag::Ccs => "CCS", + StreamTag::Cetv => "CETV", + StreamTag::Cfcw => "CFCW", + StreamTag::Chlo => "CHLO", + StreamTag::Copt => "COPT", + StreamTag::Csct => "CSCT", + StreamTag::Ctim => "CTIM", + StreamTag::Icsl => "ICSL", + StreamTag::Irtt => "IRTT", + StreamTag::Kexs => "KEXS", + StreamTag::Mids => "MIDS", + StreamTag::Mspc => "MSPC", + StreamTag::Nonc => "NONC", + StreamTag::Nonp => "NONP", + StreamTag::Pad => "PAD", + StreamTag::Pdmd => "PDMD", + StreamTag::Pubs => "PUBS", + StreamTag::Scid => "SCID", + StreamTag::Scls => "SCLS", + StreamTag::Sfcw => "SFCW", + StreamTag::Smhl => "SMHL", + StreamTag::Sni => "SNI", + StreamTag::Sno => "SNO", + StreamTag::Stk => "STK", + StreamTag::Tcid => "TCID", + StreamTag::Uaid => "UAID", + StreamTag::Ver => "VER", + StreamTag::Xlct => "XLCT", + } + ) + } +} + +#[derive(Debug, PartialEq)] +pub(crate) enum Frame { + Padding, + Stream(Stream), + Unknown(Vec), +} + +fn parse_tag(input: &[u8]) -> IResult<&[u8], StreamTag, QuicError> { + let (rest, tag) = be_u32(input)?; + + let tag = StreamTag::from_u32(tag).ok_or(nom::Err::Error(QuicError::StreamTagNoMatch(tag)))?; + + Ok((rest, tag)) +} + +fn parse_tag_and_offset(input: &[u8]) -> IResult<&[u8], TagOffset, QuicError> { + pair(parse_tag, le_u32)(input) +} + +fn parse_crypto_stream(input: &[u8]) -> IResult<&[u8], Vec, QuicError> { + // [message tag][number of tag entries: N][pad][[tag][end offset], ...N][value data] + let (rest, _message_tag) = parse_tag(input)?; + + let (rest, num_entries) = le_u16(rest)?; + let (rest, _padding) = take(2usize)(rest)?; + + let (rest, tags_offset) = count(complete(parse_tag_and_offset), num_entries.into())(rest)?; + + // Convert (Tag, Offset) to (Tag, Value) + let mut tags = Vec::new(); + let mut previous_offset = 0; + let mut rest = rest; + for (tag, offset) in tags_offset { + // offsets should be increasing + let value_len = offset + .checked_sub(previous_offset) + .ok_or(nom::Err::Error(QuicError::InvalidPacket))?; + let (new_rest, value) = take(value_len)(rest)?; + + previous_offset = offset; + rest = new_rest; + + tags.push((tag, value.to_vec())) + } + + Ok((rest, tags)) +} + +fn parse_stream_frame(input: &[u8], frame_ty: u8) -> IResult<&[u8], Frame, QuicError> { + let rest = input; + + // 0b1_f_d_ooo_ss + let fin = frame_ty & 0x40 == 0x40; + let has_data_length = frame_ty & 0x20 == 0x20; + + let offset_hdr_length = { + let mut offset_length = (frame_ty & 0x1c) >> 2; + if offset_length != 0 { + offset_length += 1; + } + offset_length + }; + + let stream_id_hdr_length = usize::from((frame_ty & 0x03) + 1); + + let (rest, stream_id) = take(stream_id_hdr_length)(rest)?; + let (rest, offset) = take(offset_hdr_length)(rest)?; + + let (rest, data_length) = if has_data_length { + let (rest, data_length) = be_u16(rest)?; + + (rest, usize::from(data_length)) + } else { + (rest, rest.len()) + }; + + let (rest, stream_data) = take(data_length)(rest)?; + + let tags = if let Ok((_, tags)) = all_consuming(parse_crypto_stream)(stream_data) { + Some(tags) + } else { + None + }; + + Ok(( + rest, + Frame::Stream(Stream { + fin, + stream_id: stream_id.to_vec(), + offset: offset.to_vec(), + tags, + }), + )) +} + +impl Frame { + fn decode_frame(input: &[u8]) -> IResult<&[u8], Frame, QuicError> { + let (rest, frame_ty) = be_u8(input)?; + + // Special frame types + let (rest, value) = if frame_ty & 0x80 == 0x80 { + // STREAM + parse_stream_frame(rest, frame_ty)? + } else { + match frame_ty { + 0x00 => (rest, Frame::Padding), + _ => ([].as_ref(), Frame::Unknown(rest.to_vec())), + } + }; + + Ok((rest, value)) + } + + pub(crate) fn decode_frames(input: &[u8]) -> IResult<&[u8], Vec, QuicError> { + let (rest, frames) = many0(complete(Frame::decode_frame))(input)?; + + Ok((rest, frames)) + } +} diff --git a/rust/src/quic/logger.rs b/rust/src/quic/logger.rs new file mode 100644 index 0000000000..619364ea68 --- /dev/null +++ b/rust/src/quic/logger.rs @@ -0,0 +1,44 @@ +/* Copyright (C) 2021 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use super::quic::QuicTransaction; +use crate::jsonbuilder::{JsonBuilder, JsonError}; + +fn log_template(tx: &QuicTransaction, js: &mut JsonBuilder) -> Result<(), JsonError> { + js.open_object("quic")?; + js.set_uint("version", u32::from(tx.header.version).into())?; + + js.open_array("cyu")?; + for cyu in &tx.cyu { + js.start_object()?; + js.set_string("hash", &cyu.hash)?; + js.set_string("string", &cyu.string)?; + js.close()?; + } + js.close()?; + + js.close()?; + Ok(()) +} + +#[no_mangle] +pub unsafe extern "C" fn rs_quic_to_json( + tx: *mut std::os::raw::c_void, js: &mut JsonBuilder, +) -> bool { + let tx = cast_pointer!(tx, QuicTransaction); + log_template(tx, js).is_ok() +} diff --git a/rust/src/quic/mod.rs b/rust/src/quic/mod.rs new file mode 100644 index 0000000000..5a330e9c1c --- /dev/null +++ b/rust/src/quic/mod.rs @@ -0,0 +1,24 @@ +/* Copyright (C) 2021 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +mod cyu; +pub mod detect; +mod error; +mod frames; +mod logger; +mod parser; +pub mod quic; diff --git a/rust/src/quic/parser.rs b/rust/src/quic/parser.rs new file mode 100644 index 0000000000..d3283de7cc --- /dev/null +++ b/rust/src/quic/parser.rs @@ -0,0 +1,316 @@ +/* Copyright (C) 2021 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ +use super::error::QuicError; +use super::frames::Frame; +use nom::bytes::complete::take; +use nom::combinator::{all_consuming, map}; +use nom::number::complete::{be_u32, be_u8}; +use nom::IResult; + +/* + gQUIC is the Google version of QUIC. + + The following docs were referenced when writing this parser + + References: + - https://docs.google.com/document/d/1WJvyZflAO2pq77yOLbp9NsGjC1CHetAXV8I0fQe-B_U/edit + - https://docs.google.com/document/d/1g5nIXAIkN_Y-7XJW5K45IblHd_L2f5LTaDUDwvZ5L6g/edit + - https://www.slideshare.net/shigeki_ohtsu/quic-overview + - https://tools.ietf.org/html/draft-ietf-quic-transport-17#section-19.8 + - https://github.com/salesforce/GQUIC_Protocol_Analyzer/blob/master/src/gquic-protocol.pac +*/ + +// List of accepted and tested quic versions format +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub struct QuicVersion(pub u32); + +impl QuicVersion { + pub const Q043: QuicVersion = QuicVersion(0x51303433); + pub const Q044: QuicVersion = QuicVersion(0x51303434); + pub const Q045: QuicVersion = QuicVersion(0x51303435); + pub const Q046: QuicVersion = QuicVersion(0x51303436); + + fn is_gquic(&self) -> bool { + *self == QuicVersion::Q043 + || *self == QuicVersion::Q044 + || *self == QuicVersion::Q045 + || *self == QuicVersion::Q046 + } +} + +impl From for u32 { + fn from(from: QuicVersion) -> Self { + from.0 + } +} + +impl From for QuicVersion { + fn from(from: u32) -> Self { + QuicVersion(from) + } +} + +#[derive(Debug, PartialEq)] +pub enum QuicType { + Initial, + Retry, + Handshake, + ZeroRTT, + VersionNegotiation, + Short, +} + +#[derive(Debug, PartialEq)] +pub struct PublicFlags { + is_long: bool, +} + +impl PublicFlags { + pub fn new(value: u8) -> Self { + let is_long = value & 0x80 == 0x80; + + PublicFlags { is_long } + } +} + +/// A QUIC packet's header. +#[derive(Debug, PartialEq)] +pub struct QuicHeader { + pub flags: PublicFlags, + pub ty: QuicType, + pub version: QuicVersion, + pub dcid: Vec, + pub scid: Vec, +} + +#[derive(Debug, PartialEq)] +pub(crate) struct QuicData { + pub frames: Vec, +} + +impl QuicHeader { + #[cfg(test)] + pub(crate) fn new( + flags: PublicFlags, ty: QuicType, version: QuicVersion, dcid: Vec, scid: Vec, + ) -> Self { + Self { + flags, + ty, + version, + dcid, + scid, + } + } + + pub(crate) fn from_bytes( + input: &[u8], dcid_len: usize, + ) -> IResult<&[u8], QuicHeader, QuicError> { + let (rest, first) = be_u8(input)?; + let flags = PublicFlags::new(first); + + if !flags.is_long { + // Decode short header + let (rest, dcid) = take(dcid_len)(rest)?; + + return Ok(( + rest, + QuicHeader { + flags, + ty: QuicType::Short, + version: QuicVersion(0), + dcid: dcid.to_vec(), + scid: Vec::new(), + }, + )); + } else { + // Decode Long header + let (rest, version) = map(be_u32, QuicVersion)(rest)?; + + let ty = if version == QuicVersion(0) { + QuicType::VersionNegotiation + } else { + // Q046 is when they started using IETF + if version.is_gquic() && version != QuicVersion::Q046 { + match first & 0x7f { + 0x7f => QuicType::Initial, + 0x7e => QuicType::Retry, + 0x7d => QuicType::Handshake, + 0x7c => QuicType::ZeroRTT, + _ => { + return Err(nom::Err::Error(QuicError::InvalidPacket)); + } + } + } else { + match (first & 0x30) >> 4 { + 0x00 => QuicType::Initial, + 0x01 => QuicType::ZeroRTT, + 0x02 => QuicType::Handshake, + 0x03 => QuicType::Retry, + _ => { + return Err(nom::Err::Error(QuicError::InvalidPacket)); + } + } + } + }; + + let (rest, dcid, scid) = if version.is_gquic() { + // [DCID_LEN (4)][SCID_LEN (4)] + let (rest, lengths) = be_u8(rest)?; + + let mut dcid_len = (lengths & 0xF0) >> 4; + let mut scid_len = lengths & 0x0F; + + // Decode dcid length if not 0 + if dcid_len != 0 { + dcid_len += 3; + } + + // Decode scid length if not 0 + if scid_len != 0 { + scid_len += 3; + } + + let (rest, dcid) = take(dcid_len as usize)(rest)?; + let (rest, scid) = take(scid_len as usize)(rest)?; + + (rest, dcid.to_vec(), scid.to_vec()) + } else { + let (rest, dcid_len) = be_u8(rest)?; + let (rest, dcid) = take(dcid_len as usize)(rest)?; + + let (rest, scid_len) = be_u8(rest)?; + let (rest, scid) = take(scid_len as usize)(rest)?; + (rest, dcid.to_vec(), scid.to_vec()) + }; + + let rest = match ty { + QuicType::Initial => { + let (rest, _pkt_num) = be_u32(rest)?; + let (rest, _msg_auth_hash) = take(12usize)(rest)?; + + rest + } + _ => rest, + }; + + Ok(( + rest, + QuicHeader { + flags, + ty, + version, + dcid, + scid, + }, + )) + } + } +} + +impl QuicData { + pub(crate) fn from_bytes(input: &[u8]) -> Result { + let (_, frames) = all_consuming(Frame::decode_frames)(input)?; + Ok(QuicData { frames }) + } +} + +#[cfg(test)] +mod tests { + + use super::*; + use crate::quic::frames::{Frame, Stream, StreamTag}; + + #[test] + fn public_flags_test() { + let pf = PublicFlags::new(0xcb); + assert_eq!(PublicFlags { is_long: true }, pf); + } + + const TEST_DEFAULT_CID_LENGTH: usize = 8; + + #[test] + fn test_parse_gquic_unknown_version() { + // Test that we can parse unknown versions + let data = hex::decode("cbff00001d1091d0b10ac886039973885dfa07c469431409b15e86dd0990aaf906c5de620c4538398ffa58004482d2b19e732fc58818e57cb63569ce11abc00ea4fbac0725c5690a0838c27faf88663b48eca63baf0fba52af4eff7b4117384457c5cf1c1c0e52a1843c7676a4a35cf60c4c179e7186274c121110ace964771f31090f586b283bddbf82e9dd1d6a0e41fbaf243540dfb64f4543e1e87857c77cfc1ee9f883b97b89b6321ce30436119acfdbf2b31f4d0dbac0e5ea740ee59c8619d7c431320504c67f5c3aa9be5192f28ae378e0c8305fb95f01e7cb47c27f92cad7e8d55f699a41df3afe3894939f79e5f164771a6fe987602d975a06bfe8e6906b23601d08bcf2026eac25eca958a7b19ed7ba415e4d31b474264a479c53f01e1d35745ae62a9b148e39e2d7d33176f384d6ce4beb25d2177a8e0fbe5503ea034c9a645e5a8c98098bc5db4e11a351ac72b7079db1a858e11a6c6a4a1f44e1073903029cc08e82c48e6de00f5da7a546db371a4e49d4a213339ca074832cfeb4c39731f98a1683d7fb7db8a5b48c763440d6003fdfadd6a7fb23a62074064aafd585f6a887d5648ce71099d7d21e5cc1e14645f066016a7570d885bde4f239226884ee64fb8ec1218efec83d46ca104d9104bf46637ba3a3d8d6a88967859d60f46198e3a8495f2f211d717c6ca39987d2f4f971b502809932d973736dac67e5e28152c23d844d99fe7a5def822ca97aa433603423ee7fef57e6daa4579bb8f4f14a93663c54db415da5e8b9000d673d99c065c5922ba193eada475f2366b422d42dd86dd3b86fdef67d0e71cd200e3e24b77578f90e0e60717e3a1d6078b836d262028fc73efe7b3684b635f3001225acfd249fbe950dae7c539f015a0ce51c983c4d8e01d7e73e16946e681b2148d0ae4e72fb44d1048eb25572dae0a8016434b8c9e3fd3c93045b8afe67adc6cf7ce61a46819b712a8c24980e6c75bf007adf8910badfa102cd60c96238c8719b5e2b405905cfa6840176c7f71b7d9a2f480c36806f415b93b72821f0547b06f298584be093710a381fa352c34ba221cbcf1bbcd0b7d1aea354e460f6824df14d4bf4377a4503397e70f9993a55905ba298e798d9c69386eae8d0ebf6d871ff75e2d5a546bb8ee6ad9c92d88f950e2d8bc371aaad0d948e9f81c8151c51ee17c9257df4fd27cfeb9944b301a0fff1cb0a1b18836969457edd42f6ba370ecc2e5700bbb9fc15dc9f88c9bfc12c7dda64d423179c1eff8c53cca97056e09a07e29d02b4e553141b78d224cd79ae8056d923d41bc67eec00c943e3a62304487261d0877d54c40b7453c52e6c02141c2fa6601a357d53dddf39ae6e152501813e562a0613ca727ef3b0548c1f5a7e5036a8da84e166cec45de83bf217fb8f6c9a0ea20db0b16d1d2bb9e5e305e9d1f35e3065ab7188f79b9a841d1f6000ea744df1ba49116f7558feedf70677e35985d71b1c87c988d0b1ef2e436a54a77397546513c82bf307fc4b29152cafab11c8527eeda2addd00081c3b7b836a39920322a405c4e3774f20feda9998bf703fd10e93748b7834f3f3794d5b1f3f3099c608e84b025f5675b1526e8feee91ed04f4e91e37bd8e7089ec5a48edc2537bcddbd9d118d7937e2c25fa383186efd2f48fa3f5ebe7eaf544835bb330b61af1a95158c5e").unwrap(); + let (_rest, value) = + QuicHeader::from_bytes(data.as_ref(), TEST_DEFAULT_CID_LENGTH).unwrap(); + assert_eq!( + QuicHeader { + flags: PublicFlags { is_long: true }, + ty: QuicType::Initial, + version: QuicVersion(0xff00001d), + dcid: hex::decode("91d0b10ac886039973885dfa07c46943") + .unwrap() + .to_vec(), + scid: hex::decode("09b15e86dd0990aaf906c5de620c4538398ffa58") + .unwrap() + .to_vec() + }, + value + ); + } + + #[test] + fn test_parse_gquic_q044() { + let test_data = hex::decode("ff513034345005cad2cc06c4d0e400000001afac230bc5b56fb89800171b800143484c4f09000000504144008f030000534e490098030000564552009c03000043435300ac03000050444d44b00300004943534cb40300004d494453b803000043464357bc03000053464357c003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003132372e302e302e310000000001e8816092921ae87eed8086a215829158353039803a09006400000000c0000000800000").unwrap(); + let (rest, header) = + QuicHeader::from_bytes(test_data.as_ref(), TEST_DEFAULT_CID_LENGTH).unwrap(); + + assert_eq!( + QuicHeader { + flags: PublicFlags { is_long: true }, + ty: QuicType::Initial, + version: QuicVersion::Q044, + dcid: hex::decode("05cad2cc06c4d0e4").unwrap().to_vec(), + scid: Vec::new(), + }, + header + ); + + let data = QuicData::from_bytes(rest).unwrap(); + assert_eq!( + QuicData { + frames: vec![Frame::Stream(Stream { + fin: false, + stream_id: vec![0x1], + offset: vec![], + tags: Some(vec![ + (StreamTag::Pad, [0x0; 911].to_vec()), + ( + StreamTag::Sni, + vec![0x31, 0x32, 0x37, 0x2e, 0x30, 0x2e, 0x30, 0x2e, 0x31] + ), + (StreamTag::Ver, vec![0x0, 0x0, 0x0, 0x0]), + ( + StreamTag::Ccs, + vec![ + 0x1, 0xe8, 0x81, 0x60, 0x92, 0x92, 0x1a, 0xe8, 0x7e, 0xed, 0x80, + 0x86, 0xa2, 0x15, 0x82, 0x91 + ] + ), + (StreamTag::Pdmd, vec![0x58, 0x35, 0x30, 0x39]), + (StreamTag::Icsl, vec![0x80, 0x3a, 0x9, 0x0]), + (StreamTag::Mids, vec![0x64, 0x0, 0x0, 0x0]), + (StreamTag::Cfcw, vec![0x0, 0xc0, 0x0, 0x0]), + (StreamTag::Sfcw, vec![0x0, 0x80, 0x0, 0x0]), + ]) + })] + }, + data, + ); + } +} diff --git a/rust/src/quic/quic.rs b/rust/src/quic/quic.rs new file mode 100644 index 0000000000..a7a6cb5255 --- /dev/null +++ b/rust/src/quic/quic.rs @@ -0,0 +1,329 @@ +/* Copyright (C) 2021 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use super::{ + cyu::Cyu, + parser::{QuicData, QuicHeader}, +}; +use crate::applayer::{self, *}; +use crate::core::{self, AppProto, Flow, ALPROTO_FAILED, ALPROTO_UNKNOWN, IPPROTO_UDP}; +use std::ffi::CString; + +static mut ALPROTO_QUIC: AppProto = ALPROTO_UNKNOWN; + +const DEFAULT_DCID_LEN: usize = 16; + +#[derive(Debug)] +pub struct QuicTransaction { + tx_id: u64, + pub header: QuicHeader, + pub cyu: Vec, + + de_state: Option<*mut core::DetectEngineState>, + events: *mut core::AppLayerDecoderEvents, + tx_data: AppLayerTxData, +} + +impl QuicTransaction { + fn new(header: QuicHeader, data: QuicData) -> Self { + let cyu = Cyu::generate(&header, &data.frames); + QuicTransaction { + tx_id: 0, + header, + cyu, + de_state: None, + events: std::ptr::null_mut(), + tx_data: AppLayerTxData::new(), + } + } + + fn free(&mut self) { + if !self.events.is_null() { + core::sc_app_layer_decoder_events_free_events(&mut self.events); + } + if let Some(state) = self.de_state { + core::sc_detect_engine_state_free(state); + } + } +} + +impl Drop for QuicTransaction { + fn drop(&mut self) { + self.free(); + } +} + +pub struct QuicState { + max_tx_id: u64, + transactions: Vec, +} + +impl Default for QuicState { + fn default() -> Self { + Self { + max_tx_id: 0, + transactions: Vec::new(), + } + } +} + +impl QuicState { + fn new() -> Self { + Self::default() + } + + // Free a transaction by ID. + fn free_tx(&mut self, tx_id: u64) { + let tx = self + .transactions + .iter() + .position(|tx| tx.tx_id == tx_id + 1); + if let Some(idx) = tx { + let _ = self.transactions.remove(idx); + } + } + + fn get_tx(&mut self, tx_id: u64) -> Option<&QuicTransaction> { + self.transactions.iter().find(|&tx| tx.tx_id == tx_id + 1) + } + + fn new_tx(&mut self, header: QuicHeader, data: QuicData) -> QuicTransaction { + let mut tx = QuicTransaction::new(header, data); + self.max_tx_id += 1; + tx.tx_id = self.max_tx_id; + return tx; + } + + fn tx_iterator( + &mut self, min_tx_id: u64, state: &mut u64, + ) -> Option<(&QuicTransaction, u64, bool)> { + let mut index = *state as usize; + let len = self.transactions.len(); + + while index < len { + let tx = &self.transactions[index]; + if tx.tx_id < min_tx_id + 1 { + index += 1; + continue; + } + *state = index as u64; + return Some((tx, tx.tx_id - 1, (len - index) > 1)); + } + + return None; + } + + fn parse(&mut self, input: &[u8]) -> bool { + match QuicHeader::from_bytes(input, DEFAULT_DCID_LEN) { + Ok((rest, header)) => match QuicData::from_bytes(rest) { + Ok(data) => { + let transaction = self.new_tx(header, data); + self.transactions.push(transaction); + + return true; + } + Err(_e) => { + return false; + } + }, + Err(_e) => { + return false; + } + } + } +} + +#[no_mangle] +pub extern "C" fn rs_quic_state_new( + _orig_state: *mut std::os::raw::c_void, _orig_proto: AppProto, +) -> *mut std::os::raw::c_void { + let state = QuicState::new(); + let boxed = Box::new(state); + return Box::into_raw(boxed) as *mut _; +} + +#[no_mangle] +pub extern "C" fn rs_quic_state_free(state: *mut std::os::raw::c_void) { + // Just unbox... + std::mem::drop(unsafe { Box::from_raw(state as *mut QuicState) }); +} + +#[no_mangle] +pub unsafe extern "C" fn rs_quic_state_tx_free(state: *mut std::os::raw::c_void, tx_id: u64) { + let state = cast_pointer!(state, QuicState); + state.free_tx(tx_id); +} + +#[no_mangle] +pub unsafe extern "C" fn rs_quic_probing_parser( + _flow: *const Flow, _direction: u8, input: *const u8, input_len: u32, _rdir: *mut u8, +) -> AppProto { + let slice = build_slice!(input, input_len as usize); + + if QuicHeader::from_bytes(slice, DEFAULT_DCID_LEN).is_ok() { + return ALPROTO_QUIC; + } else { + return ALPROTO_FAILED; + } +} + +#[no_mangle] +pub unsafe extern "C" fn rs_quic_parse( + _flow: *const Flow, state: *mut std::os::raw::c_void, _pstate: *mut std::os::raw::c_void, + stream_slice: StreamSlice, + _data: *const std::os::raw::c_void, +) -> AppLayerResult { + let state = cast_pointer!(state, QuicState); + let buf = stream_slice.as_slice(); + + if state.parse(buf) { + return AppLayerResult::ok(); + } + return AppLayerResult::err(); +} + +#[no_mangle] +pub unsafe extern "C" fn rs_quic_state_get_tx( + state: *mut std::os::raw::c_void, tx_id: u64, +) -> *mut std::os::raw::c_void { + let state = cast_pointer!(state, QuicState); + match state.get_tx(tx_id) { + Some(tx) => { + return tx as *const _ as *mut _; + } + None => { + return std::ptr::null_mut(); + } + } +} + +#[no_mangle] +pub unsafe extern "C" fn rs_quic_state_get_tx_count(state: *mut std::os::raw::c_void) -> u64 { + let state = cast_pointer!(state, QuicState); + return state.max_tx_id; +} + +#[no_mangle] +pub extern "C" fn rs_quic_state_progress_completion_status(_direction: u8) -> std::os::raw::c_int { + // This parser uses 1 to signal transaction completion status. + return 1; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_quic_tx_get_alstate_progress( + tx: *mut std::os::raw::c_void, _direction: u8, +) -> std::os::raw::c_int { + let _tx = cast_pointer!(tx, QuicTransaction); + return 1; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_quic_state_get_events( + tx: *mut std::os::raw::c_void, +) -> *mut core::AppLayerDecoderEvents { + let tx = cast_pointer!(tx, QuicTransaction); + return tx.events; +} + +#[no_mangle] +pub extern "C" fn rs_quic_state_get_event_info( + _event_name: *const std::os::raw::c_char, _event_id: *mut std::os::raw::c_int, + _event_type: *mut core::AppLayerEventType, +) -> std::os::raw::c_int { + return -1; +} + +#[no_mangle] +pub extern "C" fn rs_quic_state_get_event_info_by_id( + _event_id: std::os::raw::c_int, _event_name: *mut *const std::os::raw::c_char, + _event_type: *mut core::AppLayerEventType, +) -> i8 { + return -1; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_quic_state_get_tx_iterator( + _ipproto: u8, _alproto: AppProto, state: *mut std::os::raw::c_void, min_tx_id: u64, + _max_tx_id: u64, istate: &mut u64, +) -> applayer::AppLayerGetTxIterTuple { + let state = cast_pointer!(state, QuicState); + match state.tx_iterator(min_tx_id, istate) { + Some((tx, out_tx_id, has_next)) => { + let c_tx = tx as *const _ as *mut _; + let ires = applayer::AppLayerGetTxIterTuple::with_values(c_tx, out_tx_id, has_next); + return ires; + } + None => { + return applayer::AppLayerGetTxIterTuple::not_found(); + } + } +} + +export_tx_data_get!(rs_quic_get_tx_data, QuicTransaction); + +// Parser name as a C style string. +const PARSER_NAME: &[u8] = b"quic\0"; + +#[no_mangle] +pub unsafe extern "C" fn rs_quic_register_parser() { + let default_port = CString::new("[443,80]").unwrap(); + let parser = RustParser { + name: PARSER_NAME.as_ptr() as *const std::os::raw::c_char, + default_port: default_port.as_ptr(), + ipproto: IPPROTO_UDP, + probe_ts: Some(rs_quic_probing_parser), + probe_tc: Some(rs_quic_probing_parser), + min_depth: 0, + max_depth: 16, + state_new: rs_quic_state_new, + state_free: rs_quic_state_free, + tx_free: rs_quic_state_tx_free, + parse_ts: rs_quic_parse, + parse_tc: rs_quic_parse, + get_tx_count: rs_quic_state_get_tx_count, + get_tx: rs_quic_state_get_tx, + tx_comp_st_ts: 1, + tx_comp_st_tc: 1, + tx_get_progress: rs_quic_tx_get_alstate_progress, + get_eventinfo: Some(rs_quic_state_get_event_info), + get_eventinfo_byid: Some(rs_quic_state_get_event_info_by_id), + localstorage_new: None, + localstorage_free: None, + get_files: None, + get_tx_iterator: Some(rs_quic_state_get_tx_iterator), + get_tx_data: rs_quic_get_tx_data, + apply_tx_config: None, + flags: 0, + truncate: None, + get_frame_id_by_name: None, + get_frame_name_by_id: None, + + }; + + let ip_proto_str = CString::new("udp").unwrap(); + + if AppLayerProtoDetectConfProtoDetectionEnabled(ip_proto_str.as_ptr(), parser.name) != 0 { + let alproto = AppLayerRegisterProtocolDetection(&parser, 1); + ALPROTO_QUIC = alproto; + if AppLayerParserConfParserEnabled(ip_proto_str.as_ptr(), parser.name) != 0 { + let _ = AppLayerRegisterParser(&parser, alproto); + } + SCLogDebug!("Rust quic parser registered."); + } else { + SCLogDebug!("Protocol detector and parser disabled for quic."); + } +} diff --git a/src/Makefile.am b/src/Makefile.am index 475f0892fc..cb4f497374 100755 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -36,6 +36,7 @@ noinst_HEADERS = \ app-layer-ike.h \ app-layer-krb5.h \ app-layer-modbus.h \ + app-layer-quic.h \ app-layer-mqtt.h \ app-layer-nbss.h \ app-layer-nfs-tcp.h \ @@ -241,6 +242,9 @@ noinst_HEADERS = \ detect-mark.h \ detect-metadata.h \ detect-modbus.h \ + detect-quic-version.h \ + detect-quic-cyu-hash.h \ + detect-quic-cyu-string.h \ detect-mqtt-connack-sessionpresent.h \ detect-mqtt-connect-clientid.h \ detect-mqtt-connect-flags.h \ @@ -400,6 +404,7 @@ noinst_HEADERS = \ output-json-krb5.h \ output-json-metadata.h \ output-json-modbus.h \ + output-json-quic.h \ output-json-mqtt.h \ output-json-netflow.h \ output-json-nfs.h \ @@ -627,6 +632,7 @@ libsuricata_c_a_SOURCES = \ app-layer-ike.c \ app-layer-krb5.c \ app-layer-modbus.c \ + app-layer-quic.c \ app-layer-mqtt.c \ app-layer-nfs-tcp.c \ app-layer-nfs-udp.c \ @@ -826,6 +832,9 @@ libsuricata_c_a_SOURCES = \ detect-mark.c \ detect-metadata.c \ detect-modbus.c \ + detect-quic-version.c \ + detect-quic-cyu-hash.c \ + detect-quic-cyu-string.c \ detect-mqtt-connack-sessionpresent.c \ detect-mqtt-connect-clientid.c \ detect-mqtt-connect-flags.c \ @@ -985,6 +994,7 @@ libsuricata_c_a_SOURCES = \ output-json-krb5.c \ output-json-metadata.c \ output-json-modbus.c \ + output-json-quic.c \ output-json-mqtt.c \ output-json-netflow.c \ output-json-nfs.c \ diff --git a/src/app-layer-detect-proto.c b/src/app-layer-detect-proto.c index e422297e0f..c5a3ba87cd 100644 --- a/src/app-layer-detect-proto.c +++ b/src/app-layer-detect-proto.c @@ -896,6 +896,8 @@ static void AppLayerProtoDetectPrintProbingParsers(AppLayerProtoDetectProbingPar printf(" alproto: ALPROTO_KRB5\n"); else if (pp_pe->alproto == ALPROTO_DHCP) printf(" alproto: ALPROTO_DHCP\n"); + else if (pp_pe->alproto == ALPROTO_QUIC) + printf(" alproto: ALPROTO_QUIC\n"); else if (pp_pe->alproto == ALPROTO_SNMP) printf(" alproto: ALPROTO_SNMP\n"); else if (pp_pe->alproto == ALPROTO_SIP) @@ -973,6 +975,8 @@ static void AppLayerProtoDetectPrintProbingParsers(AppLayerProtoDetectProbingPar printf(" alproto: ALPROTO_IKE\n"); else if (pp_pe->alproto == ALPROTO_KRB5) printf(" alproto: ALPROTO_KRB5\n"); + else if (pp_pe->alproto == ALPROTO_QUIC) + printf(" alproto: ALPROTO_QUIC\n"); else if (pp_pe->alproto == ALPROTO_DHCP) printf(" alproto: ALPROTO_DHCP\n"); else if (pp_pe->alproto == ALPROTO_SNMP) diff --git a/src/app-layer-parser.c b/src/app-layer-parser.c index a2f643e445..8ba1826c66 100644 --- a/src/app-layer-parser.c +++ b/src/app-layer-parser.c @@ -65,6 +65,7 @@ #include "app-layer-sip.h" #include "app-layer-rfb.h" #include "app-layer-mqtt.h" +#include "app-layer-quic.h" #include "app-layer-template.h" #include "app-layer-template-rust.h" #include "app-layer-rdp.h" @@ -1657,6 +1658,7 @@ void AppLayerParserRegisterProtocolParsers(void) rs_dhcp_register_parser(); RegisterSNMPParsers(); RegisterSIPParsers(); + RegisterQuicParsers(); RegisterTemplateRustParsers(); RegisterRFBParsers(); RegisterMQTTParsers(); diff --git a/src/app-layer-protos.c b/src/app-layer-protos.c index b75a8a5616..11bed306bf 100644 --- a/src/app-layer-protos.c +++ b/src/app-layer-protos.c @@ -93,6 +93,9 @@ const char *AppProtoToString(AppProto alproto) case ALPROTO_KRB5: proto_name = "krb5"; break; + case ALPROTO_QUIC: + proto_name = "quic"; + break; case ALPROTO_DHCP: proto_name = "dhcp"; break; @@ -170,6 +173,8 @@ AppProto StringToAppProto(const char *proto_name) if (strcmp(proto_name, "ike") == 0) return ALPROTO_IKE; if (strcmp(proto_name,"krb5")==0) return ALPROTO_KRB5; + if (strcmp(proto_name, "quic") == 0) + return ALPROTO_QUIC; if (strcmp(proto_name,"dhcp")==0) return ALPROTO_DHCP; if (strcmp(proto_name,"snmp")==0) return ALPROTO_SNMP; if (strcmp(proto_name,"sip")==0) return ALPROTO_SIP; diff --git a/src/app-layer-protos.h b/src/app-layer-protos.h index c31d5c6249..f9a8d213da 100644 --- a/src/app-layer-protos.h +++ b/src/app-layer-protos.h @@ -48,6 +48,7 @@ enum AppProtoEnum { ALPROTO_TFTP, ALPROTO_IKE, ALPROTO_KRB5, + ALPROTO_QUIC, ALPROTO_DHCP, ALPROTO_SNMP, ALPROTO_SIP, diff --git a/src/app-layer-quic.c b/src/app-layer-quic.c new file mode 100644 index 0000000000..837aa9c496 --- /dev/null +++ b/src/app-layer-quic.c @@ -0,0 +1,40 @@ +/* Copyright (C) 2021 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +/** + * \file + * + * Quic Application Layer + * + */ + +#include "suricata-common.h" +#include "stream.h" +#include "conf.h" + +#include "util-unittest.h" + +#include "app-layer-detect-proto.h" +#include "app-layer-parser.h" + +#include "app-layer-quic.h" +#include "rust.h" + +void RegisterQuicParsers(void) +{ + rs_quic_register_parser(); +} diff --git a/src/app-layer-quic.h b/src/app-layer-quic.h new file mode 100644 index 0000000000..a0038bc086 --- /dev/null +++ b/src/app-layer-quic.h @@ -0,0 +1,29 @@ +/* Copyright (C) 2021 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +/** + * \file + * + */ + +#ifndef __APP_LAYER_QUIC_H__ +#define __APP_LAYER_QUIC_H__ + +void RegisterQuicParsers(void); +void QuicParserRegisterTests(void); + +#endif /* __APP_LAYER_QUIC_H__ */ diff --git a/src/detect-engine-register.c b/src/detect-engine-register.c index c043566749..d3a7c8a425 100644 --- a/src/detect-engine-register.c +++ b/src/detect-engine-register.c @@ -217,6 +217,9 @@ #include "detect-mqtt-publish-message.h" #include "detect-mqtt-subscribe-topic.h" #include "detect-mqtt-unsubscribe-topic.h" +#include "detect-quic-version.h" +#include "detect-quic-cyu-hash.h" +#include "detect-quic-cyu-string.h" #include "detect-template-buffer.h" #include "detect-bypass.h" @@ -647,6 +650,9 @@ void SigTableSetup(void) DetectMQTTPublishMessageRegister(); DetectMQTTSubscribeTopicRegister(); DetectMQTTUnsubscribeTopicRegister(); + DetectQuicVersionRegister(); + DetectQuicCyuHashRegister(); + DetectQuicCyuStringRegister(); DetectTemplateBufferRegister(); DetectBypassRegister(); diff --git a/src/detect-engine-register.h b/src/detect-engine-register.h index a805d4ba29..ac54611516 100644 --- a/src/detect-engine-register.h +++ b/src/detect-engine-register.h @@ -286,6 +286,9 @@ enum DetectKeywordId { DETECT_AL_MQTT_PUBLISH_MESSAGE, DETECT_AL_MQTT_SUBSCRIBE_TOPIC, DETECT_AL_MQTT_UNSUBSCRIBE_TOPIC, + DETECT_AL_QUIC_VERSION, + DETECT_AL_QUIC_CYU_HASH, + DETECT_AL_QUIC_CYU_STRING, DETECT_AL_TEMPLATE_BUFFER, DETECT_BYPASS, diff --git a/src/detect-quic-cyu-hash.c b/src/detect-quic-cyu-hash.c new file mode 100644 index 0000000000..b2a8a0f1fe --- /dev/null +++ b/src/detect-quic-cyu-hash.c @@ -0,0 +1,428 @@ +/* Copyright (C) 2021 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +/** + * + * Implements the quic.cyu.hash sticky buffer + */ + +#include "suricata-common.h" +#include "detect.h" +#include "detect-parse.h" +#include "detect-engine.h" +#include "detect-engine-mpm.h" +#include "detect-engine-prefilter.h" +#include "detect-engine-content-inspection.h" +#include "detect-quic-cyu-hash.h" +#include "rust.h" + +#ifdef UNITTESTS +static void DetectQuicCyuHashRegisterTests(void); +#endif + +#define KEYWORD_NAME "quic.cyu.hash" +#define KEYWORD_DOC "quic-cyu.html#quic-cyu-hash" +#define BUFFER_NAME "quic.cyu.hash" +#define BUFFER_DESC "QUIC CYU Hash" +static int g_buffer_id = 0; + +struct QuicHashGetDataArgs { + uint32_t local_id; /**< used as index into thread inspect array */ + void *txv; +}; + +static int DetectQuicCyuHashSetup(DetectEngineCtx *de_ctx, Signature *s, const char *arg) +{ + if (DetectBufferSetActiveList(s, g_buffer_id) < 0) + return -1; + + if (DetectSignatureSetAppProto(s, ALPROTO_QUIC) < 0) + return -1; + + return 0; +} + +static InspectionBuffer *QuicHashGetData(DetectEngineThreadCtx *det_ctx, + const DetectEngineTransforms *transforms, Flow *f, struct QuicHashGetDataArgs *cbdata, + int list_id, bool first) +{ + SCEnter(); + + InspectionBuffer *buffer = + InspectionBufferMultipleForListGet(det_ctx, list_id, cbdata->local_id); + if (buffer == NULL) + return NULL; + if (!first && buffer->inspect != NULL) + return buffer; + + const uint8_t *data; + uint32_t data_len; + if (rs_quic_tx_get_cyu_hash(cbdata->txv, (uint16_t)cbdata->local_id, &data, &data_len) == 0) { + return NULL; + } + + InspectionBufferSetupMulti(buffer, transforms, data, data_len); + + SCReturnPtr(buffer, "InspectionBuffer"); +} + +static int DetectEngineInspectQuicHash(DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx, + const DetectEngineAppInspectionEngine *engine, const Signature *s, Flow *f, uint8_t flags, + void *alstate, void *txv, uint64_t tx_id) +{ + uint32_t local_id = 0; + + const DetectEngineTransforms *transforms = NULL; + if (!engine->mpm) { + transforms = engine->v2.transforms; + } + + while (1) { + struct QuicHashGetDataArgs cbdata = { + local_id, + txv, + }; + InspectionBuffer *buffer = + QuicHashGetData(det_ctx, transforms, f, &cbdata, engine->sm_list, false); + if (buffer == NULL || buffer->inspect == NULL) + break; + + det_ctx->buffer_offset = 0; + det_ctx->discontinue_matching = 0; + det_ctx->inspection_recursion_counter = 0; + + const int match = DetectEngineContentInspection(de_ctx, det_ctx, s, engine->smd, NULL, f, + (uint8_t *)buffer->inspect, buffer->inspect_len, buffer->inspect_offset, + DETECT_CI_FLAGS_SINGLE, DETECT_ENGINE_CONTENT_INSPECTION_MODE_STATE); + if (match == 1) { + return DETECT_ENGINE_INSPECT_SIG_MATCH; + } + local_id++; + } + return DETECT_ENGINE_INSPECT_SIG_NO_MATCH; +} + +typedef struct PrefilterMpmQuicHash { + int list_id; + const MpmCtx *mpm_ctx; + const DetectEngineTransforms *transforms; +} PrefilterMpmQuicHash; + +/** \brief QuicHash Mpm prefilter callback + * + * \param det_ctx detection engine thread ctx + * \param p packet to inspect + * \param f flow to inspect + * \param txv tx to inspect + * \param pectx inspection context + */ +static void PrefilterTxQuicHash(DetectEngineThreadCtx *det_ctx, const void *pectx, Packet *p, + Flow *f, void *txv, const uint64_t idx, const uint8_t flags) +{ + SCEnter(); + + const PrefilterMpmQuicHash *ctx = (const PrefilterMpmQuicHash *)pectx; + const MpmCtx *mpm_ctx = ctx->mpm_ctx; + const int list_id = ctx->list_id; + + uint32_t local_id = 0; + while (1) { + // loop until we get a NULL + + struct QuicHashGetDataArgs cbdata = { local_id, txv }; + InspectionBuffer *buffer = + QuicHashGetData(det_ctx, ctx->transforms, f, &cbdata, list_id, true); + if (buffer == NULL) + break; + + if (buffer->inspect_len >= mpm_ctx->minlen) { + (void)mpm_table[mpm_ctx->mpm_type].Search( + mpm_ctx, &det_ctx->mtcu, &det_ctx->pmq, buffer->inspect, buffer->inspect_len); + } + + local_id++; + } +} + +static void PrefilterMpmQuicHashFree(void *ptr) +{ + SCFree(ptr); +} + +static int PrefilterMpmQuicHashRegister(DetectEngineCtx *de_ctx, SigGroupHead *sgh, MpmCtx *mpm_ctx, + const DetectBufferMpmRegistery *mpm_reg, int list_id) +{ + PrefilterMpmQuicHash *pectx = SCCalloc(1, sizeof(*pectx)); + if (pectx == NULL) + return -1; + pectx->list_id = list_id; + pectx->mpm_ctx = mpm_ctx; + pectx->transforms = &mpm_reg->transforms; + + return PrefilterAppendTxEngine(de_ctx, sgh, PrefilterTxQuicHash, mpm_reg->app_v2.alproto, + mpm_reg->app_v2.tx_min_progress, pectx, PrefilterMpmQuicHashFree, mpm_reg->pname); +} + +static bool DetectQuicHashValidateCallback(const Signature *s, const char **sigerror) +{ + const SigMatch *sm = s->init_data->smlists[g_buffer_id]; + for (; sm != NULL; sm = sm->next) { + if (sm->type != DETECT_CONTENT) + continue; + + const DetectContentData *cd = (DetectContentData *)sm->ctx; + + if (cd->flags & DETECT_CONTENT_NOCASE) { + *sigerror = BUFFER_NAME " should not be used together with " + "nocase, since the rule is automatically " + "lowercased anyway which makes nocase redundant."; + SCLogWarning(SC_WARN_POOR_RULE, "rule %u: %s", s->id, *sigerror); + } + + if (cd->content_len != 32) { + *sigerror = "Invalid length of the specified" BUFFER_NAME " (should " + "be 32 characters long). This rule will therefore " + "never match."; + SCLogWarning(SC_WARN_POOR_RULE, "rule %u: %s", s->id, *sigerror); + return FALSE; + } + for (size_t i = 0; i < cd->content_len; ++i) { + if (!isxdigit(cd->content[i])) { + *sigerror = "Invalid " BUFFER_NAME + " string (should be string of hexadecimal characters)." + "This rule will therefore never match."; + SCLogWarning(SC_WARN_POOR_RULE, "rule %u: %s", s->id, *sigerror); + return FALSE; + } + } + } + + return TRUE; +} + +void DetectQuicCyuHashRegister(void) +{ + /* quic.cyu.hash sticky buffer */ + sigmatch_table[DETECT_AL_QUIC_CYU_HASH].name = KEYWORD_NAME; + sigmatch_table[DETECT_AL_QUIC_CYU_HASH].desc = "sticky buffer to match on the QUIC CYU hash"; + sigmatch_table[DETECT_AL_QUIC_CYU_HASH].url = "/rules/" KEYWORD_DOC; + sigmatch_table[DETECT_AL_QUIC_CYU_HASH].Setup = DetectQuicCyuHashSetup; + sigmatch_table[DETECT_AL_QUIC_CYU_HASH].flags |= SIGMATCH_NOOPT; +#ifdef UNITTESTS + sigmatch_table[DETECT_AL_QUIC_CYU_HASH].RegisterTests = DetectQuicCyuHashRegisterTests; +#endif + + DetectAppLayerMpmRegister2( + BUFFER_NAME, SIG_FLAG_TOSERVER, 2, PrefilterMpmQuicHashRegister, NULL, ALPROTO_QUIC, 1); + + DetectAppLayerInspectEngineRegister2( + BUFFER_NAME, ALPROTO_QUIC, SIG_FLAG_TOSERVER, 0, DetectEngineInspectQuicHash, NULL); + + DetectBufferTypeSetDescriptionByName(BUFFER_NAME, BUFFER_DESC); + + g_buffer_id = DetectBufferTypeGetByName(BUFFER_NAME); + + DetectBufferTypeRegisterValidateCallback(BUFFER_NAME, DetectQuicHashValidateCallback); +} + +#ifdef UNITTESTS +#include "app-layer-parser.h" +#include "util-unittest.h" +#include "util-unittest-helper.h" +#include "flow-util.h" + +/** + * \test DetectQuicCyuHashTest01 is a test for a valid quic packet, matching + * on the cyu hash + * + * \retval 1 on success + * \retval 0 on failure + */ +static int DetectQuicCyuHashTest01(void) +{ + /* quic packet */ + uint8_t buf[] = { 0xc3, 0x51, 0x30, 0x34, 0x36, 0x50, 0x76, 0xd8, 0x63, 0xb7, 0x54, 0xf7, 0xab, + 0x32, 0x00, 0x00, 0x00, 0x01, 0x54, 0xfd, 0xf4, 0x79, 0x48, 0x76, 0xd0, 0x87, 0x58, 0x8d, + 0x26, 0x8f, 0xa0, 0x01, 0x04, 0x00, 0x43, 0x48, 0x4c, 0x4f, 0x11, 0x00, 0x00, 0x00, 0x50, + 0x41, 0x44, 0x00, 0xe4, 0x02, 0x00, 0x00, 0x53, 0x4e, 0x49, 0x00, 0xf7, 0x02, 0x00, 0x00, + 0x56, 0x45, 0x52, 0x00, 0xfb, 0x02, 0x00, 0x00, 0x43, 0x43, 0x53, 0x00, 0x0b, 0x03, 0x00, + 0x00, 0x55, 0x41, 0x49, 0x44, 0x2c, 0x03, 0x00, 0x00, 0x54, 0x43, 0x49, 0x44, 0x30, 0x03, + 0x00, 0x00, 0x50, 0x44, 0x4d, 0x44, 0x34, 0x03, 0x00, 0x00, 0x53, 0x4d, 0x48, 0x4c, 0x38, + 0x03, 0x00, 0x00, 0x49, 0x43, 0x53, 0x4c, 0x3c, 0x03, 0x00, 0x00, 0x4e, 0x4f, 0x4e, 0x50, + 0x5c, 0x03, 0x00, 0x00, 0x4d, 0x49, 0x44, 0x53, 0x60, 0x03, 0x00, 0x00, 0x53, 0x43, 0x4c, + 0x53, 0x64, 0x03, 0x00, 0x00, 0x43, 0x53, 0x43, 0x54, 0x64, 0x03, 0x00, 0x00, 0x43, 0x4f, + 0x50, 0x54, 0x64, 0x03, 0x00, 0x00, 0x49, 0x52, 0x54, 0x54, 0x68, 0x03, 0x00, 0x00, 0x43, + 0x46, 0x43, 0x57, 0x6c, 0x03, 0x00, 0x00, 0x53, 0x46, 0x43, 0x57, 0x70, 0x03, 0x00, 0x00, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x31, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x51, 0x30, 0x34, 0x36, 0x01, 0xe8, + 0x81, 0x60, 0x92, 0x92, 0x1a, 0xe8, 0x7e, 0xed, 0x80, 0x86, 0xa2, 0x15, 0x82, 0x91, 0x43, + 0x68, 0x72, 0x6f, 0x6d, 0x65, 0x2f, 0x37, 0x39, 0x2e, 0x30, 0x2e, 0x33, 0x39, 0x34, 0x35, + 0x2e, 0x31, 0x31, 0x37, 0x20, 0x4c, 0x69, 0x6e, 0x75, 0x78, 0x20, 0x78, 0x38, 0x36, 0x5f, + 0x36, 0x34, 0x00, 0x00, 0x00, 0x00, 0x58, 0x35, 0x30, 0x39, 0x01, 0x00, 0x00, 0x00, 0x1e, + 0x00, 0x00, 0x00, 0x82, 0x88, 0x09, 0x00, 0xfa, 0x0f, 0xde, 0xb7, 0x2e, 0x7e, 0x6c, 0x78, + 0xcc, 0x09, 0x65, 0xab, 0x06, 0x0c, 0x31, 0x05, 0xfa, 0xd9, 0xa2, 0x0b, 0xdd, 0x74, 0x5c, + 0x28, 0xdf, 0x7b, 0x74, 0x23, 0x64, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1d, 0x43, + 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00 }; + + Flow f; + void *quic_state = NULL; + Packet *p = NULL; + Signature *s = NULL; + ThreadVars tv; + DetectEngineThreadCtx *det_ctx = NULL; + AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc(); + + memset(&tv, 0, sizeof(ThreadVars)); + memset(&f, 0, sizeof(Flow)); + + p = UTHBuildPacketReal(buf, sizeof(buf), IPPROTO_UDP, "192.168.1.5", "192.168.1.1", 41424, 443); + + FLOW_INITIALIZE(&f); + f.flags |= FLOW_IPV4; + f.proto = IPPROTO_UDP; + f.protomap = FlowGetProtoMapping(f.proto); + + p->flow = &f; + p->flags |= PKT_HAS_FLOW; + p->flowflags |= FLOW_PKT_TOSERVER; + f.alproto = ALPROTO_QUIC; + + DetectEngineCtx *de_ctx = DetectEngineCtxInit(); + FAIL_IF_NULL(de_ctx); + de_ctx->mpm_matcher = mpm_default_matcher; + de_ctx->flags |= DE_QUIET; + + s = DetectEngineAppendSig(de_ctx, + "alert quic any any -> any any " + "(msg:\"Test QUIC CYU hash\"; " + "quic.cyu.hash; content:\"910a5e3a4d51593bd59a44611544f209\"; " + "sid:1;)"); + FAIL_IF_NULL(s); + + SigGroupBuild(de_ctx); + DetectEngineThreadCtxInit(&tv, (void *)de_ctx, (void *)&det_ctx); + + FLOWLOCK_WRLOCK(&f); + int r = AppLayerParserParse( + NULL, alp_tctx, &f, ALPROTO_QUIC, STREAM_TOSERVER, buf, sizeof(buf)); + if (r != 0) { + printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r); + FLOWLOCK_UNLOCK(&f); + FAIL; + } + FLOWLOCK_UNLOCK(&f); + + quic_state = f.alstate; + FAIL_IF_NULL(quic_state); + + /* do detect */ + SigMatchSignatures(&tv, de_ctx, det_ctx, p); + + if (!(PacketAlertCheck(p, 1))) { + printf("sig 1 didn't alert, but it should have: "); + FAIL; + } + + if (alp_tctx != NULL) + AppLayerParserThreadCtxFree(alp_tctx); + if (det_ctx != NULL) + DetectEngineThreadCtxDeinit(&tv, det_ctx); + if (de_ctx != NULL) + SigGroupCleanup(de_ctx); + if (de_ctx != NULL) + DetectEngineCtxFree(de_ctx); + + FLOW_DESTROY(&f); + UTHFreePacket(p); + PASS; +} + +static void DetectQuicCyuHashRegisterTests(void) +{ + UtRegisterTest("DetectQuicCyuHashTest01", DetectQuicCyuHashTest01); +} + +#endif /* UNITTESTS */ diff --git a/src/detect-quic-cyu-hash.h b/src/detect-quic-cyu-hash.h new file mode 100644 index 0000000000..579f1ca92d --- /dev/null +++ b/src/detect-quic-cyu-hash.h @@ -0,0 +1,28 @@ +/* Copyright (C) 2021 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +/** + * \file + * + */ + +#ifndef __DETECT_QUIC_CYU_HASH_H__ +#define __DETECT_QUIC_CYU_HASH_H__ + +void DetectQuicCyuHashRegister(void); + +#endif /* __DETECT_QUIC_CYU_HASH_H__ */ diff --git a/src/detect-quic-cyu-string.c b/src/detect-quic-cyu-string.c new file mode 100644 index 0000000000..5f44f707fa --- /dev/null +++ b/src/detect-quic-cyu-string.c @@ -0,0 +1,390 @@ +/* Copyright (C) 2021 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +/** + * + * Implements the quic.cyu.string sticky buffer + */ + +#include "suricata-common.h" +#include "detect.h" +#include "detect-parse.h" +#include "detect-engine.h" +#include "detect-engine-mpm.h" +#include "detect-engine-prefilter.h" +#include "detect-engine-content-inspection.h" +#include "detect-quic-cyu-string.h" +#include "rust.h" + +#ifdef UNITTESTS +static void DetectQuicCyuStringRegisterTests(void); +#endif + +#define KEYWORD_NAME "quic.cyu.string" +#define KEYWORD_DOC "quic-cyu.html#quic-cyu-string" +#define BUFFER_NAME "quic.cyu.string" +#define BUFFER_DESC "QUIC CYU String" +static int g_buffer_id = 0; + +struct QuicStringGetDataArgs { + uint32_t local_id; /**< used as index into thread inspect array */ + void *txv; +}; + +static int DetectQuicCyuStringSetup(DetectEngineCtx *de_ctx, Signature *s, const char *arg) +{ + if (DetectBufferSetActiveList(s, g_buffer_id) < 0) + return -1; + + if (DetectSignatureSetAppProto(s, ALPROTO_QUIC) < 0) + return -1; + + return 0; +} + +static InspectionBuffer *QuicStringGetData(DetectEngineThreadCtx *det_ctx, + const DetectEngineTransforms *transforms, Flow *f, struct QuicStringGetDataArgs *cbdata, + int list_id, bool first) +{ + SCEnter(); + + InspectionBuffer *buffer = + InspectionBufferMultipleForListGet(det_ctx, list_id, cbdata->local_id); + if (buffer == NULL) + return NULL; + if (!first && buffer->inspect != NULL) + return buffer; + + const uint8_t *data; + uint32_t data_len; + if (rs_quic_tx_get_cyu_string(cbdata->txv, cbdata->local_id, &data, &data_len) == 0) { + return NULL; + } + + InspectionBufferSetupMulti(buffer, transforms, data, data_len); + + SCReturnPtr(buffer, "InspectionBuffer"); +} + +static int DetectEngineInspectQuicString(DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx, + const DetectEngineAppInspectionEngine *engine, const Signature *s, Flow *f, uint8_t flags, + void *alstate, void *txv, uint64_t tx_id) +{ + uint32_t local_id = 0; + + const DetectEngineTransforms *transforms = NULL; + if (!engine->mpm) { + transforms = engine->v2.transforms; + } + + while (1) { + struct QuicStringGetDataArgs cbdata = { + local_id, + txv, + }; + InspectionBuffer *buffer = + QuicStringGetData(det_ctx, transforms, f, &cbdata, engine->sm_list, false); + if (buffer == NULL || buffer->inspect == NULL) + break; + + det_ctx->buffer_offset = 0; + det_ctx->discontinue_matching = 0; + det_ctx->inspection_recursion_counter = 0; + + const int match = DetectEngineContentInspection(de_ctx, det_ctx, s, engine->smd, NULL, f, + (uint8_t *)buffer->inspect, buffer->inspect_len, buffer->inspect_offset, + DETECT_CI_FLAGS_SINGLE, DETECT_ENGINE_CONTENT_INSPECTION_MODE_STATE); + if (match == 1) { + return DETECT_ENGINE_INSPECT_SIG_MATCH; + } + local_id++; + } + return DETECT_ENGINE_INSPECT_SIG_NO_MATCH; +} + +/** \brief QuicString Mpm prefilter callback + * + * \param det_ctx detection engine thread ctx + * \param p packet to inspect + * \param f flow to inspect + * \param txv tx to inspect + * \param pectx inspection context + */ +static void PrefilterTxQuicString(DetectEngineThreadCtx *det_ctx, const void *pectx, Packet *p, + Flow *f, void *txv, const uint64_t idx, const uint8_t flags) +{ + SCEnter(); + + const PrefilterMpmListId *ctx = (const PrefilterMpmListId *)pectx; + const MpmCtx *mpm_ctx = ctx->mpm_ctx; + const int list_id = ctx->list_id; + + uint32_t local_id = 0; + while (1) { + // loop until we get a NULL + + struct QuicStringGetDataArgs cbdata = { local_id, txv }; + InspectionBuffer *buffer = + QuicStringGetData(det_ctx, ctx->transforms, f, &cbdata, list_id, true); + if (buffer == NULL) + break; + + if (buffer->inspect_len >= mpm_ctx->minlen) { + (void)mpm_table[mpm_ctx->mpm_type].Search( + mpm_ctx, &det_ctx->mtcu, &det_ctx->pmq, buffer->inspect, buffer->inspect_len); + } + + local_id++; + } +} + +static void PrefilterMpmListIdFree(void *ptr) +{ + SCFree(ptr); +} + +static int PrefilterMpmListIdRegister(DetectEngineCtx *de_ctx, SigGroupHead *sgh, MpmCtx *mpm_ctx, + const DetectBufferMpmRegistery *mpm_reg, int list_id) +{ + PrefilterMpmListId *pectx = SCCalloc(1, sizeof(*pectx)); + if (pectx == NULL) + return -1; + pectx->list_id = list_id; + pectx->mpm_ctx = mpm_ctx; + pectx->transforms = &mpm_reg->transforms; + + return PrefilterAppendTxEngine(de_ctx, sgh, PrefilterTxQuicString, mpm_reg->app_v2.alproto, + mpm_reg->app_v2.tx_min_progress, pectx, PrefilterMpmListIdFree, mpm_reg->pname); +} + +void DetectQuicCyuStringRegister(void) +{ + /* quic.cyu.string sticky buffer */ + sigmatch_table[DETECT_AL_QUIC_CYU_STRING].name = KEYWORD_NAME; + sigmatch_table[DETECT_AL_QUIC_CYU_STRING].desc = + "sticky buffer to match on the QUIC CYU string"; + sigmatch_table[DETECT_AL_QUIC_CYU_STRING].url = "/rules/" KEYWORD_DOC; + sigmatch_table[DETECT_AL_QUIC_CYU_STRING].Setup = DetectQuicCyuStringSetup; + sigmatch_table[DETECT_AL_QUIC_CYU_STRING].flags |= SIGMATCH_NOOPT; +#ifdef UNITTESTS + sigmatch_table[DETECT_AL_QUIC_CYU_STRING].RegisterTests = DetectQuicCyuStringRegisterTests; +#endif + + DetectAppLayerMpmRegister2( + BUFFER_NAME, SIG_FLAG_TOSERVER, 2, PrefilterMpmListIdRegister, NULL, ALPROTO_QUIC, 1); + + DetectAppLayerInspectEngineRegister2( + BUFFER_NAME, ALPROTO_QUIC, SIG_FLAG_TOSERVER, 0, DetectEngineInspectQuicString, NULL); + + DetectBufferTypeSetDescriptionByName(BUFFER_NAME, BUFFER_DESC); + + g_buffer_id = DetectBufferTypeGetByName(BUFFER_NAME); + + SCLogDebug("registering " BUFFER_NAME " rule option"); +} + +#ifdef UNITTESTS +#include "app-layer-parser.h" +#include "util-unittest.h" +#include "util-unittest-helper.h" +#include "flow-util.h" + +/** + * \test DetectQuicCyuStringTest01 is a test for a valid quic packet, matching + * on the cyu string + * + * \retval 1 on success + * \retval 0 on failure + */ +static int DetectQuicCyuStringTest01(void) +{ + /* quic packet */ + uint8_t buf[] = { 0xc3, 0x51, 0x30, 0x34, 0x36, 0x50, 0x76, 0xd8, 0x63, 0xb7, 0x54, 0xf7, 0xab, + 0x32, 0x00, 0x00, 0x00, 0x01, 0x54, 0xfd, 0xf4, 0x79, 0x48, 0x76, 0xd0, 0x87, 0x58, 0x8d, + 0x26, 0x8f, 0xa0, 0x01, 0x04, 0x00, 0x43, 0x48, 0x4c, 0x4f, 0x11, 0x00, 0x00, 0x00, 0x50, + 0x41, 0x44, 0x00, 0xe4, 0x02, 0x00, 0x00, 0x53, 0x4e, 0x49, 0x00, 0xf7, 0x02, 0x00, 0x00, + 0x56, 0x45, 0x52, 0x00, 0xfb, 0x02, 0x00, 0x00, 0x43, 0x43, 0x53, 0x00, 0x0b, 0x03, 0x00, + 0x00, 0x55, 0x41, 0x49, 0x44, 0x2c, 0x03, 0x00, 0x00, 0x54, 0x43, 0x49, 0x44, 0x30, 0x03, + 0x00, 0x00, 0x50, 0x44, 0x4d, 0x44, 0x34, 0x03, 0x00, 0x00, 0x53, 0x4d, 0x48, 0x4c, 0x38, + 0x03, 0x00, 0x00, 0x49, 0x43, 0x53, 0x4c, 0x3c, 0x03, 0x00, 0x00, 0x4e, 0x4f, 0x4e, 0x50, + 0x5c, 0x03, 0x00, 0x00, 0x4d, 0x49, 0x44, 0x53, 0x60, 0x03, 0x00, 0x00, 0x53, 0x43, 0x4c, + 0x53, 0x64, 0x03, 0x00, 0x00, 0x43, 0x53, 0x43, 0x54, 0x64, 0x03, 0x00, 0x00, 0x43, 0x4f, + 0x50, 0x54, 0x64, 0x03, 0x00, 0x00, 0x49, 0x52, 0x54, 0x54, 0x68, 0x03, 0x00, 0x00, 0x43, + 0x46, 0x43, 0x57, 0x6c, 0x03, 0x00, 0x00, 0x53, 0x46, 0x43, 0x57, 0x70, 0x03, 0x00, 0x00, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x31, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x51, 0x30, 0x34, 0x36, 0x01, 0xe8, + 0x81, 0x60, 0x92, 0x92, 0x1a, 0xe8, 0x7e, 0xed, 0x80, 0x86, 0xa2, 0x15, 0x82, 0x91, 0x43, + 0x68, 0x72, 0x6f, 0x6d, 0x65, 0x2f, 0x37, 0x39, 0x2e, 0x30, 0x2e, 0x33, 0x39, 0x34, 0x35, + 0x2e, 0x31, 0x31, 0x37, 0x20, 0x4c, 0x69, 0x6e, 0x75, 0x78, 0x20, 0x78, 0x38, 0x36, 0x5f, + 0x36, 0x34, 0x00, 0x00, 0x00, 0x00, 0x58, 0x35, 0x30, 0x39, 0x01, 0x00, 0x00, 0x00, 0x1e, + 0x00, 0x00, 0x00, 0x82, 0x88, 0x09, 0x00, 0xfa, 0x0f, 0xde, 0xb7, 0x2e, 0x7e, 0x6c, 0x78, + 0xcc, 0x09, 0x65, 0xab, 0x06, 0x0c, 0x31, 0x05, 0xfa, 0xd9, 0xa2, 0x0b, 0xdd, 0x74, 0x5c, + 0x28, 0xdf, 0x7b, 0x74, 0x23, 0x64, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1d, 0x43, + 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00 }; + + Flow f; + void *quic_state = NULL; + Packet *p = NULL; + Signature *s = NULL; + ThreadVars tv; + DetectEngineThreadCtx *det_ctx = NULL; + AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc(); + + memset(&tv, 0, sizeof(ThreadVars)); + memset(&f, 0, sizeof(Flow)); + + p = UTHBuildPacketReal(buf, sizeof(buf), IPPROTO_UDP, "192.168.1.5", "192.168.1.1", 41424, 443); + + FLOW_INITIALIZE(&f); + f.flags |= FLOW_IPV4; + f.proto = IPPROTO_UDP; + f.protomap = FlowGetProtoMapping(f.proto); + + p->flow = &f; + p->flags |= PKT_HAS_FLOW; + p->flowflags |= FLOW_PKT_TOSERVER; + f.alproto = ALPROTO_QUIC; + + DetectEngineCtx *de_ctx = DetectEngineCtxInit(); + FAIL_IF_NULL(de_ctx); + de_ctx->mpm_matcher = mpm_default_matcher; + de_ctx->flags |= DE_QUIET; + + s = DetectEngineAppendSig(de_ctx, "alert quic any any -> any any " + "(msg:\"Test QUIC CYU string\"; " + "quic.cyu.string; " + "content:\"46,PAD-SNI-VER-CCS-UAID-TCID-PDMD-SMHL-ICSL-NONP-" + "MIDS-SCLS-CSCT-COPT-IRTT-CFCW-SFCW\"; " + "sid:1;)"); + FAIL_IF_NULL(s); + + SigGroupBuild(de_ctx); + DetectEngineThreadCtxInit(&tv, (void *)de_ctx, (void *)&det_ctx); + + FLOWLOCK_WRLOCK(&f); + int r = AppLayerParserParse( + NULL, alp_tctx, &f, ALPROTO_QUIC, STREAM_TOSERVER, buf, sizeof(buf)); + if (r != 0) { + printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r); + FLOWLOCK_UNLOCK(&f); + FAIL; + } + FLOWLOCK_UNLOCK(&f); + + quic_state = f.alstate; + FAIL_IF_NULL(quic_state); + + /* do detect */ + SigMatchSignatures(&tv, de_ctx, det_ctx, p); + + if (!(PacketAlertCheck(p, 1))) { + printf("sig 1 didn't alert, but it should have: "); + FAIL; + } + + if (alp_tctx != NULL) + AppLayerParserThreadCtxFree(alp_tctx); + if (det_ctx != NULL) + DetectEngineThreadCtxDeinit(&tv, det_ctx); + if (de_ctx != NULL) + SigGroupCleanup(de_ctx); + if (de_ctx != NULL) + DetectEngineCtxFree(de_ctx); + + FLOW_DESTROY(&f); + UTHFreePacket(p); + PASS; +} + +/** + * \brief this function registers unit tests for Quic Cyu String + */ +static void DetectQuicCyuStringRegisterTests(void) +{ + UtRegisterTest("DetectQuicCyuStringTest01", DetectQuicCyuStringTest01); +} + +#endif /* UNITTESTS */ diff --git a/src/detect-quic-cyu-string.h b/src/detect-quic-cyu-string.h new file mode 100644 index 0000000000..b521d4faba --- /dev/null +++ b/src/detect-quic-cyu-string.h @@ -0,0 +1,28 @@ +/* Copyright (C) 2021 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +/** + * \file + * + */ + +#ifndef __DETECT_QUIC_CYU_STRING_H__ +#define __DETECT_QUIC_CYU_STRING_H__ + +void DetectQuicCyuStringRegister(void); + +#endif /* __DETECT_QUIC_CYU_STRING_H__ */ diff --git a/src/detect-quic-version.c b/src/detect-quic-version.c new file mode 100644 index 0000000000..12640dac92 --- /dev/null +++ b/src/detect-quic-version.c @@ -0,0 +1,260 @@ +/* Copyright (C) 2021 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +/** + * + * Implements the quic.version + */ + +#include "suricata-common.h" +#include "conf.h" +#include "detect.h" +#include "detect-parse.h" +#include "detect-engine.h" +#include "detect-engine-content-inspection.h" +#include "detect-engine-uint.h" +#include "detect-quic-version.h" +#include "util-byte.h" +#include "util-unittest.h" +#include "rust.h" + +#ifdef UNITTESTS +static void DetectQuicVersionRegisterTests(void); +#endif + +static int quic_version_id = 0; + +static int DetectQuicVersionMatch(DetectEngineThreadCtx *det_ctx, Flow *f, uint8_t flags, + void *state, void *txv, const Signature *s, const SigMatchCtx *ctx); +static int DetectQuicVersionSetup(DetectEngineCtx *, Signature *, const char *); +void DetectQuicVersionFree(DetectEngineCtx *de_ctx, void *); + +static int DetectEngineInspectQuicVersionGeneric(DetectEngineCtx *de_ctx, + DetectEngineThreadCtx *det_ctx, const struct DetectEngineAppInspectionEngine_ *engine, + const Signature *s, Flow *f, uint8_t flags, void *alstate, void *txv, uint64_t tx_id); + +/** + * \brief Registration function for quic.version: keyword + */ +void DetectQuicVersionRegister(void) +{ + sigmatch_table[DETECT_AL_QUIC_VERSION].name = "quic.version"; + sigmatch_table[DETECT_AL_QUIC_VERSION].desc = "match Quic version"; + sigmatch_table[DETECT_AL_QUIC_VERSION].url = "/rules/quic-keywords.html#quic-version"; + sigmatch_table[DETECT_AL_QUIC_VERSION].AppLayerTxMatch = DetectQuicVersionMatch; + sigmatch_table[DETECT_AL_QUIC_VERSION].Setup = DetectQuicVersionSetup; + sigmatch_table[DETECT_AL_QUIC_VERSION].Free = DetectQuicVersionFree; +#ifdef UNITTESTS + sigmatch_table[DETECT_AL_QUIC_VERSION].RegisterTests = DetectQuicVersionRegisterTests; +#endif + + DetectAppLayerInspectEngineRegister2("quic.version", ALPROTO_QUIC, SIG_FLAG_TOSERVER, 1, + DetectEngineInspectQuicVersionGeneric, NULL); + + quic_version_id = DetectBufferTypeGetByName("quic.version"); +} + +static int DetectEngineInspectQuicVersionGeneric(DetectEngineCtx *de_ctx, + DetectEngineThreadCtx *det_ctx, const struct DetectEngineAppInspectionEngine_ *engine, + const Signature *s, Flow *f, uint8_t flags, void *alstate, void *txv, uint64_t tx_id) +{ + return DetectEngineInspectGenericList( + de_ctx, det_ctx, s, engine->smd, f, flags, alstate, txv, tx_id); +} + +/** + * \internal + * \brief Function to match protocol version of an Quic Tx + * + * \param det_ctx Pointer to the pattern matcher thread. + * \param f Pointer to the current flow. + * \param flags Flags. + * \param state App layer state. + * \param txv Pointer to the transaction. + * \param s Pointer to the Signature. + * \param ctx Pointer to the sigmatch that we will cast into DetectQuicVersionData. + * + * \retval 0 no match. + * \retval 1 match. + */ +static int DetectQuicVersionMatch(DetectEngineThreadCtx *det_ctx, Flow *f, uint8_t flags, + void *state, void *txv, const Signature *s, const SigMatchCtx *ctx) +{ + const DetectU32Data *de = (const DetectU32Data *)ctx; + uint32_t version; + + version = rs_quic_tx_get_version(txv); + + return DetectU32Match(version, de); +} + +/** + * \internal + * \brief this function is used to add the parsed sigmatch into the current signature + * + * \param de_ctx pointer to the Detection Engine Context + * \param s pointer to the Current Signature + * \param rawstr pointer to the user provided options + * + * \retval 0 on Success + * \retval -1 on Failure + */ +static int DetectQuicVersionSetup(DetectEngineCtx *de_ctx, Signature *s, const char *rawstr) +{ + SigMatch *sm = NULL; + DetectU32Data *de = NULL; + + if (DetectSignatureSetAppProto(s, ALPROTO_QUIC) < 0) + return -1; + + de = DetectU32Parse(rawstr); + if (de == NULL) + return -1; + + sm = SigMatchAlloc(); + if (sm == NULL) + goto error; + + sm->type = DETECT_AL_QUIC_VERSION; + sm->ctx = (SigMatchCtx *)de; + + SigMatchAppendSMToList(s, sm, quic_version_id); + + return 0; + +error: + if (de != NULL) + SCFree(de); + if (sm != NULL) + SCFree(sm); + return -1; +} + +/** + * \internal + * \brief this function will free memory associated with DetectQuicVersionData + * + * \param de pointer to DetectQuicVersionData + */ +void DetectQuicVersionFree(DetectEngineCtx *de_ctx, void *de_ptr) +{ + if (de_ptr != NULL) + SCFree(de_ptr); +} + +#ifdef UNITTESTS + +/** + * \test QuicVersionTestParse01 is a test for a valid value + * + * \retval 1 on success + * \retval 0 on failure + */ +static int QuicVersionTestParse01(void) +{ + DetectEngineCtx *de_ctx = DetectEngineCtxInit(); + FAIL_IF_NULL(de_ctx); + + Signature *sig = DetectEngineAppendSig( + de_ctx, "alert ip any any -> any any (quic.version:3; sid:1; rev:1;)"); + FAIL_IF_NULL(sig); + + sig = DetectEngineAppendSig( + de_ctx, "alert ip any any -> any any (quic.version:3; sid:2; rev:1;)"); + FAIL_IF_NULL(sig); + + DetectEngineCtxFree(de_ctx); + + PASS; +} + +/** + * \test QuicVersionTestParse02 is a test for a valid value + * + * \retval 1 on success + * \retval 0 on failure + */ +static int QuicVersionTestParse02(void) +{ + DetectEngineCtx *de_ctx = DetectEngineCtxInit(); + FAIL_IF_NULL(de_ctx); + + Signature *sig = DetectEngineAppendSig( + de_ctx, "alert ip any any -> any any (quic.version:>3; sid:1; rev:1;)"); + FAIL_IF_NULL(sig); + + sig = DetectEngineAppendSig( + de_ctx, "alert ip any any -> any any (quic.version:<44; sid:2; rev:1;)"); + FAIL_IF_NULL(sig); + + DetectEngineCtxFree(de_ctx); + + PASS; +} + +/** + * \test QuicVersionTestParse03 is a test for an invalid value + * + * \retval 1 on success + * \retval 0 on failure + */ +static int QuicVersionTestParse03(void) +{ + DetectEngineCtx *de_ctx = DetectEngineCtxInit(); + FAIL_IF_NULL(de_ctx); + + Signature *sig = DetectEngineAppendSig( + de_ctx, "alert ip any any -> any any (quic.version:; sid:1; rev:1;)"); + FAIL_IF_NOT_NULL(sig); + + DetectEngineCtxFree(de_ctx); + + PASS; +} + +/** + * \test QuicVersionTestParse04 is a test for an invalid value + * + * \retval 1 on success + * \retval 0 on failure + */ +static int QuicVersionTestParse04(void) +{ + DetectEngineCtx *de_ctx = DetectEngineCtxInit(); + FAIL_IF_NULL(de_ctx); + + Signature *sig = DetectEngineAppendSig( + de_ctx, "alert ip any any -> any any (quic.version:<4294967296; sid:1; rev:1;)"); + FAIL_IF_NOT_NULL(sig); + + DetectEngineCtxFree(de_ctx); + + PASS; +} + +/** + * \brief this function registers unit tests for QuicVersion + */ +void DetectQuicVersionRegisterTests(void) +{ + UtRegisterTest("QuicVersionTestParse01", QuicVersionTestParse01); + UtRegisterTest("QuicVersionTestParse02", QuicVersionTestParse02); + UtRegisterTest("QuicVersionTestParse03", QuicVersionTestParse03); + UtRegisterTest("QuicVersionTestParse04", QuicVersionTestParse04); +} + +#endif /* UNITTESTS */ diff --git a/src/detect-quic-version.h b/src/detect-quic-version.h new file mode 100644 index 0000000000..c9e3e19c89 --- /dev/null +++ b/src/detect-quic-version.h @@ -0,0 +1,28 @@ +/* Copyright (C) 2021 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +/** + * \file + * + */ + +#ifndef __DETECT_QUIC_VERSION_H__ +#define __DETECT_QUIC_VERSION_H__ + +void DetectQuicVersionRegister(void); + +#endif /* __DETECT_QUIC_VERSION_H__ */ diff --git a/src/output-json-alert.c b/src/output-json-alert.c index dac4e3ff27..a9307921da 100644 --- a/src/output-json-alert.c +++ b/src/output-json-alert.c @@ -75,6 +75,7 @@ #include "output-json-ike.h" #include "output-json-modbus.h" #include "output-json-frame.h" +#include "output-json-quic.h" #include "util-byte.h" #include "util-privs.h" @@ -541,6 +542,12 @@ static void AlertAddAppLayer(const Packet *p, JsonBuilder *jb, jb_restore_mark(jb, &mark); } break; + case ALPROTO_QUIC: + jb_get_mark(jb, &mark); + if (!JsonQuicAddMetadata(p->flow, tx_id, jb)) { + jb_restore_mark(jb, &mark); + } + break; case ALPROTO_SNMP: AlertJsonSNMP(p->flow, tx_id, jb); break; diff --git a/src/output-json-quic.c b/src/output-json-quic.c new file mode 100644 index 0000000000..8f249ff9bb --- /dev/null +++ b/src/output-json-quic.c @@ -0,0 +1,165 @@ +/* Copyright (C) 2021 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +/** + * \file + * + * Implements JSON/eve logging for Quic app-layer. + */ + +#include "suricata-common.h" +#include "debug.h" +#include "detect.h" +#include "pkt-var.h" +#include "conf.h" +#include "threads.h" +#include "threadvars.h" +#include "tm-threads.h" +#include "util-unittest.h" +#include "util-buffer.h" +#include "util-debug.h" +#include "util-byte.h" +#include "output.h" +#include "output-json.h" +#include "app-layer.h" +#include "app-layer-parser.h" +#include "output-json-quic.h" +#include "rust.h" + +typedef struct LogQuicFileCtx_ { + LogFileCtx *file_ctx; + OutputJsonCtx *eve_ctx; +} LogQuicFileCtx; + +typedef struct JsonQuicLogThread_ { + LogQuicFileCtx *quiclog_ctx; + OutputJsonThreadCtx *ctx; +} JsonQuicLogThread; + +static int JsonQuicLogger(ThreadVars *tv, void *thread_data, const Packet *p, Flow *f, void *state, + void *tx, uint64_t tx_id) +{ + JsonQuicLogThread *thread = thread_data; + + JsonBuilder *js = + CreateEveHeader(p, LOG_DIR_PACKET, "quic", NULL, thread->quiclog_ctx->eve_ctx); + if (unlikely(js == NULL)) { + return TM_ECODE_OK; + } + if (!rs_quic_to_json(tx, js)) { + jb_free(js); + return TM_ECODE_FAILED; + } + OutputJsonBuilderBuffer(js, thread->ctx); + + jb_free(js); + return TM_ECODE_OK; +} + +static void OutputQuicLogDeInitCtxSub(OutputCtx *output_ctx) +{ + LogQuicFileCtx *quiclog_ctx = (LogQuicFileCtx *)output_ctx->data; + SCFree(quiclog_ctx); + SCFree(output_ctx); +} + +static OutputInitResult OutputQuicLogInitSub(ConfNode *conf, OutputCtx *parent_ctx) +{ + OutputInitResult result = { NULL, false }; + OutputJsonCtx *ajt = parent_ctx->data; + + LogQuicFileCtx *quiclog_ctx = SCCalloc(1, sizeof(*quiclog_ctx)); + if (unlikely(quiclog_ctx == NULL)) { + return result; + } + quiclog_ctx->file_ctx = ajt->file_ctx; + quiclog_ctx->eve_ctx = ajt; + + OutputCtx *output_ctx = SCCalloc(1, sizeof(*output_ctx)); + if (unlikely(output_ctx == NULL)) { + SCFree(quiclog_ctx); + return result; + } + output_ctx->data = quiclog_ctx; + output_ctx->DeInit = OutputQuicLogDeInitCtxSub; + + AppLayerParserRegisterLogger(IPPROTO_UDP, ALPROTO_QUIC); + + result.ctx = output_ctx; + result.ok = true; + return result; +} + +static TmEcode JsonQuicLogThreadInit(ThreadVars *t, const void *initdata, void **data) +{ + if (initdata == NULL) { + SCLogDebug("Error getting context for EveLogQuic. \"initdata\" is NULL."); + return TM_ECODE_FAILED; + } + + JsonQuicLogThread *thread = SCCalloc(1, sizeof(*thread)); + if (unlikely(thread == NULL)) { + return TM_ECODE_FAILED; + } + + thread->quiclog_ctx = ((OutputCtx *)initdata)->data; + thread->ctx = CreateEveThreadCtx(t, thread->quiclog_ctx->eve_ctx); + if (thread->ctx == NULL) { + goto error_exit; + } + + *data = (void *)thread; + return TM_ECODE_OK; + +error_exit: + SCFree(thread); + return TM_ECODE_FAILED; +} + +static TmEcode JsonQuicLogThreadDeinit(ThreadVars *t, void *data) +{ + JsonQuicLogThread *thread = (JsonQuicLogThread *)data; + if (thread == NULL) { + return TM_ECODE_OK; + } + FreeEveThreadCtx(thread->ctx); + SCFree(thread); + return TM_ECODE_OK; +} + +bool JsonQuicAddMetadata(const Flow *f, uint64_t tx_id, JsonBuilder *js) +{ + void *state = FlowGetAppState(f); + if (state) { + void *tx = AppLayerParserGetTx(f->proto, ALPROTO_QUIC, state, tx_id); + if (tx) { + return rs_quic_to_json(tx, js); + } + } + + return false; +} + +void JsonQuicLogRegister(void) +{ + /* Register as an eve sub-module. */ + OutputRegisterTxSubModule(LOGGER_JSON_QUIC, "eve-log", "JsonQuicLog", "eve-log.quic", + OutputQuicLogInitSub, ALPROTO_QUIC, JsonQuicLogger, JsonQuicLogThreadInit, + JsonQuicLogThreadDeinit, NULL); + + SCLogDebug("quic json logger registered."); +} diff --git a/src/output-json-quic.h b/src/output-json-quic.h new file mode 100644 index 0000000000..2448d5063a --- /dev/null +++ b/src/output-json-quic.h @@ -0,0 +1,28 @@ +/* Copyright (C) 2021 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +/** + * \file + */ + +#ifndef __OUTPUT_JSON_QUIC_H__ +#define __OUTPUT_JSON_QUIC_H__ + +bool JsonQuicAddMetadata(const Flow *f, uint64_t tx_id, JsonBuilder *js); +void JsonQuicLogRegister(void); + +#endif /* __OUTPUT_JSON_QUIC_H__ */ diff --git a/src/output.c b/src/output.c index fcd1eec512..405771513e 100644 --- a/src/output.c +++ b/src/output.c @@ -71,6 +71,7 @@ #include "output-json-smb.h" #include "output-json-ike.h" #include "output-json-krb5.h" +#include "output-json-quic.h" #include "output-json-dhcp.h" #include "output-json-snmp.h" #include "output-json-sip.h" @@ -1101,6 +1102,8 @@ void OutputRegisterLoggers(void) JsonIKELogRegister(); /* KRB5 JSON logger. */ JsonKRB5LogRegister(); + /* QUIC JSON logger. */ + JsonQuicLogRegister(); /* DHCP JSON logger. */ JsonDHCPLogRegister(); /* SNMP JSON logger. */ diff --git a/src/suricata-common.h b/src/suricata-common.h index 84b578d747..f6f8bb7621 100644 --- a/src/suricata-common.h +++ b/src/suricata-common.h @@ -454,6 +454,7 @@ typedef enum { LOGGER_JSON_SMB, LOGGER_JSON_IKE, LOGGER_JSON_KRB5, + LOGGER_JSON_QUIC, LOGGER_JSON_MODBUS, LOGGER_JSON_DHCP, LOGGER_JSON_SNMP, diff --git a/src/tests/fuzz/confyaml.c b/src/tests/fuzz/confyaml.c index d361313380..65da8a5518 100644 --- a/src/tests/fuzz/confyaml.c +++ b/src/tests/fuzz/confyaml.c @@ -104,4 +104,6 @@ app-layer:\n\ enabled: yes\n\ http2:\n\ enabled: yes\n\ + quic:\n\ + enabled: yes\n\ "; diff --git a/src/util-profiling.c b/src/util-profiling.c index 4be72e58d0..2a756477fa 100644 --- a/src/util-profiling.c +++ b/src/util-profiling.c @@ -1306,6 +1306,7 @@ const char * PacketProfileLoggertIdToString(LoggerId id) CASE_CODE (LOGGER_JSON_DNP3_TS); CASE_CODE (LOGGER_JSON_DNP3_TC); CASE_CODE (LOGGER_JSON_HTTP); + CASE_CODE(LOGGER_JSON_QUIC); CASE_CODE (LOGGER_JSON_DHCP); CASE_CODE (LOGGER_JSON_KRB5); CASE_CODE(LOGGER_JSON_IKE); diff --git a/suricata.yaml.in b/suricata.yaml.in index 1ed10d0b92..9e86844ae9 100644 --- a/suricata.yaml.in +++ b/suricata.yaml.in @@ -280,6 +280,7 @@ outputs: - snmp - rfb - sip + - quic - dhcp: enabled: yes # When extended mode is on, all DHCP messages are logged @@ -1029,6 +1030,9 @@ app-layer: ntp: enabled: yes + quic: + enabled: yes + dhcp: enabled: yes