diff options
Diffstat (limited to 'rust/src/smb/smb.rs')
-rw-r--r-- | rust/src/smb/smb.rs | 2435 |
1 files changed, 2435 insertions, 0 deletions
diff --git a/rust/src/smb/smb.rs b/rust/src/smb/smb.rs new file mode 100644 index 0000000..d6b0a56 --- /dev/null +++ b/rust/src/smb/smb.rs @@ -0,0 +1,2435 @@ +/* Copyright (C) 2017-2022 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. + */ + +/* TODO + * - check all parsers for calls on non-SUCCESS status + */ + +/* GAP processing: + * - if post-gap we've seen a succesful tx req/res: we consider "re-sync'd" + */ + +// written by Victor Julien + +use std; +use std::str; +use std::ffi::{self, CString}; + +use std::collections::HashMap; +use std::collections::VecDeque; + +use nom7::{Err, Needed}; +use nom7::error::{make_error, ErrorKind}; + +use crate::core::*; +use crate::applayer; +use crate::applayer::*; +use crate::frames::*; +use crate::conf::*; +use crate::applayer::{AppLayerResult, AppLayerTxData, AppLayerEvent}; + +use crate::smb::nbss_records::*; +use crate::smb::smb1_records::*; +use crate::smb::smb2_records::*; + +use crate::smb::smb1::*; +use crate::smb::smb2::*; +use crate::smb::smb3::*; +use crate::smb::dcerpc::*; +use crate::smb::session::*; +use crate::smb::events::*; +use crate::smb::files::*; +use crate::smb::smb2_ioctl::*; + +#[derive(AppLayerFrameType)] +pub enum SMBFrameType { + NBSSPdu, + NBSSHdr, + NBSSData, + SMB1Pdu, + SMB1Hdr, + SMB1Data, + SMB2Pdu, + SMB2Hdr, + SMB2Data, + SMB3Pdu, + SMB3Hdr, + SMB3Data, +} + +pub const MIN_REC_SIZE: u16 = 32 + 4; // SMB hdr + nbss hdr +pub const SMB_CONFIG_DEFAULT_STREAM_DEPTH: u32 = 0; + +pub static mut SMB_CFG_MAX_READ_SIZE: u32 = 16777216; +pub static mut SMB_CFG_MAX_READ_QUEUE_SIZE: u32 = 67108864; +pub static mut SMB_CFG_MAX_READ_QUEUE_CNT: u32 = 64; +pub static mut SMB_CFG_MAX_WRITE_SIZE: u32 = 16777216; +pub static mut SMB_CFG_MAX_WRITE_QUEUE_SIZE: u32 = 67108864; +pub static mut SMB_CFG_MAX_WRITE_QUEUE_CNT: u32 = 64; + +static mut ALPROTO_SMB: AppProto = ALPROTO_UNKNOWN; + +static mut SMB_MAX_TX: usize = 1024; + +pub static mut SURICATA_SMB_FILE_CONFIG: Option<&'static SuricataFileContext> = None; + +#[no_mangle] +pub extern "C" fn rs_smb_init(context: &'static mut SuricataFileContext) +{ + unsafe { + SURICATA_SMB_FILE_CONFIG = Some(context); + } +} + +pub const SMB_SRV_ERROR: u16 = 1; +pub const SMB_SRV_BADPW: u16 = 2; +pub const SMB_SRV_BADTYPE: u16 = 3; +pub const SMB_SRV_ACCESS: u16 = 4; +pub const SMB_SRV_BADUID: u16 = 91; + +pub fn smb_srv_error_string(c: u16) -> String { + match c { + SMB_SRV_ERROR => "SRV_ERROR", + SMB_SRV_BADPW => "SRV_BADPW", + SMB_SRV_BADTYPE => "SRV_BADTYPE", + SMB_SRV_ACCESS => "SRV_ACCESS", + SMB_SRV_BADUID => "SRV_BADUID", + _ => { return (c).to_string(); }, + }.to_string() +} + +pub const SMB_DOS_SUCCESS: u16 = 0; +pub const SMB_DOS_BAD_FUNC: u16 = 1; +pub const SMB_DOS_BAD_FILE: u16 = 2; +pub const SMB_DOS_BAD_PATH: u16 = 3; +pub const SMB_DOS_TOO_MANY_OPEN_FILES: u16 = 4; +pub const SMB_DOS_ACCESS_DENIED: u16 = 5; + +pub fn smb_dos_error_string(c: u16) -> String { + match c { + SMB_DOS_SUCCESS => "DOS_SUCCESS", + SMB_DOS_BAD_FUNC => "DOS_BAD_FUNC", + SMB_DOS_BAD_FILE => "DOS_BAD_FILE", + SMB_DOS_BAD_PATH => "DOS_BAD_PATH", + SMB_DOS_TOO_MANY_OPEN_FILES => "DOS_TOO_MANY_OPEN_FILES", + SMB_DOS_ACCESS_DENIED => "DOS_ACCESS_DENIED", + _ => { return (c).to_string(); }, + }.to_string() +} + +pub const NTLMSSP_NEGOTIATE: u32 = 1; +#[cfg(feature = "debug")] +pub const NTLMSSP_CHALLENGE: u32 = 2; +pub const NTLMSSP_AUTH: u32 = 3; + +#[cfg(feature = "debug")] +pub fn ntlmssp_type_string(c: u32) -> String { + match c { + NTLMSSP_NEGOTIATE => "NTLMSSP_NEGOTIATE", + NTLMSSP_CHALLENGE => "NTLMSSP_CHALLENGE", + NTLMSSP_AUTH => "NTLMSSP_AUTH", + _ => { return (c).to_string(); }, + }.to_string() +} + +#[derive(Default, Eq, PartialEq, Debug, Clone)] +pub struct SMBVerCmdStat { + smb_ver: u8, + smb1_cmd: u8, + smb2_cmd: u16, + + status_set: bool, + status_is_dos_error: bool, + status_error_class: u8, + status: u32, +} + +impl SMBVerCmdStat { + pub fn new() -> Self { + Default::default() + } + pub fn new1(cmd: u8) -> Self { + return Self { + smb_ver: 1, + smb1_cmd: cmd, + ..Default::default() + } + } + pub fn new1_with_ntstatus(cmd: u8, status: u32) -> Self { + return Self { + smb_ver: 1, + smb1_cmd: cmd, + status_set: true, + status, + ..Default::default() + } + } + pub fn new2(cmd: u16) -> Self { + return Self { + smb_ver: 2, + smb2_cmd: cmd, + ..Default::default() + } + } + + pub fn new2_with_ntstatus(cmd: u16, status: u32) -> Self { + return Self { + smb_ver: 2, + smb2_cmd: cmd, + status_set: true, + status, + ..Default::default() + } + } + + pub fn set_smb1_cmd(&mut self, cmd: u8) -> bool { + if self.smb_ver != 0 { + return false; + } + self.smb_ver = 1; + self.smb1_cmd = cmd; + return true; + } + + pub fn set_smb2_cmd(&mut self, cmd: u16) -> bool { + if self.smb_ver != 0 { + return false; + } + self.smb_ver = 2; + self.smb2_cmd = cmd; + return true; + } + + pub fn get_version(&self) -> u8 { + self.smb_ver + } + + pub fn get_smb1_cmd(&self) -> (bool, u8) { + if self.smb_ver != 1 { + return (false, 0); + } + return (true, self.smb1_cmd); + } + + pub fn get_smb2_cmd(&self) -> (bool, u16) { + if self.smb_ver != 2 { + return (false, 0); + } + return (true, self.smb2_cmd); + } + + pub fn get_ntstatus(&self) -> (bool, u32) { + (self.status_set && !self.status_is_dos_error, self.status) + } + + pub fn get_dos_error(&self) -> (bool, u8, u16) { + (self.status_set && self.status_is_dos_error, self.status_error_class, self.status as u16) + } + + fn set_status(&mut self, status: u32, is_dos_error: bool) + { + if is_dos_error { + self.status_is_dos_error = true; + self.status_error_class = (status & 0x0000_00ff) as u8; + self.status = (status & 0xffff_0000) >> 16; + } else { + self.status = status; + } + self.status_set = true; + } + + pub fn set_ntstatus(&mut self, status: u32) + { + self.set_status(status, false) + } + + pub fn set_status_dos_error(&mut self, status: u32) + { + self.set_status(status, true) + } +} + +/// "The FILETIME structure is a 64-bit value that represents the number of +/// 100-nanosecond intervals that have elapsed since January 1, 1601, +/// Coordinated Universal Time (UTC)." +#[derive(Eq, PartialEq, Debug, Clone)] +pub struct SMBFiletime { + ts: u64, +} + +impl SMBFiletime { + pub fn new(raw: u64) -> Self { + Self { + ts: raw, + } + } + + /// inspired by Bro, convert FILETIME to secs since unix epoch + pub fn as_unix(&self) -> u32 { + if self.ts > 116_444_736_000_000_000_u64 { + let ts = self.ts / 10000000 - 11644473600; + ts as u32 + } else { + 0 + } + } +} + +#[derive(Debug)] +pub enum SMBTransactionTypeData { + FILE(SMBTransactionFile), + TREECONNECT(SMBTransactionTreeConnect), + NEGOTIATE(SMBTransactionNegotiate), + DCERPC(SMBTransactionDCERPC), + CREATE(SMBTransactionCreate), + SESSIONSETUP(SMBTransactionSessionSetup), + IOCTL(SMBTransactionIoctl), + RENAME(SMBTransactionRename), + SETFILEPATHINFO(SMBTransactionSetFilePathInfo), +} + +// Used for Trans2 SET_PATH_INFO and SET_FILE_INFO +#[derive(Debug)] +pub struct SMBTransactionSetFilePathInfo { + pub subcmd: u16, + pub loi: u16, + pub delete_on_close: bool, + pub filename: Vec<u8>, + pub fid: Vec<u8>, +} + +impl SMBTransactionSetFilePathInfo { + pub fn new(filename: Vec<u8>, fid: Vec<u8>, subcmd: u16, loi: u16, delete_on_close: bool) + -> Self + { + return Self { + filename, fid, + subcmd, + loi, + delete_on_close, + }; + } +} + +impl SMBState { + pub fn new_setfileinfo_tx(&mut self, filename: Vec<u8>, fid: Vec<u8>, + subcmd: u16, loi: u16, delete_on_close: bool) + -> &mut SMBTransaction + { + let mut tx = self.new_tx(); + + tx.type_data = Some(SMBTransactionTypeData::SETFILEPATHINFO( + SMBTransactionSetFilePathInfo::new( + filename, fid, subcmd, loi, delete_on_close))); + tx.request_done = true; + tx.response_done = self.tc_trunc; // no response expected if tc is truncated + + SCLogDebug!("SMB: TX SETFILEPATHINFO created: ID {}", tx.id); + self.transactions.push_back(tx); + let tx_ref = self.transactions.back_mut(); + return tx_ref.unwrap(); + } + + pub fn new_setpathinfo_tx(&mut self, filename: Vec<u8>, + subcmd: u16, loi: u16, delete_on_close: bool) + -> &mut SMBTransaction + { + let mut tx = self.new_tx(); + + let fid : Vec<u8> = Vec::new(); + tx.type_data = Some(SMBTransactionTypeData::SETFILEPATHINFO( + SMBTransactionSetFilePathInfo::new(filename, fid, + subcmd, loi, delete_on_close))); + tx.request_done = true; + tx.response_done = self.tc_trunc; // no response expected if tc is truncated + + SCLogDebug!("SMB: TX SETFILEPATHINFO created: ID {}", tx.id); + self.transactions.push_back(tx); + let tx_ref = self.transactions.back_mut(); + return tx_ref.unwrap(); + } +} + +#[derive(Debug)] +pub struct SMBTransactionRename { + pub oldname: Vec<u8>, + pub newname: Vec<u8>, + pub fuid: Vec<u8>, +} + +impl SMBTransactionRename { + pub fn new(fuid: Vec<u8>, oldname: Vec<u8>, newname: Vec<u8>) -> Self { + return Self { + fuid, oldname, newname, + }; + } +} + +impl SMBState { + pub fn new_rename_tx(&mut self, fuid: Vec<u8>, oldname: Vec<u8>, newname: Vec<u8>) + -> &mut SMBTransaction + { + let mut tx = self.new_tx(); + + tx.type_data = Some(SMBTransactionTypeData::RENAME( + SMBTransactionRename::new(fuid, oldname, newname))); + tx.request_done = true; + tx.response_done = self.tc_trunc; // no response expected if tc is truncated + + SCLogDebug!("SMB: TX RENAME created: ID {}", tx.id); + self.transactions.push_back(tx); + let tx_ref = self.transactions.back_mut(); + return tx_ref.unwrap(); + } +} + +#[derive(Default, Debug)] +pub struct SMBTransactionCreate { + pub disposition: u32, + pub delete_on_close: bool, + pub directory: bool, + pub filename: Vec<u8>, + pub guid: Vec<u8>, + + pub create_ts: u32, + pub last_access_ts: u32, + pub last_write_ts: u32, + pub last_change_ts: u32, + + pub size: u64, +} + +impl SMBTransactionCreate { + pub fn new(filename: Vec<u8>, disp: u32, del: bool, dir: bool) -> Self { + return Self { + disposition: disp, + delete_on_close: del, + directory: dir, + filename, + ..Default::default() + } + } +} + +#[derive(Default, Debug)] +pub struct SMBTransactionNegotiate { + pub smb_ver: u8, + pub dialects: Vec<Vec<u8>>, + pub dialects2: Vec<Vec<u8>>, + + // SMB1 doesn't have the client GUID + pub client_guid: Option<Vec<u8>>, + pub server_guid: Vec<u8>, +} + +impl SMBTransactionNegotiate { + pub fn new(smb_ver: u8) -> Self { + return Self { + smb_ver, + server_guid: Vec::with_capacity(16), + ..Default::default() + }; + } +} + +#[derive(Default, Debug)] +pub struct SMBTransactionTreeConnect { + pub is_pipe: bool, + pub share_type: u8, + pub tree_id: u32, + pub share_name: Vec<u8>, + + /// SMB1 service strings + pub req_service: Option<Vec<u8>>, + pub res_service: Option<Vec<u8>>, +} + +impl SMBTransactionTreeConnect { + pub fn new(share_name: Vec<u8>) -> Self { + return Self { + share_name, + ..Default::default() + }; + } +} + +#[derive(Debug)] +pub struct SMBTransaction { + pub id: u64, /// internal id + + /// version, command and status + pub vercmd: SMBVerCmdStat, + /// session id, tree id, etc. + pub hdr: SMBCommonHdr, + + /// for state tracking. false means this side is in progress, true + /// that it's complete. + pub request_done: bool, + pub response_done: bool, + + /// Command specific data + pub type_data: Option<SMBTransactionTypeData>, + + pub tx_data: AppLayerTxData, +} + +impl Transaction for SMBTransaction { + fn id(&self) -> u64 { + self.id + } +} + +impl Default for SMBTransaction { + fn default() -> Self { + Self::new() + } +} + +impl SMBTransaction { + pub fn new() -> Self { + return Self { + id: 0, + vercmd: SMBVerCmdStat::new(), + hdr: SMBCommonHdr::default(), + request_done: false, + response_done: false, + type_data: None, + tx_data: AppLayerTxData::new(), + } + } + + pub fn set_status(&mut self, status: u32, is_dos_error: bool) + { + if is_dos_error { + self.vercmd.set_status_dos_error(status); + } else { + self.vercmd.set_ntstatus(status); + } + } + + pub fn free(&mut self) { + SCLogDebug!("SMB TX {:p} free ID {}", &self, self.id); + debug_validate_bug_on!(self.tx_data.files_opened > 1); + debug_validate_bug_on!(self.tx_data.files_logged > 1); + } +} + +impl Drop for SMBTransaction { + fn drop(&mut self) { + if let Some(SMBTransactionTypeData::FILE(ref mut tdf)) = self.type_data { + if let Some(sfcm) = unsafe { SURICATA_SMB_FILE_CONFIG } { + tdf.file_tracker.file.free(sfcm); + } + } + self.free(); + } +} + +#[derive(Hash, Eq, PartialEq, Debug, Clone)] +pub struct SMBFileGUIDOffset { + pub guid: Vec<u8>, + pub offset: u64, +} + +impl SMBFileGUIDOffset { + pub fn new(guid: Vec<u8>, offset: u64) -> Self { + Self { + guid, + offset, + } + } +} + +/// type values to make sure we're not mixing things +/// up in hashmap lookups +pub const SMBHDR_TYPE_GUID: u32 = 1; +pub const SMBHDR_TYPE_SHARE: u32 = 2; +pub const SMBHDR_TYPE_FILENAME: u32 = 3; +pub const SMBHDR_TYPE_OFFSET: u32 = 4; +pub const SMBHDR_TYPE_GENERICTX: u32 = 5; +pub const SMBHDR_TYPE_HEADER: u32 = 6; +//unused pub const SMBHDR_TYPE_MAX_SIZE: u32 = 7; // max resp size for SMB1_COMMAND_TRANS +pub const SMBHDR_TYPE_TRANS_FRAG: u32 = 8; +pub const SMBHDR_TYPE_TREE: u32 = 9; +pub const SMBHDR_TYPE_DCERPCTX: u32 = 10; + +#[derive(Default, Hash, Eq, PartialEq, Debug)] +pub struct SMBCommonHdr { + pub ssn_id: u64, + pub tree_id: u32, + pub rec_type: u32, + pub msg_id: u64, +} + +impl SMBCommonHdr { + pub fn new(rec_type: u32, ssn_id: u64, tree_id: u32, msg_id: u64) -> Self { + Self { + rec_type, + ssn_id, + tree_id, + msg_id, + } + } + pub fn from2(r: &Smb2Record, rec_type: u32) -> SMBCommonHdr { + let tree_id = match rec_type { + SMBHDR_TYPE_TREE => { 0 }, + _ => r.tree_id, + }; + let msg_id = match rec_type { + SMBHDR_TYPE_TRANS_FRAG | SMBHDR_TYPE_SHARE => { 0 }, + _ => { r.message_id }, + }; + + SMBCommonHdr { + rec_type, + ssn_id : r.session_id, + tree_id, + msg_id, + } + + } + pub fn from2_notree(r: &Smb2Record, rec_type: u32) -> SMBCommonHdr { + // async responses do not have a tree id (even if the request has it) + // making thus the match between the two impossible. + // Per spec, MessageId should be enough to identify a message request and response uniquely + // across all messages that are sent on the same SMB2 Protocol transport connection. + // cf https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/ea4560b7-90da-4803-82b5-344754b92a79 + let msg_id = match rec_type { + SMBHDR_TYPE_TRANS_FRAG | SMBHDR_TYPE_SHARE => { 0 }, + _ => { r.message_id }, + }; + + SMBCommonHdr { + rec_type, + ssn_id : r.session_id, + tree_id : 0, + msg_id, + } + } + pub fn from1(r: &SmbRecord, rec_type: u32) -> SMBCommonHdr { + let tree_id = match rec_type { + SMBHDR_TYPE_TREE => { 0 }, + _ => r.tree_id as u32, + }; + let msg_id = match rec_type { + SMBHDR_TYPE_TRANS_FRAG | SMBHDR_TYPE_SHARE => { 0 }, + _ => { r.multiplex_id as u64 }, + }; + + SMBCommonHdr { + rec_type, + ssn_id : r.ssn_id as u64, + tree_id, + msg_id, + } + } + + // don't include tree id + pub fn compare(&self, hdr: &SMBCommonHdr) -> bool { + self.rec_type == hdr.rec_type && self.ssn_id == hdr.ssn_id && + self.msg_id == hdr.msg_id + } +} + +#[derive(Hash, Eq, PartialEq, Debug)] +pub struct SMBHashKeyHdrGuid { + hdr: SMBCommonHdr, + guid: Vec<u8>, +} + +impl SMBHashKeyHdrGuid { + pub fn new(hdr: SMBCommonHdr, guid: Vec<u8>) -> Self { + Self { + hdr, guid, + } + } +} + +#[derive(Hash, Eq, PartialEq, Debug)] +pub struct SMBTree { + pub name: Vec<u8>, + pub is_pipe: bool, +} + +impl SMBTree { + pub fn new(name: Vec<u8>, is_pipe: bool) -> Self { + Self { + name, + is_pipe, + } + } +} + +pub fn u32_as_bytes(i: u32) -> [u8;4] { + let o1: u8 = ((i >> 24) & 0xff) as u8; + let o2: u8 = ((i >> 16) & 0xff) as u8; + let o3: u8 = ((i >> 8) & 0xff) as u8; + let o4: u8 = (i & 0xff) as u8; + return [o1, o2, o3, o4] +} + +#[derive(Default, Debug)] +pub struct SMBState<> { + pub state_data: AppLayerStateData, + + /// map ssn/tree/msgid to vec (guid/name/share) + pub ssn2vec_map: HashMap<SMBCommonHdr, Vec<u8>>, + /// map guid to filename + pub guid2name_map: HashMap<Vec<u8>, Vec<u8>>, + /// map ssn key to read offset + pub ssn2vecoffset_map: HashMap<SMBCommonHdr, SMBFileGUIDOffset>, + + pub ssn2tree_map: HashMap<SMBCommonHdr, SMBTree>, + + // store partial data records that are transferred in multiple + // requests for DCERPC. + pub ssnguid2vec_map: HashMap<SMBHashKeyHdrGuid, Vec<u8>>, + + skip_ts: u32, + skip_tc: u32, + + pub file_ts_left : u32, + pub file_tc_left : u32, + pub file_ts_guid : Vec<u8>, + pub file_tc_guid : Vec<u8>, + + pub ts_ssn_gap: bool, + pub tc_ssn_gap: bool, + + pub ts_gap: bool, // last TS update was gap + pub tc_gap: bool, // last TC update was gap + + pub ts_trunc: bool, // no more data for TOSERVER + pub tc_trunc: bool, // no more data for TOCLIENT + + /// true as long as we have file txs that are in a post-gap + /// state. It means we'll do extra house keeping for those. + check_post_gap_file_txs: bool, + post_gap_files_checked: bool, + + /// transactions list + pub transactions: VecDeque<SMBTransaction>, + tx_index_completed: usize, + + /// tx counter for assigning incrementing id's to tx's + tx_id: u64, + + /// SMB2 dialect or 0 if not set or SMB1 + pub dialect: u16, + /// contains name of SMB1 dialect + pub dialect_vec: Option<Vec<u8>>, // used if dialect == 0 + + /// dcerpc interfaces, stored here to be able to match + /// them while inspecting DCERPC REQUEST txs + pub dcerpc_ifaces: Option<Vec<DCERPCIface>>, + + pub max_read_size: u32, + pub max_write_size: u32, + + /// Timestamp in seconds of last update. This is packet time, + /// potentially coming from pcaps. + ts: u64, +} + +impl State<SMBTransaction> for SMBState { + fn get_transaction_count(&self) -> usize { + self.transactions.len() + } + + fn get_transaction_by_index(&self, index: usize) -> Option<&SMBTransaction> { + self.transactions.get(index) + } +} + +impl SMBState { + /// Allocation function for a new TLS parser instance + pub fn new() -> Self { + Self { + state_data:AppLayerStateData::new(), + ssn2vec_map:HashMap::new(), + guid2name_map:HashMap::new(), + ssn2vecoffset_map:HashMap::new(), + ssn2tree_map:HashMap::new(), + ssnguid2vec_map:HashMap::new(), + skip_ts:0, + skip_tc:0, + file_ts_left:0, + file_tc_left:0, + file_ts_guid:Vec::new(), + file_tc_guid:Vec::new(), + ts_ssn_gap: false, + tc_ssn_gap: false, + ts_gap: false, + tc_gap: false, + ts_trunc: false, + tc_trunc: false, + check_post_gap_file_txs: false, + post_gap_files_checked: false, + transactions: VecDeque::new(), + tx_index_completed: 0, + tx_id:0, + dialect:0, + dialect_vec: None, + dcerpc_ifaces: None, + ts: 0, + ..Default::default() + } + } + + pub fn free(&mut self) { + //self._debug_state_stats(); + self._debug_tx_stats(); + } + + pub fn new_tx(&mut self) -> SMBTransaction { + let mut tx = SMBTransaction::new(); + self.tx_id += 1; + tx.id = self.tx_id; + SCLogDebug!("TX {} created", tx.id); + if self.transactions.len() > unsafe { SMB_MAX_TX } { + let mut index = self.tx_index_completed; + for tx_old in &mut self.transactions.range_mut(self.tx_index_completed..) { + index += 1; + if !tx_old.request_done || !tx_old.response_done { + tx_old.request_done = true; + tx_old.response_done = true; + tx_old.set_event(SMBEvent::TooManyTransactions); + break; + } + } + self.tx_index_completed = index; + + } + return tx; + } + + pub fn free_tx(&mut self, tx_id: u64) { + SCLogDebug!("Freeing TX with ID {} TX.ID {}", tx_id, tx_id+1); + let len = self.transactions.len(); + let mut found = false; + let mut index = 0; + for i in 0..len { + let tx = &self.transactions[i]; + if tx.id == tx_id + 1 { + found = true; + index = i; + SCLogDebug!("tx {} progress {}/{}", tx.id, tx.request_done, tx.response_done); + break; + } + } + if found { + SCLogDebug!("freeing TX with ID {} TX.ID {} at index {} left: {} max id: {}", + tx_id, tx_id+1, index, self.transactions.len(), self.tx_id); + self.tx_index_completed = 0; + self.transactions.remove(index); + } + } + + pub fn get_tx_by_id(&mut self, tx_id: u64) -> Option<&SMBTransaction> { +/* + if self.transactions.len() > 100 { + SCLogNotice!("get_tx_by_id: tx_id={} in list={}", tx_id, self.transactions.len()); + self._dump_txs(); + panic!("txs exploded"); + } +*/ + for tx in &mut self.transactions { + if tx.id == tx_id + 1 { + let ver = tx.vercmd.get_version(); + let mut _smbcmd; + if ver == 2 { + let (_, cmd) = tx.vercmd.get_smb2_cmd(); + _smbcmd = cmd; + } else { + let (_, cmd) = tx.vercmd.get_smb1_cmd(); + _smbcmd = cmd as u16; + } + SCLogDebug!("Found SMB TX: id {} ver:{} cmd:{} progress {}/{} type_data {:?}", + tx.id, ver, _smbcmd, tx.request_done, tx.response_done, tx.type_data); + /* hack: apply flow file flags to file tx here to make sure its propagated */ + if let Some(SMBTransactionTypeData::FILE(ref mut d)) = tx.type_data { + tx.tx_data.update_file_flags(self.state_data.file_flags); + d.update_file_flags(tx.tx_data.file_flags); + } + return Some(tx); + } + } + SCLogDebug!("Failed to find SMB TX with ID {}", tx_id); + return None; + } + + fn update_ts(&mut self, ts: u64) { + if ts != self.ts { + self.ts = ts; + self.post_gap_files_checked = false; + } + } + + /* generic TX has no type_data and is only used to + * track a single cmd request/reply pair. */ + + pub fn new_generic_tx(&mut self, smb_ver: u8, smb_cmd: u16, key: SMBCommonHdr) + -> &mut SMBTransaction + { + let mut tx = self.new_tx(); + if smb_ver == 1 && smb_cmd <= 255 { + tx.vercmd.set_smb1_cmd(smb_cmd as u8); + } else if smb_ver == 2 { + tx.vercmd.set_smb2_cmd(smb_cmd); + } + + tx.type_data = None; + tx.request_done = true; + tx.response_done = self.tc_trunc; // no response expected if tc is truncated + tx.hdr = key; + + SCLogDebug!("SMB: TX GENERIC created: ID {} tx list {} {:?}", + tx.id, self.transactions.len(), &tx); + self.transactions.push_back(tx); + let tx_ref = self.transactions.back_mut(); + return tx_ref.unwrap(); + } + + pub fn get_last_tx(&mut self, smb_ver: u8, smb_cmd: u16) + -> Option<&mut SMBTransaction> + { + let tx_ref = self.transactions.back_mut(); + if let Some(tx) = tx_ref { + let found = if tx.vercmd.get_version() == smb_ver { + if smb_ver == 1 { + let (_, cmd) = tx.vercmd.get_smb1_cmd(); + cmd as u16 == smb_cmd + } else if smb_ver == 2 { + let (_, cmd) = tx.vercmd.get_smb2_cmd(); + cmd == smb_cmd + } else { + false + } + } else { + false + }; + if found { + return Some(tx); + } + } + return None; + } + + pub fn get_generic_tx(&mut self, smb_ver: u8, smb_cmd: u16, key: &SMBCommonHdr) + -> Option<&mut SMBTransaction> + { + for tx in &mut self.transactions { + let found = if tx.vercmd.get_version() == smb_ver { + if smb_ver == 1 { + let (_, cmd) = tx.vercmd.get_smb1_cmd(); + cmd as u16 == smb_cmd && tx.hdr.compare(key) + } else if smb_ver == 2 { + let (_, cmd) = tx.vercmd.get_smb2_cmd(); + cmd == smb_cmd && tx.hdr.compare(key) + } else { + false + } + } else { + false + }; + if found { + return Some(tx); + } + } + return None; + } + + pub fn new_negotiate_tx(&mut self, smb_ver: u8) + -> &mut SMBTransaction + { + let mut tx = self.new_tx(); + if smb_ver == 1 { + tx.vercmd.set_smb1_cmd(SMB1_COMMAND_NEGOTIATE_PROTOCOL); + } else if smb_ver == 2 { + tx.vercmd.set_smb2_cmd(SMB2_COMMAND_NEGOTIATE_PROTOCOL); + } + + tx.type_data = Some(SMBTransactionTypeData::NEGOTIATE( + SMBTransactionNegotiate::new(smb_ver))); + tx.request_done = true; + tx.response_done = self.tc_trunc; // no response expected if tc is truncated + + SCLogDebug!("SMB: TX NEGOTIATE created: ID {} SMB ver {}", tx.id, smb_ver); + self.transactions.push_back(tx); + let tx_ref = self.transactions.back_mut(); + return tx_ref.unwrap(); + } + + pub fn get_negotiate_tx(&mut self, smb_ver: u8) + -> Option<&mut SMBTransaction> + { + for tx in &mut self.transactions { + let found = match tx.type_data { + Some(SMBTransactionTypeData::NEGOTIATE(ref x)) => { + x.smb_ver == smb_ver + }, + _ => { false }, + }; + if found { + return Some(tx); + } + } + return None; + } + + pub fn new_treeconnect_tx(&mut self, hdr: SMBCommonHdr, name: Vec<u8>) + -> &mut SMBTransaction + { + let mut tx = self.new_tx(); + + tx.hdr = hdr; + tx.type_data = Some(SMBTransactionTypeData::TREECONNECT( + SMBTransactionTreeConnect::new(name.to_vec()))); + tx.request_done = true; + tx.response_done = self.tc_trunc; // no response expected if tc is truncated + + SCLogDebug!("SMB: TX TREECONNECT created: ID {} NAME {}", + tx.id, String::from_utf8_lossy(&name)); + self.transactions.push_back(tx); + let tx_ref = self.transactions.back_mut(); + return tx_ref.unwrap(); + } + + pub fn get_treeconnect_tx(&mut self, hdr: SMBCommonHdr) + -> Option<&mut SMBTransaction> + { + for tx in &mut self.transactions { + let hit = tx.hdr.compare(&hdr) && match tx.type_data { + Some(SMBTransactionTypeData::TREECONNECT(_)) => { true }, + _ => { false }, + }; + if hit { + return Some(tx); + } + } + return None; + } + + pub fn new_create_tx(&mut self, file_name: &[u8], + disposition: u32, del: bool, dir: bool, + hdr: SMBCommonHdr) + -> &mut SMBTransaction + { + let mut tx = self.new_tx(); + tx.hdr = hdr; + tx.type_data = Some(SMBTransactionTypeData::CREATE( + SMBTransactionCreate::new( + file_name.to_vec(), disposition, + del, dir))); + tx.request_done = true; + tx.response_done = self.tc_trunc; // no response expected if tc is truncated + + self.transactions.push_back(tx); + let tx_ref = self.transactions.back_mut(); + return tx_ref.unwrap(); + } + + pub fn get_service_for_guid(&self, guid: &[u8]) -> (&'static str, bool) + { + let (name, is_dcerpc) = match self.guid2name_map.get(&guid.to_vec()) { + Some(n) => { + let mut s = n.as_slice(); + // skip leading \ if we have it + if s.len() > 1 && s[0] == 0x5c_u8 { + s = &s[1..]; + } + match str::from_utf8(s) { + Ok("PSEXESVC") => ("PSEXESVC", false), + Ok("svcctl") => ("svcctl", true), + Ok("srvsvc") => ("srvsvc", true), + Ok("atsvc") => ("atsvc", true), + Ok("lsarpc") => ("lsarpc", true), + Ok("samr") => ("samr", true), + Ok("spoolss") => ("spoolss", true), + Ok("winreg") => ("winreg", true), + Ok("suricata::dcerpc") => ("unknown", true), + Err(_) => ("MALFORMED", false), + Ok(&_) => { + SCLogDebug!("don't know {}", String::from_utf8_lossy(n)); + ("UNKNOWN", false) + }, + } + }, + _ => { ("UNKNOWN", false) }, + }; + SCLogDebug!("service {} is_dcerpc {}", name, is_dcerpc); + (name, is_dcerpc) + } + + fn post_gap_housekeeping_for_files(&mut self) + { + let mut post_gap_txs = false; + for tx in &mut self.transactions { + if let Some(SMBTransactionTypeData::FILE(ref mut f)) = tx.type_data { + if f.post_gap_ts > 0 { + if self.ts > f.post_gap_ts { + tx.request_done = true; + tx.response_done = true; + filetracker_trunc(&mut f.file_tracker); + } else { + post_gap_txs = true; + } + } + } + } + self.check_post_gap_file_txs = post_gap_txs; + } + + /* after a gap we will consider all transactions complete for our + * direction. File transfer transactions are an exception. Those + * can handle gaps. For the file transactions we set the current + * (flow) time and prune them in 60 seconds if no update for them + * was received. */ + fn post_gap_housekeeping(&mut self, dir: Direction) + { + if self.ts_ssn_gap && dir == Direction::ToServer { + for tx in &mut self.transactions { + if tx.id >= self.tx_id { + SCLogDebug!("post_gap_housekeeping: done"); + break; + } + if let Some(SMBTransactionTypeData::FILE(ref mut f)) = tx.type_data { + // leaving FILE txs open as they can deal with gaps. We + // remove them after 60 seconds of no activity though. + if f.post_gap_ts == 0 { + f.post_gap_ts = self.ts + 60; + self.check_post_gap_file_txs = true; + } + } else { + SCLogDebug!("post_gap_housekeeping: tx {} marked as done TS", tx.id); + tx.request_done = true; + } + } + } else if self.tc_ssn_gap && dir == Direction::ToClient { + for tx in &mut self.transactions { + if tx.id >= self.tx_id { + SCLogDebug!("post_gap_housekeeping: done"); + break; + } + if let Some(SMBTransactionTypeData::FILE(ref mut f)) = tx.type_data { + // leaving FILE txs open as they can deal with gaps. We + // remove them after 60 seconds of no activity though. + if f.post_gap_ts == 0 { + f.post_gap_ts = self.ts + 60; + self.check_post_gap_file_txs = true; + } + } else { + SCLogDebug!("post_gap_housekeeping: tx {} marked as done TC", tx.id); + tx.request_done = true; + tx.response_done = true; + } + } + + } + } + + pub fn set_file_left(&mut self, direction: Direction, rec_size: u32, data_size: u32, fuid: Vec<u8>) + { + let left = rec_size.saturating_sub(data_size); + if direction == Direction::ToServer { + self.file_ts_left = left; + self.file_ts_guid = fuid; + } else { + self.file_tc_left = left; + self.file_tc_guid = fuid; + } + } + + pub fn set_skip(&mut self, direction: Direction, nbss_remaining: u32) + { + if direction == Direction::ToServer { + self.skip_ts = nbss_remaining; + } else { + self.skip_tc = nbss_remaining; + } + } + + // return how much data we consumed + fn handle_skip(&mut self, direction: Direction, input_size: u32) -> u32 { + let mut skip_left = if direction == Direction::ToServer { + self.skip_ts + } else { + self.skip_tc + }; + if skip_left == 0 { + return 0 + } + SCLogDebug!("skip_left {} input_size {}", skip_left, input_size); + + let consumed = if skip_left >= input_size { + input_size + } else { + skip_left + }; + + if skip_left <= input_size { + skip_left = 0; + } else { + skip_left -= input_size; + } + + if direction == Direction::ToServer { + self.skip_ts = skip_left; + } else { + self.skip_tc = skip_left; + } + return consumed; + } + + fn add_nbss_ts_frames(&mut self, flow: *const Flow, stream_slice: &StreamSlice, input: &[u8], nbss_len: i64) -> (Option<Frame>, Option<Frame>, Option<Frame>) { + let nbss_pdu = Frame::new(flow, stream_slice, input, nbss_len + 4, SMBFrameType::NBSSPdu as u8); + SCLogDebug!("NBSS PDU frame {:?}", nbss_pdu); + let nbss_hdr_frame = Frame::new(flow, stream_slice, input, 4_i64, SMBFrameType::NBSSHdr as u8); + SCLogDebug!("NBSS HDR frame {:?}", nbss_hdr_frame); + let nbss_data_frame = Frame::new(flow, stream_slice, &input[4..], nbss_len, SMBFrameType::NBSSData as u8); + SCLogDebug!("NBSS DATA frame {:?}", nbss_data_frame); + (nbss_pdu, nbss_hdr_frame, nbss_data_frame) + } + + fn add_smb1_ts_pdu_frame(&mut self, flow: *const Flow, stream_slice: &StreamSlice, input: &[u8], nbss_len: i64) -> Option<Frame> { + let smb_pdu = Frame::new(flow, stream_slice, input, nbss_len, SMBFrameType::SMB1Pdu as u8); + SCLogDebug!("SMB PDU frame {:?}", smb_pdu); + smb_pdu + } + fn add_smb1_ts_hdr_data_frames(&mut self, flow: *const Flow, stream_slice: &StreamSlice, input: &[u8], nbss_len: i64) { + let _smb1_hdr = Frame::new(flow, stream_slice, input, 32_i64, SMBFrameType::SMB1Hdr as u8); + SCLogDebug!("SMBv1 HDR frame {:?}", _smb1_hdr); + if input.len() > 32 { + let _smb1_data = Frame::new(flow, stream_slice, &input[32..], nbss_len - 32, SMBFrameType::SMB1Data as u8); + SCLogDebug!("SMBv1 DATA frame {:?}", _smb1_data); + } + } + + fn add_smb2_ts_pdu_frame(&mut self, flow: *const Flow, stream_slice: &StreamSlice, input: &[u8], nbss_len: i64) -> Option<Frame> { + let smb_pdu = Frame::new(flow, stream_slice, input, nbss_len, SMBFrameType::SMB2Pdu as u8); + SCLogDebug!("SMBv2 PDU frame {:?}", smb_pdu); + smb_pdu + } + fn add_smb2_ts_hdr_data_frames(&mut self, flow: *const Flow, stream_slice: &StreamSlice, input: &[u8], nbss_len: i64, hdr_len: i64) { + let _smb2_hdr = Frame::new(flow, stream_slice, input, hdr_len, SMBFrameType::SMB2Hdr as u8); + SCLogDebug!("SMBv2 HDR frame {:?}", _smb2_hdr); + if input.len() > hdr_len as usize { + let _smb2_data = Frame::new(flow, stream_slice, &input[hdr_len as usize..], nbss_len - hdr_len, SMBFrameType::SMB2Data as u8); + SCLogDebug!("SMBv2 DATA frame {:?}", _smb2_data); + } + } + + fn add_smb3_ts_pdu_frame(&mut self, flow: *const Flow, stream_slice: &StreamSlice, input: &[u8], nbss_len: i64) -> Option<Frame> { + let smb_pdu = Frame::new(flow, stream_slice, input, nbss_len, SMBFrameType::SMB3Pdu as u8); + SCLogDebug!("SMBv3 PDU frame {:?}", smb_pdu); + smb_pdu + } + fn add_smb3_ts_hdr_data_frames(&mut self, flow: *const Flow, stream_slice: &StreamSlice, input: &[u8], nbss_len: i64) { + let _smb3_hdr = Frame::new(flow, stream_slice, input, 52_i64, SMBFrameType::SMB3Hdr as u8); + SCLogDebug!("SMBv3 HDR frame {:?}", _smb3_hdr); + if input.len() > 52 { + let _smb3_data = Frame::new(flow, stream_slice, &input[52..], nbss_len - 52, SMBFrameType::SMB3Data as u8); + SCLogDebug!("SMBv3 DATA frame {:?}", _smb3_data); + } + } + + /// return bytes consumed + pub fn parse_tcp_data_ts_partial(&mut self, flow: *const Flow, stream_slice: &StreamSlice, input: &[u8]) -> usize + { + SCLogDebug!("incomplete of size {}", input.len()); + if input.len() < 512 { + // check for malformed data. Wireshark reports as + // 'NBSS continuation data'. If it's invalid we're + // lost so we give up. + if input.len() > 8 { + if let Ok((_, ref hdr)) = parse_nbss_record_partial(input) { + if !hdr.is_smb() { + SCLogDebug!("partial NBSS, not SMB and no known msg type {}", hdr.message_type); + self.trunc_ts(); + return 0; + } + } + } + return 0; + } + + if let Ok((output, ref nbss_part_hdr)) = parse_nbss_record_partial(input) { + SCLogDebug!("parse_nbss_record_partial ok, output len {}", output.len()); + if nbss_part_hdr.message_type == NBSS_MSGTYPE_SESSION_MESSAGE { + if let Ok((_, ref smb)) = parse_smb_version(nbss_part_hdr.data) { + SCLogDebug!("SMB {:?}", smb); + if smb.version == 0xff_u8 { // SMB1 + SCLogDebug!("SMBv1 record"); + if let Ok((_, ref r)) = parse_smb_record(nbss_part_hdr.data) { + if r.command == SMB1_COMMAND_WRITE_ANDX { + // see if it's a write to a pipe. We only handle those + // if complete. + let tree_key = SMBCommonHdr::new(SMBHDR_TYPE_SHARE, + r.ssn_id as u64, r.tree_id as u32, 0); + let is_pipe = match self.ssn2tree_map.get(&tree_key) { + Some(n) => n.is_pipe, + None => false, + }; + if is_pipe { + return 0; + } + // how many more bytes are expected within this NBSS record + // So that we can check that further parsed offsets and lengths + // stay within the NBSS record. + let nbss_remaining = nbss_part_hdr.length - nbss_part_hdr.data.len() as u32; + smb1_write_request_record(self, r, SMB1_HEADER_SIZE, SMB1_COMMAND_WRITE_ANDX, nbss_remaining); + + self.add_nbss_ts_frames(flow, stream_slice, input, nbss_part_hdr.length as i64); + self.add_smb1_ts_pdu_frame(flow, stream_slice, nbss_part_hdr.data, nbss_part_hdr.length as i64); + self.add_smb1_ts_hdr_data_frames(flow, stream_slice, nbss_part_hdr.data, nbss_part_hdr.length as i64); + + let consumed = input.len() - output.len(); + return consumed; + } + } + } else if smb.version == 0xfe_u8 { // SMB2 + SCLogDebug!("SMBv2 record"); + if let Ok((_, ref smb_record)) = parse_smb2_request_record(nbss_part_hdr.data) { + SCLogDebug!("SMB2: partial record {}", + &smb2_command_string(smb_record.command)); + if smb_record.command == SMB2_COMMAND_WRITE { + // how many more bytes are expected within this NBSS record + // So that we can check that further parsed offsets and lengths + // stay within the NBSS record. + let nbss_remaining = nbss_part_hdr.length - nbss_part_hdr.data.len() as u32; + smb2_write_request_record(self, smb_record, nbss_remaining); + + self.add_nbss_ts_frames(flow, stream_slice, input, nbss_part_hdr.length as i64); + self.add_smb2_ts_pdu_frame(flow, stream_slice, nbss_part_hdr.data, nbss_part_hdr.length as i64); + self.add_smb2_ts_hdr_data_frames(flow, stream_slice, nbss_part_hdr.data, nbss_part_hdr.length as i64, smb_record.header_len as i64); + + let consumed = input.len() - output.len(); + SCLogDebug!("consumed {}", consumed); + return consumed; + } + } + } + // no SMB3 here yet, will buffer full records + } + } + } + + return 0; + } + + /// Parsing function, handling TCP chunks fragmentation + pub fn parse_tcp_data_ts(&mut self, flow: *const Flow, stream_slice: &StreamSlice) -> AppLayerResult + { + let mut cur_i = stream_slice.as_slice(); + let consumed = self.handle_skip(Direction::ToServer, cur_i.len() as u32); + if consumed > 0 { + if consumed > cur_i.len() as u32 { + self.set_event(SMBEvent::InternalError); + return AppLayerResult::err(); + } + cur_i = &cur_i[consumed as usize..]; + } + // take care of in progress file chunk transfers + // and skip buffer beyond it + let consumed = self.filetracker_update(Direction::ToServer, cur_i, 0); + if consumed > 0 { + if consumed > cur_i.len() as u32 { + self.set_event(SMBEvent::InternalError); + return AppLayerResult::err(); + } + cur_i = &cur_i[consumed as usize..]; + } + if cur_i.is_empty() { + return AppLayerResult::ok(); + } + // gap + if self.ts_gap { + SCLogDebug!("TS trying to catch up after GAP (input {})", cur_i.len()); + while !cur_i.is_empty() { // min record size + match search_smb_record(cur_i) { + Ok((_, pg)) => { + SCLogDebug!("smb record found"); + let smb2_offset = cur_i.len() - pg.len(); + if smb2_offset < 4 { + cur_i = &cur_i[smb2_offset+4..]; + continue; // see if we have another record in our data + } + let nbss_offset = smb2_offset - 4; + cur_i = &cur_i[nbss_offset..]; + + self.ts_gap = false; + break; + }, + _ => { + let mut consumed = stream_slice.len(); + if consumed < 4 { + consumed = 0; + } else { + consumed -= 3; + } + SCLogDebug!("smb record NOT found"); + return AppLayerResult::incomplete(consumed, 8); + }, + } + } + } + while !cur_i.is_empty() { // min record size + match parse_nbss_record(cur_i) { + Ok((rem, ref nbss_hdr)) => { + SCLogDebug!("nbss frame offset {} len {}", stream_slice.offset_from(cur_i), cur_i.len() - rem.len()); + let (_, _, nbss_data_frame) = self.add_nbss_ts_frames(flow, stream_slice, cur_i, nbss_hdr.length as i64); + + if nbss_hdr.message_type == NBSS_MSGTYPE_SESSION_MESSAGE { + // we have the full records size worth of data, + // let's parse it + match parse_smb_version(nbss_hdr.data) { + Ok((_, ref smb)) => { + + SCLogDebug!("SMB {:?}", smb); + if smb.version == 0xff_u8 { // SMB1 + + SCLogDebug!("SMBv1 record"); + match parse_smb_record(nbss_hdr.data) { + Ok((_, ref smb_record)) => { + let pdu_frame = self.add_smb1_ts_pdu_frame(flow, stream_slice, nbss_hdr.data, nbss_hdr.length as i64); + self.add_smb1_ts_hdr_data_frames(flow, stream_slice, nbss_hdr.data, nbss_hdr.length as i64); + if smb_record.is_request() { + smb1_request_record(self, smb_record); + } else { + // If we received a response when expecting a request, set an event + // on the PDU frame instead of handling the response. + SCLogDebug!("SMB1 reply seen from client to server"); + if let Some(frame) = pdu_frame { + frame.add_event(flow, SMBEvent::ResponseToServer as u8); + } + } + }, + _ => { + if let Some(frame) = nbss_data_frame { + frame.add_event(flow, SMBEvent::MalformedData as u8); + } + self.set_event(SMBEvent::MalformedData); + return AppLayerResult::err(); + }, + } + } else if smb.version == 0xfe_u8 { // SMB2 + let mut nbss_data = nbss_hdr.data; + while !nbss_data.is_empty() { + SCLogDebug!("SMBv2 record"); + match parse_smb2_request_record(nbss_data) { + Ok((nbss_data_rem, ref smb_record)) => { + let record_len = (nbss_data.len() - nbss_data_rem.len()) as i64; + let pdu_frame = self.add_smb2_ts_pdu_frame(flow, stream_slice, nbss_data, record_len); + self.add_smb2_ts_hdr_data_frames(flow, stream_slice, nbss_data, record_len, smb_record.header_len as i64); + SCLogDebug!("nbss_data_rem {}", nbss_data_rem.len()); + if smb_record.is_request() { + smb2_request_record(self, smb_record); + } else { + // If we received a response when expecting a request, set an event + // on the PDU frame instead of handling the response. + SCLogDebug!("SMB2 reply seen from client to server"); + if let Some(frame) = pdu_frame { + frame.add_event(flow, SMBEvent::ResponseToServer as u8); + } + } + nbss_data = nbss_data_rem; + }, + _ => { + if let Some(frame) = nbss_data_frame { + frame.add_event(flow, SMBEvent::MalformedData as u8); + } + self.set_event(SMBEvent::MalformedData); + return AppLayerResult::err(); + }, + } + } + } else if smb.version == 0xfd_u8 { // SMB3 transform + + let mut nbss_data = nbss_hdr.data; + while !nbss_data.is_empty() { + SCLogDebug!("SMBv3 transform record"); + match parse_smb3_transform_record(nbss_data) { + Ok((nbss_data_rem, ref _smb3_record)) => { + let record_len = (nbss_data.len() - nbss_data_rem.len()) as i64; + self.add_smb3_ts_pdu_frame(flow, stream_slice, nbss_data, record_len); + self.add_smb3_ts_hdr_data_frames(flow, stream_slice, nbss_data, record_len); + nbss_data = nbss_data_rem; + }, + _ => { + if let Some(frame) = nbss_data_frame { + frame.add_event(flow, SMBEvent::MalformedData as u8); + } + self.set_event(SMBEvent::MalformedData); + return AppLayerResult::err(); + }, + } + } + } + }, + _ => { + self.set_event(SMBEvent::MalformedData); + return AppLayerResult::err(); + }, + } + } else { + SCLogDebug!("NBSS message {:X}", nbss_hdr.message_type); + } + cur_i = rem; + }, + Err(Err::Incomplete(needed)) => { + if let Needed::Size(n) = needed { + let n = usize::from(n) + cur_i.len(); + // 512 is the minimum for parse_tcp_data_ts_partial + if n >= 512 && cur_i.len() < 512 { + let total_consumed = stream_slice.offset_from(cur_i); + return AppLayerResult::incomplete(total_consumed, 512); + } + let consumed = self.parse_tcp_data_ts_partial(flow, stream_slice, cur_i); + if consumed == 0 { + // if we consumed none we will buffer the entire record + let total_consumed = stream_slice.offset_from(cur_i); + SCLogDebug!("setting consumed {} need {} needed {:?} total input {}", + total_consumed, n, needed, stream_slice.len()); + let need = n; + return AppLayerResult::incomplete(total_consumed, need as u32); + } + // tracking a write record, which we don't need to + // queue up at the stream level, but can feed to us + // in small chunks + return AppLayerResult::ok(); + } else { + self.set_event(SMBEvent::InternalError); + return AppLayerResult::err(); + } + }, + Err(_) => { + self.set_event(SMBEvent::MalformedData); + return AppLayerResult::err(); + }, + } + }; + + self.post_gap_housekeeping(Direction::ToServer); + if self.check_post_gap_file_txs && !self.post_gap_files_checked { + self.post_gap_housekeeping_for_files(); + self.post_gap_files_checked = true; + } + AppLayerResult::ok() + } + + fn add_nbss_tc_frames(&mut self, flow: *const Flow, stream_slice: &StreamSlice, input: &[u8], nbss_len: i64) -> (Option<Frame>, Option<Frame>, Option<Frame>) { + let nbss_pdu = Frame::new(flow, stream_slice, input, nbss_len + 4, SMBFrameType::NBSSPdu as u8); + SCLogDebug!("NBSS PDU frame {:?}", nbss_pdu); + let nbss_hdr_frame = Frame::new(flow, stream_slice, input, 4_i64, SMBFrameType::NBSSHdr as u8); + SCLogDebug!("NBSS HDR frame {:?}", nbss_hdr_frame); + let nbss_data_frame = Frame::new(flow, stream_slice, &input[4..], nbss_len, SMBFrameType::NBSSData as u8); + SCLogDebug!("NBSS DATA frame {:?}", nbss_data_frame); + (nbss_pdu, nbss_hdr_frame, nbss_data_frame) + } + + fn add_smb1_tc_pdu_frame(&mut self, flow: *const Flow, stream_slice: &StreamSlice, input: &[u8], nbss_len: i64) -> Option<Frame> { + let smb_pdu = Frame::new(flow, stream_slice, input, nbss_len, SMBFrameType::SMB1Pdu as u8); + SCLogDebug!("SMB PDU frame {:?}", smb_pdu); + smb_pdu + } + fn add_smb1_tc_hdr_data_frames(&mut self, flow: *const Flow, stream_slice: &StreamSlice, input: &[u8], nbss_len: i64) { + let _smb1_hdr = Frame::new(flow, stream_slice, input, SMB1_HEADER_SIZE as i64, SMBFrameType::SMB1Hdr as u8); + SCLogDebug!("SMBv1 HDR frame {:?}", _smb1_hdr); + if input.len() > SMB1_HEADER_SIZE { + let _smb1_data = Frame::new(flow, stream_slice, &input[SMB1_HEADER_SIZE..], nbss_len - SMB1_HEADER_SIZE as i64, + SMBFrameType::SMB1Data as u8); + SCLogDebug!("SMBv1 DATA frame {:?}", _smb1_data); + } + } + + fn add_smb2_tc_pdu_frame(&mut self, flow: *const Flow, stream_slice: &StreamSlice, input: &[u8], nbss_len: i64) -> Option<Frame> { + let smb_pdu = Frame::new(flow, stream_slice, input, nbss_len, SMBFrameType::SMB2Pdu as u8); + SCLogDebug!("SMBv2 PDU frame {:?}", smb_pdu); + smb_pdu + } + fn add_smb2_tc_hdr_data_frames(&mut self, flow: *const Flow, stream_slice: &StreamSlice, input: &[u8], nbss_len: i64, hdr_len: i64) { + let _smb2_hdr = Frame::new(flow, stream_slice, input, hdr_len, SMBFrameType::SMB2Hdr as u8); + SCLogDebug!("SMBv2 HDR frame {:?}", _smb2_hdr); + if input.len() > hdr_len as usize { + let _smb2_data = Frame::new(flow, stream_slice, &input[hdr_len as usize ..], nbss_len - hdr_len, SMBFrameType::SMB2Data as u8); + SCLogDebug!("SMBv2 DATA frame {:?}", _smb2_data); + } + } + + fn add_smb3_tc_pdu_frame(&mut self, flow: *const Flow, stream_slice: &StreamSlice, input: &[u8], nbss_len: i64) { + let _smb_pdu = Frame::new(flow, stream_slice, input, nbss_len, SMBFrameType::SMB3Pdu as u8); + SCLogDebug!("SMBv3 PDU frame {:?}", _smb_pdu); + } + fn add_smb3_tc_hdr_data_frames(&mut self, flow: *const Flow, stream_slice: &StreamSlice, input: &[u8], nbss_len: i64) { + let _smb3_hdr = Frame::new(flow, stream_slice, input, 52_i64, SMBFrameType::SMB3Hdr as u8); + SCLogDebug!("SMBv3 HDR frame {:?}", _smb3_hdr); + if input.len() > 52 { + let _smb3_data = Frame::new(flow, stream_slice, &input[52..], nbss_len - 52, SMBFrameType::SMB3Data as u8); + SCLogDebug!("SMBv3 DATA frame {:?}", _smb3_data); + } + } + + /// return bytes consumed + pub fn parse_tcp_data_tc_partial(&mut self, flow: *const Flow, stream_slice: &StreamSlice, input: &[u8]) -> usize + { + SCLogDebug!("incomplete of size {}", input.len()); + if input.len() < 512 { + // check for malformed data. Wireshark reports as + // 'NBSS continuation data'. If it's invalid we're + // lost so we give up. + if input.len() > 8 { + if let Ok((_, ref hdr)) = parse_nbss_record_partial(input) { + if !hdr.is_smb() { + SCLogDebug!("partial NBSS, not SMB and no known msg type {}", hdr.message_type); + self.trunc_tc(); + return 0; + } + } + } + return 0; + } + + if let Ok((output, ref nbss_part_hdr)) = parse_nbss_record_partial(input) { + SCLogDebug!("parse_nbss_record_partial ok, output len {}", output.len()); + if nbss_part_hdr.message_type == NBSS_MSGTYPE_SESSION_MESSAGE { + if let Ok((_, ref smb)) = parse_smb_version(nbss_part_hdr.data) { + SCLogDebug!("SMB {:?}", smb); + if smb.version == 255u8 { // SMB1 + SCLogDebug!("SMBv1 record"); + if let Ok((_, ref r)) = parse_smb_record(nbss_part_hdr.data) { + SCLogDebug!("SMB1: partial record {}", + r.command); + if r.command == SMB1_COMMAND_READ_ANDX { + let tree_key = SMBCommonHdr::new(SMBHDR_TYPE_SHARE, + r.ssn_id as u64, r.tree_id as u32, 0); + let is_pipe = match self.ssn2tree_map.get(&tree_key) { + Some(n) => n.is_pipe, + None => false, + }; + if is_pipe { + return 0; + } + + // create NBSS frames here so we don't get double frames + // when we don't consume the data now. + self.add_nbss_tc_frames(flow, stream_slice, input, nbss_part_hdr.length as i64); + self.add_smb1_tc_pdu_frame(flow, stream_slice, nbss_part_hdr.data, nbss_part_hdr.length as i64); + self.add_smb1_tc_hdr_data_frames(flow, stream_slice, nbss_part_hdr.data, nbss_part_hdr.length as i64); + + // how many more bytes are expected within this NBSS record + // So that we can check that further parsed offsets and lengths + // stay within the NBSS record. + let nbss_remaining = nbss_part_hdr.length - nbss_part_hdr.data.len() as u32; + smb1_read_response_record(self, r, SMB1_HEADER_SIZE, nbss_remaining); + let consumed = input.len() - output.len(); + return consumed; + } + } + } else if smb.version == 254u8 { // SMB2 + SCLogDebug!("SMBv2 record"); + if let Ok((_, ref smb_record)) = parse_smb2_response_record(nbss_part_hdr.data) { + SCLogDebug!("SMB2: partial record {}", + &smb2_command_string(smb_record.command)); + if smb_record.command == SMB2_COMMAND_READ { + // create NBSS frames here so we don't get double frames + // when we don't consume the data now. + self.add_nbss_tc_frames(flow, stream_slice, input, nbss_part_hdr.length as i64); + self.add_smb2_tc_pdu_frame(flow, stream_slice, nbss_part_hdr.data, nbss_part_hdr.length as i64); + self.add_smb2_tc_hdr_data_frames(flow, stream_slice, nbss_part_hdr.data, nbss_part_hdr.length as i64, smb_record.header_len as i64); + + // how many more bytes are expected within this NBSS record + // So that we can check that further parsed offsets and lengths + // stay within the NBSS record. + let nbss_remaining = nbss_part_hdr.length - nbss_part_hdr.data.len() as u32; + smb2_read_response_record(self, smb_record, nbss_remaining); + let consumed = input.len() - output.len(); + return consumed; + } + } + } + // no SMB3 here yet, will buffer full records + } + } + } + return 0; + } + + /// Parsing function, handling TCP chunks fragmentation + pub fn parse_tcp_data_tc(&mut self, flow: *const Flow, stream_slice: &StreamSlice) -> AppLayerResult + { + let mut cur_i = stream_slice.as_slice(); + let consumed = self.handle_skip(Direction::ToClient, cur_i.len() as u32); + if consumed > 0 { + if consumed > cur_i.len() as u32 { + self.set_event(SMBEvent::InternalError); + return AppLayerResult::err(); + } + cur_i = &cur_i[consumed as usize..]; + } + // take care of in progress file chunk transfers + // and skip buffer beyond it + let consumed = self.filetracker_update(Direction::ToClient, cur_i, 0); + if consumed > 0 { + if consumed > cur_i.len() as u32 { + self.set_event(SMBEvent::InternalError); + return AppLayerResult::err(); + } + cur_i = &cur_i[consumed as usize..]; + } + if cur_i.is_empty() { + return AppLayerResult::ok(); + } + // gap + if self.tc_gap { + SCLogDebug!("TC trying to catch up after GAP (input {})", cur_i.len()); + while !cur_i.is_empty() { // min record size + match search_smb_record(cur_i) { + Ok((_, pg)) => { + SCLogDebug!("smb record found"); + let smb2_offset = cur_i.len() - pg.len(); + if smb2_offset < 4 { + cur_i = &cur_i[smb2_offset+4..]; + continue; // see if we have another record in our data + } + let nbss_offset = smb2_offset - 4; + cur_i = &cur_i[nbss_offset..]; + + self.tc_gap = false; + break; + }, + _ => { + let mut consumed = stream_slice.len(); + if consumed < 4 { + consumed = 0; + } else { + consumed -= 3; + } + SCLogDebug!("smb record NOT found"); + return AppLayerResult::incomplete(consumed, 8); + }, + } + } + } + while !cur_i.is_empty() { // min record size + match parse_nbss_record(cur_i) { + Ok((rem, ref nbss_hdr)) => { + SCLogDebug!("nbss record offset {} len {}", stream_slice.offset_from(cur_i), cur_i.len() - rem.len()); + self.add_nbss_tc_frames(flow, stream_slice, cur_i, nbss_hdr.length as i64); + SCLogDebug!("nbss frames added"); + + if nbss_hdr.message_type == NBSS_MSGTYPE_SESSION_MESSAGE { + // we have the full records size worth of data, + // let's parse it + match parse_smb_version(nbss_hdr.data) { + Ok((_, ref smb)) => { + SCLogDebug!("SMB {:?}", smb); + if smb.version == 0xff_u8 { // SMB1 + SCLogDebug!("SMBv1 record"); + match parse_smb_record(nbss_hdr.data) { + Ok((_, ref smb_record)) => { + let pdu_frame = self.add_smb1_tc_pdu_frame(flow, stream_slice, nbss_hdr.data, nbss_hdr.length as i64); + self.add_smb1_tc_hdr_data_frames(flow, stream_slice, nbss_hdr.data, nbss_hdr.length as i64); + if smb_record.is_response() { + smb1_response_record(self, smb_record); + } else { + SCLogDebug!("SMB1 request seen from server to client"); + if let Some(frame) = pdu_frame { + frame.add_event(flow, SMBEvent::RequestToClient as u8); + } + } + }, + _ => { + self.set_event(SMBEvent::MalformedData); + return AppLayerResult::err(); + }, + } + } else if smb.version == 0xfe_u8 { // SMB2 + let mut nbss_data = nbss_hdr.data; + while !nbss_data.is_empty() { + SCLogDebug!("SMBv2 record"); + match parse_smb2_response_record(nbss_data) { + Ok((nbss_data_rem, ref smb_record)) => { + let record_len = (nbss_data.len() - nbss_data_rem.len()) as i64; + let pdu_frame = self.add_smb2_tc_pdu_frame(flow, stream_slice, nbss_data, record_len); + self.add_smb2_tc_hdr_data_frames(flow, stream_slice, nbss_data, record_len, smb_record.header_len as i64); + if smb_record.is_response() { + smb2_response_record(self, smb_record); + } else { + SCLogDebug!("SMB2 request seen from server to client"); + if let Some(frame) = pdu_frame { + frame.add_event(flow, SMBEvent::RequestToClient as u8); + } + } + nbss_data = nbss_data_rem; + }, + _ => { + self.set_event(SMBEvent::MalformedData); + return AppLayerResult::err(); + }, + } + } + } else if smb.version == 0xfd_u8 { // SMB3 transform + let mut nbss_data = nbss_hdr.data; + while !nbss_data.is_empty() { + SCLogDebug!("SMBv3 transform record"); + match parse_smb3_transform_record(nbss_data) { + Ok((nbss_data_rem, ref _smb3_record)) => { + let record_len = (nbss_data.len() - nbss_data_rem.len()) as i64; + self.add_smb3_tc_pdu_frame(flow, stream_slice, nbss_data, record_len); + self.add_smb3_tc_hdr_data_frames(flow, stream_slice, nbss_data, record_len); + nbss_data = nbss_data_rem; + }, + _ => { + self.set_event(SMBEvent::MalformedData); + return AppLayerResult::err(); + }, + } + } + } + }, + Err(Err::Incomplete(_)) => { + // not enough data to contain basic SMB hdr + // TODO event: empty NBSS_MSGTYPE_SESSION_MESSAGE + }, + Err(_) => { + self.set_event(SMBEvent::MalformedData); + return AppLayerResult::err(); + }, + } + } else { + SCLogDebug!("NBSS message {:X}", nbss_hdr.message_type); + } + cur_i = rem; + }, + Err(Err::Incomplete(needed)) => { + SCLogDebug!("INCOMPLETE have {} needed {:?}", cur_i.len(), needed); + if let Needed::Size(n) = needed { + let n = usize::from(n) + cur_i.len(); + // 512 is the minimum for parse_tcp_data_tc_partial + if n >= 512 && cur_i.len() < 512 { + let total_consumed = stream_slice.offset_from(cur_i); + return AppLayerResult::incomplete(total_consumed, 512); + } + let consumed = self.parse_tcp_data_tc_partial(flow, stream_slice, cur_i); + if consumed == 0 { + // if we consumed none we will buffer the entire record + let total_consumed = stream_slice.offset_from(cur_i); + SCLogDebug!("setting consumed {} need {} needed {:?} total input {}", + total_consumed, n, needed, stream_slice.len()); + let need = n; + return AppLayerResult::incomplete(total_consumed, need as u32); + } + // tracking a read record, which we don't need to + // queue up at the stream level, but can feed to us + // in small chunks + return AppLayerResult::ok(); + } else { + self.set_event(SMBEvent::InternalError); + return AppLayerResult::err(); + } + }, + Err(_) => { + self.set_event(SMBEvent::MalformedData); + return AppLayerResult::err(); + }, + } + }; + self.post_gap_housekeeping(Direction::ToClient); + if self.check_post_gap_file_txs && !self.post_gap_files_checked { + self.post_gap_housekeeping_for_files(); + self.post_gap_files_checked = true; + } + self._debug_tx_stats(); + AppLayerResult::ok() + } + + /// handle a gap in the TOSERVER direction + /// returns: 0 ok, 1 unrecoverable error + pub fn parse_tcp_data_ts_gap(&mut self, gap_size: u32) -> AppLayerResult { + let consumed = self.handle_skip(Direction::ToServer, gap_size); + if consumed < gap_size { + let new_gap_size = gap_size - consumed; + let gap = vec![0; new_gap_size as usize]; + + let consumed2 = self.filetracker_update(Direction::ToServer, &gap, new_gap_size); + if consumed2 > new_gap_size { + SCLogDebug!("consumed more than GAP size: {} > {}", consumed2, new_gap_size); + self.set_event(SMBEvent::InternalError); + return AppLayerResult::err(); + } + } + SCLogDebug!("GAP of size {} in toserver direction", gap_size); + self.ts_ssn_gap = true; + self.ts_gap = true; + return AppLayerResult::ok(); + } + + /// handle a gap in the TOCLIENT direction + /// returns: 0 ok, 1 unrecoverable error + pub fn parse_tcp_data_tc_gap(&mut self, gap_size: u32) -> AppLayerResult { + let consumed = self.handle_skip(Direction::ToClient, gap_size); + if consumed < gap_size { + let new_gap_size = gap_size - consumed; + let gap = vec![0; new_gap_size as usize]; + + let consumed2 = self.filetracker_update(Direction::ToClient, &gap, new_gap_size); + if consumed2 > new_gap_size { + SCLogDebug!("consumed more than GAP size: {} > {}", consumed2, new_gap_size); + self.set_event(SMBEvent::InternalError); + return AppLayerResult::err(); + } + } + SCLogDebug!("GAP of size {} in toclient direction", gap_size); + self.tc_ssn_gap = true; + self.tc_gap = true; + return AppLayerResult::ok(); + } + + pub fn trunc_ts(&mut self) { + SCLogDebug!("TRUNC TS"); + self.ts_trunc = true; + + for tx in &mut self.transactions { + if !tx.request_done { + SCLogDebug!("TRUNCATING TX {} in TOSERVER direction", tx.id); + tx.request_done = true; + } + } + } + pub fn trunc_tc(&mut self) { + SCLogDebug!("TRUNC TC"); + self.tc_trunc = true; + + for tx in &mut self.transactions { + if !tx.response_done { + SCLogDebug!("TRUNCATING TX {} in TOCLIENT direction", tx.id); + tx.response_done = true; + } + } + } +} + +/// Returns *mut SMBState +#[no_mangle] +pub extern "C" fn rs_smb_state_new(_orig_state: *mut std::os::raw::c_void, _orig_proto: AppProto) -> *mut std::os::raw::c_void { + let state = SMBState::new(); + let boxed = Box::new(state); + SCLogDebug!("allocating state"); + return Box::into_raw(boxed) as *mut _; +} + +/// Params: +/// - state: *mut SMBState as void pointer +#[no_mangle] +pub extern "C" fn rs_smb_state_free(state: *mut std::os::raw::c_void) { + SCLogDebug!("freeing state"); + let mut smb_state = unsafe { Box::from_raw(state as *mut SMBState) }; + smb_state.free(); +} + +/// C binding parse a SMB request. Returns 1 on success, -1 on failure. +#[no_mangle] +pub unsafe extern "C" fn rs_smb_parse_request_tcp(flow: *const Flow, + state: *mut ffi::c_void, + _pstate: *mut std::os::raw::c_void, + stream_slice: StreamSlice, + _data: *const std::os::raw::c_void, + ) + -> AppLayerResult +{ + let state = cast_pointer!(state, SMBState); + let flow = cast_pointer!(flow, Flow); + + if stream_slice.is_gap() { + return rs_smb_parse_request_tcp_gap(state, stream_slice.gap_size()); + } + + SCLogDebug!("parsing {} bytes of request data", stream_slice.len()); + + /* START with MISTREAM set: record might be starting the middle. */ + if stream_slice.flags() & (STREAM_START|STREAM_MIDSTREAM) == (STREAM_START|STREAM_MIDSTREAM) { + state.ts_gap = true; + } + + state.update_ts(flow.get_last_time().as_secs()); + state.parse_tcp_data_ts(flow, &stream_slice) +} + +#[no_mangle] +pub extern "C" fn rs_smb_parse_request_tcp_gap( + state: &mut SMBState, + input_len: u32) + -> AppLayerResult +{ + state.parse_tcp_data_ts_gap(input_len) +} + + +#[no_mangle] +pub unsafe extern "C" fn rs_smb_parse_response_tcp(flow: *const Flow, + state: *mut ffi::c_void, + _pstate: *mut std::os::raw::c_void, + stream_slice: StreamSlice, + _data: *const ffi::c_void, + ) + -> AppLayerResult +{ + let state = cast_pointer!(state, SMBState); + let flow = cast_pointer!(flow, Flow); + + if stream_slice.is_gap() { + return rs_smb_parse_response_tcp_gap(state, stream_slice.gap_size()); + } + + /* START with MISTREAM set: record might be starting the middle. */ + if stream_slice.flags() & (STREAM_START|STREAM_MIDSTREAM) == (STREAM_START|STREAM_MIDSTREAM) { + state.tc_gap = true; + } + + state.update_ts(flow.get_last_time().as_secs()); + state.parse_tcp_data_tc(flow, &stream_slice) +} + +#[no_mangle] +pub extern "C" fn rs_smb_parse_response_tcp_gap( + state: &mut SMBState, + input_len: u32) + -> AppLayerResult +{ + state.parse_tcp_data_tc_gap(input_len) +} + +fn smb_probe_tcp_midstream(direction: Direction, slice: &[u8], rdir: *mut u8, begins: bool) -> i8 +{ + let r = if begins { + // if pattern was found in the beginning, just check first byte + if slice[0] == NBSS_MSGTYPE_SESSION_MESSAGE { + Ok((&slice[..4], &slice[4..])) + } else { + Err(Err::Error(make_error(slice, ErrorKind::Eof))) + } + } else { + search_smb_record(slice) + }; + match r { + Ok((_, data)) => { + SCLogDebug!("smb found"); + match parse_smb_version(data) { + Ok((_, ref smb)) => { + SCLogDebug!("SMB {:?}", smb); + if smb.version == 0xff_u8 { // SMB1 + SCLogDebug!("SMBv1 record"); + if let Ok((_, ref smb_record)) = parse_smb_record(data) { + if smb_record.flags & 0x80 != 0 { + SCLogDebug!("RESPONSE {:02x}", smb_record.flags); + if direction == Direction::ToServer { + unsafe { *rdir = Direction::ToClient as u8; } + } + } else { + SCLogDebug!("REQUEST {:02x}", smb_record.flags); + if direction == Direction::ToClient { + unsafe { *rdir = Direction::ToServer as u8; } + } + } + return 1; + } + } else if smb.version == 0xfe_u8 { // SMB2 + SCLogDebug!("SMB2 record"); + if let Ok((_, ref smb_record)) = parse_smb2_record_direction(data) { + if direction == Direction::ToServer { + SCLogDebug!("direction Direction::ToServer smb_record {:?}", smb_record); + if !smb_record.request { + unsafe { *rdir = Direction::ToClient as u8; } + } + } else { + SCLogDebug!("direction Direction::ToClient smb_record {:?}", smb_record); + if smb_record.request { + unsafe { *rdir = Direction::ToServer as u8; } + } + } + } + } + else if smb.version == 0xfd_u8 { // SMB3 transform + SCLogDebug!("SMB3 record"); + } + return 1; + }, + _ => { + SCLogDebug!("smb not found in {:?}", slice); + }, + } + }, + _ => { + SCLogDebug!("no dice"); + }, + } + return 0; +} + +fn smb_probe_tcp(flags: u8, slice: &[u8], rdir: *mut u8, begins: bool) -> AppProto +{ + if flags & STREAM_MIDSTREAM == STREAM_MIDSTREAM && smb_probe_tcp_midstream(flags.into(), slice, rdir, begins) == 1 { + unsafe { return ALPROTO_SMB; } + } + if let Ok((_, ref hdr)) = parse_nbss_record_partial(slice) { + if hdr.is_smb() { + SCLogDebug!("smb found"); + unsafe { return ALPROTO_SMB; } + } else if hdr.needs_more(){ + return 0; + } else if hdr.is_valid() && + hdr.message_type != NBSS_MSGTYPE_SESSION_MESSAGE { + //we accept a first small netbios message before real SMB + let hl = hdr.length as usize; + if hdr.data.len() >= hl + 8 { + // 8 is 4 bytes NBSS + 4 bytes SMB0xFX magic + if let Ok((_, ref hdr2)) = parse_nbss_record_partial(&hdr.data[hl..]) { + if hdr2.is_smb() { + SCLogDebug!("smb found"); + unsafe { return ALPROTO_SMB; } + } + } + } else if hdr.length < 256 { + // we want more data, 256 is some random value + return 0; + } + // default is failure + } + } + SCLogDebug!("no smb"); + unsafe { return ALPROTO_FAILED; } +} + +// probing confirmation parser +// return 1 if found, 0 is not found +#[no_mangle] +pub unsafe extern "C" fn rs_smb_probe_begins_tcp(_f: *const Flow, + flags: u8, input: *const u8, len: u32, rdir: *mut u8) + -> AppProto +{ + if len < MIN_REC_SIZE as u32 { + return ALPROTO_UNKNOWN; + } + let slice = build_slice!(input, len as usize); + return smb_probe_tcp(flags, slice, rdir, true); +} + +// probing parser +// return 1 if found, 0 is not found +#[no_mangle] +pub unsafe extern "C" fn rs_smb_probe_tcp(_f: *const Flow, + flags: u8, input: *const u8, len: u32, rdir: *mut u8) + -> AppProto +{ + if len < MIN_REC_SIZE as u32 { + return ALPROTO_UNKNOWN; + } + let slice = build_slice!(input, len as usize); + return smb_probe_tcp(flags, slice, rdir, false); +} + +#[no_mangle] +pub unsafe extern "C" fn rs_smb_state_get_tx_count(state: *mut ffi::c_void) + -> u64 +{ + let state = cast_pointer!(state, SMBState); + SCLogDebug!("rs_smb_state_get_tx_count: returning {}", state.tx_id); + return state.tx_id; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_smb_state_get_tx(state: *mut ffi::c_void, + tx_id: u64) + -> *mut ffi::c_void +{ + let state = cast_pointer!(state, SMBState); + match state.get_tx_by_id(tx_id) { + Some(tx) => { + return tx as *const _ as *mut _; + } + None => { + return std::ptr::null_mut(); + } + } +} + +#[no_mangle] +pub unsafe extern "C" fn rs_smb_state_tx_free(state: *mut ffi::c_void, + tx_id: u64) +{ + let state = cast_pointer!(state, SMBState); + SCLogDebug!("freeing tx {}", tx_id); + state.free_tx(tx_id); +} + +#[no_mangle] +pub unsafe extern "C" fn rs_smb_tx_get_alstate_progress(tx: *mut ffi::c_void, + direction: u8) + -> i32 +{ + let tx = cast_pointer!(tx, SMBTransaction); + + if direction == Direction::ToServer as u8 && tx.request_done { + SCLogDebug!("tx {} TOSERVER progress 1 => {:?}", tx.id, tx); + return 1; + } else if direction == Direction::ToClient as u8 && tx.response_done { + SCLogDebug!("tx {} TOCLIENT progress 1 => {:?}", tx.id, tx); + return 1; + } else { + SCLogDebug!("tx {} direction {} progress 0 => {:?}", tx.id, direction, tx); + return 0; + } +} + + +export_state_data_get!(rs_smb_get_state_data, SMBState); + +#[no_mangle] +pub unsafe extern "C" fn rs_smb_get_tx_data( + tx: *mut std::os::raw::c_void) + -> *mut AppLayerTxData +{ + let tx = cast_pointer!(tx, SMBTransaction); + return &mut tx.tx_data; +} + + +#[no_mangle] +pub unsafe extern "C" fn rs_smb_state_truncate( + state: *mut std::ffi::c_void, + direction: u8) +{ + let state = cast_pointer!(state, SMBState); + match direction.into() { + Direction::ToServer => { + state.trunc_ts(); + } + Direction::ToClient => { + state.trunc_tc(); + } + } +} + +#[no_mangle] +pub unsafe extern "C" fn rs_smb_state_get_event_info_by_id( + event_id: std::os::raw::c_int, + event_name: *mut *const std::os::raw::c_char, + event_type: *mut AppLayerEventType, +) -> i8 { + SMBEvent::get_event_info_by_id(event_id, event_name, event_type) +} + +#[no_mangle] +pub unsafe extern "C" fn rs_smb_state_get_event_info( + event_name: *const std::os::raw::c_char, + event_id: *mut std::os::raw::c_int, + event_type: *mut AppLayerEventType, +) -> std::os::raw::c_int { + SMBEvent::get_event_info(event_name, event_id, event_type) +} + +pub unsafe extern "C" fn smb3_probe_tcp(f: *const Flow, dir: u8, input: *const u8, len: u32, rdir: *mut u8) -> u16 { + let retval = rs_smb_probe_tcp(f, dir, input, len, rdir); + let f = cast_pointer!(f, Flow); + if retval != ALPROTO_SMB { + return retval; + } + let (sp, dp) = f.get_ports(); + let flags = f.get_flags(); + let fsp = if (flags & FLOW_DIR_REVERSED) != 0 { dp } else { sp }; + let fdp = if (flags & FLOW_DIR_REVERSED) != 0 { sp } else { dp }; + if fsp == 445 && fdp != 445 { + match dir.into() { + Direction::ToServer => { + *rdir = Direction::ToClient as u8; + } + Direction::ToClient => { + *rdir = Direction::ToServer as u8; + } + } + } + return ALPROTO_SMB; +} + +fn register_pattern_probe() -> i8 { + let mut r = 0; + unsafe { + // SMB1 + r |= AppLayerProtoDetectPMRegisterPatternCSwPP(IPPROTO_TCP, ALPROTO_SMB, + b"|ff|SMB\0".as_ptr() as *const std::os::raw::c_char, 8, 4, + Direction::ToServer as u8, rs_smb_probe_begins_tcp, MIN_REC_SIZE, MIN_REC_SIZE); + r |= AppLayerProtoDetectPMRegisterPatternCSwPP(IPPROTO_TCP, ALPROTO_SMB, + b"|ff|SMB\0".as_ptr() as *const std::os::raw::c_char, 8, 4, + Direction::ToClient as u8, rs_smb_probe_begins_tcp, MIN_REC_SIZE, MIN_REC_SIZE); + // SMB2/3 + r |= AppLayerProtoDetectPMRegisterPatternCSwPP(IPPROTO_TCP, ALPROTO_SMB, + b"|fe|SMB\0".as_ptr() as *const std::os::raw::c_char, 8, 4, + Direction::ToServer as u8, rs_smb_probe_begins_tcp, MIN_REC_SIZE, MIN_REC_SIZE); + r |= AppLayerProtoDetectPMRegisterPatternCSwPP(IPPROTO_TCP, ALPROTO_SMB, + b"|fe|SMB\0".as_ptr() as *const std::os::raw::c_char, 8, 4, + Direction::ToClient as u8, rs_smb_probe_begins_tcp, MIN_REC_SIZE, MIN_REC_SIZE); + // SMB3 encrypted records + r |= AppLayerProtoDetectPMRegisterPatternCSwPP(IPPROTO_TCP, ALPROTO_SMB, + b"|fd|SMB\0".as_ptr() as *const std::os::raw::c_char, 8, 4, + Direction::ToServer as u8, smb3_probe_tcp, MIN_REC_SIZE, MIN_REC_SIZE); + r |= AppLayerProtoDetectPMRegisterPatternCSwPP(IPPROTO_TCP, ALPROTO_SMB, + b"|fd|SMB\0".as_ptr() as *const std::os::raw::c_char, 8, 4, + Direction::ToClient as u8, smb3_probe_tcp, MIN_REC_SIZE, MIN_REC_SIZE); + } + + if r == 0 { + return 0; + } else { + return -1; + } +} + +// Parser name as a C style string. +const PARSER_NAME: &[u8] = b"smb\0"; + +#[no_mangle] +pub unsafe extern "C" fn rs_smb_register_parser() { + let default_port = CString::new("445").unwrap(); + let mut stream_depth = SMB_CONFIG_DEFAULT_STREAM_DEPTH; + let parser = RustParser { + name: PARSER_NAME.as_ptr() as *const std::os::raw::c_char, + default_port: std::ptr::null(), + ipproto: IPPROTO_TCP, + probe_ts: None, + probe_tc: None, + min_depth: 0, + max_depth: 16, + state_new: rs_smb_state_new, + state_free: rs_smb_state_free, + tx_free: rs_smb_state_tx_free, + parse_ts: rs_smb_parse_request_tcp, + parse_tc: rs_smb_parse_response_tcp, + get_tx_count: rs_smb_state_get_tx_count, + get_tx: rs_smb_state_get_tx, + tx_comp_st_ts: 1, + tx_comp_st_tc: 1, + tx_get_progress: rs_smb_tx_get_alstate_progress, + get_eventinfo: Some(rs_smb_state_get_event_info), + get_eventinfo_byid : Some(rs_smb_state_get_event_info_by_id), + localstorage_new: None, + localstorage_free: None, + get_tx_files: Some(rs_smb_gettxfiles), + get_tx_iterator: Some(applayer::state_get_tx_iterator::<SMBState, SMBTransaction>), + get_tx_data: rs_smb_get_tx_data, + get_state_data: rs_smb_get_state_data, + apply_tx_config: None, + flags: APP_LAYER_PARSER_OPT_ACCEPT_GAPS, + truncate: Some(rs_smb_state_truncate), + get_frame_id_by_name: Some(SMBFrameType::ffi_id_from_name), + get_frame_name_by_id: Some(SMBFrameType::ffi_name_from_id), + }; + + let ip_proto_str = CString::new("tcp").unwrap(); + + if AppLayerProtoDetectConfProtoDetectionEnabled( + ip_proto_str.as_ptr(), + parser.name, + ) != 0 + { + let alproto = AppLayerRegisterProtocolDetection(&parser, 1); + ALPROTO_SMB = alproto; + if register_pattern_probe() < 0 { + return; + } + + let have_cfg = AppLayerProtoDetectPPParseConfPorts(ip_proto_str.as_ptr(), + IPPROTO_TCP, parser.name, ALPROTO_SMB, 0, + MIN_REC_SIZE, rs_smb_probe_tcp, rs_smb_probe_tcp); + + if have_cfg == 0 { + AppLayerProtoDetectPPRegister(IPPROTO_TCP, default_port.as_ptr(), ALPROTO_SMB, + 0, MIN_REC_SIZE, Direction::ToServer as u8, rs_smb_probe_tcp, rs_smb_probe_tcp); + } + + if AppLayerParserConfParserEnabled( + ip_proto_str.as_ptr(), + parser.name, + ) != 0 + { + let _ = AppLayerRegisterParser(&parser, alproto); + } + SCLogDebug!("Rust SMB parser registered."); + let retval = conf_get("app-layer.protocols.smb.stream-depth"); + if let Some(val) = retval { + match get_memval(val) { + Ok(retval) => { stream_depth = retval as u32; } + Err(_) => { SCLogError!("Invalid depth value"); } + } + } + AppLayerParserSetStreamDepth(IPPROTO_TCP, ALPROTO_SMB, stream_depth); + let retval = conf_get("app-layer.protocols.smb.max-read-size"); + if let Some(val) = retval { + match get_memval(val) { + Ok(retval) => { SMB_CFG_MAX_READ_SIZE = retval as u32; } + Err(_) => { SCLogError!("Invalid max-read-size value"); } + } + } + let retval = conf_get("app-layer.protocols.smb.max-write-size"); + if let Some(val) = retval { + match get_memval(val) { + Ok(retval) => { SMB_CFG_MAX_WRITE_SIZE = retval as u32; } + Err(_) => { SCLogError!("Invalid max-write-size value"); } + } + } + let retval = conf_get("app-layer.protocols.smb.max-write-queue-size"); + if let Some(val) = retval { + match get_memval(val) { + Ok(retval) => { SMB_CFG_MAX_WRITE_QUEUE_SIZE = retval as u32; } + Err(_) => { SCLogError!("Invalid max-write-queue-size value"); } + } + } + let retval = conf_get("app-layer.protocols.smb.max-write-queue-cnt"); + if let Some(val) = retval { + match get_memval(val) { + Ok(retval) => { SMB_CFG_MAX_WRITE_QUEUE_CNT = retval as u32; } + Err(_) => { SCLogError!("Invalid max-write-queue-cnt value"); } + } + } + let retval = conf_get("app-layer.protocols.smb.max-read-queue-size"); + if let Some(val) = retval { + match get_memval(val) { + Ok(retval) => { SMB_CFG_MAX_READ_QUEUE_SIZE = retval as u32; } + Err(_) => { SCLogError!("Invalid max-read-queue-size value"); } + } + } + let retval = conf_get("app-layer.protocols.smb.max-read-queue-cnt"); + if let Some(val) = retval { + match get_memval(val) { + Ok(retval) => { SMB_CFG_MAX_READ_QUEUE_CNT = retval as u32; } + Err(_) => { SCLogError!("Invalid max-read-queue-cnt value"); } + } + } + if let Some(val) = conf_get("app-layer.protocols.smb.max-tx") { + if let Ok(v) = val.parse::<usize>() { + SMB_MAX_TX = v; + } else { + SCLogError!("Invalid value for smb.max-tx"); + } + } + SCLogConfig!("read: max record size: {}, max queued chunks {}, max queued size {}", + SMB_CFG_MAX_READ_SIZE, SMB_CFG_MAX_READ_QUEUE_CNT, SMB_CFG_MAX_READ_QUEUE_SIZE); + SCLogConfig!("write: max record size: {}, max queued chunks {}, max queued size {}", + SMB_CFG_MAX_WRITE_SIZE, SMB_CFG_MAX_WRITE_QUEUE_CNT, SMB_CFG_MAX_WRITE_QUEUE_SIZE); + } else { + SCLogDebug!("Protocol detector and parser disabled for SMB."); + } +} |