From 05a11e28976c6aecfb075ca327673ca62a80d160 Mon Sep 17 00:00:00 2001 From: Philippe Antoine Date: Fri, 6 Mar 2026 17:02:49 +0100 Subject: [PATCH] dcerpc: store persistently contexts So that we can log and detect on interface uuids, even if AlterContext packets change one of the contexts, but other contexts are still used after it in request/responses Ticket: 8378 --- rust/src/dcerpc/dcerpc.rs | 127 +++++++++++++++++++------------------- rust/src/dcerpc/detect.rs | 7 ++- rust/src/dcerpc/log.rs | 9 ++- 3 files changed, 72 insertions(+), 71 deletions(-) diff --git a/rust/src/dcerpc/dcerpc.rs b/rust/src/dcerpc/dcerpc.rs index 5d71f7d254..b61b83ec42 100644 --- a/rust/src/dcerpc/dcerpc.rs +++ b/rust/src/dcerpc/dcerpc.rs @@ -263,6 +263,8 @@ pub struct DCERPCUuidEntry { pub version: u16, pub versionminor: u16, pub flags: u16, + pub call_id: u32, + pub acked: bool, } impl DCERPCUuidEntry { @@ -325,8 +327,7 @@ pub struct DCERPCBindAck { #[derive(Default, Debug)] pub struct DCERPCState { - pub bind: Option, - pub bindack: Option, + pub interface_uuids: Vec, pub transactions: VecDeque, tx_index_completed: usize, pub pad: u8, @@ -539,6 +540,7 @@ impl DCERPCState { let mut uuidentry = DCERPCUuidEntry::new(); uuidentry.uuid = ctxitem.uuid; uuidentry.internal_id = uuid_internal_id; + uuidentry.call_id = hdr.call_id; uuidentry.ctxid = ctxitem.ctxid; uuidentry.version = ctxitem.version; uuidentry.versionminor = ctxitem.versionminor; @@ -548,9 +550,15 @@ impl DCERPCState { if pfcflags & PFC_FIRST_FRAG > 0 { uuidentry.flags |= DCERPC_UUID_ENTRY_FLAG_FF; } - if let Some(ref mut bind) = self.bind { - SCLogDebug!("DCERPC BIND CtxItem: Pushing uuid: {:?}", uuidentry); - bind.uuid_list.push(uuidentry); + for uuid in self.interface_uuids.iter_mut() { + if uuid.ctxid == uuidentry.ctxid { + *uuid = uuidentry; + return (input.len() - leftover_bytes.len()) as i32; + } + } + // arbitrary bound + if self.interface_uuids.len() < 64 { + self.interface_uuids.push(uuidentry); } (input.len() - leftover_bytes.len()) as i32 } @@ -573,7 +581,6 @@ impl DCERPCState { match parser::parse_dcerpc_bind(input) { Ok((leftover_bytes, header)) => { let numctxitems = header.numctxitems; - self.bind = Some(header); for i in 0..numctxitems { retval = self.handle_bindctxitem(&input[idx as usize..], i as u16, hdr); if retval == -1 { @@ -610,23 +617,19 @@ impl DCERPCState { } } - pub fn process_bindack_pdu(&mut self, input: &[u8]) -> i32 { + pub fn process_bindack_pdu(&mut self, input: &[u8], call_id: u32) -> i32 { match parser::parse_dcerpc_bindack(input) { - Ok((leftover_bytes, mut back)) => { - if let Some(ref mut bind) = self.bind { - for (uuid_internal_id, r) in back.ctxitems.iter().enumerate() { - for uuid in bind.uuid_list.iter_mut() { - if uuid.internal_id == uuid_internal_id as u16 { - uuid.result = r.ack_result; - if uuid.result != 0 { - break; - } - back.accepted_uuid_list.push(uuid.clone()); + Ok((leftover_bytes, back)) => { + for (uuid_internal_id, r) in back.ctxitems.iter().enumerate() { + for uuid in self.interface_uuids.iter_mut().rev() { + if uuid.internal_id == uuid_internal_id as u16 && uuid.call_id == call_id{ + uuid.result = r.ack_result; + uuid.acked = true; + if uuid.result == 0 { SCLogDebug!("DCERPC BINDACK accepted UUID: {:?}", uuid); } } } - self.bindack = Some(back); } (input.len() - leftover_bytes.len()) as i32 } @@ -942,7 +945,7 @@ impl DCERPCState { } DCERPC_TYPE_BINDACK | DCERPC_TYPE_ALTER_CONTEXT_RESP => { let retval = - self.process_bindack_pdu(&cur_i[parsed as usize..fraglen as usize]); + self.process_bindack_pdu(&cur_i[parsed as usize..fraglen as usize], hdr.call_id); if retval == -1 { return AppLayerResult::err(); } @@ -1566,14 +1569,20 @@ mod tests { let mut dcerpc_state = DCERPCState::new(); let hdr = parser::parse_dcerpc_header(bind).unwrap().1; assert_eq!(1068, dcerpc_state.process_bind_pdu(&bind[16..], &hdr)); - assert_eq!(604, dcerpc_state.process_bindack_pdu(bindack)); - if let Some(back) = dcerpc_state.bindack { - assert_eq!(1, back.accepted_uuid_list.len()); - assert_eq!( - vec!(57, 25, 40, 106, 177, 12, 17, 208, 155, 168, 0, 192, 79, 217, 46, 245), - back.accepted_uuid_list[0].uuid - ); - assert_eq!(11, back.accepted_uuid_list[0].internal_id); + assert_eq!(604, dcerpc_state.process_bindack_pdu(bindack, hdr.call_id)); + assert_eq!(24, dcerpc_state.interface_uuids.len()); + for i in 0..24 { + assert!(dcerpc_state.interface_uuids[i].acked); + if i == 11 { + assert_eq!( + vec!(57, 25, 40, 106, 177, 12, 17, 208, 155, 168, 0, 192, 79, 217, 46, 245), + dcerpc_state.interface_uuids[11].uuid + ); + assert_eq!(11, dcerpc_state.interface_uuids[11].internal_id); + assert_eq!(0, dcerpc_state.interface_uuids[11].result); + } else { + assert_ne!(0, dcerpc_state.interface_uuids[i].result); + } } } @@ -1813,19 +1822,17 @@ mod tests { Direction::ToServer ) ); - if let Some(ref bind) = dcerpc_state.bind { - let bind_uuid = &bind.uuid_list[0].uuid; - assert_eq!(1, bind.uuid_list.len()); - assert_eq!( - cmp::Ordering::Equal, - bind_uuid - .iter() - .zip(expected_uuid) - .map(|(x, y)| x.cmp(y)) - .find(|&ord| ord != cmp::Ordering::Equal) - .unwrap_or_else(|| bind_uuid.len().cmp(&expected_uuid.len())) - ); - } + assert_eq!(1, dcerpc_state.interface_uuids.len()); + let bind_uuid = &dcerpc_state.interface_uuids[0].uuid; + assert_eq!( + cmp::Ordering::Equal, + bind_uuid + .iter() + .zip(expected_uuid) + .map(|(x, y)| x.cmp(y)) + .find(|&ord| ord != cmp::Ordering::Equal) + .unwrap_or_else(|| bind_uuid.len().cmp(&expected_uuid.len())) + ); } #[test] @@ -2087,11 +2094,9 @@ mod tests { Direction::ToServer ) ); - if let Some(ref back) = dcerpc_state.bindack { - assert_eq!(1, back.accepted_uuid_list.len()); - assert_eq!(12, back.accepted_uuid_list[0].ctxid); - assert_eq!(expected_uuid1, back.accepted_uuid_list[0].uuid); - } + assert_eq!(13, dcerpc_state.interface_uuids.len()); + assert_eq!(12, dcerpc_state.interface_uuids[12].ctxid); + assert_eq!(expected_uuid1, dcerpc_state.interface_uuids[12].uuid); assert_eq!( AppLayerResult::ok(), dcerpc_state.handle_input_data( @@ -2106,11 +2111,9 @@ mod tests { Direction::ToServer ) ); - if let Some(ref back) = dcerpc_state.bindack { - assert_eq!(1, back.accepted_uuid_list.len()); - assert_eq!(15, back.accepted_uuid_list[0].ctxid); - assert_eq!(expected_uuid2, back.accepted_uuid_list[0].uuid); - } + assert_eq!(16, dcerpc_state.interface_uuids.len()); + assert_eq!(15, dcerpc_state.interface_uuids[15].ctxid); + assert_eq!(expected_uuid2, dcerpc_state.interface_uuids[15].uuid); assert_eq!( AppLayerResult::ok(), dcerpc_state.handle_input_data( @@ -2125,11 +2128,9 @@ mod tests { Direction::ToServer ) ); - if let Some(ref back) = dcerpc_state.bindack { - assert_eq!(1, back.accepted_uuid_list.len()); - assert_eq!(11, back.accepted_uuid_list[0].ctxid); - assert_eq!(expected_uuid3, back.accepted_uuid_list[0].uuid); - } + assert_eq!(16, dcerpc_state.interface_uuids.len()); + assert_eq!(11, dcerpc_state.interface_uuids[11].ctxid); + assert_eq!(expected_uuid3, dcerpc_state.interface_uuids[11].uuid); } #[test] @@ -2187,11 +2188,9 @@ mod tests { Direction::ToServer ) ); - if let Some(ref back) = dcerpc_state.bindack { - assert_eq!(1, back.accepted_uuid_list.len()); - assert_eq!(0, back.accepted_uuid_list[0].ctxid); - assert_eq!(expected_uuid1, back.accepted_uuid_list[0].uuid); - } + assert_eq!(1, dcerpc_state.interface_uuids.len()); + assert_eq!(0, dcerpc_state.interface_uuids[0].ctxid); + assert_eq!(expected_uuid1, dcerpc_state.interface_uuids[0].uuid); assert_eq!( AppLayerResult::ok(), dcerpc_state.handle_input_data( @@ -2206,10 +2205,8 @@ mod tests { Direction::ToServer ) ); - if let Some(ref back) = dcerpc_state.bindack { - assert_eq!(1, back.accepted_uuid_list.len()); - assert_eq!(1, back.accepted_uuid_list[0].ctxid); - assert_eq!(expected_uuid2, back.accepted_uuid_list[0].uuid); - } + assert_eq!(2, dcerpc_state.interface_uuids.len()); + assert_eq!(1, dcerpc_state.interface_uuids[1].ctxid); + assert_eq!(expected_uuid2, dcerpc_state.interface_uuids[1].uuid); } } diff --git a/rust/src/dcerpc/detect.rs b/rust/src/dcerpc/detect.rs index 29fe4b9255..7599adc89b 100644 --- a/rust/src/dcerpc/detect.rs +++ b/rust/src/dcerpc/detect.rs @@ -63,8 +63,8 @@ fn match_backuuid( tx: &DCERPCTransaction, state: &mut DCERPCState, if_data: &mut DCEIfaceData, ) -> u8 { let mut ret = 0; - if let Some(ref bindack) = state.bindack { - for uuidentry in bindack.accepted_uuid_list.iter() { + if !state.interface_uuids.is_empty() { + for uuidentry in &state.interface_uuids { ret = 1; // if any_frag is not enabled, we need to match only against the first fragment if if_data.any_frag == 0 && (uuidentry.flags & DCERPC_UUID_ENTRY_FLAG_FF == 0) { @@ -72,7 +72,8 @@ fn match_backuuid( continue; } // if the uuid has been rejected(uuidentry->result == 1), we skip to the next uuid - if uuidentry.result != 0 { + if !uuidentry.acked || uuidentry.result != 0 { + ret = 0; SCLogDebug!("Skipping to next UUID"); continue; } diff --git a/rust/src/dcerpc/log.rs b/rust/src/dcerpc/log.rs index 9c94944d12..5a00bdf8df 100644 --- a/rust/src/dcerpc/log.rs +++ b/rust/src/dcerpc/log.rs @@ -21,16 +21,19 @@ use crate::dcerpc::dcerpc_udp::*; use crate::jsonbuilder::{JsonBuilder, JsonError}; fn log_bind_interfaces(jsb: &mut JsonBuilder, state: &DCERPCState) -> Result<(), JsonError> { - if let Some(bind) = &state.bind { + if !state.interface_uuids.is_empty() { jsb.open_array("interfaces")?; - for uuid in &bind.uuid_list { + for uuid in &state.interface_uuids { jsb.start_object()?; let ifstr = Uuid::from_slice(uuid.uuid.as_slice()); let ifstr = ifstr.map(|uuid| uuid.to_hyphenated().to_string()).unwrap(); jsb.set_string("uuid", &ifstr)?; let vstr = format!("{}.{}", uuid.version, uuid.versionminor); jsb.set_string("version", &vstr)?; - jsb.set_uint("ack_result", uuid.result as u64)?; + // TODO? log only the interface for the right ctxid jsb.set_uint("ctxid", uuid.ctxid as u64)?; + if uuid.acked { + jsb.set_uint("ack_result", uuid.result as u64)?; + } jsb.close()?; } jsb.close()?;