diff --git a/doc/userguide/rules/dcerpc-keywords.rst b/doc/userguide/rules/dcerpc-keywords.rst index a84d099847..35c3c42233 100644 --- a/doc/userguide/rules/dcerpc-keywords.rst +++ b/doc/userguide/rules/dcerpc-keywords.rst @@ -59,6 +59,22 @@ Examples:: dcerpc.opnum can since suricata 9 use an :ref:`unsigned 16-bits integer `. +dcerpc.flags +------------ + +Match on the flags in a DCERPC header. The value is an +:ref:`unsigned 16-bit integer `. + +The 16-bit value carries the PFC flags of the transaction. Over TCP the low +byte holds ``pfc_flags``; over UDP the low byte holds ``flags1`` and the high +byte holds ``flags2``. + +Examples:: + + dcerpc.flags:0x03; + dcerpc.flags:&0x03=0x03; + + dcerpc.stub_data ---------------- diff --git a/rust/src/dcerpc/dcerpc.rs b/rust/src/dcerpc/dcerpc.rs index f5a7f2b816..040a853a17 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_flags: u16, + pub resp_flags: u16, pub activityuuid: Vec, pub seqnum: u32, pub tx_data: AppLayerTxData, @@ -539,8 +541,6 @@ impl DCERPCState { uuidentry.version = ctxitem.version; uuidentry.versionminor = ctxitem.versionminor; let pfcflags = hdr.pfc_flags; - // Store the first frag flag in the uuid as pfc_flags will - // be overwritten by new packets if pfcflags & PFC_FIRST_FRAG > 0 { uuidentry.flags |= DCERPC_UUID_ENTRY_FLAG_FF; } @@ -585,6 +585,7 @@ impl DCERPCState { } let mut tx = self.create_tx(hdr); tx.req_cmd = hdr.hdrtype; + tx.req_flags |= hdr.pfc_flags as u16; tx.req_done = true; if let Some(flow) = self.flow { sc_app_layer_parser_trigger_raw_stream_inspection( @@ -762,6 +763,7 @@ impl DCERPCState { match transaction { Some(ref mut tx) => { tx.req_cmd = hdr_type; + tx.req_flags |= hdr.pfc_flags as u16; tx.ctxid = request.ctxid; tx.opnum = request.opnum; tx.first_request_seen = request.first_request_seen; @@ -769,6 +771,7 @@ impl DCERPCState { None => { let mut tx = self.create_tx(hdr); tx.req_cmd = hdr_type; + tx.req_flags |= hdr.pfc_flags as u16; tx.ctxid = request.ctxid; tx.opnum = request.opnum; tx.first_request_seen = request.first_request_seen; @@ -949,10 +952,12 @@ impl DCERPCState { self.get_tx_by_call_id(current_call_id, Direction::ToClient, hdrtype) { tx.resp_cmd = hdrtype; + tx.resp_flags |= hdr.pfc_flags as u16; tx } else { let mut tx = self.create_tx(&hdr); tx.resp_cmd = hdrtype; + tx.resp_flags |= hdr.pfc_flags as u16; self.transactions.push_back(tx); self.transactions.back_mut().unwrap() }; @@ -980,10 +985,12 @@ impl DCERPCState { match transaction { Some(tx) => { tx.resp_cmd = hdrtype; + tx.resp_flags |= hdr.pfc_flags as u16; } None => { let mut tx = self.create_tx(&hdr); tx.resp_cmd = hdrtype; + tx.resp_flags |= hdr.pfc_flags as u16; self.transactions.push_back(tx); } }; diff --git a/rust/src/dcerpc/dcerpc_udp.rs b/rust/src/dcerpc/dcerpc_udp.rs index 538ee3d3c6..383ca4c140 100644 --- a/rust/src/dcerpc/dcerpc_udp.rs +++ b/rust/src/dcerpc/dcerpc_udp.rs @@ -190,6 +190,7 @@ impl DCERPCUDPState { let max_size = cfg_max_stub_size() as usize; match hdr.pkt_type { DCERPC_TYPE_REQUEST => { + tx.req_flags |= (hdr.flags1 as u16) | ((hdr.flags2 as u16) << 8); 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 +204,7 @@ impl DCERPCUDPState { return true; } DCERPC_TYPE_RESPONSE => { + tx.resp_flags |= (hdr.flags1 as u16) | ((hdr.flags2 as u16) << 8); 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); diff --git a/rust/src/dcerpc/detect.rs b/rust/src/dcerpc/detect.rs index 03241f7a87..3d859c7248 100644 --- a/rust/src/dcerpc/detect.rs +++ b/rust/src/dcerpc/detect.rs @@ -22,7 +22,9 @@ use super::dcerpc::{ use crate::core::{STREAM_TOCLIENT, STREAM_TOSERVER}; use crate::detect::uint::{detect_match_uint, detect_parse_uint, DetectUintData}; use crate::detect::{helper_keyword_register_sticky_buffer, SigTableElmtStickyBuffer}; -use crate::smb::detect::{smb_tx_get_stub_data, smb_tx_match_dce_iface, smb_tx_match_dce_opnum}; +use crate::smb::detect::{ + smb_tx_get_stub_data, smb_tx_match_dce_flags, smb_tx_match_dce_iface, smb_tx_match_dce_opnum, +}; use crate::smb::smb::ALPROTO_SMB; use std::ffi::CStr; use std::os::raw::{c_char, c_int, c_void}; @@ -393,6 +395,74 @@ unsafe extern "C" fn dcerpc_opnum_free(_de: *mut DetectEngineCtx, ptr: *mut c_vo } } +unsafe extern "C" fn dcerpc_tx_match_dce_flags( + tx: *mut c_void, ctx: *const SigMatchCtx, flags: u8, +) -> c_int { + let tx = cast_pointer!(tx, DCERPCTransaction); + let ctx = cast_pointer!(ctx, DetectUintData); + let value = if flags & STREAM_TOSERVER != 0 { + tx.req_flags + } else { + tx.resp_flags + }; + + return detect_match_uint(ctx, value) as i32; +} + +unsafe fn dcerpc_flags_parse(carg: *const c_char) -> *mut c_void { + let arg = match CStr::from_ptr(carg).to_str() { + Ok(arg) => arg, + Err(_) => { + return std::ptr::null_mut(); + } + }; + match detect_parse_uint::(arg) { + Ok((_, ctx)) => Box::into_raw(Box::new(ctx)) as *mut c_void, + Err(_) => std::ptr::null_mut(), + } +} + +unsafe extern "C" fn dcerpc_flags_match( + _de: *mut DetectEngineThreadCtx, f: *mut crate::flow::Flow, flags: u8, _state: *mut c_void, + tx: *mut c_void, _sig: *const Signature, ctx: *const SigMatchCtx, +) -> c_int { + if SCFlowGetAppProtocol(f) == ALPROTO_DCERPC { + return dcerpc_tx_match_dce_flags(tx, ctx, flags); + } + return smb_tx_match_dce_flags(tx, ctx, flags) as i32; +} + +unsafe extern "C" fn dcerpc_flags_setup( + de: *mut DetectEngineCtx, s: *mut Signature, raw: *const libc::c_char, +) -> c_int { + if SCDetectSignatureSetAppProto(s, ALPROTO_DCERPC) != 0 { + return -1; + } + let ctx = dcerpc_flags_parse(raw); + if ctx.is_null() { + return -1; + } + if SCSigMatchAppendSMToList( + de, + s, + G_DCERPC_FLAGS_KW_ID, + ctx as *mut SigMatchCtx, + G_DCERPC_GENERIC_BUFFER_ID, + ) + .is_null() + { + dcerpc_flags_free(std::ptr::null_mut(), ctx); + return -1; + } + return 0; +} + +unsafe extern "C" fn dcerpc_flags_free(_de: *mut DetectEngineCtx, ptr: *mut c_void) { + if !ptr.is_null() { + std::mem::drop(Box::from_raw(ptr as *mut DetectUintData)); + } +} + unsafe extern "C" fn dcerpc_stub_data_setup( de_ctx: *mut DetectEngineCtx, s: *mut Signature, _str: *const c_char, ) -> c_int { @@ -440,6 +510,7 @@ unsafe extern "C" fn dcerpc_tx_get_stub_data( } static mut G_DCERPC_OPNUM_KW_ID: u16 = 0; +static mut G_DCERPC_FLAGS_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; @@ -457,6 +528,18 @@ pub unsafe extern "C" fn SCDetectDcerpcRegister() { flags: 0, }; G_DCERPC_OPNUM_KW_ID = SCDetectHelperKeywordRegister(&kw); + + let kw = SCSigTableAppLiteElmt { + name: b"dcerpc.flags\0".as_ptr() as *const libc::c_char, + desc: b"match on the PFC flags in a DCERPC header\0".as_ptr() as *const libc::c_char, + url: b"/rules/dcerpc-keywords.html#dcerpc-flags\0".as_ptr() as *const libc::c_char, + AppLayerTxMatch: Some(dcerpc_flags_match), + Setup: Some(dcerpc_flags_setup), + Free: Some(dcerpc_flags_free), + flags: 0, + }; + G_DCERPC_FLAGS_KW_ID = SCDetectHelperKeywordRegister(&kw); + G_DCERPC_GENERIC_BUFFER_ID = SCDetectHelperBufferProgressRegister( b"dce_generic\0".as_ptr() as *const libc::c_char, ALPROTO_DCERPC, diff --git a/rust/src/smb/dcerpc.rs b/rust/src/smb/dcerpc.rs index 148e2f6215..50512aa5dc 100644 --- a/rust/src/smb/dcerpc.rs +++ b/rust/src/smb/dcerpc.rs @@ -94,6 +94,8 @@ pub struct SMBTransactionDCERPC { pub res_cmd: u8, pub res_set: bool, pub call_id: u32, + pub req_flags: u8, + pub resp_flags: u8, pub frag_cnt_ts: u16, pub frag_cnt_tc: u16, pub stub_data_ts: Vec, @@ -101,19 +103,21 @@ pub struct SMBTransactionDCERPC { } impl SMBTransactionDCERPC { - fn new_request(req: u8, call_id: u32) -> Self { + fn new_request(req: u8, call_id: u32, flags: u8) -> Self { return Self { opnum: 0, context_id: 0, req_cmd: req, req_set: true, + req_flags: flags, call_id, ..Default::default() }; } - fn new_response(call_id: u32) -> Self { + fn new_response(call_id: u32, flags: u8) -> Self { return Self { call_id, + resp_flags: flags, ..Default::default() }; } @@ -126,12 +130,13 @@ impl SMBTransactionDCERPC { impl SMBState { fn new_dcerpc_tx( &mut self, hdr: SMBCommonHdr, vercmd: SMBVerCmdStat, cmd: u8, call_id: u32, + packet_flags: u8, ) -> Option<&mut SMBTransaction> { let mut tx = self.new_tx()?; tx.hdr = hdr; tx.vercmd = vercmd; tx.type_data = Some(SMBTransactionTypeData::DCERPC( - SMBTransactionDCERPC::new_request(cmd, call_id), + SMBTransactionDCERPC::new_request(cmd, call_id, packet_flags), )); SCLogDebug!("SMB: TX DCERPC created: ID {} hdr {:?}", tx.id, tx.hdr); @@ -140,13 +145,13 @@ impl SMBState { } fn new_dcerpc_tx_for_response( - &mut self, hdr: SMBCommonHdr, vercmd: SMBVerCmdStat, call_id: u32, + &mut self, hdr: SMBCommonHdr, vercmd: SMBVerCmdStat, call_id: u32, packet_flags: u8, ) -> Option<&mut SMBTransaction> { let mut tx = self.new_tx()?; tx.hdr = hdr; tx.vercmd = vercmd; tx.type_data = Some(SMBTransactionTypeData::DCERPC( - SMBTransactionDCERPC::new_response(call_id), + SMBTransactionDCERPC::new_response(call_id, packet_flags), )); SCLogDebug!("SMB: TX DCERPC created: ID {} hdr {:?}", tx.id, tx.hdr); @@ -213,6 +218,7 @@ pub fn smb_write_dcerpc_record( if let Some(SMBTransactionTypeData::DCERPC(ref mut tdn)) = tx.type_data { + tdn.req_flags |= dcer.packet_flags; tdn.frag_cnt_ts = tdn.frag_cnt_ts.saturating_add(1); let max_size = cfg_max_stub_size() as usize; if recr.data.len() + tdn.stub_data_ts.len() < max_size { @@ -249,7 +255,13 @@ pub fn smb_write_dcerpc_record( } } - let Some(tx) = state.new_dcerpc_tx(hdr, vercmd, dcer.packet_type, dcer.call_id) else { + let Some(tx) = state.new_dcerpc_tx( + hdr, + vercmd, + dcer.packet_type, + dcer.call_id, + dcer.packet_flags, + ) else { return false; }; match dcer.packet_type { @@ -390,6 +402,7 @@ fn smb_dcerpc_response_bindack( Some(tx) => { if let Some(SMBTransactionTypeData::DCERPC(ref mut tdn)) = tx.type_data { tdn.set_result(DCERPC_TYPE_BINDACK); + tdn.resp_flags |= dcer.packet_flags; } tx.vercmd.set_ntstatus(ntstatus); tx.response_done = true; @@ -460,6 +473,7 @@ fn dcerpc_response_handle(tx: &mut SMBTransaction, vercmd: SMBVerCmdStat, dcer: tdn.stub_data_tc .extend_from_slice(&respr.data[..max_size - tdn.stub_data_tc.len()]); } + tdn.resp_flags |= dcer.packet_flags; } tx.vercmd.set_ntstatus(ntstatus); tx.response_done = dcer.last_frag; @@ -474,6 +488,7 @@ fn dcerpc_response_handle(tx: &mut SMBTransaction, vercmd: SMBVerCmdStat, dcer: 21..=255 => { if let Some(SMBTransactionTypeData::DCERPC(ref mut tdn)) = tx.type_data { tdn.set_result(dcer.packet_type); + tdn.resp_flags |= dcer.packet_flags; } tx.vercmd.set_ntstatus(ntstatus); tx.response_done = true; @@ -483,6 +498,7 @@ fn dcerpc_response_handle(tx: &mut SMBTransaction, vercmd: SMBVerCmdStat, dcer: // valid type w/o special processing if let Some(SMBTransactionTypeData::DCERPC(ref mut tdn)) = tx.type_data { tdn.set_result(dcer.packet_type); + tdn.resp_flags |= dcer.packet_flags; } tx.vercmd.set_ntstatus(ntstatus); tx.response_done = true; @@ -556,9 +572,12 @@ pub fn smb_read_dcerpc_record( }; if !found { // pick up DCERPC tx even if we missed the request - let Some(tx) = - state.new_dcerpc_tx_for_response(hdr, vercmd.clone(), dcer.call_id) - else { + let Some(tx) = state.new_dcerpc_tx_for_response( + hdr, + vercmd.clone(), + dcer.call_id, + dcer.packet_flags, + ) else { return false; }; dcerpc_response_handle(tx, vercmd, &dcer); diff --git a/rust/src/smb/dcerpc_records.rs b/rust/src/smb/dcerpc_records.rs index 5e2a659e81..5bb81a4c1b 100644 --- a/rust/src/smb/dcerpc_records.rs +++ b/rust/src/smb/dcerpc_records.rs @@ -212,6 +212,7 @@ pub struct DceRpcRecord<'a> { pub little_endian: bool, pub packet_type: u8, + pub packet_flags: u8, pub call_id: u32, pub data: &'a [u8], @@ -256,6 +257,7 @@ pub fn parse_dcerpc_record(i: &[u8]) -> IResult<&[u8], DceRpcRecord<'_>> { version_major, version_minor, packet_type, + packet_flags: (packet_flags.0 << 2) | (packet_flags.1 << 1) | packet_flags.2, first_frag: packet_flags.2 == 1, last_frag: packet_flags.1 == 1, frag_len, diff --git a/rust/src/smb/detect.rs b/rust/src/smb/detect.rs index dcb16bc4d5..855bf31346 100644 --- a/rust/src/smb/detect.rs +++ b/rust/src/smb/detect.rs @@ -19,7 +19,7 @@ use super::smb::ALPROTO_SMB; use crate::core::{STREAM_TOCLIENT, STREAM_TOSERVER}; use crate::dcerpc::dcerpc::DCERPC_TYPE_REQUEST; use crate::dcerpc::detect::{DCEIfaceData, DCEOpnumData, DETECT_DCE_OPNUM_RANGE_UNINITIALIZED}; -use crate::detect::uint::detect_match_uint; +use crate::detect::uint::{detect_match_uint, DetectUintData}; use crate::detect::{helper_keyword_register_sticky_buffer, SigTableElmtStickyBuffer}; use crate::direction::Direction; use crate::smb::smb::*; @@ -125,6 +125,26 @@ pub(crate) unsafe extern "C" fn smb_tx_match_dce_opnum( return 0; } +pub(crate) unsafe extern "C" fn smb_tx_match_dce_flags( + tx: *mut c_void, ctx: *const SigMatchCtx, flags: u8, +) -> u8 { + let tx = cast_pointer!(tx, SMBTransaction); + let dce_data = cast_pointer!(ctx, DetectUintData); + + if let Some(SMBTransactionTypeData::DCERPC(ref x)) = tx.type_data { + let pflags = if flags & STREAM_TOSERVER != 0 { + x.req_flags + } else { + x.resp_flags + }; + if detect_match_uint(dce_data, pflags as u16) { + return 1; + } + } + + return 0; +} + /* mimic logic that is/was in the C code: * - match on REQUEST (so not on BIND/BINDACK (probably for mixing with * dce_opnum and dce_stub_data)