From: Victor Julien Date: Mon, 26 Feb 2018 12:39:42 +0000 (+0100) Subject: rust/smb: initial support X-Git-Tag: suricata-4.1.0-beta1~106 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=75d7c9d64af9a758c3b6f76c474a787b4e1d1d85;p=thirdparty%2Fsuricata.git rust/smb: initial support Implement SMB app-layer parser for SMB1/2/3. Features: - file extraction - eve logging - existing dce keyword support - smb_share/smb_named_pipe keyword support (stickybuffers) - auth meta data extraction (ntlmssp, kerberos5) --- diff --git a/rules/smb-events.rules b/rules/smb-events.rules new file mode 100644 index 0000000000..618e357e29 --- /dev/null +++ b/rules/smb-events.rules @@ -0,0 +1,14 @@ +# SMB app layer event rules +# +# SID's fall in the 2225000+ range. See https://redmine.openinfosecfoundation.org/projects/suricata/wiki/AppLayer +# +# These sigs fire at most once per connection. +# + +alert smb any any -> any any (msg:"SURICATA SMB internal parser error"; flow:to_server; app-layer-event:smb.internal_error; classtype:protocol-command-decode; sid:2225000; rev:1;) +alert smb any any -> any any (msg:"SURICATA SMB internal parser error"; flow:to_client; app-layer-event:smb.internal_error; classtype:protocol-command-decode; sid:2225001; rev:1;) + +alert smb any any -> any any (msg:"SURICATA SMB malformed request data"; flow:to_server; app-layer-event:smb.malformed_data; classtype:protocol-command-decode; sid:2225002; rev:1;) +alert smb any any -> any any (msg:"SURICATA SMB malformed response data"; flow:to_client; app-layer-event:smb.malformed_data; classtype:protocol-command-decode; sid:2225003; rev:1;) + +alert smb any any -> any any (msg:"SURICATA SMB malformed NTLMSSP record"; flow:to_server; app-layer-event:smb.malformed_ntlmssp_request; classtype:protocol-command-decode; sid:2225004; rev:1;) diff --git a/rust/Cargo.toml.in b/rust/Cargo.toml.in index 9cd079f7a2..55ae38997c 100644 --- a/rust/Cargo.toml.in +++ b/rust/Cargo.toml.in @@ -18,5 +18,6 @@ debug = [] nom = "~3.2.1" libc = "^0.2.36" crc = "~1.7.0" +der-parser = "0.5.0" ntp-parser = { version = "^0", optional = true } diff --git a/rust/gen-c-headers.py b/rust/gen-c-headers.py index a6e803a9ba..d0b737222f 100755 --- a/rust/gen-c-headers.py +++ b/rust/gen-c-headers.py @@ -82,6 +82,8 @@ type_map = { "NTPTransaction": "NTPTransaction", "TFTPTransaction": "TFTPTransaction", "TFTPState": "TFTPState", + "SMBState": "SMBState", + "SMBTransaction": "SMBTransaction", "JsonT": "json_t", "DetectEngineState": "DetectEngineState", "core::DetectEngineState": "DetectEngineState", diff --git a/rust/src/filetracker.rs b/rust/src/filetracker.rs index aa9acfa63a..e393a8bbf2 100644 --- a/rust/src/filetracker.rs +++ b/rust/src/filetracker.rs @@ -106,6 +106,7 @@ impl FileTransferTracker { pub fn close(&mut self, files: &mut FileContainer, flags: u16) { if !self.file_is_truncated { + SCLogDebug!("closing file with id {}", self.track_id); files.file_close(&self.track_id, flags); } self.file_open = false; diff --git a/rust/src/lib.rs b/rust/src/lib.rs index b322c08b68..c2d7ff99bb 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -24,6 +24,8 @@ extern crate nom; extern crate crc; +extern crate der_parser; + #[macro_use] pub mod log; @@ -44,6 +46,7 @@ pub mod lua; pub mod dns; pub mod nfs; pub mod ftp; +pub mod smb; #[cfg(feature = "experimental")] pub mod ntp; diff --git a/rust/src/smb/auth.rs b/rust/src/smb/auth.rs new file mode 100644 index 0000000000..6eca8609cd --- /dev/null +++ b/rust/src/smb/auth.rs @@ -0,0 +1,439 @@ +/* Copyright (C) 2018 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. + */ + +use log::*; +use smb::ntlmssp_records::*; +use smb::smb::*; + +use nom; +use nom::{IResult, ErrorKind}; +use der_parser; + +#[derive(Debug,PartialEq)] +pub struct Kerberos5Ticket { + pub realm: Vec, + pub snames: Vec>, +} + +/// ticket starts with custom header [APPLICATION 1] +fn parse_kerberos5_request_ticket(blob: &[u8]) -> IResult<&[u8], Kerberos5Ticket> +{ + let (rem, ticket_hdr) = match der_parser::der_read_element_header(blob) { + IResult::Done(rem, o) => (rem, o), + IResult::Incomplete(needed) => { return IResult::Incomplete(needed); }, + IResult::Error(err) => { return IResult::Error(err); }, + }; + SCLogDebug!("parse_kerberos5_request_ticket: ticket {:?}, remaining data {}", ticket_hdr, rem.len()); + + if !(ticket_hdr.class == 1 && ticket_hdr.structured == 1 && ticket_hdr.tag == 1 && ticket_hdr.len == rem.len() as u64) { + SCLogDebug!("parse_kerberos5_request_ticket: bad data"); + return IResult::Error(error_code!(ErrorKind::Custom(SECBLOB_KRB_FMT_ERR))); + } + let (_, ticket_seq) = match der_parser::parse_der_sequence(rem) { + IResult::Done(rem, o) => (rem, o), + IResult::Incomplete(needed) => { return IResult::Incomplete(needed); }, + IResult::Error(err) => { return IResult::Error(err); }, + }; + SCLogDebug!("parse_kerberos5_request_ticket: ticket {:?}", ticket_seq); + + let ticket_vec = ticket_seq.as_sequence().unwrap(); // parse_der_sequence is checked + SCLogDebug!("parse_kerberos5_request_ticket: ticket_vec {:?}", ticket_vec); + if ticket_vec.len() != 4 { + SCLogDebug!("parse_kerberos5_request_ticket: unexpected format"); + return IResult::Error(error_code!(ErrorKind::Custom(SECBLOB_KRB_FMT_ERR))); + } + + SCLogDebug!("parse_kerberos5_request_ticket: tkt-vno {:?}", ticket_vec[0]); + SCLogDebug!("parse_kerberos5_request_ticket: realm {:?}", ticket_vec[1]); + SCLogDebug!("parse_kerberos5_request_ticket: sname {:?}", ticket_vec[2]); + SCLogDebug!("parse_kerberos5_request_ticket: enc-part {:?}", ticket_vec[3]); + +// TODO switch to parser_der_genericstring when der-parser 0.5.1 is out + let realm = match der_parser::parse_der(ticket_vec[1].content.as_slice().unwrap()) { + IResult::Done(_, o) => o, + IResult::Incomplete(needed) => { + SCLogDebug!("parse_kerberos5_request_ticket: needed {:?}", needed); + return IResult::Incomplete(needed); + }, + IResult::Error(err) => { + SCLogDebug!("parse_kerberos5_request_ticket: err {:?}", err); + return IResult::Error(err); + }, + }; + SCLogDebug!("parse_kerberos5_request_ticket: realm {:?}", realm); + + if !(realm.class == 0 && realm.structured == 0 && realm.tag == 27) { + SCLogDebug!("bad realm data"); + return IResult::Error(error_code!(ErrorKind::Custom(SECBLOB_KRB_FMT_ERR))); + } + let realm_v = realm.content.as_slice().unwrap().to_vec(); + SCLogDebug!("parse_kerberos5_request_ticket: realm_v {:?}", realm_v); + + let sname = match der_parser::parse_der_sequence(ticket_vec[2].content.as_slice().unwrap()) { + IResult::Done(_, o) => o, + IResult::Incomplete(needed) => { + SCLogDebug!("parse_kerberos5_request_ticket: needed {:?}", needed); + return IResult::Incomplete(needed); + }, + IResult::Error(err) => { + SCLogDebug!("parse_kerberos5_request_ticket: err {:?}", err); + return IResult::Error(err); + }, + }; + SCLogDebug!("parse_kerberos5_request_ticket: sname {:?}", sname); + + let sname_vec = sname.as_sequence().unwrap(); // parse_der_sequence is checked + SCLogDebug!("parse_kerberos5_request_ticket: sname_vec {:?}", sname_vec); + + if sname_vec.len() != 2 { + SCLogDebug!("parse_kerberos5_request_ticket: unexpected format"); + return IResult::Error(error_code!(ErrorKind::Custom(SECBLOB_KRB_FMT_ERR))); + } + let sname_seq = match der_parser::parse_der_sequence(sname_vec[1].content.as_slice().unwrap()) { + IResult::Done(_, o) => o, + IResult::Incomplete(needed) => { + SCLogDebug!("parse_kerberos5_request_ticket: needed {:?}", needed); + return IResult::Incomplete(needed); + }, + IResult::Error(err) => { + SCLogDebug!("parse_kerberos5_request_ticket: err {:?}", err); + return IResult::Error(err); + }, + }; + SCLogDebug!("parse_kerberos5_request_ticket: sname_seq {:?}", sname_seq); + let snamestr_vec = sname_seq.as_sequence().unwrap(); // parse_der_sequence is checked + + let mut snames : Vec> = Vec::new(); + for o in snamestr_vec { + SCLogDebug!("parse_kerberos5_request_ticket: sname o {:?}", o); + if o.tag == 27 { + let v = o.content.as_slice().unwrap().to_vec(); + SCLogDebug!("sname {:?}", v); + snames.push(v); + } + } + + let t = Kerberos5Ticket { + realm: realm_v, + snames: snames, + }; + SCLogDebug!("ticket {:?}", t); + IResult::Done(&[],t) +} + +// get SPNEGO +// get OIDS +// if OID has KERBEROS get KERBEROS data +// else if OID has NTLMSSP get NTLMSSP +// else bruteforce NTLMSSP + +fn parse_kerberos5_request(blob: &[u8]) -> IResult<&[u8], Kerberos5Ticket> +{ + let blob = match der_parser::parse_der(blob) { + IResult::Done(_, b) => { + match b.content.as_slice() { + Ok(b) => { b }, + _ => { return IResult::Error(error_code!(ErrorKind::Custom(SECBLOB_KRB_FMT_ERR))); }, + } + }, + IResult::Incomplete(needed) => { return IResult::Incomplete(needed); }, + IResult::Error(err) => { return IResult::Error(err); }, + }; + let (rem, base_o) = match der_parser::parse_der_oid(blob) { + IResult::Done(rem, o) => (rem, o), + IResult::Incomplete(needed) => { return IResult::Incomplete(needed); }, + IResult::Error(err) => { return IResult::Error(err); }, + }; + SCLogDebug!("parse_kerberos5_request: base_o {:?}", base_o); + + // not DER encoded 2 byte length field + let (rem, tok_id) = match nom::le_u16(rem) { + IResult::Done(rem, o) => (rem, o), + IResult::Incomplete(needed) => { return IResult::Incomplete(needed); }, + IResult::Error(err) => { return IResult::Error(err); }, + }; + SCLogDebug!("parse_kerberos5_request: tok_id {}", tok_id); + + // APPLICATION 14 + let (rem, base_o) = match der_parser::der_read_element_header(rem) { + IResult::Done(rem, o) => (rem, o), + IResult::Incomplete(needed) => { return IResult::Incomplete(needed); }, + IResult::Error(err) => { return IResult::Error(err); }, + }; + if !(base_o.class == 1 && base_o.structured == 1 && base_o.tag == 14 && base_o.len == rem.len() as u64) { + SCLogDebug!("parse_kerberos5_request_ticket: bad data"); + return IResult::Error(error_code!(ErrorKind::Custom(SECBLOB_KRB_FMT_ERR))); + } + + let base_seq = match der_parser::parse_der_sequence(rem) { + IResult::Done(_, o) => o, + IResult::Incomplete(needed) => { return IResult::Incomplete(needed); }, + IResult::Error(err) => { return IResult::Error(err); }, + }; + SCLogDebug!("parse_kerberos5_request: base_seq {:?}", base_seq); + + if base_seq.as_sequence().unwrap().len() < 4 { + SCLogDebug!("parse_kerberos5_request_ticket: bad data"); + return IResult::Error(error_code!(ErrorKind::Custom(SECBLOB_KRB_FMT_ERR))); + } + + let pvno_s = match base_seq[0].content.as_slice() { + Ok(s) => s, + Err(_) => { + return IResult::Error(error_code!(ErrorKind::Custom(SECBLOB_KRB_FMT_ERR))); + }, + }; + let pvno = match der_parser::parse_der_integer(pvno_s) { + IResult::Done(_, o) => o, + IResult::Incomplete(needed) => { return IResult::Incomplete(needed); }, + IResult::Error(err) => { return IResult::Error(err); }, + }; + SCLogDebug!("pvno {:?}", pvno); + + let msg_type_s = match base_seq[1].content.as_slice() { + Ok(s) => s, + Err(_) => { + return IResult::Error(error_code!(ErrorKind::Custom(SECBLOB_KRB_FMT_ERR))); + }, + }; + let msg_type = match der_parser::parse_der_integer(msg_type_s) { + IResult::Done(_, o) => o, + IResult::Incomplete(needed) => { return IResult::Incomplete(needed); }, + IResult::Error(err) => { return IResult::Error(err); }, + }; + SCLogDebug!("msg_type {:?}", msg_type); + + let padding_s = match base_seq[2].content.as_slice() { + Ok(s) => s, + Err(_) => { + return IResult::Error(error_code!(ErrorKind::Custom(SECBLOB_KRB_FMT_ERR))); + }, + }; + let padding = match der_parser::parse_der_bitstring(padding_s) { + IResult::Done(_, o) => o, + IResult::Incomplete(needed) => { return IResult::Incomplete(needed); }, + IResult::Error(err) => { return IResult::Error(err); }, + }; + SCLogDebug!("padding {:?}", padding); + + let ticket_s = match base_seq[3].content.as_slice() { + Ok(s) => s, + Err(_) => { + return IResult::Error(error_code!(ErrorKind::Custom(SECBLOB_KRB_FMT_ERR))); + }, + }; + parse_kerberos5_request_ticket(ticket_s) +} + + +pub const SECBLOB_NOT_SPNEGO : u32 = 128; +pub const SECBLOB_KRB_FMT_ERR : u32 = 129; + +fn parse_secblob_get_spnego(blob: &[u8]) -> IResult<&[u8], &[u8]> +{ + let (rem, base_o) = match der_parser::parse_der(blob) { + IResult::Done(rem, o) => (rem, o), + IResult::Incomplete(needed) => { return IResult::Incomplete(needed); }, + IResult::Error(err) => { return IResult::Error(err); }, + }; + SCLogDebug!("parse_secblob_get_spnego: base_o {:?}", base_o); + let d = match base_o.content.as_slice() { + Err(_) => { return IResult::Error(error_code!(ErrorKind::Custom(SECBLOB_NOT_SPNEGO))); }, + Ok(d) => d, + }; + let (next, o) = match der_parser::parse_der_oid(d) { + IResult::Done(rem,y) => { (rem,y) }, + IResult::Incomplete(needed) => { return IResult::Incomplete(needed); }, + IResult::Error(err) => { return IResult::Error(err); }, + }; + SCLogDebug!("parse_secblob_get_spnego: sub_o {:?}", o); + + let oid = match o.content.as_oid() { + Ok(oid) => oid, + Err(_) => { + return IResult::Error(error_code!(ErrorKind::Custom(SECBLOB_NOT_SPNEGO))); + }, + }; + SCLogDebug!("oid {}", oid.to_string()); + + match oid.to_string().as_str() { + "1.3.6.1.5.5.2" => { + SCLogDebug!("SPNEGO {}", oid); + }, + _ => { + return IResult::Error(error_code!(ErrorKind::Custom(SECBLOB_NOT_SPNEGO))); + }, + } + + SCLogDebug!("parse_secblob_get_spnego: next {:?}", next); + SCLogDebug!("parse_secblob_get_spnego: DONE"); + IResult::Done(rem, next) +} + +fn parse_secblob_spnego_start(blob: &[u8]) -> IResult<&[u8], &[u8]> +{ + let (rem, o) = match der_parser::parse_der(blob) { + IResult::Done(rem,o) => { + SCLogDebug!("o {:?}", o); + (rem, o) + }, + IResult::Incomplete(needed) => { return IResult::Incomplete(needed); }, + IResult::Error(err) => { return IResult::Error(err); }, + }; + let d = match o.content.as_slice() { + Ok(d) => { + SCLogDebug!("d: next data len {}",d.len()); + d + }, + _ => { + return IResult::Error(error_code!(ErrorKind::Custom(SECBLOB_NOT_SPNEGO))); + }, + }; + IResult::Done(rem, d) +} + +fn parse_secblob_spnego(state: &mut SMBState, blob: &[u8]) +{ + let mut ntlmssp = false; + let mut kerberos = false; + let mut kticket : Option = None; + + let o = match der_parser::parse_der_sequence(blob) { + IResult::Done(_, o) => o, + _ => { return; }, + }; + for s in o { + SCLogDebug!("s {:?}", s); + + let n = match s.content.as_slice() { + Ok(s) => s, + _ => { continue; }, + }; + let o = match der_parser::parse_der(n) { + IResult::Done(_,x) => x, + _ => { continue; }, + }; + SCLogDebug!("o {:?}", o); + match o.content { + der_parser::DerObjectContent::Sequence(ref seq) => { + for se in seq { + SCLogDebug!("SEQ {:?}", se); + match se.content { + der_parser::DerObjectContent::OID(ref oid) => { + SCLogDebug!("OID {:?}", oid); + match oid.to_string().as_str() { + "1.2.840.48018.1.2.2" => { SCLogDebug!("Microsoft Kerberos 5"); }, + "1.2.840.113554.1.2.2" => { SCLogDebug!("Kerberos 5"); kerberos = true; }, + "1.2.840.113554.1.2.2.1" => { SCLogDebug!("krb5-name"); }, + "1.2.840.113554.1.2.2.2" => { SCLogDebug!("krb5-principal"); }, + "1.2.840.113554.1.2.2.3" => { SCLogDebug!("krb5-user-to-user-mech"); }, + "1.3.6.1.4.1.311.2.2.10" => { SCLogDebug!("NTLMSSP"); ntlmssp = true; }, + "1.3.6.1.4.1.311.2.2.30" => { SCLogDebug!("NegoEx"); }, + _ => { SCLogNotice!("unexpected OID {:?}", oid); }, + } + }, + _ => { SCLogNotice!("expected OID, got {:?}", se); }, + } + } + }, + der_parser::DerObjectContent::OctetString(ref os) => { + if kerberos { + match parse_kerberos5_request(os) { + IResult::Done(_, t) => { + kticket = Some(t) + }, + _ => { }, + } + } + + if ntlmssp && kticket == None { + SCLogDebug!("parsing expected NTLMSSP"); + parse_ntlmssp_blob(state, os); + } + }, + _ => {}, + } + } + + state.krb_ticket = kticket; +} + +#[derive(Debug,PartialEq)] +pub struct NtlmsspData { + pub host: Vec, + pub user: Vec, + pub domain: Vec, +} + +/// take in blob, search for the header and parse it +fn parse_ntlmssp_blob(state: &mut SMBState, blob: &[u8]) +{ + SCLogDebug!("NTLMSSP {:?}", blob); + match parse_ntlmssp(blob) { + IResult::Done(_, nd) => { + SCLogDebug!("NTLMSSP TYPE {}/{} nd {:?}", + nd.msg_type, &ntlmssp_type_string(nd.msg_type), nd); + match nd.msg_type { + NTLMSSP_NEGOTIATE => { + }, + NTLMSSP_AUTH => { + match parse_ntlm_auth_record(nd.data) { + IResult::Done(_, ad) => { + SCLogDebug!("auth data {:?}", ad); + let mut host = ad.host.to_vec(); + host.retain(|&i|i != 0x00); + let mut user = ad.user.to_vec(); + user.retain(|&i|i != 0x00); + let mut domain = ad.domain.to_vec(); + domain.retain(|&i|i != 0x00); + + let d = NtlmsspData { + host: host, + user: user, + domain: domain, + }; + state.ntlmssp = Some(d); + }, + _ => {}, + } + }, + _ => {}, + } + }, + _ => {}, + } +} + +// if spnego parsing fails try to fall back to ntlmssp +pub fn parse_secblob(state: &mut SMBState, blob: &[u8]) +{ + match parse_secblob_get_spnego(blob) { + IResult::Done(_, spnego) => { + match parse_secblob_spnego_start(spnego) { + IResult::Done(_, spnego_start) => { + parse_secblob_spnego(state, spnego_start); + }, + _ => { + parse_ntlmssp_blob(state, blob); + }, + } + }, + _ => { + parse_ntlmssp_blob(state, blob); + }, + } +} diff --git a/rust/src/smb/dcerpc.rs b/rust/src/smb/dcerpc.rs new file mode 100644 index 0000000000..d944369793 --- /dev/null +++ b/rust/src/smb/dcerpc.rs @@ -0,0 +1,556 @@ +/* Copyright (C) 2017 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. + */ + +// written by Victor Julien +extern crate libc; + +use nom::IResult; +use log::*; + +use smb::smb::*; +use smb::smb2::*; +use smb::dcerpc_records::*; +use smb::events::*; + +pub const DCERPC_TYPE_REQUEST: u8 = 0; +pub const DCERPC_TYPE_PING: u8 = 1; +pub const DCERPC_TYPE_RESPONSE: u8 = 2; +pub const DCERPC_TYPE_FAULT: u8 = 3; +pub const DCERPC_TYPE_WORKING: u8 = 4; +pub const DCERPC_TYPE_NOCALL: u8 = 5; +pub const DCERPC_TYPE_REJECT: u8 = 6; +pub const DCERPC_TYPE_ACK: u8 = 7; +pub const DCERPC_TYPE_CL_CANCEL: u8 = 8; +pub const DCERPC_TYPE_FACK: u8 = 9; +pub const DCERPC_TYPE_CANCEL_ACK: u8 = 10; +pub const DCERPC_TYPE_BIND: u8 = 11; +pub const DCERPC_TYPE_BINDACK: u8 = 12; +pub const DCERPC_TYPE_BINDNAK: u8 = 13; +pub const DCERPC_TYPE_ALTER_CONTEXT: u8 = 14; +pub const DCERPC_TYPE_ALTER_CONTEXT_RESP: u8 = 15; +pub const DCERPC_TYPE_AUTH3: u8 = 16; +pub const DCERPC_TYPE_SHUTDOWN: u8 = 17; +pub const DCERPC_TYPE_CO_CANCEL: u8 = 18; +pub const DCERPC_TYPE_ORPHANED: u8 = 19; +pub const DCERPC_TYPE_RTS: u8 = 20; + +pub fn dcerpc_type_string(t: u8) -> String { + match t { + DCERPC_TYPE_REQUEST => "REQUEST", + DCERPC_TYPE_PING => "PING", + DCERPC_TYPE_RESPONSE => "RESPONSE", + DCERPC_TYPE_FAULT => "FAULT", + DCERPC_TYPE_WORKING => "WORKING", + DCERPC_TYPE_NOCALL => "NOCALL", + DCERPC_TYPE_REJECT => "REJECT", + DCERPC_TYPE_ACK => "ACK", + DCERPC_TYPE_CL_CANCEL => "CL_CANCEL", + DCERPC_TYPE_FACK => "FACK", + DCERPC_TYPE_CANCEL_ACK => "CANCEL_ACK", + DCERPC_TYPE_BIND => "BIND", + DCERPC_TYPE_BINDACK => "BINDACK", + DCERPC_TYPE_BINDNAK => "BINDNAK", + DCERPC_TYPE_ALTER_CONTEXT => "ALTER_CONTEXT", + DCERPC_TYPE_ALTER_CONTEXT_RESP => "ALTER_CONTEXT_RESP", + DCERPC_TYPE_AUTH3 => "AUTH3", + DCERPC_TYPE_SHUTDOWN => "SHUTDOWN", + DCERPC_TYPE_CO_CANCEL => "CO_CANCEL", + DCERPC_TYPE_ORPHANED => "ORPHANED", + DCERPC_TYPE_RTS => "RTS", + _ => { return (t).to_string(); }, + }.to_string() +} + +impl SMBCommonHdr { + /// helper for DCERPC tx tracking. Check if we need + /// to use the msg_id/multiplex_id in TX tracking. + /// + pub fn to_dcerpc(&self, vercmd: &SMBVerCmdStat) -> SMBCommonHdr { + // only use the msg id for IOCTL, not for READ/WRITE + // as there request/response are different transactions + let mut use_msg_id = self.msg_id; + match vercmd.get_version() { + 2 => { + let (_, cmd2) = vercmd.get_smb2_cmd(); + let x = match cmd2 as u16 { + SMB2_COMMAND_READ => { 0 }, + SMB2_COMMAND_WRITE => { 0 }, + SMB2_COMMAND_IOCTL => { self.msg_id }, + _ => { self.msg_id }, + }; + use_msg_id = x; + }, + 1 => { + SCLogDebug!("FIXME TODO"); + //let (_, cmd1) = vercmd.get_smb1_cmd(); + //if cmd1 != SMB1_COMMAND_IOCTL { + use_msg_id = 0; + //} + }, + _ => { }, + } + SMBCommonHdr { + ssn_id: self.ssn_id, + tree_id: self.tree_id, + msg_id: use_msg_id, + rec_type: SMBHDR_TYPE_DCERPCTX, + } + } +} + +#[derive(Debug)] +pub struct DCERPCIface { + pub uuid: Vec, + pub ver: u16, + pub ver_min: u16, + pub ack_result: u16, + pub ack_reason: u16, + pub acked: bool, +} + +impl DCERPCIface { + pub fn new(uuid: Vec, ver: u16, ver_min: u16) -> DCERPCIface { + DCERPCIface { + uuid: uuid, + ver:ver, + ver_min:ver_min, + ack_result:0, + ack_reason:0, + acked:false, + } + } +} + +pub fn dcerpc_uuid_to_string(i: &DCERPCIface) -> String { + let output = format!("{:02x}{:02x}{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}", + i.uuid[0], i.uuid[1], i.uuid[2], i.uuid[3], + i.uuid[4], i.uuid[5], i.uuid[6], i.uuid[7], + i.uuid[8], i.uuid[9], i.uuid[10], i.uuid[11], + i.uuid[12], i.uuid[13], i.uuid[14], i.uuid[15]); + return output; +} + + +#[derive(Debug)] +pub struct SMBTransactionDCERPC { + pub opnum: u16, + pub req_cmd: u8, + pub res_cmd: u8, + pub res_set: bool, + pub call_id: u32, + pub frag_cnt_ts: u16, + pub frag_cnt_tc: u16, + pub stub_data_ts: Vec, + pub stub_data_tc: Vec, +} + +impl SMBTransactionDCERPC { + pub fn new(req: u8, call_id: u32) -> SMBTransactionDCERPC { + return SMBTransactionDCERPC { + opnum: 0, + req_cmd: req, + res_cmd: 0, + res_set: false, + call_id: call_id, + frag_cnt_ts: 0, + frag_cnt_tc: 0, + stub_data_ts:Vec::new(), + stub_data_tc:Vec::new(), + } + } + pub fn set_result(&mut self, res: u8) { + self.res_set = true; + self.res_cmd = res; + } +} + +impl SMBState { + pub fn new_dcerpc_tx(&mut self, hdr: SMBCommonHdr, vercmd: SMBVerCmdStat, cmd: u8, call_id: u32) + -> (&mut SMBTransaction) + { + let mut tx = self.new_tx(); + tx.hdr = hdr; + tx.vercmd = vercmd; + tx.type_data = Some(SMBTransactionTypeData::DCERPC( + SMBTransactionDCERPC::new(cmd, call_id))); + + SCLogDebug!("SMB: TX DCERPC created: ID {} hdr {:?}", tx.id, tx.hdr); + self.transactions.push(tx); + let tx_ref = self.transactions.last_mut(); + return tx_ref.unwrap(); + } + + pub fn get_dcerpc_tx(&mut self, hdr: &SMBCommonHdr, vercmd: &SMBVerCmdStat, call_id: u32) + -> Option<&mut SMBTransaction> + { + let dce_hdr = hdr.to_dcerpc(vercmd); + + SCLogDebug!("looking for {:?}", dce_hdr); + for tx in &mut self.transactions { + let found = dce_hdr == tx.hdr.to_dcerpc(vercmd) && + match tx.type_data { + Some(SMBTransactionTypeData::DCERPC(ref x)) => { + x.call_id == call_id + }, + _ => { false }, + }; + if found { + return Some(tx); + } + } + return None; + } +} + +/// Handle DCERPC request data from a WRITE, IOCTL or TRANS record. +/// return bool indicating whether an tx has been created/updated. +/// +pub fn smb_write_dcerpc_record<'b>(state: &mut SMBState, + vercmd: SMBVerCmdStat, + hdr: SMBCommonHdr, + data: &'b [u8]) -> bool +{ + let mut bind_ifaces : Option> = None; + + SCLogDebug!("called for {} bytes of data", data.len()); + match parse_dcerpc_record(data) { + IResult::Done(_, dcer) => { + SCLogDebug!("DCERPC: version {}.{} write data {} => {:?}", + dcer.version_major, dcer.version_minor, dcer.data.len(), dcer); + + /* if this isn't the first frag, simply update the existing + * tx with the additional stub data */ + if dcer.packet_type == DCERPC_TYPE_REQUEST && dcer.first_frag == false { + SCLogDebug!("NOT the first frag. Need to find an existing TX"); + match parse_dcerpc_request_record(dcer.data, dcer.frag_len, dcer.little_endian) { + IResult::Done(_, recr) => { + let found = match state.get_dcerpc_tx(&hdr, &vercmd, dcer.call_id) { + Some(tx) => { + SCLogDebug!("previous CMD {} found at tx {} => {:?}", + dcer.packet_type, tx.id, tx); + if let Some(SMBTransactionTypeData::DCERPC(ref mut tdn)) = tx.type_data { + SCLogDebug!("additional frag of size {}", recr.data.len()); + tdn.stub_data_ts.extend_from_slice(&recr.data); + tdn.frag_cnt_ts += 1; + SCLogDebug!("stub_data now {}", tdn.stub_data_ts.len()); + } + if dcer.last_frag { + SCLogDebug!("last frag set, so request side of DCERPC closed"); + tx.request_done = true; + } else { + SCLogDebug!("NOT last frag, so request side of DCERPC remains open"); + } + true + }, + None => { + SCLogDebug!("NO previous CMD {} found", dcer.packet_type); + false + }, + }; + return found; + }, + _ => { + state.set_event(SMBEvent::MalformedData); + return false; + }, + } + } + + let tx = state.new_dcerpc_tx(hdr, vercmd, dcer.packet_type, dcer.call_id); + match dcer.packet_type { + DCERPC_TYPE_REQUEST => { + match parse_dcerpc_request_record(dcer.data, dcer.frag_len, dcer.little_endian) { + IResult::Done(_, recr) => { + SCLogDebug!("DCERPC: REQUEST {:?}", recr); + if let Some(SMBTransactionTypeData::DCERPC(ref mut tdn)) = tx.type_data { + SCLogDebug!("first frag size {}", recr.data.len()); + tdn.stub_data_ts.extend_from_slice(&recr.data); + tdn.opnum = recr.opnum; + tdn.frag_cnt_ts += 1; + SCLogDebug!("DCERPC: REQUEST opnum {} stub data len {}", + tdn.opnum, tdn.stub_data_ts.len()); + } + if dcer.last_frag { + tx.request_done = true; + } else { + SCLogDebug!("NOT last frag, so request side of DCERPC remains open"); + } + }, + _ => { + tx.set_event(SMBEvent::MalformedData); + }, + } + }, + DCERPC_TYPE_BIND => { + let brec = if dcer.little_endian == true { + parse_dcerpc_bind_record(dcer.data) + } else { + parse_dcerpc_bind_record_big(dcer.data) + }; + match brec { + IResult::Done(_, bindr) => { + SCLogDebug!("SMB DCERPC {:?} BIND {:?}", dcer, bindr); + + if bindr.ifaces.len() > 0 { + let mut ifaces: Vec = Vec::new(); + for i in bindr.ifaces { + let x = if dcer.little_endian == true { + vec![i.iface[3], i.iface[2], i.iface[1], i.iface[0], + i.iface[5], i.iface[4], i.iface[7], i.iface[6], + i.iface[8], i.iface[9], i.iface[10], i.iface[11], + i.iface[12], i.iface[13], i.iface[14], i.iface[15]] + } else { + i.iface.to_vec() + }; + let d = DCERPCIface::new(x,i.ver,i.ver_min); + SCLogDebug!("UUID {} version {}/{} bytes {:?}", + dcerpc_uuid_to_string(&d), + i.ver, i.ver_min,i.iface); + ifaces.push(d); + } + bind_ifaces = Some(ifaces); + } + tx.request_done = true; + }, + _ => { + tx.set_event(SMBEvent::MalformedData); + }, + } + } + 21...255 => { + tx.set_event(SMBEvent::MalformedData); + }, + _ => { }, // valid type w/o special processing + } + }, + _ => { + state.set_event(SMBEvent::MalformedData); + }, + } + + state.dcerpc_ifaces = bind_ifaces; // TODO store per ssn + return true; +} + +/// Update TX for bind ack. Needs to update both tx and state. +/// +fn smb_dcerpc_response_bindack( + state: &mut SMBState, + vercmd: SMBVerCmdStat, + hdr: SMBCommonHdr, + dcer: &DceRpcRecord, + ntstatus: u32) +{ + match parse_dcerpc_bindack_record(dcer.data) { + IResult::Done(_, bindackr) => { + SCLogDebug!("SMB READ BINDACK {:?}", bindackr); + + let found = match state.get_dcerpc_tx(&hdr, &vercmd, dcer.call_id) { + Some(tx) => { + if let Some(SMBTransactionTypeData::DCERPC(ref mut tdn)) = tx.type_data { + tdn.set_result(DCERPC_TYPE_BINDACK); + } + tx.vercmd.set_ntstatus(ntstatus); + tx.response_done = true; + true + }, + None => false, + }; + if found { + match state.dcerpc_ifaces { + Some(ref mut ifaces) => { + let mut i = 0; + for r in bindackr.results { + if i >= ifaces.len() { + // TODO set event: more acks that requests + break; + } + ifaces[i].ack_result = r.ack_result; + ifaces[i].acked = true; + i = i + 1; + } + }, + _ => {}, + } + } + }, + _ => { + state.set_event(SMBEvent::MalformedData); + }, + } +} + +fn smb_read_dcerpc_record_error(state: &mut SMBState, + hdr: SMBCommonHdr, vercmd: SMBVerCmdStat, ntstatus: u32) + -> bool +{ + let ver = vercmd.get_version(); + let cmd = if ver == 2 { + let (_, c) = vercmd.get_smb2_cmd(); + c + } else { + let (_, c) = vercmd.get_smb1_cmd(); + c as u16 + }; + + let found = match state.get_generic_tx(ver, cmd, &hdr) { + Some(tx) => { + SCLogDebug!("found"); + tx.set_status(ntstatus, false); + tx.response_done = true; + true + }, + None => { + SCLogNotice!("NOT found"); + false + }, + }; + return found; +} + +/// Handle DCERPC reply record. Called for READ, TRANS, IOCTL +/// +pub fn smb_read_dcerpc_record<'b>(state: &mut SMBState, + vercmd: SMBVerCmdStat, + hdr: SMBCommonHdr, + guid: &[u8], + indata: &'b [u8]) -> bool +{ + let (_, ntstatus) = vercmd.get_ntstatus(); + + if ntstatus != SMB_NTSTATUS_SUCCESS { + return smb_read_dcerpc_record_error(state, hdr, vercmd, ntstatus); + } + + SCLogDebug!("lets first see if we have prior data"); + // msg_id 0 as this data crosses cmd/reply pairs + let ehdr = SMBCommonHdr::new(SMBHDR_TYPE_TRANS_FRAG, + hdr.ssn_id as u64, hdr.tree_id as u32, 0 as u64); + let ekey = SMBHashKeyHdrGuid::new(ehdr, guid.to_vec()); + SCLogDebug!("ekey {:?}", ekey); + let mut prevdata = match state.ssnguid2vec_map.remove(&ekey) { + Some(s) => s, + None => Vec::new(), + }; + SCLogDebug!("indata {} prevdata {}", indata.len(), prevdata.len()); + prevdata.extend_from_slice(&indata); + let data = prevdata; + + let mut malformed = false; + + if data.len() == 0 { + SCLogNotice!("weird: no DCERPC data"); // TODO + // TODO set event? + return false; + + } else { + match parse_dcerpc_record(&data) { + IResult::Done(_, dcer) => { + SCLogDebug!("DCERPC: version {}.{} read data {} => {:?}", + dcer.version_major, dcer.version_minor, dcer.data.len(), dcer); + + if ntstatus == SMB_NTSTATUS_BUFFER_OVERFLOW && data.len() < dcer.frag_len as usize { + SCLogDebug!("short record {} < {}: lets see if we expected this", data.len(), dcer.frag_len); + let ehdr = SMBCommonHdr::new(SMBHDR_TYPE_MAX_SIZE, + hdr.ssn_id as u64, hdr.tree_id as u32, hdr.msg_id as u64); + let max_size = match state.ssn2maxsize_map.remove(&ehdr) { + Some(s) => s, + None => 0, + }; + if max_size as usize == data.len() { + SCLogDebug!("YES this is expected. We should now see a follow up READ for {} bytes", + dcer.frag_len as usize - data.len()); + let store = match state.get_dcerpc_tx(&hdr, &vercmd, dcer.call_id) { + Some(_) => true, + None => { + SCLogDebug!("no TX found"); + false + }, + }; + if store { + SCLogDebug!("storing partial data in state"); + let ehdr = SMBCommonHdr::new(SMBHDR_TYPE_MAX_SIZE, + hdr.ssn_id as u64, hdr.tree_id as u32, 0 as u64); + state.ssn2vec_map.insert(ehdr, data.to_vec()); + } + return true; // TODO review + } + } + + if dcer.packet_type == DCERPC_TYPE_BINDACK { + smb_dcerpc_response_bindack(state, vercmd, hdr, &dcer, ntstatus); + return true; + } + + let tx = match state.get_dcerpc_tx(&hdr, &vercmd, dcer.call_id) { + Some(tx) => tx, + None => { + SCLogNotice!("no tx"); + return false; }, + }; + + match dcer.packet_type { + DCERPC_TYPE_RESPONSE => { + match parse_dcerpc_response_record(dcer.data, dcer.frag_len) { + IResult::Done(_, respr) => { + SCLogDebug!("SMBv1 READ RESPONSE {:?}", respr); + 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.stub_data_tc.extend_from_slice(&respr.data); + tdn.frag_cnt_tc += 1; + } + tx.vercmd.set_ntstatus(ntstatus); + tx.response_done = dcer.last_frag; + }, + _ => { + tx.set_event(SMBEvent::MalformedData); + }, + } + }, + DCERPC_TYPE_BINDACK => { + // handled above + }, + 21...255 => { + if let Some(SMBTransactionTypeData::DCERPC(ref mut tdn)) = tx.type_data { + tdn.set_result(dcer.packet_type); + } + tx.vercmd.set_ntstatus(ntstatus); + tx.response_done = true; + tx.set_event(SMBEvent::MalformedData); + } + _ => { // valid type w/o special processing + if let Some(SMBTransactionTypeData::DCERPC(ref mut tdn)) = tx.type_data { + tdn.set_result(dcer.packet_type); + } + tx.vercmd.set_ntstatus(ntstatus); + tx.response_done = true; + }, + } + }, + _ => { + malformed = true; + }, + } + } + + if malformed { + state.set_event(SMBEvent::MalformedData); + } + + return true; +} diff --git a/rust/src/smb/dcerpc_records.rs b/rust/src/smb/dcerpc_records.rs new file mode 100644 index 0000000000..b4ed08f39e --- /dev/null +++ b/rust/src/smb/dcerpc_records.rs @@ -0,0 +1,233 @@ +/* Copyright (C) 2017 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. + */ + +use nom::{rest, le_u8, be_u16, le_u16, le_u32, IResult, ErrorKind, Endianness}; + +#[derive(Debug,PartialEq)] +pub struct DceRpcResponseRecord<'a> { + pub data: &'a[u8], +} + +/// parse a packet type 'response' DCERPC record. Implemented +/// as function to be able to pass the fraglen in. +pub fn parse_dcerpc_response_record(i:&[u8], frag_len: u16 ) + -> IResult<&[u8], DceRpcResponseRecord> +{ + if frag_len < 24 { + return IResult::Error(error_code!(ErrorKind::Custom(128))); + } + do_parse!(i, + take!(8) + >> data:take!(frag_len - 24) + >> (DceRpcResponseRecord { + data:data, + }) + ) +} + +#[derive(Debug,PartialEq)] +pub struct DceRpcRequestRecord<'a> { + pub opnum: u16, + pub data: &'a[u8], +} + +/// parse a packet type 'request' DCERPC record. Implemented +/// as function to be able to pass the fraglen in. +pub fn parse_dcerpc_request_record(i:&[u8], frag_len: u16, little: bool) + -> IResult<&[u8], DceRpcRequestRecord> +{ + if frag_len < 24 { + return IResult::Error(error_code!(ErrorKind::Custom(128))); + } + do_parse!(i, + take!(6) + >> endian: value!(if little { Endianness::Little } else { Endianness::Big }) + >> opnum: u16!(endian) + >> data:take!(frag_len - 24) + >> (DceRpcRequestRecord { + opnum:opnum, + data:data, + }) + ) +} + +#[derive(Debug,PartialEq)] +pub struct DceRpcBindIface<'a> { + pub iface: &'a[u8], + pub ver: u16, + pub ver_min: u16, +} + +named!(pub parse_dcerpc_bind_iface, + do_parse!( + ctx_id: le_u16 + >> num_trans_items: le_u8 + >> take!(1) // reserved + >> interface: take!(16) + >> ver: le_u16 + >> ver_min: le_u16 + >> take!(20) + >> (DceRpcBindIface { + iface:interface, + ver:ver, + ver_min:ver_min, + }) +)); + +named!(pub parse_dcerpc_bind_iface_big, + do_parse!( + ctx_id: le_u16 + >> num_trans_items: le_u8 + >> take!(1) // reserved + >> interface: take!(16) + >> ver_min: be_u16 + >> ver: be_u16 + >> take!(20) + >> (DceRpcBindIface { + iface:interface, + ver:ver, + ver_min:ver_min, + }) +)); + +#[derive(Debug,PartialEq)] +pub struct DceRpcBindRecord<'a> { + pub num_ctx_items: u8, + pub ifaces: Vec>, +} + +named!(pub parse_dcerpc_bind_record, + do_parse!( + max_xmit_frag: le_u16 + >> max_recv_frag: le_u16 + >> assoc_group: take!(4) + >> num_ctx_items: le_u8 + >> take!(3) // reserved + >> ifaces: count!(parse_dcerpc_bind_iface, num_ctx_items as usize) + >> (DceRpcBindRecord { + num_ctx_items:num_ctx_items, + ifaces:ifaces, + }) +)); + +named!(pub parse_dcerpc_bind_record_big, + do_parse!( + max_xmit_frag: be_u16 + >> max_recv_frag: be_u16 + >> assoc_group: take!(4) + >> num_ctx_items: le_u8 + >> take!(3) // reserved + >> ifaces: count!(parse_dcerpc_bind_iface_big, num_ctx_items as usize) + >> (DceRpcBindRecord { + num_ctx_items:num_ctx_items, + ifaces:ifaces, + }) +)); + +#[derive(Debug,PartialEq)] +pub struct DceRpcBindAckResult<'a> { + pub ack_result: u16, + pub ack_reason: u16, + pub transfer_syntax: &'a[u8], + pub syntax_version: u32, +} + +named!(pub parse_dcerpc_bindack_result, + do_parse!( + ack_result: le_u16 + >> ack_reason: le_u16 + >> transfer_syntax: take!(16) + >> syntax_version: le_u32 + >> (DceRpcBindAckResult { + ack_result:ack_result, + ack_reason:ack_reason, + transfer_syntax:transfer_syntax, + syntax_version:syntax_version, + }) +)); + +#[derive(Debug,PartialEq)] +pub struct DceRpcBindAckRecord<'a> { + pub num_results: u8, + pub results: Vec>, +} + +named!(pub parse_dcerpc_bindack_record, + do_parse!( + max_xmit_frag: le_u16 + >> max_recv_frag: le_u16 + >> assoc_group: take!(4) + >> sec_addr_len: le_u16 + >> take!(sec_addr_len) + >> cond!((sec_addr_len+2) % 4 != 0, take!(4 - (sec_addr_len+2) % 4)) + >> num_results: le_u8 + >> take!(3) // padding + >> results: count!(parse_dcerpc_bindack_result, num_results as usize) + >> (DceRpcBindAckRecord { + num_results:num_results, + results:results, + }) +)); + +#[derive(Debug,PartialEq)] +pub struct DceRpcRecord<'a> { + pub version_major: u8, + pub version_minor: u8, + + pub first_frag: bool, + pub last_frag: bool, + + pub frag_len: u16, + + pub little_endian: bool, + + pub packet_type: u8, + + pub call_id: u32, + pub data: &'a[u8], +} + +named!(pub parse_dcerpc_record, + do_parse!( + version_major: le_u8 + >> version_minor: le_u8 + >> packet_type: le_u8 + >> packet_flags: bits!(tuple!( + take_bits!(u8, 6), + take_bits!(u8, 1), // last (1) + take_bits!(u8, 1))) // first (2) + >> data_rep: bits!(tuple!( + take_bits!(u32, 3), + take_bits!(u32, 1), // endianess + take_bits!(u32, 28))) + >> endian: value!(if data_rep.1 == 0 { Endianness::Big } else { Endianness::Little }) + >> frag_len: u16!(endian) + >> auth: u16!(endian) + >> call_id: u32!(endian) + >> data:rest + >> (DceRpcRecord { + version_major:version_major, + version_minor:version_minor, + packet_type:packet_type, + first_frag:packet_flags.2==1, + last_frag:packet_flags.1==1, + frag_len: frag_len, + little_endian:data_rep.1==1, + call_id:call_id, + data:data, + }) +)); diff --git a/rust/src/smb/debug.rs b/rust/src/smb/debug.rs new file mode 100644 index 0000000000..f231e6d576 --- /dev/null +++ b/rust/src/smb/debug.rs @@ -0,0 +1,65 @@ +/* Copyright (C) 2018 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. + */ + +use log::*; +use smb::smb::*; + +impl SMBState { + pub fn _debug_tx_stats(&self) { + if self.transactions.len() > 1 { + let txf = self.transactions.first().unwrap(); + let txl = self.transactions.last().unwrap(); + + SCLogNotice!("TXs {} MIN {} MAX {}", self.transactions.len(), txf.id, txl.id); + SCLogNotice!("- OLD tx.id {}: {:?}", txf.id, txf); + SCLogNotice!("- NEW tx.id {}: {:?}", txl.id, txl); + self._dump_txs(); + } + } + + pub fn _dump_txs(&self) { + let len = self.transactions.len(); + for i in 0..len { + let tx = &self.transactions[i]; + let ver = tx.vercmd.get_version(); + let _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; + } + + match tx.type_data { + Some(SMBTransactionTypeData::FILE(ref d)) => { + SCLogDebug!("idx {} tx id {} progress {}/{} filename {} type_data {:?}", + i, tx.id, tx.request_done, tx.response_done, + String::from_utf8_lossy(&d.file_name), tx.type_data); + }, + _ => { + SCLogNotice!("idx {} tx id {} ver:{} cmd:{} progress {}/{} type_data {:?} tx {:?}", + i, tx.id, ver, _smbcmd, tx.request_done, tx.response_done, tx.type_data, tx); + }, + } + } + } + + pub fn _debug_state_stats(&self) { + SCLogNotice!("ssn2vec_map {} guid2name_map {} ssn2vecoffset_map {} ssn2tree_map {} ssn2maxsize_map {} ssnguid2vec_map {} tcp_buffer_ts {} tcp_buffer_tc {} file_ts_guid {} file_tc_guid {} transactions {}", self.ssn2vec_map.len(), self.guid2name_map.len(), self.ssn2vecoffset_map.len(), self.ssn2tree_map.len(), self.ssn2maxsize_map.len(), self.ssnguid2vec_map.len(), self.tcp_buffer_ts.len(), self.tcp_buffer_tc.len(), self.file_ts_guid.len(), self.file_tc_guid.len(), self.transactions.len()); + } +} diff --git a/rust/src/smb/detect.rs b/rust/src/smb/detect.rs new file mode 100644 index 0000000000..698e2c7d43 --- /dev/null +++ b/rust/src/smb/detect.rs @@ -0,0 +1,214 @@ +/* Copyright (C) 2017 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. + */ + +extern crate libc; + +use std; +use std::ptr; +use core::*; +use log::*; +use smb::smb::*; + +#[no_mangle] +pub extern "C" fn rs_smb_tx_get_share(tx: &mut SMBTransaction, + buffer: *mut *const libc::uint8_t, + buffer_len: *mut libc::uint32_t) + -> libc::uint8_t +{ + match tx.type_data { + Some(SMBTransactionTypeData::TREECONNECT(ref x)) => { + SCLogDebug!("is_pipe {}", x.is_pipe); + if !x.is_pipe { + unsafe { + *buffer = x.share_name.as_ptr(); + *buffer_len = x.share_name.len() as libc::uint32_t; + return 1; + } + } + } + _ => { + } + } + + unsafe { + *buffer = ptr::null(); + *buffer_len = 0; + } + return 0; +} + +#[no_mangle] +pub extern "C" fn rs_smb_tx_get_named_pipe(tx: &mut SMBTransaction, + buffer: *mut *const libc::uint8_t, + buffer_len: *mut libc::uint32_t) + -> libc::uint8_t +{ + match tx.type_data { + Some(SMBTransactionTypeData::TREECONNECT(ref x)) => { + SCLogDebug!("is_pipe {}", x.is_pipe); + if x.is_pipe { + unsafe { + *buffer = x.share_name.as_ptr(); + *buffer_len = x.share_name.len() as libc::uint32_t; + return 1; + } + } + } + _ => { + } + } + + unsafe { + *buffer = ptr::null(); + *buffer_len = 0; + } + return 0; +} + +#[no_mangle] +pub extern "C" fn rs_smb_tx_get_stub_data(tx: &mut SMBTransaction, + direction: u8, + buffer: *mut *const libc::uint8_t, + buffer_len: *mut libc::uint32_t) + -> libc::uint8_t +{ + match tx.type_data { + Some(SMBTransactionTypeData::DCERPC(ref x)) => { + let vref = if direction == STREAM_TOSERVER { + &x.stub_data_ts + } else { + &x.stub_data_tc + }; + if vref.len() > 0 { + unsafe { + *buffer = vref.as_ptr(); + *buffer_len = vref.len() as libc::uint32_t; + return 1; + } + } + } + _ => { + } + } + + unsafe { + *buffer = ptr::null(); + *buffer_len = 0; + } + return 0; +} + +#[no_mangle] +pub extern "C" fn rs_smb_tx_get_dce_opnum(tx: &mut SMBTransaction, + opnum: *mut libc::uint16_t) + -> libc::uint8_t +{ + SCLogNotice!("rs_smb_tx_get_dce_opnum: start"); + match tx.type_data { + Some(SMBTransactionTypeData::DCERPC(ref x)) => { + if x.req_cmd == 1 { // REQUEST + unsafe { + *opnum = x.opnum as libc::uint16_t; + return 1; + } + } + } + _ => { + } + } + + unsafe { + *opnum = 0; + } + return 0; +} + +/* based on: + * typedef enum DetectDceIfaceOperators_ { + * DETECT_DCE_IFACE_OP_NONE = 0, + * DETECT_DCE_IFACE_OP_LT, + * DETECT_DCE_IFACE_OP_GT, + * DETECT_DCE_IFACE_OP_EQ, + * DETECT_DCE_IFACE_OP_NE, + * } DetectDceIfaceOperators; + */ +#[inline] +fn match_version(op: u8, them: u16, us: u16) -> bool { + let result = match op { + 0 => { // NONE + true + }, + 1 => { // LT + (them < us) + }, + 2 => { // GT + (them > us) + }, + 3 => { // EQ + (them == us) + }, + 4 => { // NE + (them != us) + }, + _ => { + panic!("called with invalid op {}", op); + }, + }; + result +} + +/* 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) + * - 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, + uuid_ptr: *mut libc::uint8_t, + uuid_len: libc::uint16_t, + ver_op: libc::uint8_t, + ver_check: libc::uint16_t) + -> libc::uint8_t +{ + let is_dcerpc_request = match tx.type_data { + Some(SMBTransactionTypeData::DCERPC(ref x)) => { x.req_cmd == 1 }, + _ => { false }, + }; + if !is_dcerpc_request { + return 0; + } + let ifaces = match state.dcerpc_ifaces { + Some(ref x) => x, + _ => { + return 0; + }, + }; + + let uuid = unsafe{std::slice::from_raw_parts(uuid_ptr, uuid_len as usize)}; + SCLogDebug!("looking for UUID {:?}", uuid); + + for i in ifaces { + SCLogDebug!("stored UUID {:?} acked {} ack_result {}", i, i.acked, i.ack_result); + + if i.acked && i.ack_result == 0 && i.uuid == uuid { + if match_version(ver_op as u8, ver_check as u16, i.ver) { + return 1; + } + } + } + return 0; +} diff --git a/rust/src/smb/events.rs b/rust/src/smb/events.rs new file mode 100644 index 0000000000..8e1f56f98a --- /dev/null +++ b/rust/src/smb/events.rs @@ -0,0 +1,71 @@ +/* Copyright (C) 2018 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. + */ + +use core::*; +use log::*; +use smb::smb::*; + +#[repr(u32)] +pub enum SMBEvent { + InternalError = 0, + MalformedData = 1, + RecordOverflow = 2, + MalformedNtlmsspRequest = 3, + MalformedNtlmsspResponse = 4, + DuplicateNegotiate = 5, +} + +pub fn smb_str_to_event(instr: &str) -> i32 { + SCLogDebug!("checking {}", instr); + match instr { + "internal_error" => SMBEvent::InternalError as i32, + "malformed_data" => SMBEvent::MalformedData as i32, + "record_overflow" => SMBEvent::RecordOverflow as i32, + "malformed_ntlmssp_request" => SMBEvent::MalformedNtlmsspRequest as i32, + "malformed_ntlmssp_response" => SMBEvent::MalformedNtlmsspResponse as i32, + "duplicate_negotiate" => SMBEvent::DuplicateNegotiate as i32, + _ => -1, + } +} + +impl SMBTransaction { + /// Set event. + pub fn set_event(&mut self, e: SMBEvent) { + sc_app_layer_decoder_events_set_event_raw(&mut self.events, e as u8); + } + + /// Set events from vector of events. + pub fn set_events(&mut self, events: Vec) { + for e in events { + sc_app_layer_decoder_events_set_event_raw(&mut self.events, e as u8); + } + } +} + +impl SMBState { + /// Set an event. The event is set on the most recent transaction. + pub fn set_event(&mut self, event: SMBEvent) { + let len = self.transactions.len(); + if len == 0 { + return; + } + + let tx = &mut self.transactions[len - 1]; + tx.set_event(event); + //sc_app_layer_decoder_events_set_event_raw(&mut tx.events, event as u8); + } +} diff --git a/rust/src/smb/files.rs b/rust/src/smb/files.rs new file mode 100644 index 0000000000..7064ab2ec1 --- /dev/null +++ b/rust/src/smb/files.rs @@ -0,0 +1,233 @@ +/* Copyright (C) 2018 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. + */ + +use core::*; +use log::*; +use filetracker::*; +use filecontainer::*; + +use smb::smb::*; + +/// File tracking transaction. Single direction only. +#[derive(Debug)] +pub struct SMBTransactionFile { + pub direction: u8, + pub guid: Vec, + pub file_name: Vec, + pub share_name: Vec, + pub file_tracker: FileTransferTracker, +} + +impl SMBTransactionFile { + pub fn new() -> SMBTransactionFile { + return SMBTransactionFile { + direction: 0, + guid: Vec::new(), + file_name: Vec::new(), + share_name: Vec::new(), + file_tracker: FileTransferTracker::new(), + } + } +} + +/// Wrapper around Suricata's internal file container logic. +#[derive(Debug)] +pub struct SMBFiles { + pub files_ts: FileContainer, + pub files_tc: FileContainer, + pub flags_ts: u16, + pub flags_tc: u16, +} + +impl SMBFiles { + pub fn new() -> SMBFiles { + SMBFiles { + files_ts:FileContainer::default(), + files_tc:FileContainer::default(), + flags_ts:0, + flags_tc:0, + } + } + pub fn free(&mut self) { + self.files_ts.free(); + self.files_tc.free(); + } + + pub fn get(&mut self, direction: u8) -> (&mut FileContainer, u16) + { + if direction == STREAM_TOSERVER { + (&mut self.files_ts, self.flags_ts) + } else { + (&mut self.files_tc, self.flags_tc) + } + } +} + +/// little wrapper around the FileTransferTracker::new_chunk method +pub fn filetracker_newchunk(ft: &mut FileTransferTracker, files: &mut FileContainer, + flags: u16, name: &Vec, data: &[u8], + chunk_offset: u64, chunk_size: u32, fill_bytes: u8, is_last: bool, xid: &u32) +{ + match unsafe {SURICATA_SMB_FILE_CONFIG} { + Some(sfcm) => { + ft.new_chunk(sfcm, files, flags, &name, data, chunk_offset, + chunk_size, fill_bytes, is_last, xid); } + None => panic!("BUG"), + } +} + +impl SMBState { + pub fn new_file_tx(&mut self, file_guid: &Vec, file_name: &Vec, direction: u8) + -> (&mut SMBTransaction, &mut FileContainer, u16) + { + let mut tx = self.new_tx(); + tx.type_data = Some(SMBTransactionTypeData::FILE(SMBTransactionFile::new())); + match tx.type_data { + Some(SMBTransactionTypeData::FILE(ref mut d)) => { + d.direction = direction; + d.guid = file_guid.to_vec(); + d.file_name = file_name.to_vec(); + d.file_tracker.tx_id = tx.id - 1; + }, + _ => { }, + } + SCLogDebug!("SMB: new_file_tx: TX FILE created: ID {} NAME {}", + tx.id, String::from_utf8_lossy(file_name)); + self.transactions.push(tx); + let tx_ref = self.transactions.last_mut(); + let (files, flags) = self.files.get(direction); + return (tx_ref.unwrap(), files, flags) + } + + pub fn get_file_tx_by_guid(&mut self, guid: &Vec, direction: u8) + -> Option<(&mut SMBTransaction, &mut FileContainer, u16)> + { + let g = guid.to_vec(); + for tx in &mut self.transactions { + let found = match tx.type_data { + Some(SMBTransactionTypeData::FILE(ref mut d)) => { + direction == d.direction && g == d.guid + }, + _ => { false }, + }; + + if found { + SCLogDebug!("SMB: Found SMB file TX with ID {}", tx.id); + let (files, flags) = self.files.get(direction); + return Some((tx, files, flags)); + } + } + SCLogDebug!("SMB: Failed to find SMB TX with GUID {:?}", guid); + return None; + } + + fn getfiles(&mut self, direction: u8) -> * mut FileContainer { + //SCLogDebug!("direction: {}", direction); + if direction == STREAM_TOCLIENT { + &mut self.files.files_tc as *mut FileContainer + } else { + &mut self.files.files_ts as *mut FileContainer + } + } + fn setfileflags(&mut self, direction: u8, flags: u16) { + SCLogDebug!("direction: {}, flags: {}", direction, flags); + if direction == 1 { + self.files.flags_tc = flags; + } else { + self.files.flags_ts = flags; + } + } + + // update in progress chunks for file transfers + // return how much data we consumed + pub fn filetracker_update(&mut self, direction: u8, data: &[u8], gap_size: u32) -> u32 { + let mut chunk_left = if direction == STREAM_TOSERVER { + self.file_ts_left + } else { + self.file_tc_left + }; + if chunk_left == 0 { + return 0 + } + SCLogDebug!("chunk_left {} data {}", chunk_left, data.len()); + let file_handle = if direction == STREAM_TOSERVER { + self.file_ts_guid.to_vec() + } else { + self.file_tc_guid.to_vec() + }; + + let data_to_handle_len = if chunk_left as usize >= data.len() { + data.len() + } else { + chunk_left as usize + }; + + if chunk_left <= data.len() as u32 { + chunk_left = 0; + } else { + chunk_left -= data.len() as u32; + } + + if direction == STREAM_TOSERVER { + self.file_ts_left = chunk_left; + } else { + self.file_tc_left = chunk_left; + } + + let ssn_gap = self.ts_ssn_gap | self.tc_ssn_gap; + // get the tx and update it + let consumed = match self.get_file_tx_by_guid(&file_handle, direction) { + Some((tx, files, flags)) => { + if let Some(SMBTransactionTypeData::FILE(ref mut tdf)) = tx.type_data { + if ssn_gap { + let queued_data = tdf.file_tracker.get_queued_size(); + if queued_data > 2000000 { // TODO should probably be configurable + SCLogDebug!("QUEUED size {} while we've seen GAPs. Truncating file.", queued_data); + tdf.file_tracker.trunc(files, flags); + } + } + + let file_data = &data[0..data_to_handle_len]; + let cs = tdf.file_tracker.update(files, flags, file_data, gap_size); + cs + } else { + 0 + } + }, + None => { + SCLogNotice!("not found for handle {:?}", file_handle); + 0 }, + }; + + return consumed; + } +} + +#[no_mangle] +pub extern "C" fn rs_smb_getfiles(direction: u8, ptr: *mut SMBState) -> * mut FileContainer { + if ptr.is_null() { panic!("NULL ptr"); }; + let parser = unsafe { &mut *ptr }; + parser.getfiles(direction) +} + +#[no_mangle] +pub extern "C" fn rs_smb_setfileflags(direction: u8, ptr: *mut SMBState, flags: u16) { + if ptr.is_null() { panic!("NULL ptr"); }; + let parser = unsafe { &mut *ptr }; + SCLogDebug!("direction {} flags {}", direction, flags); + parser.setfileflags(direction, flags) +} diff --git a/rust/src/smb/log.rs b/rust/src/smb/log.rs new file mode 100644 index 0000000000..c22accce13 --- /dev/null +++ b/rust/src/smb/log.rs @@ -0,0 +1,275 @@ +/* Copyright (C) 2017 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. + */ + +extern crate libc; + +use std::str; +use std::string::String; +use json::*; +use smb::smb::*; +use smb::smb1::*; +use smb::smb2::*; +use smb::dcerpc::*; +use nom; + +fn smb_common_header(state: &SMBState, tx: &SMBTransaction) -> Json +{ + let js = Json::object(); + js.set_integer("id", tx.id as u64); + + if state.dialect != 0 { + let dialect = &smb2_dialect_string(state.dialect); + js.set_string("dialect", &dialect); + } else { + let dialect = match &state.dialect_vec { + &Some(ref d) => { + match str::from_utf8(&d) { + Ok(v) => v, + Err(_) => "invalid", + } + }, + &None => { "unknown" }, + }; + js.set_string("dialect", &dialect); + } + + match tx.vercmd.get_version() { + 1 => { + let (ok, cmd) = tx.vercmd.get_smb1_cmd(); + if ok { + js.set_string("command", &smb1_command_string(cmd)); + } + }, + 2 => { + let (ok, cmd) = tx.vercmd.get_smb2_cmd(); + if ok { + js.set_string("command", &smb2_command_string(cmd)); + } + }, + _ => { }, + } + + match tx.vercmd.get_ntstatus() { + (true, ntstatus) => { + let status = smb_ntstatus_string(ntstatus); + js.set_string("status", &status); + let status_hex = format!("0x{:x}", ntstatus); + js.set_string("statux", &status_hex); + }, + (false, _) => { + match tx.vercmd.get_dos_error() { + (true, doserr) => { + let status = smb_dos_error_string(doserr); + js.set_string("status", &status); + let status_hex = format!("0x{:x}", doserr); + js.set_string("statux", &status_hex); + }, + (_, _) => { + }, + } + }, + } + + + js.set_integer("session_id", tx.hdr.ssn_id); + js.set_integer("tree_id", tx.hdr.tree_id as u64); + + js.set_boolean("request_done", tx.request_done); + js.set_boolean("response_done", tx.request_done); + + match state.ntlmssp { + Some(ref ntlmssp) => { + let jsd = Json::object(); + let domain = match str::from_utf8(&ntlmssp.domain) { + Ok(v) => v, + Err(_) => "UTF8_ERROR", + }; + jsd.set_string("domain", &domain); + + let user = match str::from_utf8(&ntlmssp.user) { + Ok(v) => v, + Err(_) => "UTF8_ERROR", + }; + jsd.set_string("user", &user); + + let host = match str::from_utf8(&ntlmssp.host) { + Ok(v) => v, + Err(_) => "UTF8_ERROR", + }; + jsd.set_string("host", &host); + js.set("ntlmssp", jsd); + } + None => {}, + } + + match state.krb_ticket { + Some(ref ticket) => { + let jsd = Json::object(); + let realm = match str::from_utf8(&ticket.realm) { + Ok(v) => v, + Err(_) => "UTF8_ERROR", + }; + jsd.set_string("realm", &realm); + let jsa = Json::array(); + for sname in &ticket.snames { + let name = match str::from_utf8(&sname) { + Ok(v) => v, + Err(_) => "UTF8_ERROR", + }; + jsa.array_append_string(&name); + } + jsd.set("snames", jsa); + js.set("kerberos", jsd); + }, + None => { }, + } + + match tx.type_data { + Some(SMBTransactionTypeData::CREATE(ref x)) => { + let mut name_raw = x.filename.to_vec(); + name_raw.retain(|&i|i != 0x00); + let name = String::from_utf8_lossy(&name_raw); + if x.directory { + js.set_string("directory", &name); + } else { + js.set_string("file", &name); + } + match x.disposition { + 1 => { js.set_string("disposition", "open"); }, + 2 => { js.set_string("disposition", "create"); }, + 5 => { js.set_string("disposition", "overwrite"); }, + _ => { js.set_string("disposition", "UNKNOWN"); }, + } + if x.delete_on_close { + js.set_string("access", "delete on close"); + } else { + js.set_string("access", "normal"); + } + }, + Some(SMBTransactionTypeData::NEGOTIATE(ref x)) => { + if x.smb_ver == 1 { + let jsa = Json::array(); + for d in &x.dialects { + let dialect = String::from_utf8_lossy(&d); + jsa.array_append_string(&dialect); + } + js.set("client_dialects", jsa); + } else if x.smb_ver == 2 { + let jsa = Json::array(); + for d in &x.dialects2 { + let dialect = String::from_utf8_lossy(&d); + jsa.array_append_string(&dialect); + } + js.set("client_dialects", jsa); + } + }, + Some(SMBTransactionTypeData::TREECONNECT(ref x)) => { + js.set_integer("tree_id", x.tree_id as u64); + + let share_name = String::from_utf8_lossy(&x.share_name); + if x.is_pipe { + js.set_string("named_pipe", &share_name); + } else { + js.set_string("share", &share_name); + } + }, + Some(SMBTransactionTypeData::FILE(ref x)) => { + let file_name = String::from_utf8_lossy(&x.file_name); + js.set_string("file", &file_name); + let share_name = String::from_utf8_lossy(&x.share_name); + js.set_string("share", &share_name); + + + if x.guid.len() >= 2 { + let fid_s = &x.guid[0..2]; + let fid_n = match nom::le_u16(&fid_s) { + nom::IResult::Done(_, x) => { + x as u16 + } + _ => 0 as u16 + }; + let fid_hex_str = format!("0x{:00x}", fid_n); + js.set_string("fid", &fid_hex_str); + } + }, + Some(SMBTransactionTypeData::DCERPC(ref x)) => { + let jsd = Json::object(); + jsd.set_string("request", &dcerpc_type_string(x.req_cmd)); + if x.res_set { + jsd.set_string("response", &dcerpc_type_string(x.res_cmd)); + } else { + jsd.set_string("response", "UNREPLIED"); + } + match x.req_cmd { + DCERPC_TYPE_REQUEST => { + jsd.set_integer("opnum", x.opnum as u64); + let req = Json::object(); + req.set_integer("frag_cnt", x.frag_cnt_ts as u64); + req.set_integer("stub_data_size", x.stub_data_ts.len() as u64); + jsd.set("req", req); + let res = Json::object(); + res.set_integer("frag_cnt", x.frag_cnt_tc as u64); + res.set_integer("stub_data_size", x.stub_data_tc.len() as u64); + jsd.set("res", res); + }, + DCERPC_TYPE_BIND => { + match state.dcerpc_ifaces { + Some(ref ifaces) => { + let jsa = Json::array(); + for i in ifaces { + let jso = Json::object(); + let ifstr = dcerpc_uuid_to_string(&i); + jso.set_string("uuid", &ifstr); + let vstr = format!("{}.{}", i.ver, i.ver_min); + jso.set_string("version", &vstr); + + if i.acked { + jso.set_integer("ack_result", i.ack_result as u64); + jso.set_integer("ack_reason", i.ack_reason as u64); + } + + jsa.array_append(jso); + } + + jsd.set("interfaces", jsa); + }, + _ => {}, + } + }, + _ => {}, + } + jsd.set_integer("call_id", x.call_id as u64); + js.set("dcerpc", jsd); + } + _ => { }, + } + return js; +} + +#[no_mangle] +pub extern "C" fn rs_smb_log_json_request(state: &mut SMBState, tx: &mut SMBTransaction) -> *mut JsonT +{ + let js = smb_common_header(state, tx); + return js.unwrap(); +} + +#[no_mangle] +pub extern "C" fn rs_smb_log_json_response(state: &mut SMBState, tx: &mut SMBTransaction) -> *mut JsonT +{ + let js = smb_common_header(state, tx); + return js.unwrap(); +} diff --git a/rust/src/smb/mod.rs b/rust/src/smb/mod.rs new file mode 100644 index 0000000000..31761dcd6f --- /dev/null +++ b/rust/src/smb/mod.rs @@ -0,0 +1,36 @@ +/* Copyright (C) 2017 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. + */ + +pub mod smb1_records; +pub mod smb2_records; +pub mod nbss_records; +pub mod dcerpc_records; +pub mod ntlmssp_records; + +pub mod smb; +pub mod smb1; +pub mod smb2; +pub mod dcerpc; +pub mod log; +pub mod detect; +pub mod debug; +pub mod events; +pub mod auth; +pub mod files; + +//#[cfg(feature = "lua")] +//pub mod lua; diff --git a/rust/src/smb/nbss_records.rs b/rust/src/smb/nbss_records.rs new file mode 100644 index 0000000000..c594529e78 --- /dev/null +++ b/rust/src/smb/nbss_records.rs @@ -0,0 +1,58 @@ +/* Copyright (C) 2017 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. + */ + +use nom::{rest}; + +#[derive(Debug,PartialEq)] +pub struct NbssRecord<'a> { + pub message_type: u8, + pub length: u32, + pub data: &'a[u8], +} + +named!(pub parse_nbss_record, + do_parse!( + type_and_len: bits!(tuple!( + take_bits!(u8, 8), + take_bits!(u32, 24))) + >> data: take!(type_and_len.1 as usize) + >> (NbssRecord { + message_type:type_and_len.0, + length:type_and_len.1, + data:data, + }) +)); + +#[derive(Debug,PartialEq)] +pub struct NbssRecordPartial<'a> { + pub message_type: u8, + pub length: u32, + pub data: &'a[u8], +} + +named!(pub parse_nbss_record_partial, + do_parse!( + type_and_len: bits!(tuple!( + take_bits!(u8, 8), + take_bits!(u32, 24))) + >> data: rest + >> (NbssRecordPartial { + message_type:type_and_len.0, + length:type_and_len.1, + data:data, + }) +)); diff --git a/rust/src/smb/ntlmssp_records.rs b/rust/src/smb/ntlmssp_records.rs new file mode 100644 index 0000000000..c47fa1125c --- /dev/null +++ b/rust/src/smb/ntlmssp_records.rs @@ -0,0 +1,85 @@ +/* Copyright (C) 2017 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. + */ + +use nom::{rest, le_u16, le_u32}; + +#[derive(Debug,PartialEq)] +pub struct NTLMSSPAuthRecord<'a> { + pub domain: &'a[u8], + pub user: &'a[u8], + pub host: &'a[u8], +} + +named!(pub parse_ntlm_auth_record, + dbg_dmp!(do_parse!( + lm_blob_len: le_u16 + >> lm_blob_maxlen: le_u16 + >> lm_blob_offset: le_u32 + + >> ntlmresp_blob_len: le_u16 + >> ntlmresp_blob_maxlen: le_u16 + >> ntlmresp_blob_offset: le_u32 + + >> domain_blob_len: le_u16 + >> domain_blob_maxlen: le_u16 + >> domain_blob_offset: le_u32 + + >> user_blob_len: le_u16 + >> user_blob_maxlen: le_u16 + >> user_blob_offset: le_u32 + + >> host_blob_len: le_u16 + >> host_blob_maxlen: le_u16 + >> host_blob_offset: le_u32 + + >> ssnkey_blob_len: le_u16 + >> ssnkey_blob_maxlen: le_u16 + >> ssnkey_blob_offset: le_u32 + + // subtrack 12 as idenfier (8) and type (4) are cut before we are called + // subtract 48 for the len/offset/maxlen fields above + >> take!(domain_blob_offset - (12 + 48)) + + //>> lm_blob: take!(lm_blob_len) + //>> ntlmresp_blob: take!(ntlmresp_blob_len) + >> domain_blob: take!(domain_blob_len) + >> user_blob: take!(user_blob_len) + >> host_blob: take!(host_blob_len) + + >> ( NTLMSSPAuthRecord { + domain: domain_blob, + user: user_blob, + host: host_blob, + }) +))); + +#[derive(Debug,PartialEq)] +pub struct NTLMSSPRecord<'a> { + pub msg_type: u32, + pub data: &'a[u8], +} + +named!(pub parse_ntlmssp, + do_parse!( + take_until_and_consume!("NTLMSSP\x00") + >> msg_type: le_u32 + >> data: rest + >> (NTLMSSPRecord { + msg_type:msg_type, + data:data, + }) +)); diff --git a/rust/src/smb/smb.rs b/rust/src/smb/smb.rs new file mode 100644 index 0000000000..b99e013234 --- /dev/null +++ b/rust/src/smb/smb.rs @@ -0,0 +1,1798 @@ +/* Copyright (C) 2017 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 +extern crate libc; +use std; +use std::mem::transmute; +use std::str; +use std::ffi::CStr; + +use nom::IResult; +use std::collections::HashMap; + +use core::*; +use log::*; +use applayer; +use applayer::LoggerFlags; + +use smb::nbss_records::*; +use smb::smb1_records::*; +use smb::smb2_records::*; + +use smb::smb1::*; +use smb::smb2::*; +use smb::dcerpc::*; +use smb::events::*; +use smb::auth::*; +use smb::files::*; + +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 NBSS_MSGTYPE_SESSION_MESSAGE: u8 = 0x00; +//const NBSS_MSGTYPE_SESSION_REQUEST: u8 = 0x81; + +pub const SMB_NTSTATUS_SUCCESS: u32 = 0; +pub const SMB_NTSTATUS_PENDING: u32 = 0x00000103; +pub const SMB_NTSTATUS_BUFFER_OVERFLOW: u32 = 0x80000005; +pub const SMB_NTSTATUS_NO_MORE_FILES: u32 = 0x80000006; +pub const SMB_NTSTATUS_NO_MORE_ENTRIES: u32 = 0x8000001a; +pub const SMB_NTSTATUS_INVALID_HANDLE: u32 = 0xc0000008; +pub const SMB_NTSTATUS_INVALID_PARAMETER: u32 = 0xc000000d; +pub const SMB_NTSTATUS_NO_SUCH_DEVICE: u32 = 0xc000000e; +pub const SMB_NTSTATUS_NO_SUCH_FILE: u32 = 0xc000000f; +pub const SMB_NTSTATUS_INVALID_DEVICE_REQUEST: u32 = 0xc0000010; +pub const SMB_NTSTATUS_END_OF_FILE: u32 = 0xc0000011; +pub const SMB_NTSTATUS_MORE_PROCESSING_REQUIRED: u32 = 0xc0000016; +pub const SMB_NTSTATUS_ACCESS_DENIED: u32 = 0xc0000022; +pub const SMB_NTSTATUS_OBJECT_NAME_INVALID: u32 = 0xc0000033; +pub const SMB_NTSTATUS_OBJECT_NAME_NOT_FOUND: u32 = 0xc0000034; +pub const SMB_NTSTATUS_OBJECT_NAME_COLLISION: u32 = 0xc0000035; +pub const SMB_NTSTATUS_OBJECT_PATH_NOT_FOUND: u32 = 0xc000003a; +pub const SMB_NTSTATUS_SHARING_VIOLATION: u32 = 0xc0000043; +pub const SMB_NTSTATUS_LOCK_CONFLICT: u32 = 0xc0000054; +pub const SMB_NTSTATUS_LOCK_NOT_GRANTED: u32 = 0xc0000055; +pub const SMB_NTSTATUS_PRIVILEGE_NOT_HELD: u32 = 0xc0000061; +pub const SMB_NTSTATUS_LOGON_FAILURE: u32 = 0xc000006d; +pub const SMB_NTSTATUS_PIPE_DISCONNECTED: u32 = 0xc00000b0; +pub const SMB_NTSTATUS_FILE_IS_A_DIRECTORY: u32 = 0xc00000ba; +pub const SMB_NTSTATUS_NOT_SUPPORTED: u32 = 0xc00000bb; +pub const SMB_NTSTATUS_BAD_NETWORK_NAME: u32 = 0xc00000cc; +pub const SMB_NTSTATUS_OPLOCK_NOT_GRANTED: u32 = 0xc00000e2; +pub const SMB_NTSTATUS_CANCELLED: u32 = 0xc0000120; +pub const SMB_NTSTATUS_FILE_CLOSED: u32 = 0xc0000128; +pub const SMB_NTSTATUS_FS_DRIVER_REQUIRED: u32 = 0xc000019c; +pub const SMB_NTSTATUS_INSUFF_SERVER_RESOURCES: u32 = 0xc0000205; +pub const SMB_NTSTATUS_NOT_FOUND: u32 = 0xc0000225; +pub const SMB_NTSTATUS_PIPE_BROKEN: u32 = 0xc000014b; +pub const SMB_NTSTATUS_TRUSTED_RELATIONSHIP_FAILURE: u32 = 0xc000018d; +pub const SMB_NTSTATUS_NOT_A_REPARSE_POINT: u32 = 0xc0000275; +pub const SMB_NTSTATUS_NETWORK_SESSION_EXPIRED: u32 = 0xc000035c; + +pub fn smb_ntstatus_string(c: u32) -> String { + match c { + SMB_NTSTATUS_SUCCESS => "STATUS_SUCCESS", + SMB_NTSTATUS_BUFFER_OVERFLOW => "STATUS_BUFFER_OVERFLOW", + SMB_NTSTATUS_PENDING => "STATUS_PENDING", + SMB_NTSTATUS_NO_MORE_FILES => "STATUS_NO_MORE_FILES", + SMB_NTSTATUS_NO_MORE_ENTRIES => "STATUS_NO_MORE_ENTRIES", + SMB_NTSTATUS_INVALID_HANDLE => "STATUS_INVALID_HANDLE", + SMB_NTSTATUS_INVALID_PARAMETER => "STATUS_INVALID_PARAMETER", + SMB_NTSTATUS_NO_SUCH_DEVICE => "STATUS_NO_SUCH_DEVICE", + SMB_NTSTATUS_NO_SUCH_FILE => "STATUS_NO_SUCH_FILE", + SMB_NTSTATUS_INVALID_DEVICE_REQUEST => "STATUS_INVALID_DEVICE_REQUEST", + SMB_NTSTATUS_END_OF_FILE => "STATUS_END_OF_FILE", + SMB_NTSTATUS_MORE_PROCESSING_REQUIRED => "STATUS_MORE_PROCESSING_REQUIRED", + SMB_NTSTATUS_ACCESS_DENIED => "STATUS_ACCESS_DENIED", + SMB_NTSTATUS_OBJECT_NAME_INVALID => "STATUS_OBJECT_NAME_INVALID", + SMB_NTSTATUS_OBJECT_NAME_NOT_FOUND => "STATUS_OBJECT_NAME_NOT_FOUND", + SMB_NTSTATUS_OBJECT_NAME_COLLISION => "STATUS_OBJECT_NAME_COLLISION", + SMB_NTSTATUS_OBJECT_PATH_NOT_FOUND => "STATUS_OBJECT_PATH_NOT_FOUND", + SMB_NTSTATUS_SHARING_VIOLATION => "STATUS_SHARING_VIOLATION", + SMB_NTSTATUS_LOCK_CONFLICT => "STATUS_LOCK_CONFLICT", + SMB_NTSTATUS_LOCK_NOT_GRANTED => "STATUS_LOCK_NOT_GRANTED", + SMB_NTSTATUS_PRIVILEGE_NOT_HELD => "STATUS_PRIVILEGE_NOT_HELD", + SMB_NTSTATUS_LOGON_FAILURE => "STATUS_LOGON_FAILURE", + SMB_NTSTATUS_PIPE_DISCONNECTED => "STATUS_PIPE_DISCONNECTED", + SMB_NTSTATUS_FILE_IS_A_DIRECTORY => "STATUS_FILE_IS_A_DIRECTORY", + SMB_NTSTATUS_NOT_SUPPORTED => "STATUS_NOT_SUPPORTED", + SMB_NTSTATUS_BAD_NETWORK_NAME => "STATUS_BAD_NETWORK_NAME", + SMB_NTSTATUS_OPLOCK_NOT_GRANTED => "STATUS_OPLOCK_NOT_GRANTED", + SMB_NTSTATUS_CANCELLED => "STATUS_CANCELLED", + SMB_NTSTATUS_FILE_CLOSED => "STATUS_FILE_CLOSED", + SMB_NTSTATUS_FS_DRIVER_REQUIRED => "STATUS_FS_DRIVER_REQUIRED", + SMB_NTSTATUS_INSUFF_SERVER_RESOURCES => "STATUS_INSUFF_SERVER_RESOURCES", + SMB_NTSTATUS_NOT_FOUND => "STATUS_NOT_FOUND", + SMB_NTSTATUS_PIPE_BROKEN => "STATUS_PIPE_BROKEN", + SMB_NTSTATUS_TRUSTED_RELATIONSHIP_FAILURE => "STATUS_TRUSTED_RELATIONSHIP_FAILURE", + SMB_NTSTATUS_NOT_A_REPARSE_POINT => "STATUS_NOT_A_REPARSE_POINT", + SMB_NTSTATUS_NETWORK_SESSION_EXPIRED => "STATUS_NETWORK_SESSION_EXPIRED", + _ => { 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; +pub const NTLMSSP_CHALLENGE: u32 = 2; +pub const NTLMSSP_AUTH: u32 = 3; + +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(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: u32, +} + +impl SMBVerCmdStat { + pub fn new() -> SMBVerCmdStat { + return SMBVerCmdStat { + smb_ver: 0, + smb1_cmd: 0, + smb2_cmd: 0, + status_set: false, + status_is_dos_error: false, + status: 0, + } + } + pub fn new1(cmd: u8) -> SMBVerCmdStat { + return SMBVerCmdStat { + smb_ver: 1, + smb1_cmd: cmd, + smb2_cmd: 0, + status_set: false, + status_is_dos_error: false, + status: 0, + } + } + pub fn new1_with_ntstatus(cmd: u8, status: u32) -> SMBVerCmdStat { + return SMBVerCmdStat { + smb_ver: 1, + smb1_cmd: cmd, + smb2_cmd: 0, + status_set: true, + status_is_dos_error: false, + status: status, + } + } + pub fn new2(cmd: u16) -> SMBVerCmdStat { + return SMBVerCmdStat { + smb_ver: 2, + smb1_cmd: 0, + smb2_cmd: cmd, + status_set: false, + status_is_dos_error: false, + status: 0, + } + } + + pub fn new2_with_ntstatus(cmd: u16, status: u32) -> SMBVerCmdStat { + return SMBVerCmdStat { + smb_ver: 2, + smb1_cmd: 0, + smb2_cmd: cmd, + status_set: true, + status_is_dos_error: false, + status: status, + } + } + + 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, u16) { + (self.status_set && self.status_is_dos_error, 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 = (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) + } +} + +#[derive(Debug)] +pub enum SMBTransactionTypeData { + FILE(SMBTransactionFile), + TREECONNECT(SMBTransactionTreeConnect), + NEGOTIATE(SMBTransactionNegotiate), + DCERPC(SMBTransactionDCERPC), + CREATE(SMBTransactionCreate), +} + +#[derive(Debug)] +pub struct SMBTransactionCreate { + pub disposition: u32, + pub delete_on_close: bool, + pub directory: bool, + pub filename: Vec, +} + +impl SMBTransactionCreate { + pub fn new(filename: Vec, disp: u32, del: bool, dir: bool) -> SMBTransactionCreate { + return SMBTransactionCreate { + disposition: disp, + delete_on_close: del, + directory: dir, + filename: filename, + } + } +} + +#[derive(Debug)] +pub struct SMBTransactionNegotiate { + pub smb_ver: u8, + pub dialects: Vec>, + pub dialects2: Vec>, +} + +impl SMBTransactionNegotiate { + pub fn new(smb_ver: u8) -> SMBTransactionNegotiate { + return SMBTransactionNegotiate { + smb_ver: smb_ver, + dialects: Vec::new(), + dialects2: Vec::new(), + } + } +} + +#[derive(Debug)] +pub struct SMBTransactionTreeConnect { + pub is_pipe: bool, + pub tree_id: u32, + pub share_name: Vec, +} + +impl SMBTransactionTreeConnect { + pub fn new(share_name: Vec) -> SMBTransactionTreeConnect { + return SMBTransactionTreeConnect { + is_pipe:false, + tree_id:0, + share_name:share_name, + } + } +} + +#[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, + + /// detection engine flags for use by detection engine + detect_flags_ts: u64, + detect_flags_tc: u64, + pub logged: LoggerFlags, + pub de_state: Option<*mut DetectEngineState>, + pub events: *mut AppLayerDecoderEvents, +} + +impl SMBTransaction { + pub fn new() -> SMBTransaction { + return SMBTransaction{ + id: 0, + vercmd: SMBVerCmdStat::new(), + hdr: SMBCommonHdr::init(), + request_done: false, + response_done: false, + type_data: None, + detect_flags_ts: 0, + detect_flags_tc: 0, + logged: LoggerFlags::new(), + de_state: None, + events: std::ptr::null_mut(), + } + } + + 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) { + if self.events != std::ptr::null_mut() { + sc_app_layer_decoder_events_free_events(&mut self.events); + } + match self.de_state { + Some(state) => { + sc_detect_engine_state_free(state); + } + _ => {} + } + } +} + +impl Drop for SMBTransaction { + fn drop(&mut self) { + self.free(); + } +} + +#[derive(Hash, Eq, PartialEq, Debug, Clone)] +pub struct SMBFileGUIDOffset { + pub guid: Vec, + pub offset: u64, +} + +impl SMBFileGUIDOffset { + pub fn new(guid: Vec, offset: u64) -> SMBFileGUIDOffset { + SMBFileGUIDOffset { + guid:guid, + offset: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; +pub const SMBHDR_TYPE_MAX_SIZE: u32 = 7; // max resp size for SMB1_COMMAND_TRANS +pub const SMBHDR_TYPE_TXNAME: u32 = 8; // SMB1_COMMAND_TRANS tx_name +pub const SMBHDR_TYPE_TRANS_FRAG: u32 = 9; +pub const SMBHDR_TYPE_TREE: u32 = 10; +pub const SMBHDR_TYPE_DCERPCTX: u32 = 11; + +#[derive(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 init() -> SMBCommonHdr { + SMBCommonHdr { + rec_type : 0, + ssn_id : 0, + tree_id : 0, + msg_id : 0, + } + } + pub fn new(rec_type: u32, ssn_id: u64, tree_id: u32, msg_id: u64) -> SMBCommonHdr { + SMBCommonHdr { + rec_type : rec_type, + ssn_id : ssn_id, + tree_id : tree_id, + msg_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 => { 0 }, + SMBHDR_TYPE_SHARE => { 0 }, + _ => { r.message_id as u64 }, + }; + + SMBCommonHdr { + rec_type : rec_type, + ssn_id : r.session_id, + tree_id : tree_id, + msg_id : 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 => { 0 }, + SMBHDR_TYPE_SHARE => { 0 }, + _ => { r.multiplex_id as u64 }, + }; + + SMBCommonHdr { + rec_type : rec_type, + ssn_id : r.ssn_id as u64, + tree_id : tree_id, + msg_id : msg_id, + } + } +} + +#[derive(Hash, Eq, PartialEq, Debug)] +pub struct SMBHashKeyHdrGuid { + hdr: SMBCommonHdr, + guid: Vec, +} + +impl SMBHashKeyHdrGuid { + pub fn new(hdr: SMBCommonHdr, guid: Vec) -> SMBHashKeyHdrGuid { + SMBHashKeyHdrGuid { + hdr: hdr, guid: guid, + } + } +} + +#[derive(Hash, Eq, PartialEq, Debug)] +pub struct SMBTree { + pub name: Vec, + pub is_pipe: bool, +} + +impl SMBTree { + pub fn new(name: Vec, is_pipe: bool) -> SMBTree { + SMBTree { + name:name, + is_pipe: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] +} + +pub struct SMBState<> { + /// map ssn/tree/msgid to vec (guid/name/share) + pub ssn2vec_map: HashMap>, + /// map guid to filename + pub guid2name_map: HashMap, Vec>, + /// map ssn key to read offset + pub ssn2vecoffset_map: HashMap, + + pub ssn2tree_map: HashMap, + + // track the max size we expect for TRANS responses + pub ssn2maxsize_map: HashMap, + pub ssnguid2vec_map: HashMap>, + + /// TCP segments defragmentation buffer + pub tcp_buffer_ts: Vec, + pub tcp_buffer_tc: Vec, + + pub files: SMBFiles, + + pub skip_ts: u32, + pub skip_tc: u32, + + pub file_ts_left : u32, + pub file_tc_left : u32, + pub file_ts_guid : Vec, + pub file_tc_guid : Vec, + + 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 + + ts_trunc: bool, // no more data for TOSERVER + tc_trunc: bool, // no more data for TOCLIENT + + /// transactions list + pub transactions: Vec, + + /// tx counter for assigning incrementing id's to tx's + tx_id: u64, + + pub dialect: u16, + pub dialect_vec: Option>, // used if dialect == 0 + pub dialects: Option>>, + + /// dcerpc interfaces, stored here to be able to match + /// them while inspecting DCERPC REQUEST txs + pub dcerpc_ifaces: Option>, + + pub ntlmssp: Option, + pub krb_ticket: Option, +} + +impl SMBState { + /// Allocation function for a new TLS parser instance + pub fn new() -> SMBState { + SMBState { + ssn2vec_map:HashMap::new(), + guid2name_map:HashMap::new(), + ssn2vecoffset_map:HashMap::new(), + ssn2tree_map:HashMap::new(), + ssn2maxsize_map:HashMap::new(), + ssnguid2vec_map:HashMap::new(), + tcp_buffer_ts:Vec::new(), + tcp_buffer_tc:Vec::new(), + files: SMBFiles::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, + transactions: Vec::new(), + tx_id:0, + dialect:0, + dialect_vec: None, + dialects: None, + dcerpc_ifaces: None, + ntlmssp: None, + krb_ticket: None, + } + } + + pub fn free(&mut self) { + //self._debug_state_stats(); + self._debug_tx_stats(); + self.files.free(); + } + + 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); + 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.transactions.remove(index); + } + } + + // for use with the C API call StateGetTxIterator + pub fn get_tx_iterator(&mut self, min_tx_id: u64, state: &mut u64) -> + Option<(&SMBTransaction, u64, bool)> + { + let mut index = *state as usize; + let len = self.transactions.len(); + + // find tx that is >= min_tx_id + while index < len { + let tx = &self.transactions[index]; + if tx.id < min_tx_id + 1 { + index += 1; + continue; + } + *state = index as u64 + 1; + //SCLogDebug!("returning tx_id {} has_next? {} (len {} index {}), tx {:?}", + // tx.id - 1, (len - index) > 1, len, index, tx); + return Some((tx, tx.id - 1, (len - index) > 1)); + } + return None; + } + + 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); + return Some(tx); + } + } + SCLogDebug!("Failed to find SMB TX with ID {}", tx_id); + return None; + } + + /* 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(tx); + let tx_ref = self.transactions.last_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.last_mut(); + match tx_ref { + Some(tx) => { + 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); + } + }, + None => { }, + } + 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 == *key + } else if smb_ver == 2 { + let (_, cmd) = tx.vercmd.get_smb2_cmd(); + cmd == smb_cmd && tx.hdr == *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(tx); + let tx_ref = self.transactions.last_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)) => { + if x.smb_ver == smb_ver { + true + } else { + false + } + }, + _ => { false }, + }; + if found { + return Some(tx); + } + } + return None; + } + + pub fn new_treeconnect_tx(&mut self, hdr: SMBCommonHdr, name: Vec) + -> (&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(tx); + let tx_ref = self.transactions.last_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 == 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: &Vec, + 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(tx); + let tx_ref = self.transactions.last_mut(); + return tx_ref.unwrap(); + } + + pub fn get_create_tx_by_hdr(&mut self, hdr: &SMBCommonHdr) + -> Option<&mut SMBTransaction> + { + for tx in &mut self.transactions { + let found = match tx.type_data { + Some(SMBTransactionTypeData::CREATE(ref _d)) => { + *hdr == tx.hdr + }, + _ => { false }, + }; + + if found { + SCLogDebug!("SMB: Found SMB create TX with ID {}", tx.id); + return Some(tx); + } + } + SCLogDebug!("SMB: Failed to find SMB create TX with key {:?}", hdr); + return None; + } + + 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) => { + match str::from_utf8(&n) { + Ok("PSEXESVC") => ("PSEXESVC", false), + Ok("svcctl") => ("svcctl", true), + Ok("srvsvc") => ("srvsvc", true), + Ok("atsvc") => ("atsvc", true), + Ok("lsarpc") => ("lsarpc", true), + Ok("samr") => ("samr", true), + Err(_) => ("MALFORMED", false), + Ok(&_) => { + SCLogNotice!("don't know {}", String::from_utf8_lossy(&n)); + ("UNKNOWN", false) + }, + } + }, + _ => { ("UNKNOWN", false) }, + }; + SCLogDebug!("service {} is_dcerpc {}", name, is_dcerpc); + (&name, is_dcerpc) + } + + /* if we have marked the ssn as 'gapped' we check to see if + * we've caught up. The check is to see if we have the last + * tx in our list is smaller than the max id. This means we've + * seen a tx that has been fully processed and removed. */ + pub fn check_gap_resync(&mut self) + { + if self.ts_ssn_gap || self.tc_ssn_gap { + let max_id = self.tx_id; + let over = match self.transactions.last() { + Some(tx) => { + SCLogDebug!("tx.id {} max_id {}", tx.id, max_id); + tx.id < max_id + }, + None => { false }, + }; + if over { + SCLogDebug!("post-GAP resync confirmed"); + self.ts_ssn_gap = false; + self.tc_ssn_gap = false; + self.close_non_file_txs(); + } + } + } + + /* close all txs execpt file xfers. */ + fn close_non_file_txs(&mut self) { + SCLogDebug!("checking for non-file txs to wrap up"); + for tx in &mut self.transactions { + match tx.type_data { + None => { + SCLogDebug!("tx {} marked as done", tx.id); + tx.request_done = true; + tx.response_done = true; + }, + _ => { }, + } + } + } + + // return how much data we consumed + fn handle_skip(&mut self, direction: u8, input_size: u32) -> u32 { + let mut skip_left = if direction == STREAM_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 == STREAM_TOSERVER { + self.skip_ts = skip_left; + } else { + self.skip_tc = skip_left; + } + return consumed; + } + + /// return bytes consumed + pub fn parse_tcp_data_ts_partial<'b>(&mut self, input: &'b[u8]) -> usize + { + SCLogDebug!("incomplete of size {}", input.len()); + if input.len() < 512 { + return 0; + } + + match parse_nbss_record_partial(input) { + IResult::Done(output, ref nbss_part_hdr) => { + SCLogDebug!("parse_nbss_record_partial ok, output len {}", output.len()); + if nbss_part_hdr.message_type == NBSS_MSGTYPE_SESSION_MESSAGE { + match parse_smb_version(&nbss_part_hdr.data) { + IResult::Done(_, ref smb) => { + SCLogDebug!("SMB {:?}", smb); + if smb.version == 255u8 { // SMB1 + SCLogDebug!("SMBv1 record"); + match parse_smb_record(&nbss_part_hdr.data) { + IResult::Done(_, ref r) => { + 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; + } + smb1_write_request_record(self, r); + let consumed = input.len() - output.len(); + return consumed; + } + }, + _ => { }, + + } + } else if smb.version == 254u8 { // SMB2 + SCLogDebug!("SMBv2 record"); + match parse_smb2_request_record(&nbss_part_hdr.data) { + IResult::Done(_, ref smb_record) => { + SCLogDebug!("SMB2: partial record {}", + &smb2_command_string(smb_record.command)); + if smb_record.command == SMB2_COMMAND_WRITE { + smb2_write_request_record(self, smb_record); + let consumed = input.len() - output.len(); + return consumed; + } + }, + _ => { }, + } + } + }, + _ => { }, + } + } + }, + _ => { }, + } + + return 0; + } + + /// Parsing function, handling TCP chunks fragmentation + pub fn parse_tcp_data_ts<'b>(&mut self, i: &'b[u8]) -> u32 + { + self.check_gap_resync(); + + let mut v : Vec; + //println!("parse_tcp_data_ts ({})",i.len()); + //println!("{:?}",i); + // Check if TCP data is being defragmented + let tcp_buffer = match self.tcp_buffer_ts.len() { + 0 => i, + _ => { + v = self.tcp_buffer_ts.split_off(0); + if self.tcp_buffer_ts.len() + i.len() > 100000 { + self.set_event(SMBEvent::RecordOverflow); + return 1; + }; + v.extend_from_slice(i); + v.as_slice() + }, + }; + //println!("tcp_buffer ({})",tcp_buffer.len()); + let mut cur_i = tcp_buffer; + if cur_i.len() > 1000000 { + self.set_event(SMBEvent::RecordOverflow); + return 1; + } + let consumed = self.handle_skip(STREAM_TOSERVER, cur_i.len() as u32); + if consumed > 0 { + if consumed > cur_i.len() as u32 { + self.set_event(SMBEvent::InternalError); + return 1; + } + 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(STREAM_TOSERVER, cur_i, 0); + if consumed > 0 { + if consumed > cur_i.len() as u32 { + self.set_event(SMBEvent::InternalError); + return 1; + } + cur_i = &cur_i[consumed as usize..]; + } + // gap + if self.ts_gap { + SCLogDebug!("TODO TS trying to catch up after GAP (input {})", cur_i.len()); + match search_smb2_record(cur_i) { + IResult::Done(_, pg) => { + SCLogDebug!("smb record found"); + let smb2_offset = cur_i.len() - pg.data.len(); + if smb2_offset < 4 { + return 0; + } + let nbss_offset = smb2_offset - 4; + cur_i = &cur_i[nbss_offset..]; + + self.ts_gap = false; + }, + _ => { + SCLogDebug!("smb record NOT found"); + self.tcp_buffer_ts.extend_from_slice(cur_i); + return 0; + }, + } + } + while cur_i.len() > 0 { // min record size + match parse_nbss_record(cur_i) { + IResult::Done(rem, ref nbss_hdr) => { + 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) { + IResult::Done(_, ref smb) => { + SCLogDebug!("SMB {:?}", smb); + if smb.version == 255u8 { // SMB1 + SCLogDebug!("SMBv1 record"); + match parse_smb_record(&nbss_hdr.data) { + IResult::Done(_, ref smb_record) => { + smb1_request_record(self, smb_record); + }, + _ => { + self.set_event(SMBEvent::MalformedData); + return 1; + }, + } + } else if smb.version == 254u8 { // SMB2 + let mut nbss_data = nbss_hdr.data; + while nbss_data.len() > 0 { + SCLogDebug!("SMBv2 record"); + match parse_smb2_request_record(&nbss_data) { + IResult::Done(nbss_data_rem, ref smb_record) => { + SCLogDebug!("nbss_data_rem {}", nbss_data_rem.len()); + + smb2_request_record(self, smb_record); + nbss_data = nbss_data_rem; + }, + _ => { + self.set_event(SMBEvent::MalformedData); + return 1; + }, + } + } + } + }, + _ => { + self.set_event(SMBEvent::MalformedData); + return 1; + }, + } + } else { + SCLogDebug!("NBSS message {:X}", nbss_hdr.message_type); + } + cur_i = rem; + }, + IResult::Incomplete(_) => { + let consumed = self.parse_tcp_data_ts_partial(cur_i); + cur_i = &cur_i[consumed ..]; + + self.tcp_buffer_ts.extend_from_slice(cur_i); + break; + }, + IResult::Error(_) => { + self.set_event(SMBEvent::MalformedData); + return 1; + }, + } + }; + + //self.debug_tx_stats(); + 0 + } + + /// return bytes consumed + pub fn parse_tcp_data_tc_partial<'b>(&mut self, input: &'b[u8]) -> usize + { + SCLogDebug!("incomplete of size {}", input.len()); + if input.len() < 512 { + return 0; + } + + match parse_nbss_record_partial(input) { + IResult::Done(output, ref nbss_part_hdr) => { + SCLogDebug!("parse_nbss_record_partial ok, output len {}", output.len()); + if nbss_part_hdr.message_type == NBSS_MSGTYPE_SESSION_MESSAGE { + match parse_smb_version(&nbss_part_hdr.data) { + IResult::Done(_, ref smb) => { + SCLogDebug!("SMB {:?}", smb); + if smb.version == 255u8 { // SMB1 + SCLogDebug!("SMBv1 record"); + match parse_smb_record(&nbss_part_hdr.data) { + IResult::Done(_, ref r) => { + 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; + } + smb1_read_response_record(self, r); + let consumed = input.len() - output.len(); + return consumed; + } + }, + _ => { }, + } + } else if smb.version == 254u8 { // SMB2 + SCLogDebug!("SMBv2 record"); + match parse_smb2_response_record(&nbss_part_hdr.data) { + IResult::Done(_, ref smb_record) => { + SCLogDebug!("SMB2: partial record {}", + &smb2_command_string(smb_record.command)); + if smb_record.command == SMB2_COMMAND_READ { + smb2_read_response_record(self, smb_record); + let consumed = input.len() - output.len(); + return consumed; + } + }, + _ => { }, + } + } + }, + _ => { }, + } + } + }, + _ => { }, + } + + return 0; + } + + /// Parsing function, handling TCP chunks fragmentation + pub fn parse_tcp_data_tc<'b>(&mut self, i: &'b[u8]) -> u32 + { + self.check_gap_resync(); + + let mut v : Vec; + // Check if TCP data is being defragmented + let tcp_buffer = match self.tcp_buffer_tc.len() { + 0 => i, + _ => { + v = self.tcp_buffer_tc.split_off(0); + if self.tcp_buffer_tc.len() + i.len() > 100000 { + self.set_event(SMBEvent::RecordOverflow); + return 1; + }; + v.extend_from_slice(i); + v.as_slice() + }, + }; + let mut cur_i = tcp_buffer; + SCLogDebug!("cur_i.len {}", cur_i.len()); + if cur_i.len() > 100000 { + self.set_event(SMBEvent::RecordOverflow); + return 1; + } + let consumed = self.handle_skip(STREAM_TOCLIENT, cur_i.len() as u32); + if consumed > 0 { + if consumed > cur_i.len() as u32 { + self.set_event(SMBEvent::InternalError); + return 1; + } + 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(STREAM_TOCLIENT, cur_i, 0); + if consumed > 0 { + if consumed > cur_i.len() as u32 { + self.set_event(SMBEvent::InternalError); + return 1; + } + cur_i = &cur_i[consumed as usize..]; + } + // gap + if self.tc_gap { + SCLogDebug!("TODO TC trying to catch up after GAP (input {})", cur_i.len()); + match search_smb2_record(cur_i) { + IResult::Done(_, pg) => { + SCLogDebug!("smb record found"); + let smb2_offset = cur_i.len() - pg.data.len(); + if smb2_offset < 4 { + return 0; + } + let nbss_offset = smb2_offset - 4; + cur_i = &cur_i[nbss_offset..]; + + self.tc_gap = false; + }, + _ => { + SCLogDebug!("smb record NOT found"); + self.tcp_buffer_tc.extend_from_slice(cur_i); + return 0; + }, + } + } + while cur_i.len() > 0 { // min record size + match parse_nbss_record(cur_i) { + IResult::Done(rem, ref nbss_hdr) => { + 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) { + IResult::Done(_, ref smb) => { + SCLogDebug!("SMB {:?}", smb); + if smb.version == 255u8 { // SMB1 + SCLogDebug!("SMBv1 record"); + match parse_smb_record(&nbss_hdr.data) { + IResult::Done(_, ref smb_record) => { + smb1_response_record(self, smb_record); + }, + _ => { + self.set_event(SMBEvent::MalformedData); + return 1; + }, + } + } else if smb.version == 254u8 { // SMB2 + let mut nbss_data = nbss_hdr.data; + while nbss_data.len() > 0 { + SCLogDebug!("SMBv2 record"); + match parse_smb2_response_record(&nbss_data) { + IResult::Done(nbss_data_rem, ref smb_record) => { + smb2_response_record(self, smb_record); + nbss_data = nbss_data_rem; + }, + _ => { + self.set_event(SMBEvent::MalformedData); + return 1; + }, + } + } + } + }, + IResult::Incomplete(_) => { + // not enough data to contain basic SMB hdr + // TODO event: empty NBSS_MSGTYPE_SESSION_MESSAGE + }, + IResult::Error(_) => { + self.set_event(SMBEvent::MalformedData); + return 1; + }, + } + } else { + SCLogDebug!("NBSS message {:X}", nbss_hdr.message_type); + } + cur_i = rem; + }, + IResult::Incomplete(_) => { + SCLogDebug!("INCOMPLETE have {}", cur_i.len()); + let consumed = self.parse_tcp_data_tc_partial(cur_i); + cur_i = &cur_i[consumed ..]; + + SCLogDebug!("INCOMPLETE have {}", cur_i.len()); + self.tcp_buffer_tc.extend_from_slice(cur_i); + break; + }, + IResult::Error(_) => { + self.set_event(SMBEvent::MalformedData); + return 1; + }, + } + }; + 0 + } + + /// 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) -> u32 { + if self.tcp_buffer_ts.len() > 0 { + self.tcp_buffer_ts.clear(); + } + let consumed = self.handle_skip(STREAM_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(STREAM_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 1; + } + } + SCLogDebug!("GAP of size {} in toserver direction", gap_size); + self.ts_ssn_gap = true; + self.ts_gap = true; + return 0 + } + + /// 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) -> u32 { + if self.tcp_buffer_tc.len() > 0 { + self.tcp_buffer_tc.clear(); + } + let consumed = self.handle_skip(STREAM_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(STREAM_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 1; + } + } + SCLogDebug!("GAP of size {} in toclient direction", gap_size); + self.tc_ssn_gap = true; + self.tc_gap = true; + return 0 + } + + pub fn trunc_ts(&mut self) { + SCLogDebug!("TRUNC TS"); + self.ts_trunc = true; + + for tx in &mut self.transactions { + if !tx.request_done { + SCLogDebug!("TRUNCING 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!("TRUNCING TX {} in TOCLIENT direction", tx.id); + tx.response_done = true; + } + } + } +} + +/// Returns *mut SMBState +#[no_mangle] +pub extern "C" fn rs_smb_state_new() -> *mut libc::c_void { + let state = SMBState::new(); + let boxed = Box::new(state); + SCLogDebug!("allocating state"); + return unsafe{transmute(boxed)}; +} + +/// Params: +/// - state: *mut SMBState as void pointer +#[no_mangle] +pub extern "C" fn rs_smb_state_free(state: *mut libc::c_void) { + // Just unbox... + SCLogDebug!("freeing state"); + let mut smb_state: Box = unsafe{transmute(state)}; + smb_state.free(); +} + +/// C binding parse a SMB request. Returns 1 on success, -1 on failure. +#[no_mangle] +pub extern "C" fn rs_smb_parse_request_tcp(_flow: *mut Flow, + state: &mut SMBState, + _pstate: *mut libc::c_void, + input: *mut libc::uint8_t, + input_len: libc::uint32_t, + _data: *mut libc::c_void) + -> libc::int8_t +{ + let buf = unsafe{std::slice::from_raw_parts(input, input_len as usize)}; + SCLogDebug!("parsing {} bytes of request data", input_len); + + if state.parse_tcp_data_ts(buf) == 0 { + return 1; + } else { + return -1; + } +} + +#[no_mangle] +pub extern "C" fn rs_smb_parse_request_tcp_gap( + state: &mut SMBState, + input_len: libc::uint32_t) + -> libc::int8_t +{ + if state.parse_tcp_data_ts_gap(input_len as u32) == 0 { + return 1; + } + return -1; +} + + +#[no_mangle] +pub extern "C" fn rs_smb_parse_response_tcp(_flow: *mut Flow, + state: &mut SMBState, + _pstate: *mut libc::c_void, + input: *mut libc::uint8_t, + input_len: libc::uint32_t, + _data: *mut libc::c_void) + -> libc::int8_t +{ + SCLogDebug!("parsing {} bytes of response data", input_len); + let buf = unsafe{std::slice::from_raw_parts(input, input_len as usize)}; + + if state.parse_tcp_data_tc(buf) == 0 { + return 1; + } else { + return -1; + } +} + +#[no_mangle] +pub extern "C" fn rs_smb_parse_response_tcp_gap( + state: &mut SMBState, + input_len: libc::uint32_t) + -> libc::int8_t +{ + if state.parse_tcp_data_tc_gap(input_len as u32) == 0 { + return 1; + } + return -1; +} + +/// TOSERVER probe function +#[no_mangle] +pub extern "C" fn rs_smb_probe_tcp_ts(_input: *const libc::uint8_t, _len: libc::uint32_t) + -> libc::int8_t +{ +// let slice: &[u8] = unsafe { +// std::slice::from_raw_parts(input as *mut u8, len as usize) +// }; + //return smb3_probe(slice, STREAM_TOSERVER); + return 1 +} +/// TOCLIENT probe function +#[no_mangle] +pub extern "C" fn rs_smb_probe_tcp_tc(_input: *const libc::uint8_t, _len: libc::uint32_t) + -> libc::int8_t +{ +// let slice: &[u8] = unsafe { +// std::slice::from_raw_parts(input as *mut u8, len as usize) +// }; + //return smb3_probe(slice, STREAM_TOCLIENT); + return 1 +} + +#[no_mangle] +pub extern "C" fn rs_smb_state_get_tx_count(state: &mut SMBState) + -> libc::uint64_t +{ + SCLogDebug!("rs_smb_state_get_tx_count: returning {}", state.tx_id); + return state.tx_id; +} + +#[no_mangle] +pub extern "C" fn rs_smb_state_get_tx(state: &mut SMBState, + tx_id: libc::uint64_t) + -> *mut SMBTransaction +{ + match state.get_tx_by_id(tx_id) { + Some(tx) => { + return unsafe{transmute(tx)}; + } + None => { + return std::ptr::null_mut(); + } + } +} + +// for use with the C API call StateGetTxIterator +#[no_mangle] +pub extern "C" fn rs_smb_state_get_tx_iterator( + state: &mut SMBState, + min_tx_id: libc::uint64_t, + istate: &mut libc::uint64_t) + -> applayer::AppLayerGetTxIterTuple +{ + match state.get_tx_iterator(min_tx_id, istate) { + Some((tx, out_tx_id, has_next)) => { + let c_tx = unsafe { transmute(tx) }; + let ires = applayer::AppLayerGetTxIterTuple::with_values(c_tx, out_tx_id, has_next); + return ires; + } + None => { + return applayer::AppLayerGetTxIterTuple::not_found(); + } + } +} + +#[no_mangle] +pub extern "C" fn rs_smb_state_tx_free(state: &mut SMBState, + tx_id: libc::uint64_t) +{ + SCLogDebug!("freeing tx {}", tx_id as u64); + state.free_tx(tx_id); +} + +#[no_mangle] +pub extern "C" fn rs_smb_state_progress_completion_status( + _direction: libc::uint8_t) + -> libc::c_int +{ + return 1; +} + +#[no_mangle] +pub extern "C" fn rs_smb_tx_get_alstate_progress(tx: &mut SMBTransaction, + direction: libc::uint8_t) + -> libc::uint8_t +{ + if direction == STREAM_TOSERVER && tx.request_done { + SCLogDebug!("tx {} TOSERVER progress 1 => {:?}", tx.id, tx); + return 1; + } else if direction == STREAM_TOCLIENT && tx.response_done { + SCLogDebug!("tx {} TOCLIENT progress 1 => {:?}", tx.id, tx); + return 1; + } else { + SCLogDebug!("tx {} direction {} progress 0", tx.id, direction); + return 0; + } +} + +#[no_mangle] +pub extern "C" fn rs_smb_tx_set_logged(_state: &mut SMBState, + tx: &mut SMBTransaction, + bits: libc::uint32_t) +{ + tx.logged.set(bits); +} + +#[no_mangle] +pub extern "C" fn rs_smb_tx_get_logged(_state: &mut SMBState, + tx: &mut SMBTransaction) + -> u32 +{ + return tx.logged.get(); +} + +#[no_mangle] +pub extern "C" fn rs_smb_tx_set_detect_flags( + tx: &mut SMBTransaction, + direction: libc::uint8_t, + flags: libc::uint64_t) +{ + if (direction & STREAM_TOSERVER) != 0 { + tx.detect_flags_ts = flags as u64; + } else { + tx.detect_flags_tc = flags as u64; + } +} + +#[no_mangle] +pub extern "C" fn rs_smb_tx_get_detect_flags( + tx: &mut SMBTransaction, + direction: libc::uint8_t) + -> libc::uint64_t +{ + if (direction & STREAM_TOSERVER) != 0 { + return tx.detect_flags_ts as libc::uint64_t; + } else { + return tx.detect_flags_tc as libc::uint64_t; + } +} + +#[no_mangle] +pub extern "C" fn rs_smb_state_set_tx_detect_state( + tx: &mut SMBTransaction, + de_state: &mut DetectEngineState) +{ + tx.de_state = Some(de_state); +} + +#[no_mangle] +pub extern "C" fn rs_smb_state_get_tx_detect_state( + tx: &mut SMBTransaction) + -> *mut DetectEngineState +{ + match tx.de_state { + Some(ds) => { + return ds; + }, + None => { + return std::ptr::null_mut(); + } + } +} + +#[no_mangle] +pub extern "C" fn rs_smb_state_truncate( + state: &mut SMBState, + direction: libc::uint8_t) +{ + if (direction & STREAM_TOSERVER) != 0 { + state.trunc_ts(); + } else { + state.trunc_tc(); + } +} + +#[no_mangle] +pub extern "C" fn rs_smb_state_get_events(state: &mut SMBState, + tx_id: libc::uint64_t) + -> *mut AppLayerDecoderEvents +{ + match state.get_tx_by_id(tx_id) { + Some(tx) => { + return tx.events; + } + _ => { + return std::ptr::null_mut(); + } + } +} + +#[no_mangle] +pub extern "C" fn rs_smb_state_get_event_info(event_name: *const libc::c_char, + event_id: *mut libc::c_int, + event_type: *mut AppLayerEventType) + -> i8 +{ + if event_name == std::ptr::null() { + return -1; + } + let c_event_name: &CStr = unsafe { CStr::from_ptr(event_name) }; + let event = match c_event_name.to_str() { + Ok(s) => { + smb_str_to_event(s) + }, + Err(_) => -1, // UTF-8 conversion failed + }; + unsafe { + *event_type = APP_LAYER_EVENT_TYPE_TRANSACTION; + *event_id = event as libc::c_int; + }; + if event == -1 { + return -1; + } + 0 +} diff --git a/rust/src/smb/smb1.rs b/rust/src/smb/smb1.rs new file mode 100644 index 0000000000..c75832199b --- /dev/null +++ b/rust/src/smb/smb1.rs @@ -0,0 +1,822 @@ +/* Copyright (C) 2018 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 + */ + +extern crate libc; +use std::str; + +use nom::{IResult}; + +use core::*; +use log::*; + +use smb::smb1_records::*; +use smb::smb::*; +use smb::dcerpc::*; +use smb::events::*; +use smb::auth::*; +use smb::files::*; + +// https://msdn.microsoft.com/en-us/library/ee441741.aspx +pub const SMB1_COMMAND_CREATE_DIRECTORY: u8 = 0x00; +pub const SMB1_COMMAND_DELETE_DIRECTORY: u8 = 0x01; +pub const SMB1_COMMAND_OPEN: u8 = 0x02; +pub const SMB1_COMMAND_CREATE: u8 = 0x03; +pub const SMB1_COMMAND_CLOSE: u8 = 0x04; +pub const SMB1_COMMAND_FLUSH: u8 = 0x05; +pub const SMB1_COMMAND_DELETE: u8 = 0x06; +pub const SMB1_COMMAND_RENAME: u8 = 0x07; +pub const SMB1_COMMAND_QUERY_INFORMATION: u8 = 0x08; +pub const SMB1_COMMAND_SET_INFORMATION: u8 = 0x09; +pub const SMB1_COMMAND_READ: u8 = 0x0a; +pub const SMB1_COMMAND_WRITE: u8 = 0x0b; +pub const SMB1_COMMAND_LOCK_BYTE_RANGE: u8 = 0x0c; +pub const SMB1_COMMAND_UNLOCK_BYTE_RANGE: u8 = 0x0d; +pub const SMB1_COMMAND_CREATE_TEMPORARY: u8 = 0x0e; +pub const SMB1_COMMAND_CREATE_NEW: u8 = 0x0f; +pub const SMB1_COMMAND_CHECK_DIRECTORY: u8 = 0x10; +pub const SMB1_COMMAND_PROCESS_EXIT: u8 = 0x11; +pub const SMB1_COMMAND_SEEK: u8 = 0x12; +pub const SMB1_COMMAND_LOCK_AND_READ: u8 = 0x13; +pub const SMB1_COMMAND_WRITE_AND_UNLOCK: u8 = 0x14; +pub const SMB1_COMMAND_LOCKING_ANDX: u8 = 0x24; +pub const SMB1_COMMAND_TRANS: u8 = 0x25; +pub const SMB1_COMMAND_ECHO: u8 = 0x2b; +pub const SMB1_COMMAND_READ_ANDX: u8 = 0x2e; +pub const SMB1_COMMAND_WRITE_ANDX: u8 = 0x2f; +pub const SMB1_COMMAND_TRANS2: u8 = 0x32; +pub const SMB1_COMMAND_TRANS2_SECONDARY: u8 = 0x33; +pub const SMB1_COMMAND_FIND_CLOSE2: u8 = 0x34; +pub const SMB1_COMMAND_TREE_DISCONNECT: u8 = 0x71; +pub const SMB1_COMMAND_NEGOTIATE_PROTOCOL: u8 = 0x72; +pub const SMB1_COMMAND_SESSION_SETUP_ANDX: u8 = 0x73; +pub const SMB1_COMMAND_LOGOFF_ANDX: u8 = 0x74; +pub const SMB1_COMMAND_TREE_CONNECT_ANDX: u8 = 0x75; +pub const SMB1_COMMAND_NT_TRANS: u8 = 0xa0; +pub const SMB1_COMMAND_NT_CREATE_ANDX: u8 = 0xa2; +pub const SMB1_COMMAND_NT_CANCEL: u8 = 0xa4; + +pub fn smb1_command_string(c: u8) -> String { + match c { + SMB1_COMMAND_CREATE_DIRECTORY => "SMB1_COMMAND_CREATE_DIRECTORY", + SMB1_COMMAND_DELETE_DIRECTORY => "SMB1_COMMAND_DELETE_DIRECTORY", + SMB1_COMMAND_OPEN => "SMB1_COMMAND_OPEN", + SMB1_COMMAND_CREATE => "SMB1_COMMAND_CREATE", + SMB1_COMMAND_CLOSE => "SMB1_COMMAND_CLOSE", + SMB1_COMMAND_FLUSH => "SMB1_COMMAND_FLUSH", + SMB1_COMMAND_DELETE => "SMB1_COMMAND_DELETE", + SMB1_COMMAND_RENAME => "SMB1_COMMAND_RENAME", + SMB1_COMMAND_QUERY_INFORMATION => "SMB1_COMMAND_QUERY_INFORMATION", + SMB1_COMMAND_SET_INFORMATION => "SMB1_COMMAND_SET_INFORMATION", + SMB1_COMMAND_READ => "SMB1_COMMAND_READ", + SMB1_COMMAND_WRITE => "SMB1_COMMAND_WRITE", + SMB1_COMMAND_LOCK_BYTE_RANGE => "SMB1_COMMAND_LOCK_BYTE_RANGE", + SMB1_COMMAND_UNLOCK_BYTE_RANGE => "SMB1_COMMAND_UNLOCK_BYTE_RANGE", + SMB1_COMMAND_CREATE_TEMPORARY => "SMB1_COMMAND_CREATE_TEMPORARY", + SMB1_COMMAND_CREATE_NEW => "SMB1_COMMAND_CREATE_NEW", + SMB1_COMMAND_CHECK_DIRECTORY => "SMB1_COMMAND_CHECK_DIRECTORY", + SMB1_COMMAND_PROCESS_EXIT => "SMB1_COMMAND_PROCESS_EXIT", + SMB1_COMMAND_SEEK => "SMB1_COMMAND_SEEK", + SMB1_COMMAND_LOCK_AND_READ => "SMB1_COMMAND_LOCK_AND_READ", + SMB1_COMMAND_WRITE_AND_UNLOCK => "SMB1_COMMAND_WRITE_AND_UNLOCK", + SMB1_COMMAND_LOCKING_ANDX => "SMB1_COMMAND_LOCKING_ANDX", + SMB1_COMMAND_ECHO => "SMB1_COMMAND_ECHO", + SMB1_COMMAND_READ_ANDX => "SMB1_COMMAND_READ_ANDX", + SMB1_COMMAND_WRITE_ANDX => "SMB1_COMMAND_WRITE_ANDX", + SMB1_COMMAND_TRANS => "SMB1_COMMAND_TRANS", + SMB1_COMMAND_TRANS2 => "SMB1_COMMAND_TRANS2", + SMB1_COMMAND_TRANS2_SECONDARY => "SMB1_COMMAND_TRANS2_SECONDARY", + SMB1_COMMAND_FIND_CLOSE2 => "SMB1_COMMAND_FIND_CLOSE2", + SMB1_COMMAND_TREE_DISCONNECT => "SMB1_COMMAND_TREE_DISCONNECT", + SMB1_COMMAND_NEGOTIATE_PROTOCOL => "SMB1_COMMAND_NEGOTIATE_PROTOCOL", + SMB1_COMMAND_SESSION_SETUP_ANDX => "SMB1_COMMAND_SESSION_SETUP_ANDX", + SMB1_COMMAND_LOGOFF_ANDX => "SMB1_COMMAND_LOGOFF_ANDX", + SMB1_COMMAND_TREE_CONNECT_ANDX => "SMB1_COMMAND_TREE_CONNECT_ANDX", + SMB1_COMMAND_NT_TRANS => "SMB1_COMMAND_NT_TRANS", + SMB1_COMMAND_NT_CREATE_ANDX => "SMB1_COMMAND_NT_CREATE_ANDX", + SMB1_COMMAND_NT_CANCEL => "SMB1_COMMAND_NT_CANCEL", + _ => { return (c).to_string(); }, + }.to_string() +} + +// later we'll use this to determine if we need to +// track a ssn per type +pub fn smb1_create_new_tx(_cmd: u8) -> bool { +// if _cmd == SMB1_COMMAND_READ_ANDX { +// false +// } else { + true +// } +} + +pub fn smb1_request_record<'b>(state: &mut SMBState, r: &SmbRecord<'b>) -> u32 { + SCLogDebug!("record: {:?} command {}", r.greeter, r.command); + + // init default tx keys + let mut key_ssn_id = r.ssn_id; + let key_tree_id = r.tree_id; + let key_multiplex_id = r.multiplex_id; + let mut events : Vec = Vec::new(); + let mut no_response_expected = false; + + let have_tx = match r.command { + SMB1_COMMAND_READ_ANDX => { + match parse_smb_read_andx_request_record(r.data) { + IResult::Done(_, rr) => { + SCLogDebug!("rr {:?}", rr); + + // store read fid,offset in map + let fid_key = SMBCommonHdr::from1(r, SMBHDR_TYPE_OFFSET); + let mut fid = rr.fid.to_vec(); + fid.extend_from_slice(&u32_as_bytes(r.ssn_id)); + let fidoff = SMBFileGUIDOffset::new(fid, rr.offset); + state.ssn2vecoffset_map.insert(fid_key, fidoff); + }, + _ => { + events.push(SMBEvent::MalformedData); + }, + } + false + }, + SMB1_COMMAND_WRITE_ANDX => { + smb1_write_request_record(state, r); + true // tx handling in func + }, + SMB1_COMMAND_WRITE => { + smb1_write_request_record(state, r); + true // tx handling in func + }, + SMB1_COMMAND_TRANS => { + smb1_trans_request_record(state, r); + true + }, + SMB1_COMMAND_NEGOTIATE_PROTOCOL => { + match parse_smb1_negotiate_protocol_record(r.data) { + IResult::Done(_, pr) => { + SCLogDebug!("SMB_COMMAND_NEGOTIATE_PROTOCOL {:?}", pr); + + let mut dialects : Vec> = Vec::new(); + for d in &pr.dialects { + let x = &d[1..d.len()]; + let dvec = x.to_vec(); + dialects.push(dvec); + } + + let found = match state.get_negotiate_tx(1) { + Some(tx) => { + SCLogDebug!("WEIRD, should not have NEGOTIATE tx!"); + tx.set_event(SMBEvent::DuplicateNegotiate); + true + }, + None => { false }, + }; + if !found { + let tx = state.new_negotiate_tx(1); + if let Some(SMBTransactionTypeData::NEGOTIATE(ref mut tdn)) = tx.type_data { + tdn.dialects = dialects; + } + tx.request_done = true; + } + true + }, + _ => { + events.push(SMBEvent::MalformedData); + false + }, + } + }, + SMB1_COMMAND_NT_CREATE_ANDX => { + match parse_smb_create_andx_request_record(r.data) { + IResult::Done(_, cr) => { + SCLogDebug!("Create AndX {:?}", cr); + let del = cr.create_options & 0x0000_1000 != 0; + let dir = cr.create_options & 0x0000_0001 != 0; + SCLogDebug!("del {} dir {} options {:08x}", del, dir, cr.create_options); + + let name_key = SMBCommonHdr::from1(r, SMBHDR_TYPE_FILENAME); + let name_val = cr.file_name.to_vec(); + state.ssn2vec_map.insert(name_key, name_val); + + let tx_hdr = SMBCommonHdr::from1(r, SMBHDR_TYPE_GENERICTX); + let tx = state.new_create_tx(&cr.file_name.to_vec(), + cr.disposition, del, dir, tx_hdr); + tx.vercmd.set_smb1_cmd(r.command); + SCLogDebug!("TS CREATE TX {} created", tx.id); + true + }, + _ => { + events.push(SMBEvent::MalformedData); + false + }, + } + }, + SMB1_COMMAND_SESSION_SETUP_ANDX => { + SCLogDebug!("SMB1_COMMAND_SESSION_SETUP_ANDX user_id {}", r.user_id); + match parse_smb_setup_andx_record(r.data) { + IResult::Done(_, setup) => { + parse_secblob(state, setup.sec_blob); +/* + _ => { + events.push(SMBEvent::MalformedNtlmsspRequest); + }, +*/ + }, + _ => { + events.push(SMBEvent::MalformedData); + }, + } + key_ssn_id = 0; + false + }, + SMB1_COMMAND_TREE_CONNECT_ANDX => { + SCLogDebug!("SMB1_COMMAND_TREE_CONNECT_ANDX"); + match parse_smb_connect_tree_andx_record(r.data) { + IResult::Done(_, create_record) => { + let name_key = SMBCommonHdr::from1(r, SMBHDR_TYPE_TREE); + let mut name_val = create_record.share.to_vec(); + name_val.retain(|&i|i != 0x00); + if name_val.len() > 1 { + name_val = name_val[1..].to_vec(); + } + //state.ssn2vec_map.insert(name_key, name_val); + + // store hdr as SMBHDR_TYPE_TREE, so with tree id 0 + // when the response finds this we update it + let tx = state.new_treeconnect_tx(name_key, name_val); + tx.request_done = true; + tx.vercmd.set_smb1_cmd(SMB1_COMMAND_TREE_CONNECT_ANDX); + true + }, + _ => { + events.push(SMBEvent::MalformedData); + false + }, + } + }, + SMB1_COMMAND_TREE_DISCONNECT => { + let tree_key = SMBCommonHdr::from1(r, SMBHDR_TYPE_SHARE); + state.ssn2tree_map.remove(&tree_key); + false + }, + SMB1_COMMAND_CLOSE => { + match parse_smb1_close_request_record(r.data) { + IResult::Done(_, cd) => { + let mut fid = cd.fid.to_vec(); + fid.extend_from_slice(&u32_as_bytes(r.ssn_id)); + SCLogDebug!("closing FID {:?}/{:?}", cd.fid, fid); + + // we can have created 2 txs for a FID: one for reads + // and one for writes. So close both. + match state.get_file_tx_by_guid(&fid, STREAM_TOSERVER) { + Some((tx, files, flags)) => { + SCLogDebug!("found tx {}", tx.id); + if let Some(SMBTransactionTypeData::FILE(ref mut tdf)) = tx.type_data { + if !tx.request_done { + SCLogDebug!("closing file tx {} FID {:?}/{:?}", tx.id, cd.fid, fid); + tdf.file_tracker.close(files, flags); + tx.request_done = true; + tx.response_done = true; + SCLogDebug!("tx {} is done", tx.id); + } + // as a precaution, reset guid so it can be reused + tdf.guid.clear(); // TODO review + } + }, + None => { }, + } + match state.get_file_tx_by_guid(&fid, STREAM_TOCLIENT) { + Some((tx, files, flags)) => { + SCLogDebug!("found tx {}", tx.id); + if let Some(SMBTransactionTypeData::FILE(ref mut tdf)) = tx.type_data { + if !tx.request_done { + SCLogDebug!("closing file tx {} FID {:?}/{:?}", tx.id, cd.fid, fid); + tdf.file_tracker.close(files, flags); + tx.request_done = true; + tx.response_done = true; + SCLogDebug!("tx {} is done", tx.id); + } + // as a precaution, reset guid so it can be reused + tdf.guid.clear(); // TODO review now that fid is improved + } + }, + None => { }, + } + }, + _ => { + events.push(SMBEvent::MalformedData); + }, + } + false + }, + SMB1_COMMAND_NT_CANCEL => { + no_response_expected = true; + false + }, + SMB1_COMMAND_TRANS2_SECONDARY => { + no_response_expected = true; + false + }, + _ => { + if r.command == SMB1_COMMAND_TRANS2 || + r.command == SMB1_COMMAND_LOGOFF_ANDX || + r.command == SMB1_COMMAND_TREE_DISCONNECT || + r.command == SMB1_COMMAND_NT_TRANS || + r.command == SMB1_COMMAND_NT_CANCEL || + r.command == SMB1_COMMAND_RENAME || + r.command == SMB1_COMMAND_CHECK_DIRECTORY || + r.command == SMB1_COMMAND_LOCKING_ANDX || + r.command == SMB1_COMMAND_ECHO || + r.command == SMB1_COMMAND_TRANS + { } else { + SCLogDebug!("unsupported command {}/{}", + r.command, &smb1_command_string(r.command)); + } + false + }, + }; + if !have_tx { + if smb1_create_new_tx(r.command) { + let tx_key = SMBCommonHdr::new(SMBHDR_TYPE_GENERICTX, + key_ssn_id as u64, key_tree_id as u32, key_multiplex_id as u64); + let tx = state.new_generic_tx(1, r.command as u16, tx_key); + SCLogDebug!("tx {} created for {}/{}", tx.id, r.command, &smb1_command_string(r.command)); + tx.set_events(events); + if no_response_expected { + tx.response_done = true; + } + } + } + 0 +} + +pub fn smb1_response_record<'b>(state: &mut SMBState, r: &SmbRecord<'b>) -> u32 { + SCLogDebug!("record: {:?} command {}", r.greeter, r.command); + + let key_ssn_id = r.ssn_id; + let key_tree_id = r.tree_id; + let key_multiplex_id = r.multiplex_id; + let mut tx_sync = false; + let mut events : Vec = Vec::new(); + + let have_tx = match r.command { + SMB1_COMMAND_READ_ANDX => { + smb1_read_response_record(state, &r); + true // tx handling in func + }, + SMB1_COMMAND_NEGOTIATE_PROTOCOL => { + SCLogDebug!("SMB1_COMMAND_NEGOTIATE_PROTOCOL response"); + match parse_smb1_negotiate_protocol_response_record(r.data) { + IResult::Done(_, pr) => { + let (have_ntx, dialect) = match state.get_negotiate_tx(1) { + Some(tx) => { + tx.set_status(r.nt_status, r.is_dos_error); + tx.response_done = true; + SCLogDebug!("tx {} is done", tx.id); + let d = match tx.type_data { + Some(SMBTransactionTypeData::NEGOTIATE(ref mut x)) => { + let dialect_idx = pr.dialect_idx as usize; + if x.dialects.len() <= dialect_idx { + None + } else { + let d = x.dialects[dialect_idx].to_vec(); + Some(d) + } + }, + _ => { None }, + }; + if d == None { + tx.set_event(SMBEvent::MalformedData); + } + (true, d) + }, + None => { (false, None) }, + }; + match dialect { + Some(d) => { + SCLogDebug!("dialect {:?}", d); + state.dialect_vec = Some(d); + }, + _ => { }, + } + have_ntx + }, + _ => { + events.push(SMBEvent::MalformedData); + false + }, + } + }, + SMB1_COMMAND_TREE_CONNECT_ANDX => { + if r.nt_status != SMB_NTSTATUS_SUCCESS { + let name_key = SMBCommonHdr::from1(r, SMBHDR_TYPE_TREE); + match state.get_treeconnect_tx(name_key) { + Some(tx) => { + if let Some(SMBTransactionTypeData::TREECONNECT(ref mut tdn)) = tx.type_data { + tdn.tree_id = r.tree_id as u32; + } + tx.set_status(r.nt_status, r.is_dos_error); + tx.response_done = true; + SCLogDebug!("tx {} is done", tx.id); + }, + None => { }, + } + return 0; + } + + match parse_smb_connect_tree_andx_response_record(r.data) { + IResult::Done(_, tr) => { + let name_key = SMBCommonHdr::from1(r, SMBHDR_TYPE_TREE); + let is_pipe = tr.service == "IPC".as_bytes(); + let mut share_name = Vec::new(); + let found = match state.get_treeconnect_tx(name_key) { + Some(tx) => { + if let Some(SMBTransactionTypeData::TREECONNECT(ref mut tdn)) = tx.type_data { + tdn.is_pipe = is_pipe; + tdn.tree_id = r.tree_id as u32; + share_name = tdn.share_name.to_vec(); + } + tx.hdr = SMBCommonHdr::from1(r, SMBHDR_TYPE_HEADER); + tx.set_status(r.nt_status, r.is_dos_error); + tx.response_done = true; + SCLogDebug!("tx {} is done", tx.id); + true + }, + None => { false }, + }; + if found { + let tree = SMBTree::new(share_name.to_vec(), is_pipe); + let tree_key = SMBCommonHdr::from1(r, SMBHDR_TYPE_SHARE); + state.ssn2tree_map.insert(tree_key, tree); + } + found + }, + _ => { + events.push(SMBEvent::MalformedData); + false + }, + } + }, + SMB1_COMMAND_TREE_DISCONNECT => { + // normally removed when processing request, + // but in case we missed that try again here + let tree_key = SMBCommonHdr::from1(r, SMBHDR_TYPE_SHARE); + state.ssn2tree_map.remove(&tree_key); + false + }, + SMB1_COMMAND_NT_CREATE_ANDX => { + match parse_smb_create_andx_response_record(r.data) { + IResult::Done(_, cr) => { + SCLogDebug!("Create AndX {:?}", cr); + + let guid_key = SMBCommonHdr::from1(r, SMBHDR_TYPE_FILENAME); + match state.ssn2vec_map.remove(&guid_key) { + Some(mut p) => { + p.retain(|&i|i != 0x00); + + let mut fid = cr.fid.to_vec(); + fid.extend_from_slice(&u32_as_bytes(r.ssn_id)); + SCLogDebug!("SMB1_COMMAND_NT_CREATE_ANDX fid {:?}", fid); + SCLogDebug!("fid {:?} name {:?}", fid, p); + state.guid2name_map.insert(fid, p); + }, + _ => { + SCLogDebug!("SMBv1 response: GUID NOT FOUND"); + }, + } + }, + _ => { events.push(SMBEvent::MalformedData); }, + } + false + }, + SMB1_COMMAND_TRANS => { + smb1_trans_response_record(state, r); + true + }, + SMB1_COMMAND_SESSION_SETUP_ANDX => { + tx_sync = true; + false + }, + SMB1_COMMAND_LOGOFF_ANDX => { + tx_sync = true; + false + }, + _ => { + false + }, + }; + + if !have_tx && tx_sync { + match state.get_last_tx(1, r.command as u16) { + Some(tx) => { + SCLogDebug!("last TX {} is CMD {}", tx.id, &smb1_command_string(r.command)); + tx.response_done = true; + SCLogDebug!("tx {} cmd {} is done", tx.id, r.command); + tx.set_status(r.nt_status, r.is_dos_error); + tx.set_events(events); + }, + _ => {}, + } + } else if !have_tx && smb1_create_new_tx(r.command) { + let tx_key = SMBCommonHdr::new(SMBHDR_TYPE_GENERICTX, + key_ssn_id as u64, key_tree_id as u32, key_multiplex_id as u64); + let _have_tx2 = match state.get_generic_tx(1, r.command as u16, &tx_key) { + Some(tx) => { + tx.request_done = true; + tx.response_done = true; + SCLogDebug!("tx {} cmd {} is done", tx.id, r.command); + tx.set_status(r.nt_status, r.is_dos_error); + tx.set_events(events); + true + }, + _ => { + SCLogDebug!("no TX found for key {:?}", tx_key); + false + }, + }; + } else { + SCLogDebug!("have tx for cmd {}", r.command); + } + 0 +} + +pub fn get_service_for_nameslice(nameslice: &[u8]) -> (&'static str, bool) +{ + let mut name = nameslice.to_vec(); + name.retain(|&i|i != 0x00); + + match str::from_utf8(&name) { + Ok("\\PIPE\\LANMAN") => ("LANMAN", false), + Ok("\\PIPE\\") => ("PIPE", true), // TODO not sure if this is true + Err(_) => ("MALFORMED", false), + Ok(&_) => { + SCLogNotice!("don't know \"{}\"", String::from_utf8_lossy(&name)); + ("UNKNOWN", false) + }, + } +} + +pub fn smb1_trans_request_record<'b>(state: &mut SMBState, r: &SmbRecord<'b>) +{ + match parse_smb_trans_request_record(r.data, r) { + IResult::Done(_, rd) => { + SCLogDebug!("TRANS request {:?}", rd); + + /* if we have a fid, store it so the response can pick it up */ + if rd.pipe != None { + let pipe = rd.pipe.unwrap(); + state.ssn2vec_map.insert(SMBCommonHdr::from1(r, SMBHDR_TYPE_GUID), + pipe.fid.to_vec()); + } + + let (sername, is_dcerpc) = get_service_for_nameslice(&rd.txname.name); + SCLogDebug!("service: {} dcerpc {}", sername, is_dcerpc); + if is_dcerpc { + // store tx name so the response also knows this is dcerpc + let txn_hdr = SMBCommonHdr::from1(r, SMBHDR_TYPE_TXNAME); + state.ssn2vec_map.insert(txn_hdr, rd.txname.name.to_vec()); + + // trans request will tell us the max size of the response + // if there is more response data, it will first give a + // TRANS with 'max data cnt' worth of data, and the rest + // will be pulled by a 'READ'. So we setup an expectation + // here. + if rd.params.max_data_cnt > 0 { + // expect max max_data_cnt for this fid in the other dir + let ehdr = SMBCommonHdr::from1(r, SMBHDR_TYPE_MAX_SIZE); + state.ssn2maxsize_map.insert(ehdr, rd.params.max_data_cnt); + } + + SCLogDebug!("SMBv1 TRANS TO PIPE"); + let hdr = SMBCommonHdr::from1(r, SMBHDR_TYPE_HEADER); + let vercmd = SMBVerCmdStat::new1(r.command); + smb_write_dcerpc_record(state, vercmd, hdr, &rd.data.data); + } + }, + _ => { + state.set_event(SMBEvent::MalformedData); + }, + } +} + +pub fn smb1_trans_response_record<'b>(state: &mut SMBState, r: &SmbRecord<'b>) +{ + match parse_smb_trans_response_record(r.data) { + IResult::Done(_, rd) => { + SCLogDebug!("TRANS response {:?}", rd); + + // see if we have a stored fid + let fid = match state.ssn2vec_map.remove( + &SMBCommonHdr::from1(r, SMBHDR_TYPE_GUID)) { + Some(f) => f, + None => Vec::new(), + }; + SCLogDebug!("FID {:?}", fid); + + // if we get status 'BUFFER_OVERFLOW' this is only a part of + // the data. Store it in the ssn/tree for later use. + if r.nt_status == SMB_NTSTATUS_BUFFER_OVERFLOW { + state.ssnguid2vec_map.insert(SMBHashKeyHdrGuid::new( + SMBCommonHdr::from1(r, SMBHDR_TYPE_TRANS_FRAG), fid), + rd.data.to_vec()); + } else { + let txn_hdr = SMBCommonHdr::from1(r, SMBHDR_TYPE_TXNAME); + let is_dcerpc = match state.ssn2vec_map.remove(&txn_hdr) { + None => false, + Some(s) => { + let (sername, is_dcerpc) = get_service_for_nameslice(&s); + SCLogDebug!("service: {} dcerpc {}", sername, is_dcerpc); + is_dcerpc + }, + }; + if is_dcerpc { + SCLogDebug!("SMBv1 TRANS TO PIPE"); + let hdr = SMBCommonHdr::from1(r, SMBHDR_TYPE_HEADER); + let vercmd = SMBVerCmdStat::new1_with_ntstatus(r.command, r.nt_status); + smb_read_dcerpc_record(state, vercmd, hdr, &fid, &rd.data); + } + } + }, + _ => { + state.set_event(SMBEvent::MalformedData); + }, + } +} + +/// Handle WRITE and WRITE_ANDX request records +pub fn smb1_write_request_record<'b>(state: &mut SMBState, r: &SmbRecord<'b>) +{ + let result = if r.command == SMB1_COMMAND_WRITE_ANDX { + parse_smb1_write_andx_request_record(r.data) + } else { + parse_smb1_write_request_record(r.data) + }; + match result { + IResult::Done(_, rd) => { + SCLogDebug!("SMBv1: write andx => {:?}", rd); + + let mut file_fid = rd.fid.to_vec(); + file_fid.extend_from_slice(&u32_as_bytes(r.ssn_id)); + SCLogDebug!("SMBv1 WRITE: FID {:?} offset {}", + file_fid, rd.offset); + + let file_name = match state.guid2name_map.get(&file_fid) { + Some(n) => n.to_vec(), + None => Vec::new(), + }; + let found = match state.get_file_tx_by_guid(&file_fid, STREAM_TOSERVER) { + Some((tx, files, flags)) => { + let file_id : u32 = tx.id as u32; + if let Some(SMBTransactionTypeData::FILE(ref mut tdf)) = tx.type_data { + filetracker_newchunk(&mut tdf.file_tracker, files, flags, + &file_name, rd.data, rd.offset, + rd.len, 0, false, &file_id); + SCLogDebug!("FID {:?} found at tx {}", file_fid, tx.id); + } + true + }, + None => { false }, + }; + if !found { + let tree_key = SMBCommonHdr::from1(r, SMBHDR_TYPE_SHARE); + let (share_name, is_pipe) = match state.ssn2tree_map.get(&tree_key) { + Some(n) => (n.name.to_vec(), n.is_pipe), + None => (Vec::new(), false), + }; + if is_pipe { + SCLogDebug!("SMBv1 WRITE TO PIPE"); + let hdr = SMBCommonHdr::from1(r, SMBHDR_TYPE_HEADER); + let vercmd = SMBVerCmdStat::new1_with_ntstatus(r.command, r.nt_status); + smb_write_dcerpc_record(state, vercmd, hdr, &rd.data); + } else { + let (tx, files, flags) = state.new_file_tx(&file_fid, &file_name, STREAM_TOSERVER); + if let Some(SMBTransactionTypeData::FILE(ref mut tdf)) = tx.type_data { + let file_id : u32 = tx.id as u32; + SCLogDebug!("FID {:?} found at tx {}", file_fid, tx.id); + filetracker_newchunk(&mut tdf.file_tracker, files, flags, + &file_name, rd.data, rd.offset, + rd.len, 0, false, &file_id); + tdf.share_name = share_name; + } + tx.vercmd.set_smb1_cmd(SMB1_COMMAND_WRITE_ANDX); + } + } + state.file_ts_left = rd.len - rd.data.len() as u32; + state.file_ts_guid = file_fid.to_vec(); + SCLogDebug!("SMBv1 WRITE RESPONSE: {} bytes left", state.file_tc_left); + }, + _ => { + state.set_event(SMBEvent::MalformedData); + }, + } +} + +fn smb1_read_response_record_generic<'b>(state: &mut SMBState, r: &SmbRecord<'b>) { + // see if we want a tx per READ command + if smb1_create_new_tx(r.command) { + let tx_key = SMBCommonHdr::from1(r, SMBHDR_TYPE_GENERICTX); + let tx = state.get_generic_tx(1, r.command as u16, &tx_key); + if let Some(tx) = tx { + tx.request_done = true; + tx.response_done = true; + SCLogDebug!("tx {} cmd {} is done", tx.id, r.command); + tx.set_status(r.nt_status, r.is_dos_error); + } + } +} + +pub fn smb1_read_response_record<'b>(state: &mut SMBState, r: &SmbRecord<'b>) +{ + smb1_read_response_record_generic(state, r); + + if r.nt_status != SMB_NTSTATUS_SUCCESS { + return; + } + + match parse_smb_read_andx_response_record(r.data) { + IResult::Done(_, rd) => { + SCLogDebug!("SMBv1: read response => {:?}", rd); + + let fid_key = SMBCommonHdr::from1(r, SMBHDR_TYPE_OFFSET); + let (offset, file_fid) = match state.ssn2vecoffset_map.remove(&fid_key) { + Some(o) => (o.offset, o.guid), + None => { + SCLogNotice!("SMBv1 READ response: reply to unknown request: left {} {:?}", + rd.len - rd.data.len() as u32, rd); + state.skip_tc = rd.len - rd.data.len() as u32; + return; + }, + }; + SCLogDebug!("SMBv1 READ: FID {:?} offset {}", file_fid, offset); + + let tree_key = SMBCommonHdr::from1(r, SMBHDR_TYPE_SHARE); + let (is_pipe, share_name) = match state.ssn2tree_map.get(&tree_key) { + Some(n) => (n.is_pipe, n.name.to_vec()), + _ => { (false, Vec::new()) }, + }; + if !is_pipe { + let file_name = match state.guid2name_map.get(&file_fid) { + Some(n) => n.to_vec(), + None => Vec::new(), + }; + let found = match state.get_file_tx_by_guid(&file_fid, STREAM_TOCLIENT) { + Some((tx, files, flags)) => { + if let Some(SMBTransactionTypeData::FILE(ref mut tdf)) = tx.type_data { + let file_id : u32 = tx.id as u32; + SCLogDebug!("FID {:?} found at tx {}", file_fid, tx.id); + filetracker_newchunk(&mut tdf.file_tracker, files, flags, + &file_name, rd.data, offset, + rd.len, 0, false, &file_id); + } + true + }, + None => { false }, + }; + if !found { + let (tx, files, flags) = state.new_file_tx(&file_fid, &file_name, STREAM_TOCLIENT); + if let Some(SMBTransactionTypeData::FILE(ref mut tdf)) = tx.type_data { + let file_id : u32 = tx.id as u32; + SCLogDebug!("FID {:?} found at tx {}", file_fid, tx.id); + filetracker_newchunk(&mut tdf.file_tracker, files, flags, + &file_name, rd.data, offset, + rd.len, 0, false, &file_id); + tdf.share_name = share_name; + } + tx.vercmd.set_smb1_cmd(SMB1_COMMAND_READ_ANDX); + } + } else { + SCLogDebug!("SMBv1 READ response from PIPE"); + let hdr = SMBCommonHdr::from1(r, SMBHDR_TYPE_HEADER); + let vercmd = SMBVerCmdStat::new1(r.command); + + // hack: we store fid with ssn id mixed in, but here we want the + // real thing instead. + let pure_fid = if file_fid.len() > 2 { &file_fid[0..2] } else { &[] }; + smb_read_dcerpc_record(state, vercmd, hdr, &pure_fid, &rd.data); + } + + state.file_tc_left = rd.len - rd.data.len() as u32; + state.file_tc_guid = file_fid.to_vec(); + SCLogDebug!("SMBv1 READ RESPONSE: {} bytes left", state.file_tc_left); + } + _ => { + state.set_event(SMBEvent::MalformedData); + }, + } +} diff --git a/rust/src/smb/smb1_records.rs b/rust/src/smb/smb1_records.rs new file mode 100644 index 0000000000..b65fa0af0c --- /dev/null +++ b/rust/src/smb/smb1_records.rs @@ -0,0 +1,599 @@ +/* Copyright (C) 2017 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. + */ + +use log::*; +use nom::{rest, le_u8, le_u16, le_u32, le_u64, IResult}; + +#[derive(Debug,PartialEq)] +pub struct Smb1WriteRequestRecord<'a> { + pub offset: u64, + pub len: u32, + pub fid: &'a[u8], + pub data: &'a[u8], +} + +named!(pub parse_smb1_write_request_record, + do_parse!( + wct: le_u8 + >> fid: take!(2) + >> count: le_u16 + >> offset: le_u32 + >> remaining: le_u16 + >> bcc: le_u16 + >> buffer_format: le_u8 + >> data_len: le_u16 + >> file_data: take!(data_len) + >> (Smb1WriteRequestRecord { + offset: offset as u64, + len: data_len as u32, + fid:fid, + data:file_data, + })) +); + +named!(pub parse_smb1_write_andx_request_record, + do_parse!( + wct: le_u8 + >> andx_command: le_u8 + >> take!(1) // reserved + >> andx_offset: le_u16 + >> fid: take!(2) + >> offset: le_u32 + >> take!(4) // reserved + >> write_mode: le_u16 + >> remaining: le_u16 + >> data_len_high: le_u16 + >> data_len_low: le_u16 + >> data_offset: le_u16 + >> high_offset: cond!(wct==14,le_u32) + >> bcc: le_u16 + //>> padding: cond!(data_offset > 32, take!(data_offset - 32)) + >> padding: cond!(bcc > data_len_low, take!(bcc - data_len_low)) // TODO figure out how this works with data_len_high + >> file_data: rest + >> (Smb1WriteRequestRecord { + offset: if high_offset != None { ((high_offset.unwrap() as u64) << 32)|(offset as u64) } else { 0 }, + len: (((data_len_high as u32) << 16) as u32)|(data_len_low as u32), + fid:fid, + data:file_data, + })) +); + +#[derive(Debug,PartialEq)] +pub struct Smb1NegotiateProtocolResponseRecord<> { + pub dialect_idx: u16, +} + +named!(pub parse_smb1_negotiate_protocol_response_record, + do_parse!( + le_u8 + >> dialect_idx: le_u16 + >> (Smb1NegotiateProtocolResponseRecord { + dialect_idx:dialect_idx, + })) +); + +#[derive(Debug,PartialEq)] +pub struct Smb1NegotiateProtocolRecord<'a> { + pub dialects: Vec<&'a [u8]>, +} + +named!(pub parse_smb1_negotiate_protocol_record, + do_parse!( + wtc: le_u8 + >> bcc: le_u16 + // dialects is a list of [1 byte buffer format][string][0 terminator] + >> dialects: many1!(take_until_and_consume!("\0")) + >> (Smb1NegotiateProtocolRecord { + dialects:dialects, + })) +); + + +#[derive(Debug,PartialEq)] +pub struct Smb1ResponseRecordTreeConnectAndX<'a> { + pub service: &'a[u8], + pub nativefs: &'a[u8], +} + +named!(pub parse_smb_connect_tree_andx_response_record, + do_parse!( + wct: le_u8 + >> andx_command: le_u8 + >> take!(1) // reserved + >> andx_offset: le_u16 + >> cond!(wct >= 3, take!(2)) // optional support + >> cond!(wct == 7, take!(8)) // access masks + >> bcc: le_u16 + >> service: take_until_and_consume!("\x00") + >> nativefs: rest + >> (Smb1ResponseRecordTreeConnectAndX { + service:service, + nativefs:nativefs, + })) +); + +#[derive(Debug,PartialEq)] +pub struct SmbRecordTreeConnectAndX<'a> { + pub len: usize, + pub share: &'a[u8], +} + +named!(pub parse_smb_connect_tree_andx_record, + do_parse!( + skip1: take!(7) + >> pwlen: le_u16 + >> bcc: le_u16 + >> pw: take!(pwlen) + >> share: take!(bcc - (6 + pwlen)) + >> service: take!(6) + >> (SmbRecordTreeConnectAndX { + len:bcc as usize - (6 + pwlen as usize) as usize, + share:share, + })) +); + +#[derive(Debug,PartialEq)] +pub struct SmbRecordTransRequest<'a> { + pub params: SmbRecordTransRequestParams, + pub pipe: Option>, + pub txname: SmbRecordTransRequestTxname<'a>, + pub data: SmbRecordTransRequestData<'a>, +} + +#[derive(Debug,PartialEq)] +pub struct SmbPipeProtocolRecord<'a> { + pub function: u16, + pub fid: &'a[u8], +} + +named!(pub parse_smb_trans_request_record_pipe, + dbg_dmp!(do_parse!( + fun: le_u16 + >> fid: take!(2) + >> (SmbPipeProtocolRecord { + function: fun, + fid: fid, + }) + )) +); + + +#[derive(Debug,PartialEq)] +pub struct SmbRecordTransRequestParams<> { + pub max_data_cnt: u16, + param_cnt: u16, + param_offset: u16, + data_cnt: u16, + data_offset: u16, + bcc: u16, +} + +named!(pub parse_smb_trans_request_record_params<(SmbRecordTransRequestParams, Option)>, + dbg_dmp!(do_parse!( + wct: le_u8 + >> total_param_cnt: le_u16 + >> total_data_count: le_u16 + >> max_param_cnt: le_u16 + >> max_data_cnt: le_u16 + >> max_setup_cnt: le_u8 + >> take!(1) // reserved + >> take!(2) // flags + >> timeout: le_u32 + >> take!(2) // reserved + >> param_cnt: le_u16 + >> param_offset: le_u16 + >> data_cnt: le_u16 + >> data_offset: le_u16 + >> setup_cnt: le_u8 + >> take!(1) // reserved + >> pipe: cond!(wct == 16 && setup_cnt == 2, parse_smb_trans_request_record_pipe) // reserved + >> bcc: le_u16 + >> (( SmbRecordTransRequestParams { + max_data_cnt:max_data_cnt, + param_cnt:param_cnt, + param_offset:param_offset, + data_cnt:data_cnt, + data_offset:data_offset, + bcc:bcc, + }, + pipe)))) +); + +#[derive(Debug,PartialEq)] +pub struct SmbRecordTransRequestTxname<'a> { + pub name: &'a[u8], +} + +pub fn parse_smb_trans_request_tx_name_ascii(i: &[u8]) + -> IResult<&[u8], SmbRecordTransRequestTxname> +{ + do_parse!(i, + name: take_until_and_consume!("\0") + >> (SmbRecordTransRequestTxname { + name: name, + }) + ) +} + +pub fn parse_smb_trans_request_tx_name_unicode(i: &[u8], offset: usize) + -> IResult<&[u8], SmbRecordTransRequestTxname> +{ + do_parse!(i, + cond!(offset % 2 == 1, take!(1)) + >> name: take_until_and_consume!("\0\0\0") + >> (SmbRecordTransRequestTxname { + name: name, + }) + ) +} + +#[derive(Debug,PartialEq)] +pub struct SmbRecordTransRequestData<'a> { + pub data: &'a[u8], +} + +pub fn parse_smb_trans_request_record_data(i: &[u8], + pad1: usize, param_cnt: u16, pad2: usize, data_len: u16) + -> IResult<&[u8], SmbRecordTransRequestData> +{ + do_parse!(i, + take!(pad1) + >> take!(param_cnt) + >> take!(pad2) + >> data: take!(data_len) + >> (SmbRecordTransRequestData { + data:data, + }) + ) +} + +pub fn parse_smb_trans_request_record<'a, 'b>(i: &'a[u8], r: &SmbRecord<'b>) + -> IResult<&'a[u8], SmbRecordTransRequest<'a>> +{ + let (rem, (params, pipe)) = match parse_smb_trans_request_record_params(i) { + IResult::Done(rem, (rd, p)) => (rem, (rd, p)), + IResult::Incomplete(ii) => { + return IResult::Incomplete(ii); + } + IResult::Error(e) => { + return IResult::Error(e); + } + }; + let mut offset = 32 + (i.len() - rem.len()); // init with SMB header + SCLogDebug!("params {:?}: offset {}", params, offset); + + let name = if r.flags2 & 0x8000_u16 != 0 { // unicode + SCLogDebug!("unicode flag set"); + parse_smb_trans_request_tx_name_unicode(rem, offset) + } else { + SCLogDebug!("unicode flag NOT set"); + parse_smb_trans_request_tx_name_ascii(rem) + }; + let (rem2, n) = match name { + IResult::Done(rem, rd) => (rem, rd), + IResult::Incomplete(ii) => { + return IResult::Incomplete(ii); + } + IResult::Error(e) => { + return IResult::Error(e); + } + }; + offset += rem.len() - rem2.len(); + SCLogDebug!("n {:?}: offset {}", n, offset); + + // spec says pad to 4 bytes, but traffic shows this doesn't + // always happen. + let pad1 = if offset == params.param_offset as usize || + offset == params.data_offset as usize { + 0 + } else { + offset % 4 + }; + SCLogDebug!("pad1 {}", pad1); + offset += pad1; + offset += params.param_cnt as usize; + + let recdata = if params.data_cnt > 0 { + // ignore padding rule if we're already at the correct + // offset. + let pad2 = if offset == params.data_offset as usize { + 0 + } else { + offset % 4 + }; + SCLogDebug!("pad2 {}", pad2); + + let d = match parse_smb_trans_request_record_data(rem2, + pad1, params.param_cnt, pad2, params.data_cnt) { + IResult::Done(_, rd) => rd, + IResult::Incomplete(ii) => { + return IResult::Incomplete(ii); + } + IResult::Error(e) => { + return IResult::Error(e); + } + }; + SCLogDebug!("d {:?}", d); + d + } else { + SmbRecordTransRequestData { data: &[], } // no data + }; + + let res = SmbRecordTransRequest { + params: params, pipe: pipe, txname: n, data: recdata, + }; + IResult::Done(&rem, res) +} + + +#[derive(Debug,PartialEq)] +pub struct SmbRecordTransResponse<'a> { + pub data_cnt: u16, + pub bcc: u16, + pub data: &'a[u8], +} + +named!(pub parse_smb_trans_response_error_record, + do_parse!( + wct: le_u8 + >> bcc: le_u16 + >> (SmbRecordTransResponse { + data_cnt:0, + bcc:bcc, + data:&[], + })) +); + +named!(pub parse_smb_trans_response_regular_record, + do_parse!( + wct: le_u8 + >> total_param_cnt: le_u16 + >> total_data_count: le_u16 + >> take!(2) // reserved + >> param_cnt: le_u16 + >> param_offset: le_u16 + >> param_displacement: le_u16 + >> data_cnt: le_u16 + >> data_offset: le_u16 + >> data_displacement: le_u16 + >> setup_cnt: le_u8 + >> take!(1) // reserved + >> bcc: le_u16 + >> take!(1) // padding + >> data: take!(data_cnt) + >> (SmbRecordTransResponse { + data_cnt:data_cnt, + bcc:bcc, + data:data, + })) +); + +named!(pub parse_smb_trans_response_record, + switch!(peek!(le_u8), // wct + 0 => call!(parse_smb_trans_response_error_record) | + _ => call!(parse_smb_trans_response_regular_record)) +); + +#[derive(Debug,PartialEq)] +pub struct SmbRecordSetupAndX<'a> { + pub sec_blob: &'a[u8], +} + +named!(pub parse_smb_setup_andx_record, + do_parse!( + skip1: take!(15) + >> sec_blob_len: le_u16 + >> skip2: take!(8) + >> bcc: le_u16 + >> sec_blob: take!(sec_blob_len) + >> skip3: rest + >> (SmbRecordSetupAndX { + sec_blob:sec_blob, + })) +); + +#[derive(Debug,PartialEq)] +pub struct SmbRequestReadAndXRecord<'a> { + pub fid: &'a[u8], + pub size: u64, + pub offset: u64, +} + +named!(pub parse_smb_read_andx_request_record, + do_parse!( + wtc: le_u8 + >> andx_command: le_u8 + >> take!(1) // reserved + >> andx_offset: le_u16 + >> fid: take!(2) + >> offset: le_u32 + >> max_count_low: le_u16 + >> take!(2) + >> max_count_high: le_u32 + >> take!(2) + >> high_offset: cond!(wtc==12,le_u32) // only from wtc ==12? + + >> (SmbRequestReadAndXRecord { + fid:fid, + size: (((max_count_high as u64) << 16)|max_count_low as u64), + offset: if high_offset != None { ((high_offset.unwrap() as u64) << 32)|(offset as u64) } else { 0 }, + })) +); + +#[derive(Debug,PartialEq)] +pub struct SmbResponseReadAndXRecord<'a> { + pub len: u32, + pub data: &'a[u8], +} + +named!(pub parse_smb_read_andx_response_record, + do_parse!( + wtc: le_u8 + >> andx_command: le_u8 + >> take!(1) // reserved + >> andx_offset: le_u16 + >> take!(6) + >> data_len_low: le_u16 + >> data_offset: le_u16 + >> data_len_high: le_u32 + >> take!(6) // reserved + >> bcc: le_u16 + >> padding: cond!(bcc > data_len_low, take!(bcc - data_len_low)) // TODO figure out how this works with data_len_high + >> file_data: rest + + >> (SmbResponseReadAndXRecord { + len: (((data_len_high as u32) << 16)|data_len_low as u32), + data:file_data, + })) +); + +#[derive(Debug,PartialEq)] +pub struct SmbRequestCreateAndXRecord<'a> { + pub disposition: u32, + pub create_options: u32, + pub file_name: &'a[u8], +} + +named!(pub parse_smb_create_andx_request_record, + do_parse!( + skip1: take!(6) + >> file_name_len: le_u16 + >> skip3: take!(28) + >> disposition: le_u32 + >> create_options: le_u32 + >> skip2: take!(8) + >> file_name: take!(file_name_len) + >> skip3: rest + >> (SmbRequestCreateAndXRecord { + disposition: disposition, + create_options: create_options, + file_name: file_name, + })) +); + +#[derive(Debug,PartialEq)] +pub struct SmbResponseCreateAndXRecord<'a> { + pub fid: &'a[u8], + pub file_size: u64, +} + +named!(pub parse_smb_create_andx_response_record, + do_parse!( + wct: le_u8 + >> andx_command: le_u8 + >> take!(1) + >> andx_offset: le_u16 + >> oplock_level: le_u8 + >> fid: take!(2) + >> create_action: le_u32 + >> take!(36) + >> file_size: le_u64 + >> take!(8) + >> file_type: le_u16 + >> take!(2) + >> is_dir: le_u8 + >> (SmbResponseCreateAndXRecord { + fid:fid, + file_size:file_size, + })) +); + +#[derive(Debug,PartialEq)] +pub struct SmbRequestCloseRecord<'a> { + pub fid: &'a[u8], +} + +named!(pub parse_smb1_close_request_record, + do_parse!( + take!(1) + >> fid: take!(2) + >> (SmbRequestCloseRecord { + fid:fid, + })) +); + +#[derive(Debug,PartialEq)] +pub struct SmbVersion<> { + pub version: u8, +// pub data: &'a[u8], +} + +named!(pub parse_smb_version, + do_parse!( + version: le_u8 + >> tag!("SMB") +// >> data: rest + >> (SmbVersion { + version:version, +// data:data, + })) +); + +#[derive(Debug,PartialEq)] +pub struct SmbRecord<'a> { + //pub nbss_hdr: NbssRecord<'a>, + pub greeter: &'a[u8], + + pub command: u8, + pub is_dos_error: bool, + pub nt_status: u32, + pub flags: u8, + pub flags2: u16, + + pub tree_id: u16, + pub user_id: u16, + pub multiplex_id: u16, + + pub process_id: u32, + pub ssn_id: u32, + + pub data: &'a[u8], +} + +named!(pub parse_smb_record, + do_parse!( + server_component:take!(4) // ff SMB + >> command:le_u8 + >> nt_status:le_u32 + >> flags:le_u8 + >> flags2:le_u16 + >> process_id_high:le_u16 + >> signature:take!(8) + >> reserved:take!(2) + >> tree_id:le_u16 + >> process_id:le_u16 + >> user_id:le_u16 + >> multiplex_id:le_u16 + >> data: rest + + >> (SmbRecord { + greeter:server_component, + command:command, + nt_status:nt_status, + flags:flags, + flags2:flags2, + is_dos_error: ((flags2 & 0x4000 == 0) && nt_status != 0), + tree_id:tree_id, + user_id:user_id, + multiplex_id:multiplex_id, + + process_id: (process_id_high as u32) << 16 | process_id as u32, + //ssn_id: (((process_id as u32)<< 16)|(user_id as u32)), + ssn_id: user_id as u32, + data:data, + }) +)); diff --git a/rust/src/smb/smb2.rs b/rust/src/smb/smb2.rs new file mode 100644 index 0000000000..0a7acf1f2e --- /dev/null +++ b/rust/src/smb/smb2.rs @@ -0,0 +1,784 @@ +/* Copyright (C) 2017 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. + */ + +use core::*; +use log::*; +use nom::IResult; + +use smb::smb::*; +use smb::smb2_records::*; +use smb::dcerpc::*; +use smb::events::*; +use smb::auth::*; +use smb::files::*; + +pub const SMB2_COMMAND_NEGOTIATE_PROTOCOL: u16 = 0; +pub const SMB2_COMMAND_SESSION_SETUP: u16 = 1; +pub const SMB2_COMMAND_SESSION_LOGOFF: u16 = 2; +pub const SMB2_COMMAND_TREE_CONNECT: u16 = 3; +pub const SMB2_COMMAND_TREE_DISCONNECT: u16 = 4; +pub const SMB2_COMMAND_CREATE: u16 = 5; +pub const SMB2_COMMAND_CLOSE: u16 = 6; +pub const SMB2_COMMAND_READ: u16 = 8; +pub const SMB2_COMMAND_WRITE: u16 = 9; +pub const SMB2_COMMAND_IOCTL: u16 = 11; +pub const SMB2_COMMAND_KEEPALIVE: u16 = 13; +pub const SMB2_COMMAND_FIND: u16 = 14; +pub const SMB2_COMMAND_GET_INFO: u16 = 16; +pub const SMB2_COMMAND_SET_INFO: u16 = 17; + +pub fn smb2_command_string(c: u16) -> String { + match c { + SMB2_COMMAND_NEGOTIATE_PROTOCOL => "SMB2_COMMAND_NEGOTIATE_PROTOCOL", + SMB2_COMMAND_SESSION_SETUP => "SMB2_COMMAND_SESSION_SETUP", + SMB2_COMMAND_SESSION_LOGOFF => "SMB2_COMMAND_SESSION_LOGOFF", + SMB2_COMMAND_TREE_CONNECT => "SMB2_COMMAND_TREE_CONNECT", + SMB2_COMMAND_TREE_DISCONNECT => "SMB2_COMMAND_TREE_DISCONNECT", + SMB2_COMMAND_CREATE => "SMB2_COMMAND_CREATE", + SMB2_COMMAND_CLOSE => "SMB2_COMMAND_CLOSE", + SMB2_COMMAND_READ => "SMB2_COMMAND_READ", + SMB2_COMMAND_WRITE => "SMB2_COMMAND_WRITE", + SMB2_COMMAND_IOCTL => "SMB2_COMMAND_IOCTL", + SMB2_COMMAND_KEEPALIVE => "SMB2_COMMAND_KEEPALIVE", + SMB2_COMMAND_FIND => "SMB2_COMMAND_FIND", + SMB2_COMMAND_GET_INFO => "SMB2_COMMAND_GET_INFO", + SMB2_COMMAND_SET_INFO => "SMB2_COMMAND_SET_INFO", + _ => { return (c).to_string(); }, + }.to_string() + +} + +pub fn smb2_dialect_string(d: u16) -> String { + match d { + 0x0202 => "2.02", + 0x0210 => "2.10", + 0x0222 => "2.22", + 0x0224 => "2.24", + 0x02ff => "2.??", + 0x0300 => "3.00", + 0x0302 => "3.02", + 0x0310 => "3.10", + 0x0311 => "3.11", + _ => { return (d).to_string(); }, + }.to_string() +} + +// later we'll use this to determine if we need to +// track a ssn per type +fn smb2_create_new_tx(_cmd: u16) -> bool { + true +} + +fn smb2_read_response_record_generic<'b>(state: &mut SMBState, r: &Smb2Record<'b>) +{ + if smb2_create_new_tx(r.command) { + let tx_hdr = SMBCommonHdr::from2(r, SMBHDR_TYPE_GENERICTX); + let tx = state.get_generic_tx(2, r.command as u16, &tx_hdr); + if let Some(tx) = tx { + tx.set_status(r.nt_status, false); + tx.response_done = true; + } + } +} + +pub fn smb2_read_response_record<'b>(state: &mut SMBState, r: &Smb2Record<'b>) +{ + smb2_read_response_record_generic(state, r); + + if r.nt_status != SMB_NTSTATUS_SUCCESS { + return; + } + + match parse_smb2_response_read(r.data) { + IResult::Done(_, rd) => { + SCLogDebug!("SMBv2: read response => {:?}", rd); + + // get the request info. If we don't have it, there is nothing + // we can do except skip this record. + let guid_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_OFFSET); + let (offset, file_guid) = match state.ssn2vecoffset_map.remove(&guid_key) { + Some(o) => (o.offset, o.guid), + None => { + SCLogDebug!("SMBv2 READ response: reply to unknown request"); + state.skip_tc = rd.len - rd.data.len() as u32; + return; + }, + }; + SCLogDebug!("SMBv2 READ: GUID {:?} offset {}", file_guid, offset); + + // look up existing tracker and if we have it update it + let found = match state.get_file_tx_by_guid(&file_guid, STREAM_TOCLIENT) { + Some((tx, files, flags)) => { + if let Some(SMBTransactionTypeData::FILE(ref mut tdf)) = tx.type_data { + let file_id : u32 = tx.id as u32; + filetracker_newchunk(&mut tdf.file_tracker, files, flags, + &tdf.file_name, rd.data, offset, + rd.len, 0, false, &file_id); + } + true + }, + None => { false }, + }; + if !found { + let tree_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_SHARE); + let (share_name, is_pipe) = match state.ssn2tree_map.get(&tree_key) { + Some(n) => (n.name.to_vec(), n.is_pipe), + _ => { (Vec::new(), false) }, + }; + let is_dcerpc = is_pipe && match state.get_service_for_guid(&file_guid) { + (_, x) => x, + }; + if is_pipe && is_dcerpc { + SCLogDebug!("SMBv2 DCERPC read"); + let hdr = SMBCommonHdr::from2(r, SMBHDR_TYPE_HEADER); + let vercmd = SMBVerCmdStat::new2(SMB2_COMMAND_READ); + smb_read_dcerpc_record(state, vercmd, hdr, &file_guid, rd.data); + } else if is_pipe { + SCLogDebug!("non-DCERPC pipe"); + state.skip_tc = rd.len - rd.data.len() as u32; + } else { + let file_name = match state.guid2name_map.get(&file_guid) { + Some(n) => { n.to_vec() }, + None => { Vec::new() }, + }; + let (tx, files, flags) = state.new_file_tx(&file_guid, &file_name, STREAM_TOCLIENT); + if let Some(SMBTransactionTypeData::FILE(ref mut tdf)) = tx.type_data { + let file_id : u32 = tx.id as u32; + filetracker_newchunk(&mut tdf.file_tracker, files, flags, + &file_name, rd.data, offset, + rd.len, 0, false, &file_id); + tdf.share_name = share_name; + } + tx.vercmd.set_smb2_cmd(SMB2_COMMAND_READ); + tx.hdr = SMBCommonHdr::new(SMBHDR_TYPE_HEADER, + r.session_id, r.tree_id, 0); // TODO move into new_file_tx + } + } + + state.file_tc_left = rd.len - rd.data.len() as u32; + state.file_tc_guid = file_guid.to_vec(); + SCLogDebug!("SMBv2 READ RESPONSE: {} bytes left", state.file_tc_left); + } + _ => { + state.set_event(SMBEvent::MalformedData); + } + } +} + +pub fn smb2_write_request_record<'b>(state: &mut SMBState, r: &Smb2Record<'b>) +{ + if smb2_create_new_tx(r.command) { + let tx_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_GENERICTX); + let tx = state.new_generic_tx(2, r.command, tx_key); + tx.request_done = true; + } + match parse_smb2_request_write(r.data) { + IResult::Done(_, wr) => { + /* update key-guid map */ + let guid_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_GUID); + state.ssn2vec_map.insert(guid_key, wr.guid.to_vec()); + + let file_guid = wr.guid.to_vec(); + let file_name = match state.guid2name_map.get(&file_guid) { + Some(n) => n.to_vec(), + None => Vec::new(), + }; + + let found = match state.get_file_tx_by_guid(&file_guid, STREAM_TOSERVER) { + Some((tx, files, flags)) => { + if let Some(SMBTransactionTypeData::FILE(ref mut tdf)) = tx.type_data { + let file_id : u32 = tx.id as u32; + filetracker_newchunk(&mut tdf.file_tracker, files, flags, + &file_name, wr.data, wr.wr_offset, + wr.wr_len, 0, false, &file_id); + } + true + }, + None => { false }, + }; + if !found { + let tree_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_SHARE); + let is_pipe = match state.ssn2tree_map.get(&tree_key) { + Some(n) => { n.is_pipe }, + _ => { false }, + }; + let is_dcerpc = is_pipe && match state.get_service_for_guid(&wr.guid) { + (_, x) => x, + }; + if is_pipe && is_dcerpc { + SCLogDebug!("SMBv2 DCERPC write"); + let hdr = SMBCommonHdr::from2(r, SMBHDR_TYPE_HEADER); + let vercmd = SMBVerCmdStat::new2(SMB2_COMMAND_WRITE); + smb_write_dcerpc_record(state, vercmd, hdr, wr.data); + } else if is_pipe { + SCLogDebug!("non-DCERPC pipe: skip rest of the record"); + state.skip_ts = wr.wr_len - wr.data.len() as u32; + } else { + let (tx, files, flags) = state.new_file_tx(&file_guid, &file_name, STREAM_TOSERVER); + if let Some(SMBTransactionTypeData::FILE(ref mut tdf)) = tx.type_data { + let file_id : u32 = tx.id as u32; + filetracker_newchunk(&mut tdf.file_tracker, files, flags, + &file_name, wr.data, wr.wr_offset, + wr.wr_len, 0, false, &file_id); + } + tx.vercmd.set_smb2_cmd(SMB2_COMMAND_WRITE); + tx.hdr = SMBCommonHdr::new(SMBHDR_TYPE_HEADER, + r.session_id, r.tree_id, 0); // TODO move into new_file_tx + } + } + state.file_ts_left = wr.wr_len - wr.data.len() as u32; + state.file_ts_guid = file_guid.to_vec(); + SCLogDebug!("SMBv2 WRITE REQUEST: {} bytes left", state.file_ts_left); + + }, + _ => { + state.set_event(SMBEvent::MalformedData); + }, + } +} + +pub fn smb2_request_record<'b>(state: &mut SMBState, r: &Smb2Record<'b>) +{ + SCLogDebug!("SMBv2 request record, command {} tree {} session {}", + &smb2_command_string(r.command), r.tree_id, r.session_id); + + let key_session_id = r.session_id; + let mut key_tree_id = r.tree_id; + let key_message_id = r.message_id; + let mut events : Vec = Vec::new(); + + let have_tx = match r.command { + SMB2_COMMAND_IOCTL => { + // some IOCTL responses don't set the tree id + key_tree_id = 0; + + let have_ioctl_tx = match parse_smb2_request_ioctl(r.data) { + IResult::Done(_, rd) => { + SCLogDebug!("IOCTL request data: {:?}", rd); + let is_dcerpc = rd.is_pipe && match state.get_service_for_guid(&rd.guid) { + (_, x) => x, + }; + if is_dcerpc { + SCLogDebug!("IOCTL request data is_pipe. Calling smb_write_dcerpc_record"); + let hdr = SMBCommonHdr::new(SMBHDR_TYPE_HEADER, + key_session_id, key_tree_id, key_message_id); + let vercmd = SMBVerCmdStat::new2(SMB2_COMMAND_IOCTL); + smb_write_dcerpc_record(state, vercmd, hdr, rd.data) + } else { + false + } + }, + _ => { false }, + }; + have_ioctl_tx + }, + SMB2_COMMAND_TREE_DISCONNECT => { + let tree_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_SHARE); + state.ssn2tree_map.remove(&tree_key); + false + } + SMB2_COMMAND_NEGOTIATE_PROTOCOL => { + match parse_smb2_request_negotiate_protocol(r.data) { + IResult::Done(_, rd) => { + let mut dialects : Vec> = Vec::new(); + for d in rd.dialects_vec { + SCLogDebug!("dialect {:x} => {}", d, &smb2_dialect_string(d)); + let dvec = smb2_dialect_string(d).as_bytes().to_vec(); + dialects.push(dvec); + } + + let found = match state.get_negotiate_tx(2) { + Some(_) => { + SCLogNotice!("WEIRD, should not have NEGOTIATE tx!"); + true + }, + None => { false }, + }; + if !found { + let tx = state.new_negotiate_tx(2); + if let Some(SMBTransactionTypeData::NEGOTIATE(ref mut tdn)) = tx.type_data { + tdn.dialects2 = dialects; + } + tx.request_done = true; + } + true + }, + _ => { + events.push(SMBEvent::MalformedData); + false + }, + } + }, + SMB2_COMMAND_SESSION_SETUP => { + SCLogDebug!("SMB2_COMMAND_SESSION_SETUP: r.data.len() {}", r.data.len()); + match parse_smb2_request_session_setup(r.data) { + IResult::Done(_, setup) => { + SCLogDebug!("SMB2_COMMAND_SESSION_SETUP parsed. rd.data.len() {}", setup.data.len()); + parse_secblob(state, setup.data); +/* + match parse_ntlmssp(rd.data) { + IResult::Done(_, nd) => { + SCLogDebug!("NTLMSSP TYPE {}/{} nd {:?}", + nd.msg_type, &ntlmssp_type_string(nd.msg_type), nd); + match nd.msg_type { + NTLMSSP_NEGOTIATE => { + key_session_id = 0; + }, + NTLMSSP_AUTH => { + match parse_ntlm_auth_record(nd.data) { + IResult::Done(_, ad) => { + SCLogDebug!("auth data {:?}", ad); + state.ntlmssp_host = ad.host.to_vec(); + state.ntlmssp_user = ad.user.to_vec(); + state.ntlmssp_domain = ad.domain.to_vec(); + }, + _ => { + events.push(SMBEvent::MalformedData); + }, + } + }, + _ => { }, + } + }, + _ => { + SCLogNotice!("error parsing ntlmssp"); + }, + } +*/ + }, + _ => { + events.push(SMBEvent::MalformedData); + }, + } + false + }, + SMB2_COMMAND_TREE_CONNECT => { + match parse_smb2_request_tree_connect(r.data) { + IResult::Done(_, tr) => { + let name_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_TREE); + let mut name_val = tr.share_name.to_vec(); + name_val.retain(|&i|i != 0x00); + if name_val.len() > 1 { + name_val = name_val[1..].to_vec(); + } + + let tx = state.new_treeconnect_tx(name_key, name_val); + tx.request_done = true; + tx.vercmd.set_smb2_cmd(SMB2_COMMAND_TREE_CONNECT); + true + } + _ => { + events.push(SMBEvent::MalformedData); + false + }, + } + }, + SMB2_COMMAND_READ => { + match parse_smb2_request_read(r.data) { + IResult::Done(_, rd) => { + SCLogDebug!("SMBv2 READ: GUID {:?} requesting {} bytes at offset {}", + rd.guid, rd.rd_len, rd.rd_offset); + + // store read guid,offset in map + let guid_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_OFFSET); + let guidoff = SMBFileGUIDOffset::new(rd.guid.to_vec(), rd.rd_offset); + state.ssn2vecoffset_map.insert(guid_key, guidoff); + }, + _ => { + events.push(SMBEvent::MalformedData); + }, + } + false + }, + SMB2_COMMAND_CREATE => { + match parse_smb2_request_create(r.data) { + IResult::Done(_, cr) => { + let del = cr.create_options & 0x0000_1000 != 0; + let dir = cr.create_options & 0x0000_0001 != 0; + + SCLogDebug!("create_options {:08x}", cr.create_options); + + let name_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_FILENAME); + state.ssn2vec_map.insert(name_key, cr.data.to_vec()); + + let tx_hdr = SMBCommonHdr::from2(r, SMBHDR_TYPE_GENERICTX); + let tx = state.new_create_tx(&cr.data.to_vec(), + cr.disposition, del, dir, tx_hdr); + tx.vercmd.set_smb2_cmd(r.command); + SCLogDebug!("TS CREATE TX {} created", tx.id); + true + }, + _ => { + events.push(SMBEvent::MalformedData); + false + }, + } + }, + SMB2_COMMAND_WRITE => { + smb2_write_request_record(state, &r); + true // write handling creates both file tx and generic tx + }, + SMB2_COMMAND_CLOSE => { + match parse_smb2_request_close(r.data) { + IResult::Done(_, cd) => { + let found_ts = match state.get_file_tx_by_guid(&cd.guid.to_vec(), STREAM_TOSERVER) { + Some((tx, files, flags)) => { + if !tx.request_done { + if let Some(SMBTransactionTypeData::FILE(ref mut tdf)) = tx.type_data { + tdf.file_tracker.close(files, flags); + } + } + tx.request_done = true; + tx.response_done = true; + tx.set_status(SMB_NTSTATUS_SUCCESS, false); + true + }, + None => { false }, + }; + let found_tc = match state.get_file_tx_by_guid(&cd.guid.to_vec(), STREAM_TOCLIENT) { + Some((tx, files, flags)) => { + if !tx.request_done { + if let Some(SMBTransactionTypeData::FILE(ref mut tdf)) = tx.type_data { + tdf.file_tracker.close(files, flags); + } + } + tx.request_done = true; + tx.response_done = true; + tx.set_status(SMB_NTSTATUS_SUCCESS, false); + true + }, + None => { false }, + }; + if !found_ts && !found_tc { + SCLogDebug!("SMBv2: CLOSE(TS): no TX at GUID {:?}", cd.guid); + } + }, + _ => { + events.push(SMBEvent::MalformedData); + }, + } + false + }, + _ => { + false + }, + }; + /* if we don't have a tx, create it here (maybe) */ + if !have_tx { + if smb2_create_new_tx(r.command) { + let tx_key = SMBCommonHdr::new(SMBHDR_TYPE_GENERICTX, + key_session_id, key_tree_id, key_message_id); + let tx = state.new_generic_tx(2, r.command, tx_key); + SCLogDebug!("TS TX {} command {} created with session_id {} tree_id {} message_id {}", + tx.id, r.command, key_session_id, key_tree_id, key_message_id); + tx.set_events(events); + } + } +} + +fn smb2_response_record_session_setup<'b>(state: &mut SMBState, r: &Smb2Record<'b>) +{ + // first try exact match + let found = match state.get_generic_tx(2, r.command, &SMBCommonHdr::from2(r, SMBHDR_TYPE_GENERICTX)) { + Some(tx) => { + if r.nt_status != SMB_NTSTATUS_PENDING { + tx.response_done = true; + } + tx.set_status(r.nt_status, false); + true + }, + None => false, + }; + // if not found try with ssn id 0. + if !found { + let tx_key = SMBCommonHdr::new(SMBHDR_TYPE_GENERICTX, 0, 0, r.message_id); + match state.get_generic_tx(2, r.command, &tx_key) { + Some(tx) => { + if r.nt_status != SMB_NTSTATUS_PENDING { + tx.response_done = true; + } + tx.set_status(r.nt_status, false); + }, + None => { }, + } + } +} + +pub fn smb2_response_record<'b>(state: &mut SMBState, r: &Smb2Record<'b>) +{ + SCLogDebug!("SMBv2 response record, command {} status {} tree {} session {} message {}", + &smb2_command_string(r.command), &smb_ntstatus_string(r.nt_status), + r.tree_id, r.session_id, r.message_id); + + let key_session_id = r.session_id; + let mut key_tree_id = r.tree_id; + let key_message_id = r.message_id; + let mut events : Vec = Vec::new(); + + let have_tx = match r.command { + SMB2_COMMAND_IOCTL => { + // some IOCTL responses don't set the tree id + key_tree_id = 0; + + let have_ioctl_tx = match parse_smb2_response_ioctl(r.data) { + IResult::Done(_, rd) => { + SCLogDebug!("IOCTL response data: {:?}", rd); + + let is_dcerpc = rd.is_pipe && match state.get_service_for_guid(&rd.guid) { + (_, x) => x, + }; + if is_dcerpc { + SCLogDebug!("IOCTL response data is_pipe. Calling smb_read_dcerpc_record"); + let hdr = SMBCommonHdr::new(SMBHDR_TYPE_HEADER, + key_session_id, key_tree_id, key_message_id); + let vercmd = SMBVerCmdStat::new2_with_ntstatus(SMB2_COMMAND_IOCTL, r.nt_status); + SCLogNotice!("TODO passing empty GUID"); + smb_read_dcerpc_record(state, vercmd, hdr, &[],rd.data) + } else { + false + } + }, + _ => { + if r.nt_status == SMB_NTSTATUS_PENDING { + // find tx by ssn/msgid/treeid (+cmd) + + let tx_key = SMBCommonHdr::new(SMBHDR_TYPE_HEADER, + key_session_id, key_tree_id, key_message_id); + SCLogDebug!("SMB2_COMMAND_IOCTL/SMB_NTSTATUS_PENDING looking for {:?}", tx_key); + match state.get_generic_tx(2, SMB2_COMMAND_IOCTL, &tx_key) { + Some(tx) => { + tx.set_status(r.nt_status, false); + SCLogDebug!("updated status of tx {}", tx.id); + true + }, + _ => { false }, + } + } else { + SCLogDebug!("parse fail {:?}", r); + events.push(SMBEvent::MalformedData); + false + } + }, + }; + + have_ioctl_tx + }, + SMB2_COMMAND_SESSION_SETUP => { + smb2_response_record_session_setup(state, r); + true + }, + SMB2_COMMAND_WRITE => { + match parse_smb2_response_write(r.data) { + IResult::Done(_, wr) => { + SCLogDebug!("SMBv2: Write response => {:?}", wr); + + /* search key-guid map */ + let guid_key = SMBCommonHdr::new(SMBHDR_TYPE_GUID, + r.session_id, r.tree_id, r.message_id); + let guid_vec = match state.ssn2vec_map.remove(&guid_key) { + Some(p) => p, + None => { + SCLogDebug!("SMBv2 response: GUID NOT FOUND"); + Vec::new() + }, + }; + SCLogDebug!("SMBv2 write response for GUID {:?}", guid_vec); + } + _ => { + events.push(SMBEvent::MalformedData); + }, + } + false // the request may have created a generic tx, so handle that here + }, + SMB2_COMMAND_READ => { + if r.nt_status == SMB_NTSTATUS_SUCCESS { + smb2_read_response_record(state, &r); + false + + } else if r.nt_status == SMB_NTSTATUS_END_OF_FILE { + SCLogDebug!("SMBv2: read response => EOF"); + + let guid_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_OFFSET); + let file_guid = match state.ssn2vecoffset_map.remove(&guid_key) { + Some(o) => o.guid, + _ => { + SCLogNotice!("SMBv2 READ response: reply to unknown request"); + Vec::new() + }, + }; + let found = match state.get_file_tx_by_guid(&file_guid, STREAM_TOCLIENT) { + Some((tx, files, flags)) => { + if !tx.request_done { + if let Some(SMBTransactionTypeData::FILE(ref mut tdf)) = tx.type_data { + tdf.file_tracker.close(files, flags); + } + } + tx.set_status(r.nt_status, false); + tx.request_done = true; + false + }, + None => { false }, + }; + if !found { + SCLogDebug!("SMBv2 READ: no TX at GUID {:?}", file_guid); + } + false + } else { + SCLogNotice!("SMBv2 READ: status {}", &smb_ntstatus_string(r.nt_status)); + false + } + }, + SMB2_COMMAND_CREATE => { + if r.nt_status == SMB_NTSTATUS_SUCCESS { + match parse_smb2_response_create(r.data) { + IResult::Done(_, cr) => { + SCLogDebug!("SMBv2: Create response => {:?}", cr); + + let guid_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_FILENAME); + match state.ssn2vec_map.remove(&guid_key) { + Some(mut p) => { + p.retain(|&i|i != 0x00); + state.guid2name_map.insert(cr.guid.to_vec(), p); + }, + _ => { + SCLogDebug!("SMBv2 response: GUID NOT FOUND"); + }, + } + } + _ => { + events.push(SMBEvent::MalformedData); + }, + } + } + false + }, + SMB2_COMMAND_TREE_DISCONNECT => { + // normally removed when processing request, + // but in case we missed that try again here + let tree_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_SHARE); + state.ssn2tree_map.remove(&tree_key); + false + } + SMB2_COMMAND_TREE_CONNECT => { + if r.nt_status == SMB_NTSTATUS_SUCCESS { + match parse_smb2_response_tree_connect(r.data) { + IResult::Done(_, tr) => { + let name_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_TREE); + let mut share_name = Vec::new(); + let is_pipe = tr.share_type == 2; + let found = match state.get_treeconnect_tx(name_key) { + Some(tx) => { + if let Some(SMBTransactionTypeData::TREECONNECT(ref mut tdn)) = tx.type_data { + tdn.is_pipe = is_pipe; + tdn.tree_id = r.tree_id as u32; + share_name = tdn.share_name.to_vec(); + } + // update hdr now that we have a tree_id + tx.hdr = SMBCommonHdr::from2(r, SMBHDR_TYPE_HEADER); + tx.response_done = true; + tx.set_status(r.nt_status, false); + true + }, + None => { false }, + }; + if found { + let tree = SMBTree::new(share_name.to_vec(), is_pipe); + let tree_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_SHARE); + state.ssn2tree_map.insert(tree_key, tree); + } + true + } + _ => { + events.push(SMBEvent::MalformedData); + false + }, + } + } else { + let name_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_TREE); + let found = match state.get_treeconnect_tx(name_key) { + Some(tx) => { + tx.response_done = true; + tx.set_status(r.nt_status, false); + true + }, + None => { false }, + }; + found + } + }, + SMB2_COMMAND_NEGOTIATE_PROTOCOL => { + match parse_smb2_response_negotiate_protocol(r.data) { + IResult::Done(_, rd) => { + SCLogDebug!("SERVER dialect => {}", &smb2_dialect_string(rd.dialect)); + + state.dialect = rd.dialect; + let found2 = match state.get_negotiate_tx(2) { + Some(tx) => { + tx.set_status(r.nt_status, false); + tx.response_done = true; + true + }, + None => { false }, + }; + // SMB2 response to SMB1 request? + let found1 = !found2 && match state.get_negotiate_tx(1) { + Some(tx) => { + tx.set_status(r.nt_status, false); + tx.response_done = true; + true + }, + None => { + false + }, + }; + found1 || found2 + }, + _ => { + events.push(SMBEvent::MalformedData); + false + } + } + }, + _ => { + SCLogDebug!("default case: no TX"); + false + }, + }; + if !have_tx { + let tx_hdr = SMBCommonHdr::new(SMBHDR_TYPE_GENERICTX, + key_session_id, key_tree_id, key_message_id); + SCLogDebug!("looking for TX {} with session_id {} tree_id {} message_id {}", + &smb2_command_string(r.command), + key_session_id, key_tree_id, key_message_id); + let _found = match state.get_generic_tx(2, r.command, &tx_hdr) { + Some(tx) => { + SCLogDebug!("tx {} with {}/{} marked as done", + tx.id, r.command, &smb2_command_string(r.command)); + if r.nt_status != SMB_NTSTATUS_PENDING { + tx.response_done = true; + } + tx.set_status(r.nt_status, false); + tx.set_events(events); + true + }, + _ => { + SCLogNotice!("no tx found for {:?}", r); + false + }, + }; + } +} diff --git a/rust/src/smb/smb2_records.rs b/rust/src/smb/smb2_records.rs new file mode 100644 index 0000000000..cf93221207 --- /dev/null +++ b/rust/src/smb/smb2_records.rs @@ -0,0 +1,432 @@ +/* Copyright (C) 2017 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. + */ + +use nom::{rest, le_u8, le_u16, le_u32, le_u64, IResult, AsBytes}; + +#[derive(Debug,PartialEq)] +pub struct Smb2SecBlobRecord<'a> { + pub data: &'a[u8], +} + +named!(pub parse_smb2_sec_blob, + do_parse!( + data: rest + >> ( Smb2SecBlobRecord { + data: data, + }) +)); + +#[derive(Debug,PartialEq)] +pub struct Smb2Record<'a> { + pub direction: u8, // 0 req, 1 res + pub nt_status: u32, + pub command: u16, + pub message_id: u64, + pub tree_id: u32, + pub session_id: u64, + pub data: &'a[u8], +} + +named!(pub parse_smb2_request_record, + do_parse!( + server_component:take!(4) // fe SMB + >> hlen: le_u16 + >> credit_charge: le_u16 + >> channel_seq: le_u16 + >> reserved: take!(2) + >> command: le_u16 + >> credits_requested: le_u16 + >> flags: bits!(tuple!( + take_bits!(u8, 2), // reserved / unused + take_bits!(u8, 1), // replay op + take_bits!(u8, 1), // dfs op + take_bits!(u32, 24), // reserved / unused + take_bits!(u8, 1), // signing + take_bits!(u8, 1), // chained + take_bits!(u8, 1), // async + take_bits!(u8, 1) // response + )) + >> chain_offset: le_u32 + >> message_id: le_u64 + >> process_id: le_u32 + >> tree_id: le_u32 + >> session_id: le_u64 + >> signature: take!(16) + // there is probably a cleaner way to do this + >> data_c: cond!(chain_offset > hlen as u32, take!(chain_offset - hlen as u32)) + >> data_r: cond!(chain_offset <= hlen as u32, rest) + >> (Smb2Record { + direction: flags.7, + nt_status: 0, + command:command, + message_id: message_id, + tree_id: tree_id, + session_id: session_id, + data: if data_c != None { data_c.unwrap() } else { data_r.unwrap() } + }) +)); + +#[derive(Debug,PartialEq)] +pub struct Smb2NegotiateProtocolRequestRecord<> { + pub dialects_vec: Vec, +} + +named!(pub parse_smb2_request_negotiate_protocol, + do_parse!( + struct_size: take!(2) + >> dialects_count: le_u16 + >> blob1: take!(32) + >> dia_vec: count!(le_u16, dialects_count as usize) + >> blob2: rest + >> (Smb2NegotiateProtocolRequestRecord { + dialects_vec: dia_vec, + }) +)); + +#[derive(Debug,PartialEq)] +pub struct Smb2NegotiateProtocolResponseRecord<> { + pub dialect: u16, +} + +named!(pub parse_smb2_response_negotiate_protocol, + do_parse!( + struct_size: take!(2) + >> skip1: take!(2) + >> dialect: le_u16 + >> (Smb2NegotiateProtocolResponseRecord { + dialect: dialect, + }) +)); + +#[derive(Debug,PartialEq)] +pub struct Smb2SessionSetupRequestRecord<'a> { + pub data: &'a[u8], +} + +named!(pub parse_smb2_request_session_setup, + do_parse!( + struct_size: take!(2) + >> flags: le_u8 + >> security_mode: le_u8 + >> capabilities: le_u32 + >> channel: le_u32 + >> sec_offset: le_u16 + >> sec_len: le_u16 + >> prev_ssn_id: take!(8) + >> data: rest + >> (Smb2SessionSetupRequestRecord { + data:data, + }) +)); + + +#[derive(Debug,PartialEq)] +pub struct Smb2TreeConnectRequestRecord<'a> { + pub share_name: &'a[u8], +} + +named!(pub parse_smb2_request_tree_connect, + do_parse!( + struct_size: take!(2) + >> offset_length: take!(4) + >> data: rest + >> (Smb2TreeConnectRequestRecord { + share_name:data, + }) +)); + +#[derive(Debug,PartialEq)] +pub struct Smb2TreeConnectResponseRecord<> { + pub share_type: u8, +} + +named!(pub parse_smb2_response_tree_connect, + do_parse!( + struct_size: take!(2) + >> share_type: le_u8 + >> share_flags: le_u32 + >> share_caps: le_u32 + >> access_mask: le_u32 + >> (Smb2TreeConnectResponseRecord { + share_type:share_type, + }) +)); + + +#[derive(Debug,PartialEq)] +pub struct Smb2CreateRequestRecord<'a> { + pub disposition: u32, + pub create_options: u32, + pub data: &'a[u8], +} + +named!(pub parse_smb2_request_create, + do_parse!( + skip1: take!(36) + >> disposition: le_u32 + >> create_options: le_u32 + >> file_name_offset: le_u16 + >> file_name_length: le_u16 + >> skip2: take!(8) + >> data: take!(file_name_length) + >> skip3: rest + >> (Smb2CreateRequestRecord { + disposition: disposition, + create_options: create_options, + data:data, + }) +)); + +#[derive(Debug,PartialEq)] +pub struct Smb2IOCtlRequestRecord<'a> { + pub is_pipe: bool, + pub guid: &'a[u8], + pub data: &'a[u8], +} + +named!(pub parse_smb2_request_ioctl, + do_parse!( + skip: take!(2) // structure size + >> take!(2) // reserved + >> func: le_u32 + >> guid: take!(16) + >> indata_offset: le_u32 + >> indata_len: le_u32 + >> take!(4) + >> outdata_offset: le_u32 + >> outdata_len: le_u32 + >> take!(12) + >> data: take!(indata_len) + >> (Smb2IOCtlRequestRecord { + is_pipe: (func == 0x0011c017), + guid:guid, + data:data, + }) +)); + +#[derive(Debug,PartialEq)] +pub struct Smb2IOCtlResponseRecord<'a> { + pub is_pipe: bool, + pub guid: &'a[u8], + pub data: &'a[u8], + pub indata_len: u32, + pub outdata_len: u32, + pub indata_offset: u32, + pub outdata_offset: u32, +} + +named!(pub parse_smb2_response_ioctl, + do_parse!( + skip: take!(2) // structure size + >> take!(2) // reserved + >> func: le_u32 + >> guid: take!(16) + >> indata_offset: le_u32 + >> indata_len: le_u32 + >> outdata_offset: le_u32 + >> outdata_len: le_u32 + >> take!(8) + >> take!(indata_len) + >> data: take!(outdata_len) + >> (Smb2IOCtlResponseRecord { + is_pipe: (func == 0x0011c017), + guid:guid, + data:data, + indata_len:indata_len, + outdata_len:outdata_len, + indata_offset:indata_offset, + outdata_offset:outdata_offset, + }) +)); + +#[derive(Debug,PartialEq)] +pub struct Smb2CloseRequestRecord<'a> { + pub guid: &'a[u8], +} + +named!(pub parse_smb2_request_close, + do_parse!( + skip: take!(8) + >> guid: take!(16) + >> (Smb2CloseRequestRecord { + guid:guid, + }) +)); + +#[derive(Debug,PartialEq)] +pub struct Smb2WriteRequestRecord<'a> { + pub wr_len: u32, + pub wr_offset: u64, + pub guid: &'a[u8], + pub data: &'a[u8], +} + +named!(pub parse_smb2_request_write, + do_parse!( + skip1: take!(4) + >> wr_len: le_u32 + >> wr_offset: le_u64 + >> guid: take!(16) + >> channel: le_u32 + >> remaining_bytes: le_u32 + >> write_flags: le_u32 + >> skip2: take!(4) + >> data: rest + >> (Smb2WriteRequestRecord { + wr_len:wr_len, + wr_offset:wr_offset, + guid:guid, + data:data, + }) +)); + +#[derive(Debug,PartialEq)] +pub struct Smb2ReadRequestRecord<'a> { + pub rd_len: u32, + pub rd_offset: u64, + pub guid: &'a[u8], +} + +named!(pub parse_smb2_request_read, + do_parse!( + skip1: take!(4) + >> rd_len: le_u32 + >> rd_offset: le_u64 + >> guid: take!(16) + >> min_count: le_u32 + >> channel: le_u32 + >> remaining_bytes: le_u32 + >> skip2: take!(4) + >> (Smb2ReadRequestRecord { + rd_len:rd_len, + rd_offset:rd_offset, + guid:guid, + }) +)); + +#[derive(Debug,PartialEq)] +pub struct Smb2ReadResponseRecord<'a> { + pub len: u32, + pub data: &'a[u8], +} + +named!(pub parse_smb2_response_read, + do_parse!( + skip1: take!(4) + >> rd_len: le_u32 + >> skip2: take!(8) + >> data: rest + >> (Smb2ReadResponseRecord { + len : rd_len, + data : data, + }) +)); + +#[derive(Debug,PartialEq)] +pub struct Smb2CreateResponseRecord<'a> { + pub guid: &'a[u8], +} + +named!(pub parse_smb2_response_create, + do_parse!( + skip1: take!(64) + >> guid: take!(16) + >> skip2: take!(8) + >> (Smb2CreateResponseRecord { + guid : guid, + }) +)); + +#[derive(Debug,PartialEq)] +pub struct Smb2WriteResponseRecord<> { + pub wr_cnt: u32, +} + +named!(pub parse_smb2_response_write, + do_parse!( + skip1: take!(4) + >> wr_cnt: le_u32 + >> skip2: take!(6) + >> (Smb2WriteResponseRecord { + wr_cnt : wr_cnt, + }) +)); + +named!(pub parse_smb2_response_record, + do_parse!( + server_component:take!(4) // fe SMB + >> hlen: le_u16 + >> credit_charge: le_u16 + >> nt_status: le_u32 + >> command: le_u16 + >> credit_granted: le_u16 + >> flags: bits!(tuple!( + take_bits!(u8, 2), // reserved / unused + take_bits!(u8, 1), // replay op + take_bits!(u8, 1), // dfs op + take_bits!(u32, 24), // reserved / unused + take_bits!(u8, 1), // signing + take_bits!(u8, 1), // chained + take_bits!(u8, 1), // async + take_bits!(u8, 1) // response + )) + >> chain_offset: le_u32 + >> message_id: le_u64 + >> process_id: le_u32 + >> tree_id: le_u32 + >> session_id: le_u64 + >> signature: take!(16) + // there is probably a cleaner way to do this + >> data_c: cond!(chain_offset > hlen as u32, take!(chain_offset - hlen as u32)) + >> data_r: cond!(chain_offset <= hlen as u32, rest) + >> (Smb2Record { + direction: flags.7, + nt_status: nt_status, + message_id: message_id, + tree_id: tree_id, + session_id: session_id, + command:command, + data: if data_c != None { data_c.unwrap() } else { data_r.unwrap() } + }) +)); + +#[derive(Debug,PartialEq)] +pub struct Smb2RecordPostGap<'a> { + pub data: &'a[u8], +} + +named!(pub search_smb2_record, + do_parse!( + alt!(take_until!([0xfe, 0x53, 0x4d, 0x42].as_bytes())| // SMB2 + take_until!([0xff, 0x53, 0x4d, 0x42].as_bytes())) // SMB1 + >> data : rest + >> ( Smb2RecordPostGap { + data:data, + }) +)); + +pub fn search_smb2_record_f<'a>(input: &'a [u8]) + -> IResult<&'a [u8], Smb2RecordPostGap> +{ + return closure!(&'a [u8], do_parse!( + take_until!([0xfe, 0x53, 0x4d, 0x42].as_bytes()) + >> data : rest + >> ( Smb2RecordPostGap { + data:data, + }) + ))(input); +} diff --git a/src/Makefile.am b/src/Makefile.am index 15f4488a1c..0004126714 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -39,6 +39,7 @@ app-layer-parser.c app-layer-parser.h \ app-layer-protos.c app-layer-protos.h \ app-layer-smb2.c app-layer-smb2.h \ app-layer-smb.c app-layer-smb.h \ +app-layer-smb-tcp-rust.c app-layer-smb-tcp-rust.h \ app-layer-smtp.c app-layer-smtp.h \ app-layer-nfs-tcp.c app-layer-nfs-tcp.h \ app-layer-nfs-udp.c app-layer-nfs-udp.h \ @@ -238,6 +239,7 @@ detect-ssh-proto.c detect-ssh-proto.h \ detect-ssh-proto-version.c detect-ssh-proto-version.h \ detect-ssh-software.c detect-ssh-software.h \ detect-ssh-software-version.c detect-ssh-software-version.h \ +detect-smb-share.c detect-smb-share.h \ detect-ssl-state.c detect-ssl-state.h \ detect-ssl-version.c detect-ssl-version.h \ detect-stream_size.c detect-stream_size.h \ @@ -316,6 +318,7 @@ output-json-stats.c output-json-stats.h \ output-json-tls.c output-json-tls.h \ output-json-nfs.c output-json-nfs.h \ output-json-tftp.c output-json-tftp.h \ +output-json-smb.c output-json-smb.h \ output-json-template.c output-json-template.h \ output-json-metadata.c output-json-metadata.h \ output-lua.c output-lua.h \ diff --git a/src/app-layer-smb-tcp-rust.c b/src/app-layer-smb-tcp-rust.c new file mode 100644 index 0000000000..aef1b7c92a --- /dev/null +++ b/src/app-layer-smb-tcp-rust.c @@ -0,0 +1,276 @@ +/* Copyright (C) 2017 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. + */ + +#include "suricata-common.h" +#include "suricata.h" + +#include "app-layer-protos.h" +#include "app-layer-detect-proto.h" +#include "app-layer-parser.h" + +#include "util-unittest.h" + +#ifdef HAVE_RUST +#include "rust.h" +#include "app-layer-smb-tcp-rust.h" +#include "rust-smb-smb-gen.h" +#include "rust-smb-files-gen.h" + +#define MIN_REC_SIZE 32 + +static int RustSMBTCPParseRequest(Flow *f, void *state, + AppLayerParserState *pstate, uint8_t *input, uint32_t input_len, + void *local_data) +{ + SCLogDebug("RustSMBTCPParseRequest"); + uint16_t file_flags = FileFlowToFlags(f, STREAM_TOSERVER); + rs_smb_setfileflags(0, state, file_flags|FILE_USE_DETECT); + + int res; + if (input == NULL && input_len > 0) { + res = rs_smb_parse_request_tcp_gap(state, input_len); + } else { + res = rs_smb_parse_request_tcp(f, state, pstate, input, input_len, + local_data); + } + if (res != 1) { + SCLogNotice("SMB request%s of %u bytes, retval %d", + (input == NULL && input_len > 0) ? " is GAP" : "", input_len, res); + } + return res; +} + +static int RustSMBTCPParseResponse(Flow *f, void *state, + AppLayerParserState *pstate, uint8_t *input, uint32_t input_len, + void *local_data) +{ + SCLogDebug("RustSMBTCPParseResponse"); + uint16_t file_flags = FileFlowToFlags(f, STREAM_TOCLIENT); + rs_smb_setfileflags(1, state, file_flags|FILE_USE_DETECT); + + SCLogDebug("RustSMBTCPParseResponse %p/%u", input, input_len); + int res; + if (input == NULL && input_len > 0) { + res = rs_smb_parse_response_tcp_gap(state, input_len); + } else { + res = rs_smb_parse_response_tcp(f, state, pstate, input, input_len, + local_data); + } + if (res != 1) { + SCLogNotice("SMB response%s of %u bytes, retval %d", + (input == NULL && input_len > 0) ? " is GAP" : "", input_len, res); + } + return res; +} + +static uint16_t RustSMBTCPProbeTS(Flow *f, + uint8_t *input, uint32_t len, uint32_t *offset) +{ + SCLogDebug("RustSMBTCPProbe"); +/* + if (len == 0 || len < sizeof(SMBHeader)) { + return ALPROTO_UNKNOWN; + } +*/ + // Validate and return ALPROTO_FAILED if needed. + if (!rs_smb_probe_tcp_ts(input, len)) { + return ALPROTO_FAILED; + } + + return ALPROTO_SMB; +} + +static uint16_t RustSMBTCPProbeTC(Flow *f, + uint8_t *input, uint32_t len, uint32_t *offset) +{ + SCLogDebug("RustSMBTCPProbe"); +/* + if (len == 0 || len < sizeof(SMBHeader)) { + return ALPROTO_UNKNOWN; + } +*/ + // Validate and return ALPROTO_FAILED if needed. + if (!rs_smb_probe_tcp_tc(input, len)) { + return ALPROTO_FAILED; + } + + return ALPROTO_SMB; +} + +static int RustSMBGetAlstateProgress(void *tx, uint8_t direction) +{ + return rs_smb_tx_get_alstate_progress(tx, direction); +} + +static uint64_t RustSMBGetTxCnt(void *alstate) +{ + return rs_smb_state_get_tx_count(alstate); +} + +static void *RustSMBGetTx(void *alstate, uint64_t tx_id) +{ + return rs_smb_state_get_tx(alstate, tx_id); +} + +static AppLayerGetTxIterTuple RustSMBGetTxIterator( + const uint8_t ipproto, const AppProto alproto, + void *alstate, uint64_t min_tx_id, uint64_t max_tx_id, + AppLayerGetTxIterState *istate) +{ + return rs_smb_state_get_tx_iterator(alstate, min_tx_id, (uint64_t *)istate); +} + + +static void RustSMBSetTxLogged(void *alstate, void *tx, uint32_t logger) +{ + rs_smb_tx_set_logged(alstate, tx, logger); +} + +static LoggerId RustSMBGetTxLogged(void *alstate, void *tx) +{ + return rs_smb_tx_get_logged(alstate, tx); +} + +static void RustSMBStateTransactionFree(void *state, uint64_t tx_id) +{ + rs_smb_state_tx_free(state, tx_id); +} + +static DetectEngineState *RustSMBGetTxDetectState(void *tx) +{ + return rs_smb_state_get_tx_detect_state(tx); +} + +static int RustSMBSetTxDetectState(void *tx, DetectEngineState *s) +{ + rs_smb_state_set_tx_detect_state(tx, s); + return 0; +} + +static FileContainer *RustSMBGetFiles(void *state, uint8_t direction) +{ + return rs_smb_getfiles(direction, state); +} + +static AppLayerDecoderEvents *RustSMBGetEvents(void *state, uint64_t id) +{ + return rs_smb_state_get_events(state, id); +} + +static int RustSMBGetEventInfo(const char *event_name, int *event_id, + AppLayerEventType *event_type) +{ + return rs_smb_state_get_event_info(event_name, event_id, event_type); +} + +static void RustSMBSetDetectFlags(void *tx, uint8_t dir, uint64_t flags) +{ + rs_smb_tx_set_detect_flags(tx, dir, flags); +} + +static uint64_t RustSMBGetDetectFlags(void *tx, uint8_t dir) +{ + return rs_smb_tx_get_detect_flags(tx, dir); +} + +static void RustSMBStateTruncate(void *state, uint8_t direction) +{ + return rs_smb_state_truncate(state, direction); +} + +static StreamingBufferConfig sbcfg = STREAMING_BUFFER_CONFIG_INITIALIZER; +static SuricataFileContext sfc = { &sbcfg }; + +void RegisterRustSMBTCPParsers(void) +{ + const char *proto_name = "smb"; + + /** SMB */ + if (AppLayerProtoDetectConfProtoDetectionEnabled("tcp", proto_name)) { + AppLayerProtoDetectRegisterProtocol(ALPROTO_SMB, proto_name); + + rs_smb_init(&sfc); + + if (RunmodeIsUnittests()) { + AppLayerProtoDetectPPRegister(IPPROTO_TCP, "445", ALPROTO_SMB, 0, + MIN_REC_SIZE, STREAM_TOSERVER, RustSMBTCPProbeTS, + NULL); + } else { + int have_cfg = AppLayerProtoDetectPPParseConfPorts("tcp", + IPPROTO_TCP, proto_name, ALPROTO_SMB, 0, + MIN_REC_SIZE, RustSMBTCPProbeTS, RustSMBTCPProbeTC); + /* if we have no config, we enable the default port 445 */ + if (!have_cfg) { + SCLogWarning(SC_ERR_SMB_CONFIG, "no SMB TCP config found, " + "enabling SMB detection on " + "port 445."); + AppLayerProtoDetectPPRegister(IPPROTO_TCP, "445", ALPROTO_SMB, 0, + MIN_REC_SIZE, STREAM_TOSERVER, RustSMBTCPProbeTS, + RustSMBTCPProbeTC); + } + } + } else { + SCLogInfo("Protocol detection and parser disabled for %s protocol.", + proto_name); + return; + } + + if (AppLayerParserConfParserEnabled("tcp", proto_name)) { + AppLayerParserRegisterParser(IPPROTO_TCP, ALPROTO_SMB, STREAM_TOSERVER, + RustSMBTCPParseRequest); + AppLayerParserRegisterParser(IPPROTO_TCP , ALPROTO_SMB, STREAM_TOCLIENT, + RustSMBTCPParseResponse); + AppLayerParserRegisterStateFuncs(IPPROTO_TCP, ALPROTO_SMB, + rs_smb_state_new, rs_smb_state_free); + AppLayerParserRegisterTxFreeFunc(IPPROTO_TCP, ALPROTO_SMB, + RustSMBStateTransactionFree); + + AppLayerParserRegisterGetEventsFunc(IPPROTO_TCP, ALPROTO_SMB, + RustSMBGetEvents); + AppLayerParserRegisterGetEventInfo(IPPROTO_TCP, ALPROTO_SMB, + RustSMBGetEventInfo); + + AppLayerParserRegisterDetectStateFuncs(IPPROTO_TCP, ALPROTO_SMB, + RustSMBGetTxDetectState, RustSMBSetTxDetectState); + AppLayerParserRegisterGetTx(IPPROTO_TCP, ALPROTO_SMB, RustSMBGetTx); + AppLayerParserRegisterGetTxIterator(IPPROTO_TCP, ALPROTO_SMB, RustSMBGetTxIterator); + AppLayerParserRegisterGetTxCnt(IPPROTO_TCP, ALPROTO_SMB, + RustSMBGetTxCnt); + AppLayerParserRegisterLoggerFuncs(IPPROTO_TCP, ALPROTO_SMB, + RustSMBGetTxLogged, RustSMBSetTxLogged); + AppLayerParserRegisterGetStateProgressFunc(IPPROTO_TCP, ALPROTO_SMB, + RustSMBGetAlstateProgress); + AppLayerParserRegisterGetStateProgressCompletionStatus(ALPROTO_SMB, + rs_smb_state_progress_completion_status); + AppLayerParserRegisterDetectFlagsFuncs(IPPROTO_TCP, ALPROTO_SMB, + RustSMBGetDetectFlags, RustSMBSetDetectFlags); + AppLayerParserRegisterTruncateFunc(IPPROTO_TCP, ALPROTO_SMB, + RustSMBStateTruncate); + AppLayerParserRegisterGetFilesFunc(IPPROTO_TCP, ALPROTO_SMB, RustSMBGetFiles); + + /* This parser accepts gaps. */ + AppLayerParserRegisterOptionFlags(IPPROTO_TCP, ALPROTO_SMB, + APP_LAYER_PARSER_OPT_ACCEPT_GAPS); + + } else { + SCLogInfo("Parsed disabled for %s protocol. Protocol detection" + "still on.", proto_name); + } + + return; +} +#endif /* HAVE_RUST */ diff --git a/src/app-layer-smb-tcp-rust.h b/src/app-layer-smb-tcp-rust.h new file mode 100644 index 0000000000..1a850092c9 --- /dev/null +++ b/src/app-layer-smb-tcp-rust.h @@ -0,0 +1,24 @@ +/* Copyright (C) 2017 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 __APP_LAYER_SMB_TCP_RUST_H__ +#define __APP_LAYER_SMB_TCP_RUST_H__ + +void RegisterRustSMBTCPParsers(void); +void RegisterSMBParsers(void); + +#endif /* !__APP_LAYER_SMB_TCP_RUST_H__ */ diff --git a/src/app-layer-smb.c b/src/app-layer-smb.c index 3d11871136..f9ab87945b 100644 --- a/src/app-layer-smb.c +++ b/src/app-layer-smb.c @@ -48,6 +48,16 @@ #include "util-unittest.h" #include "util-memcmp.h" +#ifdef HAVE_RUST +#include "rust.h" +#include "app-layer-smb-tcp-rust.h" +void RegisterSMBParsers(void) +{ + RegisterRustSMBTCPParsers(); +} + +#else // no RUST + #include "app-layer-smb.h" enum { @@ -2803,4 +2813,5 @@ void SMBParserRegisterTests(void) UtRegisterTest("SMBParserTest10", SMBParserTest10); #endif } +#endif /* HAVE_RUST */ diff --git a/src/app-layer-smb.h b/src/app-layer-smb.h index 8e7ec4fadc..929d3c9214 100644 --- a/src/app-layer-smb.h +++ b/src/app-layer-smb.h @@ -32,6 +32,8 @@ #include "app-layer-dcerpc-common.h" #include "app-layer-dcerpc.h" +#ifndef HAVE_RUST + typedef struct SMBHdr_ { uint8_t protocol[4]; uint8_t command; @@ -159,9 +161,11 @@ typedef struct SMBState_ { #define SMB_COM_CLOSE_PRINT_FILE 0xC2 #define SMB_COM_GET_PRINT_QUEUE 0xC3 +int isAndX(SMBState *smb_state); +#endif /* HAVE_RUST */ + void RegisterSMBParsers(void); void SMBParserRegisterTests(void); -int isAndX(SMBState *smb_state); #endif /* __APP_LAYER_SMB_H__ */ diff --git a/src/detect-dce-iface.c b/src/detect-dce-iface.c index 472399e0f6..30f21935e5 100644 --- a/src/detect-dce-iface.c +++ b/src/detect-dce-iface.c @@ -47,6 +47,11 @@ #include "util-unittest-helper.h" #include "stream-tcp.h" +#ifdef HAVE_RUST +#include "rust.h" +#include "rust-smb-detect-gen.h" +#endif + #define PARSE_REGEX "^\\s*([0-9a-zA-Z]{8}-[0-9a-zA-Z]{4}-[0-9a-zA-Z]{4}-[0-9a-zA-Z]{4}-[0-9a-zA-Z]{12})(?:\\s*,(<|>|=|!)([0-9]{1,5}))?(?:\\s*,(any_frag))?\\s*$" static pcre *parse_regex = NULL; @@ -55,6 +60,12 @@ static pcre_extra *parse_regex_study = NULL; static int DetectDceIfaceMatch(ThreadVars *, DetectEngineThreadCtx *, Flow *, uint8_t, void *, void *, const Signature *, const SigMatchCtx *); +#ifdef HAVE_RUST +static int DetectDceIfaceMatchRust(ThreadVars *t, + DetectEngineThreadCtx *det_ctx, + Flow *f, uint8_t flags, void *state, void *txv, + const Signature *s, const SigMatchCtx *m); +#endif static int DetectDceIfaceSetup(DetectEngineCtx *, Signature *, const char *); static void DetectDceIfaceFree(void *); static void DetectDceIfaceRegisterTests(void); @@ -73,7 +84,11 @@ void DetectDceIfaceRegister(void) { sigmatch_table[DETECT_DCE_IFACE].name = "dce_iface"; sigmatch_table[DETECT_DCE_IFACE].Match = NULL; +#ifdef HAVE_RUST + sigmatch_table[DETECT_DCE_IFACE].AppLayerTxMatch = DetectDceIfaceMatchRust; +#else sigmatch_table[DETECT_DCE_IFACE].AppLayerTxMatch = DetectDceIfaceMatch; +#endif sigmatch_table[DETECT_DCE_IFACE].Setup = DetectDceIfaceSetup; sigmatch_table[DETECT_DCE_IFACE].Free = DetectDceIfaceFree; sigmatch_table[DETECT_DCE_IFACE].RegisterTests = DetectDceIfaceRegisterTests; @@ -231,6 +246,28 @@ static DetectDceIfaceData *DetectDceIfaceArgParse(const char *arg) return NULL; } +#include "app-layer-smb.h" +DCERPCState *DetectDceGetState(AppProto alproto, void *alstate) +{ +#ifdef HAVE_RUST + return alstate; +#else + switch (alproto) { + case ALPROTO_DCERPC: + return alstate; + case ALPROTO_SMB: { + SMBState *smb_state = (SMBState *)alstate; + return &smb_state->ds; + } + case ALPROTO_SMB2: + // not implemented + return NULL; + } + + return NULL; +#endif +} + /** * \internal * \brief Internal function that compares the dce interface version for this @@ -258,24 +295,6 @@ static inline int DetectDceIfaceMatchIfaceVersion(uint16_t version, } } -#include "app-layer-smb.h" -DCERPCState *DetectDceGetState(AppProto alproto, void *alstate) -{ - switch(alproto) { - case ALPROTO_DCERPC: - return alstate; - case ALPROTO_SMB: { - SMBState *smb_state = (SMBState *)alstate; - return &smb_state->ds; - } - case ALPROTO_SMB2: - // not implemented - return NULL; - } - - return NULL; -} - /** * \brief App layer match function for the "dce_iface" keyword. * @@ -297,9 +316,10 @@ static int DetectDceIfaceMatch(ThreadVars *t, DetectEngineThreadCtx *det_ctx, SCEnter(); int ret = 0; + DetectDceIfaceData *dce_data = (DetectDceIfaceData *)m; + DCERPCUuidEntry *item = NULL; int i = 0; - DetectDceIfaceData *dce_data = (DetectDceIfaceData *)m; DCERPCState *dcerpc_state = DetectDceGetState(f->alproto, f->alstate); if (dcerpc_state == NULL) { SCLogDebug("No DCERPCState for the flow"); @@ -353,6 +373,33 @@ end: SCReturnInt(ret); } +#ifdef HAVE_RUST +static int DetectDceIfaceMatchRust(ThreadVars *t, + DetectEngineThreadCtx *det_ctx, + Flow *f, uint8_t flags, void *state, void *txv, + const Signature *s, const SigMatchCtx *m) +{ + SCEnter(); + + if (f->alproto == ALPROTO_DCERPC) { + return DetectDceIfaceMatch(t, det_ctx, f, flags, + state, txv, s, m); + } + + int ret = 0; + DetectDceIfaceData *dce_data = (DetectDceIfaceData *)m; + + if (rs_smb_tx_get_dce_iface(f->alstate, txv, dce_data->uuid, 16, dce_data->op, dce_data->version) != 1) { + SCLogDebug("rs_smb_tx_get_dce_iface: didn't match"); + } else { + SCLogDebug("rs_smb_tx_get_dce_iface: matched!"); + ret = 1; + // TODO validate frag + } + SCReturnInt(ret); +} +#endif + /** * \brief Creates a SigMatch for the "dce_iface" keyword being sent as argument, * and appends it to the Signature(s). @@ -370,8 +417,8 @@ static int DetectDceIfaceSetup(DetectEngineCtx *de_ctx, Signature *s, const char DetectDceIfaceData *did = NULL; SigMatch *sm = NULL; - if (DetectSignatureSetAppProto(s, ALPROTO_DCERPC) != 0) - return -1; +// if (DetectSignatureSetAppProto(s, ALPROTO_DCERPC) != 0) +// return -1; did = DetectDceIfaceArgParse(arg); if (did == NULL) { diff --git a/src/detect-dce-opnum.c b/src/detect-dce-opnum.c index f76d522f0a..28b3c9de08 100644 --- a/src/detect-dce-opnum.c +++ b/src/detect-dce-opnum.c @@ -48,6 +48,11 @@ #include "util-unittest-helper.h" #include "stream-tcp.h" +#ifdef HAVE_RUST +#include "rust.h" +#include "rust-smb-detect-gen.h" +#endif + #define PARSE_REGEX "^\\s*([0-9]{1,5}(\\s*-\\s*[0-9]{1,5}\\s*)?)(,\\s*[0-9]{1,5}(\\s*-\\s*[0-9]{1,5})?\\s*)*$" static pcre *parse_regex = NULL; @@ -56,6 +61,12 @@ static pcre_extra *parse_regex_study = NULL; static int DetectDceOpnumMatch(ThreadVars *, DetectEngineThreadCtx *, Flow *, uint8_t, void *, void *, const Signature *, const SigMatchCtx *); +#ifdef HAVE_RUST +static int DetectDceOpnumMatchRust(ThreadVars *t, + DetectEngineThreadCtx *det_ctx, + Flow *f, uint8_t flags, void *state, void *txv, + const Signature *s, const SigMatchCtx *m); +#endif static int DetectDceOpnumSetup(DetectEngineCtx *, Signature *, const char *); static void DetectDceOpnumFree(void *); static void DetectDceOpnumRegisterTests(void); @@ -68,7 +79,11 @@ void DetectDceOpnumRegister(void) { sigmatch_table[DETECT_DCE_OPNUM].name = "dce_opnum"; sigmatch_table[DETECT_DCE_OPNUM].Match = NULL; +#ifdef HAVE_RUST + sigmatch_table[DETECT_DCE_OPNUM].AppLayerTxMatch = DetectDceOpnumMatchRust; +#else sigmatch_table[DETECT_DCE_OPNUM].AppLayerTxMatch = DetectDceOpnumMatch; +#endif sigmatch_table[DETECT_DCE_OPNUM].Setup = DetectDceOpnumSetup; sigmatch_table[DETECT_DCE_OPNUM].Free = DetectDceOpnumFree; sigmatch_table[DETECT_DCE_OPNUM].RegisterTests = DetectDceOpnumRegisterTests; @@ -255,15 +270,15 @@ static int DetectDceOpnumMatch(ThreadVars *t, DetectEngineThreadCtx *det_ctx, SCLogDebug("No DCERPCState for the flow"); SCReturnInt(0); } + uint16_t opnum = dcerpc_state->dcerpc.dcerpcrequest.opnum; for ( ; dor != NULL; dor = dor->next) { if (dor->range2 == DCE_OPNUM_RANGE_UNINITIALIZED) { - if (dor->range1 == dcerpc_state->dcerpc.dcerpcrequest.opnum) { + if (dor->range1 == opnum) { SCReturnInt(1); } } else { - if (dor->range1 <= dcerpc_state->dcerpc.dcerpcrequest.opnum && - dor->range2 >= dcerpc_state->dcerpc.dcerpcrequest.opnum) + if (dor->range1 <= opnum && dor->range2 >= opnum) { SCReturnInt(1); } @@ -273,6 +288,44 @@ static int DetectDceOpnumMatch(ThreadVars *t, DetectEngineThreadCtx *det_ctx, SCReturnInt(0); } +#ifdef HAVE_RUST +static int DetectDceOpnumMatchRust(ThreadVars *t, + DetectEngineThreadCtx *det_ctx, + Flow *f, uint8_t flags, void *state, void *txv, + const Signature *s, const SigMatchCtx *m) +{ + SCEnter(); + + if (f->alproto == ALPROTO_DCERPC) { + return DetectDceOpnumMatch(t, det_ctx, f, flags, + state, txv, s, m); + } + + const DetectDceOpnumData *dce_data = (DetectDceOpnumData *)m; + const DetectDceOpnumRange *dor = dce_data->range; + + uint16_t opnum; + if (rs_smb_tx_get_dce_opnum(txv, &opnum) != 1) + SCReturnInt(0); + SCLogDebug("(rust) opnum %u", opnum); + + for ( ; dor != NULL; dor = dor->next) { + if (dor->range2 == DCE_OPNUM_RANGE_UNINITIALIZED) { + if (dor->range1 == opnum) { + SCReturnInt(1); + } + } else { + if (dor->range1 <= opnum && dor->range2 >= opnum) + { + SCReturnInt(1); + } + } + } + + SCReturnInt(0); +} +#endif + /** * \brief Creates a SigMatch for the "dce_opnum" keyword being sent as argument, * and appends it to the Signature(s). @@ -296,8 +349,8 @@ static int DetectDceOpnumSetup(DetectEngineCtx *de_ctx, Signature *s, const char return -1; } - if (DetectSignatureSetAppProto(s, ALPROTO_DCERPC) != 0) - return -1; + //if (DetectSignatureSetAppProto(s, ALPROTO_DCERPC) != 0) + // return -1; dod = DetectDceOpnumArgParse(arg); if (dod == NULL) { diff --git a/src/detect-dce-stub-data.c b/src/detect-dce-stub-data.c index 569dad5df4..87fa07efb1 100644 --- a/src/detect-dce-stub-data.c +++ b/src/detect-dce-stub-data.c @@ -54,6 +54,11 @@ #include "stream-tcp.h" +#ifdef HAVE_RUST +#include "rust.h" +#include "rust-smb-detect-gen.h" +#endif + #define BUFFER_NAME "dce_stub_data" #define KEYWORD_NAME "dce_stub_data" @@ -77,13 +82,26 @@ static void PrefilterTxDceStubDataRequest(DetectEngineThreadCtx *det_ctx, SCEnter(); const MpmCtx *mpm_ctx = (MpmCtx *)pectx; - DCERPCState *dcerpc_state = DetectDceGetState(f->alproto, f->alstate); - if (dcerpc_state == NULL) - return; - - uint32_t buffer_len = dcerpc_state->dcerpc.dcerpcrequest.stub_data_buffer_len; - const uint8_t *buffer = dcerpc_state->dcerpc.dcerpcrequest.stub_data_buffer; + uint8_t *buffer; + uint32_t buffer_len; + +#ifdef HAVE_RUST + if (f->alproto == ALPROTO_SMB) { + if (rs_smb_tx_get_stub_data(txv, STREAM_TOSERVER, &buffer, &buffer_len) != 1) { + SCLogDebug("have no data!"); + return; + } + SCLogDebug("have data!"); + } else +#endif + { + DCERPCState *dcerpc_state = DetectDceGetState(f->alproto, f->alstate); + if (dcerpc_state == NULL) + return; + buffer_len = dcerpc_state->dcerpc.dcerpcrequest.stub_data_buffer_len; + buffer = dcerpc_state->dcerpc.dcerpcrequest.stub_data_buffer; + } if (buffer_len >= mpm_ctx->minlen) { (void)mpm_table[mpm_ctx->mpm_type].Search(mpm_ctx, &det_ctx->mtcu, &det_ctx->pmq, buffer, buffer_len); @@ -122,13 +140,26 @@ static void PrefilterTxDceStubDataResponse(DetectEngineThreadCtx *det_ctx, SCEnter(); const MpmCtx *mpm_ctx = (MpmCtx *)pectx; + uint8_t *buffer; + uint32_t buffer_len; + +#ifdef HAVE_RUST + if (f->alproto == ALPROTO_SMB) { + if (rs_smb_tx_get_stub_data(txv, STREAM_TOCLIENT, &buffer, &buffer_len) != 1) { + SCLogDebug("have no data!"); + return; + } + SCLogDebug("have data!"); + } else +#endif + { + DCERPCState *dcerpc_state = DetectDceGetState(f->alproto, f->alstate); + if (dcerpc_state == NULL) + return; - DCERPCState *dcerpc_state = DetectDceGetState(f->alproto, f->alstate); - if (dcerpc_state == NULL) - return; - - uint32_t buffer_len = dcerpc_state->dcerpc.dcerpcresponse.stub_data_buffer_len; - const uint8_t *buffer = dcerpc_state->dcerpc.dcerpcresponse.stub_data_buffer; + buffer_len = dcerpc_state->dcerpc.dcerpcresponse.stub_data_buffer_len; + buffer = dcerpc_state->dcerpc.dcerpcresponse.stub_data_buffer; + } if (buffer_len >= mpm_ctx->minlen) { (void)mpm_table[mpm_ctx->mpm_type].Search(mpm_ctx, @@ -159,19 +190,29 @@ static int InspectEngineDceStubData(ThreadVars *tv, { uint32_t buffer_len = 0; uint8_t *buffer = NULL; + DCERPCState *dcerpc_state = NULL; - DCERPCState *dcerpc_state = DetectDceGetState(f->alproto, f->alstate); - if (dcerpc_state == NULL) - goto end; - - if (flags & STREAM_TOSERVER) { - buffer_len = dcerpc_state->dcerpc.dcerpcrequest.stub_data_buffer_len; - buffer = dcerpc_state->dcerpc.dcerpcrequest.stub_data_buffer; - } else if (flags & STREAM_TOCLIENT) { - buffer_len = dcerpc_state->dcerpc.dcerpcresponse.stub_data_buffer_len; - buffer = dcerpc_state->dcerpc.dcerpcresponse.stub_data_buffer; +#ifdef HAVE_RUST + if (f->alproto == ALPROTO_SMB) { + uint8_t dir = flags & (STREAM_TOSERVER|STREAM_TOCLIENT); + if (rs_smb_tx_get_stub_data(tx, dir, &buffer, &buffer_len) != 1) + goto end; + SCLogDebug("have data!"); + } else +#endif + { + dcerpc_state = DetectDceGetState(f->alproto, f->alstate); + if (dcerpc_state == NULL) + goto end; + + if (flags & STREAM_TOSERVER) { + buffer_len = dcerpc_state->dcerpc.dcerpcrequest.stub_data_buffer_len; + buffer = dcerpc_state->dcerpc.dcerpcrequest.stub_data_buffer; + } else if (flags & STREAM_TOCLIENT) { + buffer_len = dcerpc_state->dcerpc.dcerpcresponse.stub_data_buffer_len; + buffer = dcerpc_state->dcerpc.dcerpcresponse.stub_data_buffer; + } } - if (buffer == NULL ||buffer_len == 0) goto end; @@ -239,8 +280,8 @@ void DetectDceStubDataRegister(void) static int DetectDceStubDataSetup(DetectEngineCtx *de_ctx, Signature *s, const char *arg) { - if (DetectSignatureSetAppProto(s, ALPROTO_DCERPC) != 0) - return -1; +// if (DetectSignatureSetAppProto(s, ALPROTO_DCERPC) != 0) +// return -1; s->init_data->list = g_dce_stub_data_buffer_id; return 0; diff --git a/src/detect-engine-register.c b/src/detect-engine-register.c index 81c37405ae..f13b560d0f 100644 --- a/src/detect-engine-register.c +++ b/src/detect-engine-register.c @@ -68,6 +68,8 @@ #include "detect-engine-event.h" #include "decode.h" +#include "detect-smb-share.h" + #include "detect-base64-decode.h" #include "detect-base64-data.h" #include "detect-ipopts.h" @@ -457,6 +459,8 @@ void SigTableSetup(void) DetectDceIfaceRegister(); DetectDceOpnumRegister(); DetectDceStubDataRegister(); + DetectSmbNamedPipeRegister(); + DetectSmbShareRegister(); DetectTlsRegister(); DetectTlsValidityRegister(); DetectTlsVersionRegister(); diff --git a/src/detect-engine-register.h b/src/detect-engine-register.h index a5761a501b..a780fbc9bf 100644 --- a/src/detect-engine-register.h +++ b/src/detect-engine-register.h @@ -153,6 +153,8 @@ enum { DETECT_DCE_IFACE, DETECT_DCE_OPNUM, DETECT_DCE_STUB_DATA, + DETECT_SMB_NAMED_PIPE, + DETECT_SMB_SHARE, DETECT_ASN1, diff --git a/src/detect-file-data.c b/src/detect-file-data.c index 51a9046717..3d7f370748 100644 --- a/src/detect-file-data.c +++ b/src/detect-file-data.c @@ -75,6 +75,14 @@ void DetectFiledataRegister(void) PrefilterGenericMpmRegister, HttpServerBodyGetDataCallback, ALPROTO_HTTP, HTP_RESPONSE_BODY); +#ifdef HAVE_RUST + DetectAppLayerMpmRegister2("file_data", SIG_FLAG_TOSERVER, 2, + PrefilterMpmFiledataRegister, NULL, + ALPROTO_SMB, 0); + DetectAppLayerMpmRegister2("file_data", SIG_FLAG_TOCLIENT, 2, + PrefilterMpmFiledataRegister, NULL, + ALPROTO_SMB, 0); +#endif DetectAppLayerInspectEngineRegister2("file_data", ALPROTO_HTTP, SIG_FLAG_TOCLIENT, HTP_RESPONSE_BODY, @@ -84,6 +92,14 @@ void DetectFiledataRegister(void) DetectEngineInspectFiledata, NULL); DetectBufferTypeRegisterSetupCallback("file_data", DetectFiledataSetupCallback); +#ifdef HAVE_RUST + DetectAppLayerInspectEngineRegister2("file_data", + ALPROTO_SMB, SIG_FLAG_TOSERVER, 0, + DetectEngineInspectFiledata, NULL); + DetectAppLayerInspectEngineRegister2("file_data", + ALPROTO_SMB, SIG_FLAG_TOCLIENT, 0, + DetectEngineInspectFiledata, NULL); +#endif DetectBufferTypeSetDescriptionByName("file_data", "http response body or smtp attachments data"); @@ -133,7 +149,7 @@ static int DetectFiledataSetup (DetectEngineCtx *de_ctx, Signature *s, const cha if (!DetectProtoContainsProto(&s->proto, IPPROTO_TCP) || (s->alproto != ALPROTO_UNKNOWN && s->alproto != ALPROTO_HTTP && - s->alproto != ALPROTO_SMTP)) { + s->alproto != ALPROTO_SMTP && s->alproto != ALPROTO_SMB)) { SCLogError(SC_ERR_CONFLICTING_RULE_KEYWORDS, "rule contains conflicting keywords."); return -1; } diff --git a/src/detect-filename.c b/src/detect-filename.c index 6cf9c2b855..d39e661e74 100644 --- a/src/detect-filename.c +++ b/src/detect-filename.c @@ -99,6 +99,13 @@ void DetectFilenameRegister(void) ALPROTO_FTPDATA, SIG_FLAG_TOCLIENT, 0, DetectFileInspectGeneric); + DetectAppLayerInspectEngineRegister("files", + ALPROTO_SMB, SIG_FLAG_TOSERVER, 0, + DetectFileInspectGeneric); + DetectAppLayerInspectEngineRegister("files", + ALPROTO_SMB, SIG_FLAG_TOCLIENT, 0, + DetectFileInspectGeneric); + g_file_match_list_id = DetectBufferTypeGetByName("files"); SCLogDebug("registering filename rule option"); diff --git a/src/detect-smb-share.c b/src/detect-smb-share.c new file mode 100644 index 0000000000..b222b022c0 --- /dev/null +++ b/src/detect-smb-share.c @@ -0,0 +1,243 @@ +/* Copyright (C) 2017 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 Victor Julien + * + */ + +#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-share.h" +#ifdef HAVE_RUST +#include "rust.h" +#include "rust-smb-detect-gen.h" + +#define BUFFER_NAME "smb_named_pipe" +#define KEYWORD_NAME BUFFER_NAME +#define KEYWORD_ID DETECT_SMB_NAMED_PIPE + +static int g_smb_named_pipe_buffer_id = 0; + +/** \brief SMB NAMED PIPE Mpm prefilter callback + * + * \param det_ctx detection engine thread ctx + * \param p packet to inspect + * \param f flow to inspect + * \param txv tx to inspect + * \param pectx inspection context + */ +static void PrefilterTxSmbNamedPipe(DetectEngineThreadCtx *det_ctx, + const void *pectx, + Packet *p, Flow *f, void *txv, + const uint64_t idx, const uint8_t flags) +{ + SCEnter(); + + const MpmCtx *mpm_ctx = (MpmCtx *)pectx; + uint8_t *buffer; + uint32_t buffer_len; + + if (rs_smb_tx_get_named_pipe(txv, &buffer, &buffer_len) != 1) { + return; + } + + if (buffer_len >= mpm_ctx->minlen) { + (void)mpm_table[mpm_ctx->mpm_type].Search(mpm_ctx, + &det_ctx->mtcu, &det_ctx->pmq, buffer, buffer_len); + } +} + +static int PrefilterTxSmbNamedPipeRequestRegister(DetectEngineCtx *de_ctx, + SigGroupHead *sgh, MpmCtx *mpm_ctx) +{ + SCEnter(); + + return PrefilterAppendTxEngine(de_ctx, sgh, PrefilterTxSmbNamedPipe, + ALPROTO_SMB, 0, + mpm_ctx, NULL, KEYWORD_NAME " (request)"); +} + +static int InspectEngineSmbNamedPipe(ThreadVars *tv, + DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx, + const Signature *s, const SigMatchData *smd, + Flow *f, uint8_t flags, void *alstate, void *tx, uint64_t tx_id) +{ + uint32_t buffer_len = 0; + uint8_t *buffer = NULL; + + if (rs_smb_tx_get_named_pipe(tx, &buffer, &buffer_len) != 1) + goto end; + if (buffer == NULL ||buffer_len == 0) + goto end; + + det_ctx->buffer_offset = 0; + det_ctx->discontinue_matching = 0; + det_ctx->inspection_recursion_counter = 0; + int r = DetectEngineContentInspection(de_ctx, det_ctx, s, smd, + f, + buffer, buffer_len, + 0, DETECT_CI_FLAGS_SINGLE, + DETECT_ENGINE_CONTENT_INSPECTION_MODE_STATE, + NULL); + if (r == 1) + return DETECT_ENGINE_INSPECT_SIG_MATCH; + +end: + return DETECT_ENGINE_INSPECT_SIG_NO_MATCH; +} + +static int DetectSmbNamedPipeSetup(DetectEngineCtx *de_ctx, Signature *s, const char *arg) +{ + s->init_data->list = g_smb_named_pipe_buffer_id; + return 0; +} + +void DetectSmbNamedPipeRegister(void) +{ + sigmatch_table[KEYWORD_ID].name = KEYWORD_NAME; + sigmatch_table[KEYWORD_ID].Setup = DetectSmbNamedPipeSetup; + sigmatch_table[KEYWORD_ID].flags |= SIGMATCH_NOOPT; + + DetectAppLayerMpmRegister(BUFFER_NAME, SIG_FLAG_TOSERVER, 2, + PrefilterTxSmbNamedPipeRequestRegister); + + DetectAppLayerInspectEngineRegister(BUFFER_NAME, + ALPROTO_SMB, SIG_FLAG_TOSERVER, 0, + InspectEngineSmbNamedPipe); + + g_smb_named_pipe_buffer_id = DetectBufferTypeGetByName(BUFFER_NAME); +} + +#undef BUFFER_NAME +#undef KEYWORD_NAME +#undef KEYWORD_ID + +#else /* NO RUST */ +void DetectSmbNamedPipeRegister(void) {} +#endif + +#ifdef HAVE_RUST +#define BUFFER_NAME "smb_share" +#define KEYWORD_NAME BUFFER_NAME +#define KEYWORD_ID DETECT_SMB_SHARE + +static int g_smb_share_buffer_id = 0; + +/** \brief SMB SHARE Mpm prefilter callback + * + * \param det_ctx detection engine thread ctx + * \param p packet to inspect + * \param f flow to inspect + * \param txv tx to inspect + * \param pectx inspection context + */ +static void PrefilterTxSmbShare(DetectEngineThreadCtx *det_ctx, + const void *pectx, + Packet *p, Flow *f, void *txv, + const uint64_t idx, const uint8_t flags) +{ + SCEnter(); + + const MpmCtx *mpm_ctx = (MpmCtx *)pectx; + uint8_t *buffer; + uint32_t buffer_len; + + if (rs_smb_tx_get_share(txv, &buffer, &buffer_len) != 1) { + return; + } + + if (buffer_len >= mpm_ctx->minlen) { + (void)mpm_table[mpm_ctx->mpm_type].Search(mpm_ctx, + &det_ctx->mtcu, &det_ctx->pmq, buffer, buffer_len); + } +} + +static int PrefilterTxSmbShareRequestRegister(DetectEngineCtx *de_ctx, + SigGroupHead *sgh, MpmCtx *mpm_ctx) +{ + SCEnter(); + + return PrefilterAppendTxEngine(de_ctx, sgh, PrefilterTxSmbShare, + ALPROTO_SMB, 0, + mpm_ctx, NULL, KEYWORD_NAME " (request)"); +} + +static int InspectEngineSmbShare(ThreadVars *tv, + DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx, + const Signature *s, const SigMatchData *smd, + Flow *f, uint8_t flags, void *alstate, void *tx, uint64_t tx_id) +{ + uint32_t buffer_len = 0; + uint8_t *buffer = NULL; + + if (rs_smb_tx_get_share(tx, &buffer, &buffer_len) != 1) + goto end; + if (buffer == NULL ||buffer_len == 0) + goto end; + + det_ctx->buffer_offset = 0; + det_ctx->discontinue_matching = 0; + det_ctx->inspection_recursion_counter = 0; + int r = DetectEngineContentInspection(de_ctx, det_ctx, s, smd, + f, + buffer, buffer_len, + 0, DETECT_CI_FLAGS_SINGLE, + DETECT_ENGINE_CONTENT_INSPECTION_MODE_STATE, + NULL); + if (r == 1) + return DETECT_ENGINE_INSPECT_SIG_MATCH; + +end: + return DETECT_ENGINE_INSPECT_SIG_NO_MATCH; +} + +static int DetectSmbShareSetup(DetectEngineCtx *de_ctx, Signature *s, const char *arg) +{ + s->init_data->list = g_smb_share_buffer_id; + return 0; +} + +void DetectSmbShareRegister(void) +{ + sigmatch_table[KEYWORD_ID].name = KEYWORD_NAME; + sigmatch_table[KEYWORD_ID].Setup = DetectSmbShareSetup; + sigmatch_table[KEYWORD_ID].flags |= SIGMATCH_NOOPT; + + DetectAppLayerMpmRegister(BUFFER_NAME, SIG_FLAG_TOSERVER, 2, + PrefilterTxSmbShareRequestRegister); + + DetectAppLayerInspectEngineRegister(BUFFER_NAME, + ALPROTO_SMB, SIG_FLAG_TOSERVER, 0, + InspectEngineSmbShare); + + g_smb_share_buffer_id = DetectBufferTypeGetByName(BUFFER_NAME); +} +#else +void DetectSmbShareRegister(void) {} +#endif diff --git a/src/detect-smb-share.h b/src/detect-smb-share.h new file mode 100644 index 0000000000..a5e5be93c9 --- /dev/null +++ b/src/detect-smb-share.h @@ -0,0 +1,30 @@ +/* Copyright (C) 2017 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 Victor Julien + */ + +#ifndef __DETECT_SMB_NAMED_PIPE_H__ +#define __DETECT_SMB_NAMED_PIPE_H__ + +void DetectSmbNamedPipeRegister(void); +void DetectSmbShareRegister(void); + +#endif /* __DETECT_SMB_NAMED_PIPE_H__ */ diff --git a/src/output-json-alert.c b/src/output-json-alert.c index 59d818bc45..5013171bef 100644 --- a/src/output-json-alert.c +++ b/src/output-json-alert.c @@ -63,6 +63,7 @@ #include "output-json-smtp.h" #include "output-json-email-common.h" #include "output-json-nfs.h" +#include "output-json-smb.h" #include "output-json-flow.h" #include "util-byte.h" @@ -460,6 +461,10 @@ static int AlertJson(ThreadVars *tv, JsonAlertLogThread *aft, const Packet *p) hjs = JsonNFSAddMetadata(p->flow, pa->tx_id); if (hjs) json_object_set_new(js, "nfs", hjs); + } else if (proto == ALPROTO_SMB) { + hjs = JsonSMBAddMetadata(p->flow, pa->tx_id); + if (hjs) + json_object_set_new(js, "smb", hjs); } #endif if (proto == ALPROTO_FTPDATA) { diff --git a/src/output-json-smb.c b/src/output-json-smb.c new file mode 100644 index 0000000000..4be4376113 --- /dev/null +++ b/src/output-json-smb.c @@ -0,0 +1,208 @@ +/* Copyright (C) 2017 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 Victor Julien + * + * Implement JSON/eve logging app-layer SMB. + */ + +#include "suricata-common.h" +#include "debug.h" +#include "pkt-var.h" +#include "conf.h" + +#include "threads.h" +#include "threadvars.h" +#include "tm-threads.h" + +#include "util-unittest.h" +#include "util-buffer.h" +#include "util-debug.h" +#include "util-byte.h" + +#include "output.h" +#include "output-json.h" + +#include "app-layer.h" +#include "app-layer-parser.h" + +#include "output-json-smb.h" + +#ifdef HAVE_RUST +#ifdef HAVE_LIBJANSSON +#include "rust.h" +#include "rust-smb-log-gen.h" + +typedef struct LogSMBFileCtx_ { + LogFileCtx *file_ctx; + uint32_t flags; +} LogSMBFileCtx; + +typedef struct LogSMBLogThread_ { + LogSMBFileCtx *smblog_ctx; + uint32_t count; + MemBuffer *buffer; +} LogSMBLogThread; + +json_t *JsonSMBAddMetadata(const Flow *f, uint64_t tx_id) +{ + SMBState *state = FlowGetAppState(f); + if (state) { + SMBTransaction *tx = AppLayerParserGetTx(f->proto, ALPROTO_SMB, state, tx_id); + if (tx) { + return rs_smb_log_json_response(state, tx); + } + } + + return NULL; +} + +static int JsonSMBLogger(ThreadVars *tv, void *thread_data, + const Packet *p, Flow *f, void *state, void *tx, uint64_t tx_id) +{ + //SMBTransaction *smbtx = tx; + LogSMBLogThread *thread = thread_data; + json_t *js, *smbjs; + + js = CreateJSONHeader((Packet *)p, 0, "smb"); + if (unlikely(js == NULL)) { + return TM_ECODE_FAILED; + } + + smbjs = rs_smb_log_json_response(state, tx); + if (unlikely(smbjs == NULL)) { + goto error; + } + json_object_set_new(js, "smb", smbjs); + + MemBufferReset(thread->buffer); + OutputJSONBuffer(js, thread->smblog_ctx->file_ctx, &thread->buffer); + + json_decref(js); + return TM_ECODE_OK; + +error: + json_decref(js); + return TM_ECODE_FAILED; +} + +static void OutputSMBLogDeInitCtxSub(OutputCtx *output_ctx) +{ + LogSMBFileCtx *smblog_ctx = (LogSMBFileCtx *)output_ctx->data; + SCFree(smblog_ctx); + SCFree(output_ctx); +} + +static OutputInitResult OutputSMBLogInitSub(ConfNode *conf, + OutputCtx *parent_ctx) +{ + OutputInitResult result = { NULL, false }; + OutputJsonCtx *ajt = parent_ctx->data; + + LogSMBFileCtx *smblog_ctx = SCCalloc(1, sizeof(*smblog_ctx)); + if (unlikely(smblog_ctx == NULL)) { + return result; + } + smblog_ctx->file_ctx = ajt->file_ctx; + + OutputCtx *output_ctx = SCCalloc(1, sizeof(*output_ctx)); + if (unlikely(output_ctx == NULL)) { + SCFree(smblog_ctx); + return result; + } + output_ctx->data = smblog_ctx; + output_ctx->DeInit = OutputSMBLogDeInitCtxSub; + + SCLogDebug("SMB log sub-module initialized."); + + AppLayerParserRegisterLogger(IPPROTO_TCP, ALPROTO_SMB); + AppLayerParserRegisterLogger(IPPROTO_UDP, ALPROTO_SMB); + + result.ctx = output_ctx; + result.ok = true; + return result; +} + +#define OUTPUT_BUFFER_SIZE 65535 + +static TmEcode JsonSMBLogThreadInit(ThreadVars *t, const void *initdata, void **data) +{ + LogSMBLogThread *thread = SCCalloc(1, sizeof(*thread)); + if (unlikely(thread == NULL)) { + return TM_ECODE_FAILED; + } + + if (initdata == NULL) { + SCLogDebug("Error getting context for EveLogSMB. \"initdata\" is NULL."); + SCFree(thread); + return TM_ECODE_FAILED; + } + + thread->buffer = MemBufferCreateNew(OUTPUT_BUFFER_SIZE); + if (unlikely(thread->buffer == NULL)) { + SCFree(thread); + return TM_ECODE_FAILED; + } + + thread->smblog_ctx = ((OutputCtx *)initdata)->data; + *data = (void *)thread; + + return TM_ECODE_OK; +} + +static TmEcode JsonSMBLogThreadDeinit(ThreadVars *t, void *data) +{ + LogSMBLogThread *thread = (LogSMBLogThread *)data; + if (thread == NULL) { + return TM_ECODE_OK; + } + if (thread->buffer != NULL) { + MemBufferFree(thread->buffer); + } + SCFree(thread); + return TM_ECODE_OK; +} + +void JsonSMBLogRegister(void) +{ + /* Register as an eve sub-module. */ + OutputRegisterTxSubModule(LOGGER_JSON_SMB, "eve-log", "JsonSMBLog", + "eve-log.smb", OutputSMBLogInitSub, ALPROTO_SMB, + JsonSMBLogger, JsonSMBLogThreadInit, + JsonSMBLogThreadDeinit, NULL); + + SCLogDebug("SMB JSON logger registered."); +} + +#else /* No JSON support. */ + +void JsonSMBLogRegister(void) +{ +} + +#endif /* HAVE_LIBJANSSON */ + +#else /* no rust */ + +void JsonSMBLogRegister(void) +{ +} + +#endif /* HAVE_RUST */ diff --git a/src/output-json-smb.h b/src/output-json-smb.h new file mode 100644 index 0000000000..6f96002551 --- /dev/null +++ b/src/output-json-smb.h @@ -0,0 +1,31 @@ +/* Copyright (C) 2017 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 Victor Julien + */ + +#ifndef __OUTPUT_JSON_SMB_H__ +#define __OUTPUT_JSON_SMB_H__ + +void JsonSMBLogRegister(void); +#ifdef HAVE_RUST +json_t *JsonSMBAddMetadata(const Flow *f, uint64_t tx_id); +#endif /* HAVE_RUST */ +#endif /* __OUTPUT_JSON_SMB_H__ */ diff --git a/src/output.c b/src/output.c index 663aac5386..65f3e7251f 100644 --- a/src/output.c +++ b/src/output.c @@ -70,6 +70,7 @@ #include "output-json.h" #include "output-json-nfs.h" #include "output-json-tftp.h" +#include "output-json-smb.h" #include "output-json-template.h" #include "output-lua.h" #include "output-json-dnp3.h" @@ -1092,6 +1093,9 @@ void OutputRegisterLoggers(void) JsonNFSLogRegister(); /* TFTP JSON logger. */ JsonTFTPLogRegister(); + /* SMB JSON logger. */ + JsonSMBLogRegister(); + /* Template JSON logger. */ JsonTemplateLogRegister(); } diff --git a/src/rust.h b/src/rust.h index c016fd493b..151dc89f7d 100644 --- a/src/rust.h +++ b/src/rust.h @@ -53,5 +53,7 @@ typedef struct _Store Store; /** Opaque Rust types. */ typedef struct NFState_ NFSState; typedef struct NFSTransaction_ NFSTransaction; +typedef struct SMBState_ SMBState; +typedef struct SMBTransaction_ SMBTransaction; #endif /* !__RUST_H__ */ diff --git a/src/suricata-common.h b/src/suricata-common.h index beeee12395..eb4be159bf 100644 --- a/src/suricata-common.h +++ b/src/suricata-common.h @@ -410,6 +410,7 @@ typedef enum { LOGGER_JSON_DNP3_TS, LOGGER_JSON_DNP3_TC, LOGGER_JSON_SSH, + LOGGER_JSON_SMB, LOGGER_JSON_TEMPLATE, LOGGER_ALERT_DEBUG, diff --git a/src/util-error.c b/src/util-error.c index 3f8209746b..354c4f80c2 100644 --- a/src/util-error.c +++ b/src/util-error.c @@ -347,6 +347,7 @@ const char * SCErrorToString(SCError err) CASE_CODE (SC_ERR_PF_RING_VLAN); CASE_CODE (SC_ERR_CREATE_DIRECTORY); CASE_CODE (SC_WARN_FLOWBIT); + CASE_CODE (SC_ERR_SMB_CONFIG); CASE_CODE (SC_ERR_MAX); } diff --git a/src/util-error.h b/src/util-error.h index 94f0a31f1e..d3fac31545 100644 --- a/src/util-error.h +++ b/src/util-error.h @@ -337,6 +337,7 @@ typedef enum { SC_ERR_PF_RING_VLAN, SC_ERR_CREATE_DIRECTORY, SC_WARN_FLOWBIT, + SC_ERR_SMB_CONFIG, SC_ERR_MAX, } SCError; diff --git a/src/util-profiling.c b/src/util-profiling.c index 01b22dfe40..70a44c3d62 100644 --- a/src/util-profiling.c +++ b/src/util-profiling.c @@ -1244,6 +1244,7 @@ const char * PacketProfileLoggertIdToString(LoggerId id) CASE_CODE (LOGGER_JSON_SSH); CASE_CODE (LOGGER_DNS_TS); CASE_CODE (LOGGER_DNS_TC); + CASE_CODE (LOGGER_JSON_SMB); CASE_CODE (LOGGER_HTTP); CASE_CODE (LOGGER_JSON_DNS_TS); CASE_CODE (LOGGER_JSON_DNS_TC); diff --git a/suricata.yaml.in b/suricata.yaml.in index 6d6f6b5b13..0bcaafa708 100644 --- a/suricata.yaml.in +++ b/suricata.yaml.in @@ -167,6 +167,8 @@ outputs: # Include top level metadata. Default yes. #metadata: no + pcap-file: false + types: - alert: # payload: yes # enable dumping payload in Base64 @@ -798,13 +800,12 @@ app-layer: enabled: detection-only msn: enabled: detection-only + # Note: --enable-rust is required for full SMB1/2 support. W/o rust + # only minimal SMB1 support is available. smb: enabled: yes detection-ports: dp: 139, 445 - # smb2 detection is disabled internally inside the engine. - #smb2: - # enabled: yes # Note: NFS parser depends on Rust support: pass --enable-rust # to configure. nfs: