From 9ba775ca96913c322c2bc567a3622aaf0e53b7ae Mon Sep 17 00:00:00 2001 From: Shivani Bhardwaj Date: Thu, 23 Jul 2026 13:27:39 +0530 Subject: [PATCH 1/3] dcerpc: move drep alloc on stack DCERPC's drep field is always 4 bytes for connection oriented PDU types. Make it a byte array of exactly 4 bytes to store the entire header on stack. --- rust/src/dcerpc/dcerpc.rs | 4 ++-- rust/src/dcerpc/parser.rs | 7 ++++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/rust/src/dcerpc/dcerpc.rs b/rust/src/dcerpc/dcerpc.rs index 7ea109b69f..75ab9b3071 100644 --- a/rust/src/dcerpc/dcerpc.rs +++ b/rust/src/dcerpc/dcerpc.rs @@ -278,13 +278,13 @@ pub struct Uuid { pub node: Vec, } -#[derive(Debug)] +#[derive(Debug, Clone, Copy)] pub struct DCERPCHdr { pub rpc_vers: u8, pub rpc_vers_minor: u8, pub hdrtype: u8, pub pfc_flags: u8, - pub packed_drep: Vec, + pub packed_drep: [u8; 4], pub frag_length: u16, pub auth_length: u16, pub call_id: u32, diff --git a/rust/src/dcerpc/parser.rs b/rust/src/dcerpc/parser.rs index a80126b1c7..3fe2adea48 100644 --- a/rust/src/dcerpc/parser.rs +++ b/rust/src/dcerpc/parser.rs @@ -233,7 +233,12 @@ pub(super) fn parse_dcerpc_header(i: &[u8]) -> IResult<&[u8], DCERPCHdr> { rpc_vers_minor, hdrtype, pfc_flags, - packed_drep: packed_drep.to_vec(), + packed_drep: [ + packed_drep[0], + packed_drep[1], + packed_drep[2], + packed_drep[3], + ], frag_length, auth_length, call_id, From ddef1225c1606e756345cc4a2deb18633dbab9b1 Mon Sep 17 00:00:00 2001 From: Shivani Bhardwaj Date: Wed, 29 Jul 2026 11:49:38 +0530 Subject: [PATCH 2/3] dcerpc: fix misleading comment --- rust/src/dcerpc/dcerpc.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/src/dcerpc/dcerpc.rs b/rust/src/dcerpc/dcerpc.rs index 75ab9b3071..ca40e12703 100644 --- a/rust/src/dcerpc/dcerpc.rs +++ b/rust/src/dcerpc/dcerpc.rs @@ -36,7 +36,7 @@ use suricata_sys::sys::{ pub static mut DCERPC_MAX_STUB_SIZE: u32 = 1048576; -// Constant DCERPC UDP Header length +// Constant DCERPC TCP Header length pub const DCERPC_HDR_LEN: u16 = 16; // FIRST flag set on the packet pub const DCERPC_UUID_ENTRY_FLAG_FF: u16 = 0x0001; From 8e99dba75e0d93efc5d4e15bd9a530b5629260ac Mon Sep 17 00:00:00 2001 From: Shivani Bhardwaj Date: Wed, 29 Jul 2026 11:59:04 +0530 Subject: [PATCH 3/3] detect: add dcerpc.hdr keyword This keyword matches on the DCERPC header per transaction. The matches are direction and carrier procotol sensitive. DCERPC/TCP has a fixed 16 byte header whereas DCERPC/UDP has a fixed 80 bytes header. The matches are done in the byte order received from the header which should match exactly as they are seen on the wire. Feature 8735 --- doc/userguide/rules/dcerpc-keywords.rst | 12 +++++ rust/src/dcerpc/dcerpc.rs | 30 ++++++++++++ rust/src/dcerpc/dcerpc_udp.rs | 14 +++++- rust/src/dcerpc/detect.rs | 62 +++++++++++++++++++++++++ 4 files changed, 116 insertions(+), 2 deletions(-) diff --git a/doc/userguide/rules/dcerpc-keywords.rst b/doc/userguide/rules/dcerpc-keywords.rst index a84d099847..6ae12a60e9 100644 --- a/doc/userguide/rules/dcerpc-keywords.rst +++ b/doc/userguide/rules/dcerpc-keywords.rst @@ -68,6 +68,18 @@ Example:: dcerpc.stub_data; content:"123456"; +dcerpc.hdr +---------- + +Match on the DCERPC header. It is a 'sticky buffer'. + +Multi-byte header fields appear in the byte order advertised in the header's +data representation. + +Example:: + + dcerpc.hdr; content:"|10|"; offset:4; depth:1; + Additional information ---------------------- diff --git a/rust/src/dcerpc/dcerpc.rs b/rust/src/dcerpc/dcerpc.rs index ca40e12703..002eeba553 100644 --- a/rust/src/dcerpc/dcerpc.rs +++ b/rust/src/dcerpc/dcerpc.rs @@ -205,6 +205,8 @@ pub struct DCERPCTransaction { pub resp_lost: bool, pub req_cmd: u8, pub resp_cmd: u8, + pub req_hdr: Option>, + pub resp_hdr: Option>, pub activityuuid: Vec, pub seqnum: u32, pub tx_data: AppLayerTxData, @@ -290,6 +292,27 @@ pub struct DCERPCHdr { pub call_id: u32, } +impl DCERPCHdr { + pub fn to_bytes(&self) -> [u8; DCERPC_HDR_LEN as usize] { + let mut b = [0u8; DCERPC_HDR_LEN as usize]; + b[0] = self.rpc_vers; + b[1] = self.rpc_vers_minor; + b[2] = self.hdrtype; + b[3] = self.pfc_flags; + b[4..8].copy_from_slice(&self.packed_drep); + if self.packed_drep[0] & 0x10 != 0 { + b[8..10].copy_from_slice(&self.frag_length.to_le_bytes()); + b[10..12].copy_from_slice(&self.auth_length.to_le_bytes()); + b[12..16].copy_from_slice(&self.call_id.to_le_bytes()); + } else { + b[8..10].copy_from_slice(&self.frag_length.to_be_bytes()); + b[10..12].copy_from_slice(&self.auth_length.to_be_bytes()); + b[12..16].copy_from_slice(&self.call_id.to_be_bytes()); + } + b + } +} + #[derive(Debug)] pub struct DCERPCBind { pub numctxitems: u8, @@ -585,6 +608,7 @@ impl DCERPCState { } let mut tx = self.create_tx(hdr); tx.req_cmd = hdr.hdrtype; + tx.req_hdr = Some(hdr.to_bytes().to_vec()); tx.req_done = true; if let Some(flow) = self.flow { sc_app_layer_parser_trigger_raw_stream_inspection( @@ -762,6 +786,7 @@ impl DCERPCState { match transaction { Some(ref mut tx) => { tx.req_cmd = hdr_type; + tx.req_hdr = Some(hdr.to_bytes().to_vec()); tx.ctxid = request.ctxid; tx.opnum = request.opnum; tx.first_request_seen = request.first_request_seen; @@ -769,6 +794,7 @@ impl DCERPCState { None => { let mut tx = self.create_tx(hdr); tx.req_cmd = hdr_type; + tx.req_hdr = Some(hdr.to_bytes().to_vec()); tx.ctxid = request.ctxid; tx.opnum = request.opnum; tx.first_request_seen = request.first_request_seen; @@ -949,10 +975,12 @@ impl DCERPCState { self.get_tx_by_call_id(current_call_id, Direction::ToClient, hdrtype) { tx.resp_cmd = hdrtype; + tx.resp_hdr = Some(hdr.to_bytes().to_vec()); tx } else { let mut tx = self.create_tx(&hdr); tx.resp_cmd = hdrtype; + tx.resp_hdr = Some(hdr.to_bytes().to_vec()); self.transactions.push_back(tx); self.transactions.back_mut().unwrap() }; @@ -980,10 +1008,12 @@ impl DCERPCState { match transaction { Some(tx) => { tx.resp_cmd = hdrtype; + tx.resp_hdr = Some(hdr.to_bytes().to_vec()); } None => { let mut tx = self.create_tx(&hdr); tx.resp_cmd = hdrtype; + tx.resp_hdr = Some(hdr.to_bytes().to_vec()); self.transactions.push_back(tx); } }; diff --git a/rust/src/dcerpc/dcerpc_udp.rs b/rust/src/dcerpc/dcerpc_udp.rs index 538ee3d3c6..6886e4eff9 100644 --- a/rust/src/dcerpc/dcerpc_udp.rs +++ b/rust/src/dcerpc/dcerpc_udp.rs @@ -162,7 +162,9 @@ impl DCERPCUDPState { }); } - pub fn handle_fragment_data(&mut self, hdr: &DCERPCHdrUdp, input: &[u8]) -> bool { + pub fn handle_fragment_data( + &mut self, hdr: &DCERPCHdrUdp, hdr_bytes: &[u8], input: &[u8], + ) -> bool { if hdr.pkt_type != DCERPC_TYPE_REQUEST && hdr.pkt_type != DCERPC_TYPE_RESPONSE { SCLogDebug!("Unrecognized packet type"); return false; @@ -186,10 +188,12 @@ impl DCERPCUDPState { tx.tx_data.0.updated_tc = true; tx.tx_data.0.updated_ts = true; let done = (hdr.flags1 & PFCL1_FRAG) == 0 || (hdr.flags1 & PFCL1_LASTFRAG) != 0; + let raw_hdr: Option> = Some(hdr_bytes.to_vec()); let max_size = cfg_max_stub_size() as usize; match hdr.pkt_type { DCERPC_TYPE_REQUEST => { + tx.req_hdr = raw_hdr; tx.frag_cnt_ts = tx.frag_cnt_ts.saturating_add(1); if input.len() + tx.stub_data_buffer_ts.len() < max_size { tx.stub_data_buffer_ts.extend_from_slice(input); @@ -203,6 +207,7 @@ impl DCERPCUDPState { return true; } DCERPC_TYPE_RESPONSE => { + tx.resp_hdr = raw_hdr; tx.frag_cnt_tc = tx.frag_cnt_tc.saturating_add(1); if input.len() + tx.stub_data_buffer_tc.len() < max_size { tx.stub_data_buffer_tc.extend_from_slice(input); @@ -244,7 +249,12 @@ impl DCERPCUDPState { ); return AppLayerResult::err(); } - if !self.handle_fragment_data(&header, &leftover_bytes[..header.fraglen as usize]) { + let hdr_len = DCERPC_UDP_HDR_LEN as usize; + if !self.handle_fragment_data( + &header, + &input[..hdr_len], + &leftover_bytes[..header.fraglen as usize], + ) { return AppLayerResult::err(); } } diff --git a/rust/src/dcerpc/detect.rs b/rust/src/dcerpc/detect.rs index 03241f7a87..367c248026 100644 --- a/rust/src/dcerpc/detect.rs +++ b/rust/src/dcerpc/detect.rs @@ -439,10 +439,56 @@ unsafe extern "C" fn dcerpc_tx_get_stub_data( return buffer; } +unsafe extern "C" fn dcerpc_hdr_setup( + de_ctx: *mut DetectEngineCtx, s: *mut Signature, _str: *const c_char, +) -> c_int { + if SCDetectSignatureSetAppProto(s, ALPROTO_DCERPC) < 0 { + return -1; + } + if SCDetectBufferSetActiveList(de_ctx, s, G_DCERPC_HDR_BUFFER_ID) < 0 { + return -1; + } + return 0; +} + +unsafe extern "C" fn dcerpc_tx_get_hdr( + det_ctx: *mut DetectEngineThreadCtx, transforms: *const DetectEngineTransforms, + _flow: *mut Flow, dir: u8, tx: *mut c_void, list_id: c_int, +) -> *mut InspectionBuffer { + let tx = cast_pointer!(tx, DCERPCTransaction); + let buffer = SCInspectionBufferGet(det_ctx, list_id); + if !(*buffer).initialized { + let hdr: Option<&[u8]> = if (dir & STREAM_TOSERVER) != 0 { + tx.req_hdr.as_deref() + } else { + tx.resp_hdr.as_deref() + }; + if let Some(hdr) = hdr { + if hdr[4] & 0x10 != 0 { + (*buffer).flags |= DETECT_CI_FLAGS_DCE_LE; + } else { + (*buffer).flags |= DETECT_CI_FLAGS_DCE_BE; + } + SCInspectionBufferSetupAndApplyTransforms( + det_ctx, + list_id, + buffer, + hdr.as_ptr(), + hdr.len() as u32, + transforms, + ); + } else { + return std::ptr::null_mut(); + } + } + return buffer; +} + static mut G_DCERPC_OPNUM_KW_ID: u16 = 0; static mut G_DCERPC_GENERIC_BUFFER_ID: c_int = 0; static mut G_DCERPC_IFACE_KW_ID: u16 = 0; static mut G_DCERPC_STUB_BUFFER_ID: c_int = 0; +static mut G_DCERPC_HDR_BUFFER_ID: c_int = 0; #[no_mangle] pub unsafe extern "C" fn SCDetectDcerpcRegister() { @@ -517,6 +563,22 @@ pub unsafe extern "C" fn SCDetectDcerpcRegister() { stub_kw_id, b"dce_stub_data\0".as_ptr() as *const libc::c_char, ); + + let kw_hdr = SigTableElmtStickyBuffer { + name: String::from("dcerpc.hdr"), + desc: String::from("match on the DCERPC header per direction"), + url: String::from("/rules/dcerpc-keywords.html#dcerpc-hdr"), + setup: dcerpc_hdr_setup, + }; + let _hdr_kw_id = helper_keyword_register_sticky_buffer(&kw_hdr); + G_DCERPC_HDR_BUFFER_ID = SCDetectRegisterMpmGeneric( + b"dcerpc.hdr\0".as_ptr() as *const libc::c_char, + b"dcerpc header\0".as_ptr() as *const libc::c_char, + ALPROTO_DCERPC, + STREAM_TOSERVER | STREAM_TOCLIENT, + Some(dcerpc_tx_get_hdr), + 0, + ); } #[cfg(test)]