Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions doc/userguide/rules/dcerpc-keywords.rst
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,21 @@ Example::

dcerpc.stub_data; content:"123456";

dcerpc.is_fragmented
--------------------

Match on whether a DCERPC PDU is fragmented. It takes a boolean value.

Syntax::

dcerpc.is_fragmented: true|false;

Example:

.. container:: example-rule

alert dcerpc any any -> any any (:example-rule-options:`dcerpc.is_fragmented: true;` sid: 1;)


Additional information
----------------------
Expand Down
9 changes: 9 additions & 0 deletions rust/src/dcerpc/dcerpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<u8>,
pub seqnum: u32,
pub tx_data: AppLayerTxData,
Expand Down Expand Up @@ -585,6 +587,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(
Expand Down Expand Up @@ -762,13 +765,15 @@ 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;
}
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;
Expand Down Expand Up @@ -949,10 +954,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()
};
Expand Down Expand Up @@ -980,10 +987,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);
}
};
Expand Down
2 changes: 2 additions & 0 deletions rust/src/dcerpc/dcerpc_udp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);
Expand Down
116 changes: 114 additions & 2 deletions rust/src/dcerpc/detect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,15 @@

use super::dcerpc::{
DCERPCState, DCERPCTransaction, ALPROTO_DCERPC, DCERPC_TYPE_REQUEST, DCERPC_TYPE_RESPONSE,
DCERPC_UUID_ENTRY_FLAG_FF,
DCERPC_UUID_ENTRY_FLAG_FF, PFCL1_FRAG, PFC_FIRST_FRAG, PFC_LAST_FRAG,
};
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_iface, smb_tx_match_dce_is_fragmented,
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};
Expand Down Expand Up @@ -393,6 +396,103 @@ unsafe extern "C" fn dcerpc_opnum_free(_de: *mut DetectEngineCtx, ptr: *mut c_vo
}
}

#[derive(Debug)]
pub struct DCERPCIsFragmentedData {
pub is_fragmented: bool,
}

fn parse_is_fragmented(arg: &str) -> Option<bool> {
match arg.trim().to_ascii_lowercase().as_str() {
"true" => Some(true),
"false" => Some(false),
_ => None,
}
}

unsafe fn dcerpc_is_fragmented_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 parse_is_fragmented(arg) {
Some(is_fragmented) => {
Box::into_raw(Box::new(DCERPCIsFragmentedData { is_fragmented })) as *mut c_void
}
None => std::ptr::null_mut(),
}
}

unsafe fn dcerpc_tx_match_is_fragmented(flags: u8, tx: *mut c_void, ctx: *const SigMatchCtx) -> u8 {
let tx = cast_pointer!(tx, DCERPCTransaction);
let ctx = cast_pointer!(ctx, DCERPCIsFragmentedData);

let (frag_cnt, tx_flags) = if flags & STREAM_TOSERVER != 0 {
(tx.frag_cnt_ts, tx.req_flags)
} else {
(tx.frag_cnt_tc, tx.resp_flags)
};
// hack to tell if it's UDP
let fragmented = if !tx.activityuuid.is_empty() {
// For UDP, the flags1 fragment bit (0x04) explicitly marks a fragment
tx_flags & PFCL1_FRAG as u16 != 0
} else {
// For TCP, a request/response PDU is a single complete message only
// when it sets both PFC_FIRST_FRAG and PFC_LAST_FRAG; any PDU missing
// either flag (first, middle or last fragment) is fragmented. The
// frag_cnt guard keeps a direction that has seen no PDU from matching.
let both = (PFC_FIRST_FRAG | PFC_LAST_FRAG) as u16;
frag_cnt > 0 && (tx_flags & both) != both
};
if fragmented == ctx.is_fragmented {
return 1;
}
return 0;
}

unsafe extern "C" fn dcerpc_is_fragmented_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_is_fragmented(flags, tx, ctx) as c_int;
}

return smb_tx_match_dce_is_fragmented(flags, tx, ctx) as c_int;
}

unsafe extern "C" fn dcerpc_is_fragmented_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_is_fragmented_parse(raw);
if ctx.is_null() {
return -1;
}
if SCSigMatchAppendSMToList(
de,
s,
G_DCERPC_IS_FRAGMENTED_KW_ID,
ctx as *mut SigMatchCtx,
G_DCERPC_GENERIC_BUFFER_ID,
)
.is_null()
{
dcerpc_is_fragmented_free(std::ptr::null_mut(), ctx);
return -1;
}
return 0;
}

unsafe extern "C" fn dcerpc_is_fragmented_free(_de: *mut DetectEngineCtx, ptr: *mut c_void) {
if !ptr.is_null() {
std::mem::drop(Box::from_raw(ptr as *mut DCERPCIsFragmentedData));
}
}

unsafe extern "C" fn dcerpc_stub_data_setup(
de_ctx: *mut DetectEngineCtx, s: *mut Signature, _str: *const c_char,
) -> c_int {
Expand Down Expand Up @@ -440,6 +540,7 @@ unsafe extern "C" fn dcerpc_tx_get_stub_data(
}

static mut G_DCERPC_OPNUM_KW_ID: u16 = 0;
static mut G_DCERPC_IS_FRAGMENTED_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;
Expand Down Expand Up @@ -474,6 +575,17 @@ pub unsafe extern "C" fn SCDetectDcerpcRegister() {
b"dce_opnum\0".as_ptr() as *const libc::c_char,
);

let kw = SCSigTableAppLiteElmt {
name: b"dcerpc.is_fragmented\0".as_ptr() as *const libc::c_char,
desc: b"match if the DCERPC PDU is fragmented\0".as_ptr() as *const libc::c_char,
url: b"/rules/dcerpc-keywords.html#dcerpc-is-fragmented\0".as_ptr() as *const libc::c_char,
AppLayerTxMatch: Some(dcerpc_is_fragmented_match),
Setup: Some(dcerpc_is_fragmented_setup),
Free: Some(dcerpc_is_fragmented_free),
flags: 0,
};
G_DCERPC_IS_FRAGMENTED_KW_ID = SCDetectHelperKeywordRegister(&kw);

let kw = SCSigTableAppLiteElmt {
name: b"dcerpc.iface\0".as_ptr() as *const libc::c_char,
desc: b"match on the value of the interface UUID in a DCERPC header\0".as_ptr()
Expand Down
4 changes: 4 additions & 0 deletions rust/src/smb/dcerpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ pub struct SMBTransactionDCERPC {
pub res_cmd: u8,
pub res_set: bool,
pub call_id: u32,
pub req_is_fragmented: bool,
pub resp_is_fragmented: bool,
pub frag_cnt_ts: u16,
pub frag_cnt_tc: u16,
pub stub_data_ts: Vec<u8>,
Expand Down Expand Up @@ -263,6 +265,7 @@ pub fn smb_write_dcerpc_record(
SCLogDebug!("first frag size {}", recr.data.len());
tdn.opnum = recr.opnum;
tdn.context_id = recr.context_id;
tdn.req_is_fragmented = !(dcer.first_frag && dcer.last_frag);
tdn.frag_cnt_ts = tdn.frag_cnt_ts.saturating_add(1);
let max_size = cfg_max_stub_size() as usize;
if tdn.stub_data_ts.len() + recr.data.len() < max_size {
Expand Down Expand Up @@ -452,6 +455,7 @@ fn dcerpc_response_handle(tx: &mut SMBTransaction, vercmd: SMBVerCmdStat, dcer:
if let Some(SMBTransactionTypeData::DCERPC(ref mut tdn)) = tx.type_data {
SCLogDebug!("CMD 11 found at tx {}", tx.id);
tdn.set_result(DCERPC_TYPE_RESPONSE);
tdn.resp_is_fragmented = !(dcer.first_frag && dcer.last_frag);
let max_size = cfg_max_stub_size() as usize;
tdn.frag_cnt_tc = tdn.frag_cnt_tc.saturating_add(1);
if tdn.stub_data_tc.len() + respr.data.len() < max_size {
Expand Down
24 changes: 23 additions & 1 deletion rust/src/smb/detect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@
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::dcerpc::detect::{
DCEIfaceData, DCEOpnumData, DCERPCIsFragmentedData, DETECT_DCE_OPNUM_RANGE_UNINITIALIZED,
};
use crate::detect::uint::detect_match_uint;
use crate::detect::{helper_keyword_register_sticky_buffer, SigTableElmtStickyBuffer};
use crate::direction::Direction;
Expand Down Expand Up @@ -125,6 +127,26 @@ pub(crate) unsafe extern "C" fn smb_tx_match_dce_opnum(
return 0;
}

pub(crate) unsafe extern "C" fn smb_tx_match_dce_is_fragmented(
flags: u8, tx: *mut c_void, ctx: *const SigMatchCtx,
) -> u8 {
let tx = cast_pointer!(tx, SMBTransaction);
let ctx = cast_pointer!(ctx, DCERPCIsFragmentedData);

if let Some(SMBTransactionTypeData::DCERPC(ref x)) = tx.type_data {
let fragmented = if flags & STREAM_TOSERVER != 0 {
x.req_is_fragmented
} else {
x.resp_is_fragmented
};
if fragmented == ctx.is_fragmented {
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)
Expand Down
Loading