From 5717554d9c702c4f62041851ba65e1c2be4462d6 Mon Sep 17 00:00:00 2001 From: jason taylor Date: Mon, 21 Aug 2023 19:56:49 +0000 Subject: [PATCH] smb: add smb version keyword Signed-off-by: jason taylor --- doc/userguide/rules/smb-keywords.rst | 42 +++++++ rust/src/smb/detect.rs | 144 +++++++++++++++-------- src/Makefile.am | 2 + src/detect-engine-register.c | 2 + src/detect-engine-register.h | 1 + src/detect-smb-version.c | 163 +++++++++++++++++++++++++++ src/detect-smb-version.h | 25 ++++ 7 files changed, 335 insertions(+), 44 deletions(-) create mode 100644 src/detect-smb-version.c create mode 100644 src/detect-smb-version.h diff --git a/doc/userguide/rules/smb-keywords.rst b/doc/userguide/rules/smb-keywords.rst index 02cf190bc794..5f73ebeada35 100644 --- a/doc/userguide/rules/smb-keywords.rst +++ b/doc/userguide/rules/smb-keywords.rst @@ -58,3 +58,45 @@ Examples:: ``smb.ntlmssp_domain`` is a 'sticky buffer'. ``smb.ntlmssp_domain`` can be used as ``fast_pattern``. + +smb.version +-------------- + +Keyword to match on SMB version. + +Examples:: + + alert smb $HOME_NET any -> any any (msg:"SMB1 version rule"; smb.version:1; sid:1;) + alert smb $HOME_NET any -> any any (msg:"SMB2 version rule"; smb.version:2; sid:2;) + + +Matching in transition from SMBv1 to SMBv2 +****************************************** + +In the initial negotiation protocol request, a client supporting SMBv1 and SMBv2 can send an initial SMBv1 request and receive an SMBv2 response from server, indicating that SMBv2 will be used. + +This first SMBv2 response made by the server will match as SMBv1, since the entire transaction will be considered a SMBv1 transaction. + +Will `smb.version` match SMBv3 traffic? +*************************************** + +Yes, it will match SMBv3 messages using `smb.version:2;`, which will match SMBv2 and SMBv3, since they use the same version identifier in the SMB header. + +This keyword will use the Protocol ID specified in SMB header to determine the version. Here is a summary of the Protocol ID codes: + +- 0xffSMB is SMB1 `header `_ +- 0xfeSMB is SMB2 `normal header `_ (can be `sync `_ or `async `_) +- 0xfdSMB is SMB2 `transform header `_. This is only valid for the SMB 3.x dialect family. +- 0xfcSMB is SMB2 `transform compression header `_ (can be `chained `_ or `unchained `_). These ones requires the use of 3.1.1 dialect. + +The Protocol ID in header distinguishes only SMB1 and SMB2 since they are totally different protocols with total different message formats, types and implementation. + +On the other hand SMB3 is more an extension for SMB2. When using SMB2 we can select one of the following dialects for the conversation between client and server: + +- 2.0.2 +- 2.1 +- 3.0 +- 3.0.2 +- 3.1.1 + +We say we are using SMB3 when we select a 3.x dialect for the conversation, so you can use SMB3.0, SMB3.0.2 or SMB3.1.1. The higher you choose, the more capabilities you have, but the message syntax and message command number remains the same. \ No newline at end of file diff --git a/rust/src/smb/detect.rs b/rust/src/smb/detect.rs index c85a6f59ce33..0b69c7f95b88 100644 --- a/rust/src/smb/detect.rs +++ b/rust/src/smb/detect.rs @@ -15,19 +15,20 @@ * 02110-1301, USA. */ -use std::ptr; use crate::core::*; -use crate::smb::smb::*; -use crate::dcerpc::detect::{DCEIfaceData, DCEOpnumData, DETECT_DCE_OPNUM_RANGE_UNINITIALIZED}; 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::smb::smb::SMBTransaction; +use crate::smb::smb::*; +use std::ffi::CStr; +use std::os::raw::{c_char, c_void}; +use std::ptr; #[no_mangle] -pub unsafe extern "C" fn rs_smb_tx_get_share(tx: &mut SMBTransaction, - buffer: *mut *const u8, - buffer_len: *mut u32) - -> u8 -{ +pub unsafe extern "C" fn rs_smb_tx_get_share( + tx: &mut SMBTransaction, buffer: *mut *const u8, buffer_len: *mut u32, +) -> u8 { if let Some(SMBTransactionTypeData::TREECONNECT(ref x)) = tx.type_data { SCLogDebug!("is_pipe {}", x.is_pipe); if !x.is_pipe { @@ -43,11 +44,9 @@ pub unsafe extern "C" fn rs_smb_tx_get_share(tx: &mut SMBTransaction, } #[no_mangle] -pub unsafe extern "C" fn rs_smb_tx_get_named_pipe(tx: &mut SMBTransaction, - buffer: *mut *const u8, - buffer_len: *mut u32) - -> u8 -{ +pub unsafe extern "C" fn rs_smb_tx_get_named_pipe( + tx: &mut SMBTransaction, buffer: *mut *const u8, buffer_len: *mut u32, +) -> u8 { if let Some(SMBTransactionTypeData::TREECONNECT(ref x)) = tx.type_data { SCLogDebug!("is_pipe {}", x.is_pipe); if x.is_pipe { @@ -63,12 +62,9 @@ pub unsafe extern "C" fn rs_smb_tx_get_named_pipe(tx: &mut SMBTransaction, } #[no_mangle] -pub unsafe extern "C" fn rs_smb_tx_get_stub_data(tx: &mut SMBTransaction, - direction: u8, - buffer: *mut *const u8, - buffer_len: *mut u32) - -> u8 -{ +pub unsafe extern "C" fn rs_smb_tx_get_stub_data( + tx: &mut SMBTransaction, direction: u8, buffer: *mut *const u8, buffer_len: *mut u32, +) -> u8 { if let Some(SMBTransactionTypeData::DCERPC(ref x)) = tx.type_data { let vref = if direction == Direction::ToServer as u8 { &x.stub_data_ts @@ -88,10 +84,9 @@ pub unsafe extern "C" fn rs_smb_tx_get_stub_data(tx: &mut SMBTransaction, } #[no_mangle] -pub extern "C" fn rs_smb_tx_match_dce_opnum(tx: &mut SMBTransaction, - dce_data: &mut DCEOpnumData) - -> u8 -{ +pub extern "C" fn rs_smb_tx_match_dce_opnum( + tx: &mut SMBTransaction, dce_data: &mut DCEOpnumData, +) -> u8 { SCLogDebug!("rs_smb_tx_get_dce_opnum: start"); if let Some(SMBTransactionTypeData::DCERPC(ref x)) = tx.type_data { if x.req_cmd == DCERPC_TYPE_REQUEST { @@ -115,17 +110,13 @@ pub extern "C" fn rs_smb_tx_match_dce_opnum(tx: &mut SMBTransaction, * dce_opnum and dce_stub_data) * - only match on approved ifaces (so ack_result == 0) */ #[no_mangle] -pub extern "C" fn rs_smb_tx_get_dce_iface(state: &mut SMBState, - tx: &mut SMBTransaction, - dce_data: &mut DCEIfaceData) - -> u8 -{ +pub extern "C" fn rs_smb_tx_get_dce_iface( + state: &mut SMBState, tx: &mut SMBTransaction, dce_data: &mut DCEIfaceData, +) -> u8 { let if_uuid = dce_data.if_uuid.as_slice(); let is_dcerpc_request = match tx.type_data { - Some(SMBTransactionTypeData::DCERPC(ref x)) => { - x.req_cmd == DCERPC_TYPE_REQUEST - }, - _ => { false }, + Some(SMBTransactionTypeData::DCERPC(ref x)) => x.req_cmd == DCERPC_TYPE_REQUEST, + _ => false, }; if !is_dcerpc_request { return 0; @@ -134,13 +125,18 @@ pub extern "C" fn rs_smb_tx_get_dce_iface(state: &mut SMBState, Some(ref x) => x, _ => { return 0; - }, + } }; SCLogDebug!("looking for UUID {:?}", if_uuid); for i in ifaces { - SCLogDebug!("stored UUID {:?} acked {} ack_result {}", i, i.acked, i.ack_result); + SCLogDebug!( + "stored UUID {:?} acked {} ack_result {}", + i, + i.acked, + i.ack_result + ); if i.acked && i.ack_result == 0 && i.uuid == if_uuid { if let Some(x) = &dce_data.du16 { @@ -156,11 +152,9 @@ pub extern "C" fn rs_smb_tx_get_dce_iface(state: &mut SMBState, } #[no_mangle] -pub unsafe extern "C" fn rs_smb_tx_get_ntlmssp_user(tx: &mut SMBTransaction, - buffer: *mut *const u8, - buffer_len: *mut u32) - -> u8 -{ +pub unsafe extern "C" fn rs_smb_tx_get_ntlmssp_user( + tx: &mut SMBTransaction, buffer: *mut *const u8, buffer_len: *mut u32, +) -> u8 { if let Some(SMBTransactionTypeData::SESSIONSETUP(ref x)) = tx.type_data { if let Some(ref ntlmssp) = x.ntlmssp { *buffer = ntlmssp.user.as_ptr(); @@ -175,11 +169,9 @@ pub unsafe extern "C" fn rs_smb_tx_get_ntlmssp_user(tx: &mut SMBTransaction, } #[no_mangle] -pub unsafe extern "C" fn rs_smb_tx_get_ntlmssp_domain(tx: &mut SMBTransaction, - buffer: *mut *const u8, - buffer_len: *mut u32) - -> u8 -{ +pub unsafe extern "C" fn rs_smb_tx_get_ntlmssp_domain( + tx: &mut SMBTransaction, buffer: *mut *const u8, buffer_len: *mut u32, +) -> u8 { if let Some(SMBTransactionTypeData::SESSIONSETUP(ref x)) = tx.type_data { if let Some(ref ntlmssp) = x.ntlmssp { *buffer = ntlmssp.domain.as_ptr(); @@ -192,3 +184,67 @@ pub unsafe extern "C" fn rs_smb_tx_get_ntlmssp_domain(tx: &mut SMBTransaction, *buffer_len = 0; return 0; } + +#[no_mangle] +pub unsafe extern "C" fn rs_smb_version_match( + tx: &mut SMBTransaction, version_data: &mut u8, +) -> u8 { + let version = tx.vercmd.get_version(); + if version == *version_data { + return 1; + } + + return 0; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_smb_version_parse(carg: *const c_char) -> *mut c_void { + if carg.is_null() { + return std::ptr::null_mut(); + } + + if let Ok(arg) = CStr::from_ptr(carg).to_str() { + if let Ok(detect) = parse_version_data(arg) { + return Box::into_raw(Box::new(detect)) as *mut _; + } + } + + return std::ptr::null_mut(); +} + +#[no_mangle] +pub unsafe extern "C" fn rs_smb_version_free(ptr: *mut c_void) { + if !ptr.is_null() { + std::mem::drop(Box::from_raw(ptr as *mut u8)); + } +} + +fn parse_version_data(arg: &str) -> Result { + let arg = arg.trim(); + let version: u8 = arg.parse().map_err(|_| ())?; + + if version != 1 && version != 2 { + return Err(()); + } + + return Ok(version); +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_parse_cmd_data() { + assert_eq!(Err(()), parse_version_data("0")); + assert_eq!(1u8, parse_version_data("1").unwrap()); + assert_eq!(2u8, parse_version_data("2").unwrap()); + assert_eq!(Err(()), parse_version_data("3")); + } + + #[test] + fn test_parse_cmd_data_with_spaces() { + assert_eq!(1u8, parse_version_data(" 1").unwrap()); + assert_eq!(2u8, parse_version_data(" 2 ").unwrap()); + } +} diff --git a/src/Makefile.am b/src/Makefile.am index 48a5ce850ce2..a5c79cf48555 100755 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -292,6 +292,7 @@ noinst_HEADERS = \ detect-sip-uri.h \ detect-smb-ntlmssp.h \ detect-smb-share.h \ + detect-smb-version.h \ detect-snmp-community.h \ detect-snmp-pdu_type.h \ detect-snmp-usm.h \ @@ -901,6 +902,7 @@ libsuricata_c_a_SOURCES = \ detect-sip-uri.c \ detect-smb-ntlmssp.c \ detect-smb-share.c \ + detect-smb-version.c \ detect-snmp-community.c \ detect-snmp-pdu_type.c \ detect-snmp-usm.c \ diff --git a/src/detect-engine-register.c b/src/detect-engine-register.c index df6e4a738ffc..7305ec663688 100644 --- a/src/detect-engine-register.c +++ b/src/detect-engine-register.c @@ -76,6 +76,7 @@ #include "detect-config.h" #include "detect-smb-share.h" +#include "detect-smb-version.h" #include "detect-base64-decode.h" #include "detect-base64-data.h" @@ -601,6 +602,7 @@ void SigTableSetup(void) DetectSmbShareRegister(); DetectSmbNtlmsspUserRegister(); DetectSmbNtlmsspDomainRegister(); + DetectSmbVersionRegister(); DetectTlsRegister(); DetectTlsValidityRegister(); DetectTlsVersionRegister(); diff --git a/src/detect-engine-register.h b/src/detect-engine-register.h index 7d6c457ef9b0..249867877482 100644 --- a/src/detect-engine-register.h +++ b/src/detect-engine-register.h @@ -197,6 +197,7 @@ enum DetectKeywordId { DETECT_SMB_SHARE, DETECT_SMB_NTLMSSP_USER, DETECT_SMB_NTLMSSP_DOMAIN, + DETECT_SMB_VERSION, DETECT_ASN1, diff --git a/src/detect-smb-version.c b/src/detect-smb-version.c new file mode 100644 index 000000000000..9563fca052c0 --- /dev/null +++ b/src/detect-smb-version.c @@ -0,0 +1,163 @@ +/* Copyright (C) 2023 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 + * + * \author Eloy Perez + * \author Jason Taylor + * + * Implements the smb.version 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-state.h" +#include "detect-engine-prefilter.h" +#include "detect-engine-content-inspection.h" + +#include "detect-smb-version.h" +#include "rust.h" + +#define BUFFER_NAME "smb_version" +#define KEYWORD_NAME "smb.version" +#define KEYWORD_NAME_LEGACY BUFFER_NAME +#define KEYWORD_ID DETECT_SMB_VERSION + +static int g_smb_version_list_id = 0; + +/** + * \brief App layer match function for the "smb_version" keyword. + * + * \param t Pointer to the ThreadVars instance. + * \param det_ctx Pointer to the DetectEngineThreadCtx. + * \param f Pointer to the flow. + * \param flags Pointer to the flags indicating the flow direction. + * \param state Pointer to the app layer state data. + * \param s Pointer to the Signature instance. + * \param m Pointer to the SigMatch. + * + * \retval 1 On Match. + * \retval 0 On no match. + */ + +static int DetectSmbVersionMatchRust(DetectEngineThreadCtx *det_ctx, Flow *f, uint8_t flags, + void *state, void *txv, const Signature *s, const SigMatchCtx *m) +{ + SCEnter(); + + SCLogDebug("smb_version: DetectSmbVersionMatchRust"); + + if (f->alproto == ALPROTO_SMB) { + return rs_smb_version_match(txv, (void *)m); + } + + if (rs_smb_version_match(txv, (void *)m) != 1) + SCReturnInt(0); + + SCReturnInt(1); +} + +static void DetectSmbVersionFree(DetectEngineCtx *de_ctx, void *ptr) +{ + SCEnter(); + + SCLogDebug("smb_version: DetectSmbVersionFree"); + + if (ptr != NULL) { + rs_smb_version_free(ptr); + } + SCReturn; +} + +/** + * \brief Creates a SigMatch for the "dce_opnum" keyword being sent as argument, + * and appends it to the rs_dcerpc_opnum_matchSignature(s). + * + * \param de_ctx Pointer to the detection engine context. + * \param s Pointer to signature for the current Signature being parsed + * from the rules. + * \param arg Pointer to the string holding the keyword value. + * + * \retval 0 on success, -1 on failure + */ + +static int DetectSmbVersionSetup(DetectEngineCtx *de_ctx, Signature *s, const char *arg) +{ + SCLogDebug("smb_version: DetectSmbVersionSetup"); + + if (DetectSignatureSetAppProto(s, ALPROTO_SMB) < 0) + return -1; + + if (arg == NULL) { + SCLogError("Error parsing smb_version option in signature, it needs a value"); + return -1; + } + + void *dod = rs_smb_version_parse(arg); + if (dod == NULL) { + SCLogError("Error parsing smb_version option in signature"); + return -1; + } + + SigMatch *sm = SigMatchAlloc(); + + if (sm == NULL) { + DetectSmbVersionFree(de_ctx, dod); + return -1; + } + + sm->type = DETECT_SMB_VERSION; + sm->ctx = dod; + + SigMatchAppendSMToList(s, sm, g_smb_version_list_id); + return 0; +} + +/** + * \brief Registers the keyword handlers for the "smb_version" keyword. + */ + +void DetectSmbVersionRegister(void) +{ + sigmatch_table[DETECT_SMB_VERSION].name = KEYWORD_NAME; + sigmatch_table[DETECT_SMB_VERSION].alias = KEYWORD_NAME_LEGACY; + sigmatch_table[DETECT_SMB_VERSION].Setup = DetectSmbVersionSetup; + sigmatch_table[DETECT_SMB_VERSION].Match = NULL; + sigmatch_table[DETECT_SMB_VERSION].AppLayerTxMatch = DetectSmbVersionMatchRust; + sigmatch_table[DETECT_SMB_VERSION].Free = DetectSmbVersionFree; + sigmatch_table[DETECT_SMB_VERSION].desc = "smb keyword to match on SMB version"; + + /* + DetectAppLayerInspectEngineRegister2( + BUFFER_NAME, ALPROTO_SMB, SIG_FLAG_TOSERVER, 0, DetectEngineInspectSmbVersion, + NULL); + + DetectAppLayerInspectEngineRegister2( + BUFFER_NAME, ALPROTO_SMB, SIG_FLAG_TOCLIENT, 0, DetectEngineInspectSmbVersion, + NULL); + */ + + g_smb_version_list_id = DetectBufferTypeRegister(BUFFER_NAME); + + SCLogDebug("registering " BUFFER_NAME " rule option"); +} \ No newline at end of file diff --git a/src/detect-smb-version.h b/src/detect-smb-version.h new file mode 100644 index 000000000000..639d7514e060 --- /dev/null +++ b/src/detect-smb-version.h @@ -0,0 +1,25 @@ +/* Copyright (C) 2023 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. + */ + +#ifndef __DETECT_SMB_VERSION_H__ +#define __DETECT_SMB_VERSION_H__ + +/** \brief registers the keyword into the engine. Called from + * detect.c::SigTableSetup() */ +void DetectSmbVersionRegister(void); + +#endif /* __DETECT_SMB-VERSION_H__ */ \ No newline at end of file