--- /dev/null
+# 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;)
nom = "~3.2.1"
libc = "^0.2.36"
crc = "~1.7.0"
+der-parser = "0.5.0"
ntp-parser = { version = "^0", optional = true }
"NTPTransaction": "NTPTransaction",
"TFTPTransaction": "TFTPTransaction",
"TFTPState": "TFTPState",
+ "SMBState": "SMBState",
+ "SMBTransaction": "SMBTransaction",
"JsonT": "json_t",
"DetectEngineState": "DetectEngineState",
"core::DetectEngineState": "DetectEngineState",
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;
extern crate crc;
+extern crate der_parser;
+
#[macro_use]
pub mod log;
pub mod dns;
pub mod nfs;
pub mod ftp;
+pub mod smb;
#[cfg(feature = "experimental")]
pub mod ntp;
--- /dev/null
+/* 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<u8>,
+ pub snames: Vec<Vec<u8>>,
+}
+
+/// 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<u8>> = 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<Kerberos5Ticket> = 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<u8>,
+ pub user: Vec<u8>,
+ pub domain: Vec<u8>,
+}
+
+/// 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);
+ },
+ }
+}
--- /dev/null
+/* 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<u8>,
+ 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<u8>, 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<u8>,
+ pub stub_data_tc: Vec<u8>,
+}
+
+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<Vec<DCERPCIface>> = 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<DCERPCIface> = 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;
+}
--- /dev/null
+/* 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<DceRpcBindIface>,
+ 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<DceRpcBindIface>,
+ 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<DceRpcBindIface<'a>>,
+}
+
+named!(pub parse_dcerpc_bind_record<DceRpcBindRecord>,
+ 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<DceRpcBindRecord>,
+ 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<DceRpcBindAckResult>,
+ 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<DceRpcBindAckResult<'a>>,
+}
+
+named!(pub parse_dcerpc_bindack_record<DceRpcBindAckRecord>,
+ 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<DceRpcRecord>,
+ 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,
+ })
+));
--- /dev/null
+/* 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());
+ }
+}
--- /dev/null
+/* 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;
+}
--- /dev/null
+/* 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<SMBEvent>) {
+ 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);
+ }
+}
--- /dev/null
+/* 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<u8>,
+ pub file_name: Vec<u8>,
+ pub share_name: Vec<u8>,
+ 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<u8>, 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<u8>, file_name: &Vec<u8>, 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<u8>, 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)
+}
--- /dev/null
+/* 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();
+}
--- /dev/null
+/* 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;
--- /dev/null
+/* 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<NbssRecord>,
+ 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<NbssRecordPartial>,
+ 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,
+ })
+));
--- /dev/null
+/* 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<NTLMSSPAuthRecord>,
+ 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<NTLMSSPRecord>,
+ do_parse!(
+ take_until_and_consume!("NTLMSSP\x00")
+ >> msg_type: le_u32
+ >> data: rest
+ >> (NTLMSSPRecord {
+ msg_type:msg_type,
+ data:data,
+ })
+));
--- /dev/null
+/* 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<u8>,
+}
+
+impl SMBTransactionCreate {
+ pub fn new(filename: Vec<u8>, 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<Vec<u8>>,
+ pub dialects2: Vec<Vec<u8>>,
+}
+
+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<u8>,
+}
+
+impl SMBTransactionTreeConnect {
+ pub fn new(share_name: Vec<u8>) -> 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<SMBTransactionTypeData>,
+
+ /// 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<u8>,
+ pub offset: u64,
+}
+
+impl SMBFileGUIDOffset {
+ pub fn new(guid: Vec<u8>, 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<u8>,
+}
+
+impl SMBHashKeyHdrGuid {
+ pub fn new(hdr: SMBCommonHdr, guid: Vec<u8>) -> SMBHashKeyHdrGuid {
+ SMBHashKeyHdrGuid {
+ hdr: hdr, guid: guid,
+ }
+ }
+}
+
+#[derive(Hash, Eq, PartialEq, Debug)]
+pub struct SMBTree {
+ pub name: Vec<u8>,
+ pub is_pipe: bool,
+}
+
+impl SMBTree {
+ pub fn new(name: Vec<u8>, is_pipe: bool) -> 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<SMBCommonHdr, Vec<u8>>,
+ /// map guid to filename
+ pub guid2name_map: HashMap<Vec<u8>, Vec<u8>>,
+ /// map ssn key to read offset
+ pub ssn2vecoffset_map: HashMap<SMBCommonHdr, SMBFileGUIDOffset>,
+
+ pub ssn2tree_map: HashMap<SMBCommonHdr, SMBTree>,
+
+ // track the max size we expect for TRANS responses
+ pub ssn2maxsize_map: HashMap<SMBCommonHdr, u16>,
+ pub ssnguid2vec_map: HashMap<SMBHashKeyHdrGuid, Vec<u8>>,
+
+ /// TCP segments defragmentation buffer
+ pub tcp_buffer_ts: Vec<u8>,
+ pub tcp_buffer_tc: Vec<u8>,
+
+ 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<u8>,
+ pub file_tc_guid : Vec<u8>,
+
+ pub ts_ssn_gap: bool,
+ pub tc_ssn_gap: bool,
+
+ pub ts_gap: bool, // last TS update was gap
+ pub tc_gap: bool, // last TC update was gap
+
+ ts_trunc: bool, // no more data for TOSERVER
+ tc_trunc: bool, // no more data for TOCLIENT
+
+ /// transactions list
+ pub transactions: Vec<SMBTransaction>,
+
+ /// tx counter for assigning incrementing id's to tx's
+ tx_id: u64,
+
+ pub dialect: u16,
+ pub dialect_vec: Option<Vec<u8>>, // used if dialect == 0
+ pub dialects: Option<Vec<Vec<u8>>>,
+
+ /// dcerpc interfaces, stored here to be able to match
+ /// them while inspecting DCERPC REQUEST txs
+ pub dcerpc_ifaces: Option<Vec<DCERPCIface>>,
+
+ pub ntlmssp: Option<NtlmsspData>,
+ pub krb_ticket: Option<Kerberos5Ticket>,
+}
+
+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<u8>)
+ -> (&mut SMBTransaction)
+ {
+ let mut tx = self.new_tx();
+
+ tx.hdr = hdr;
+ tx.type_data = Some(SMBTransactionTypeData::TREECONNECT(
+ SMBTransactionTreeConnect::new(name.to_vec())));
+ tx.request_done = true;
+ tx.response_done = self.tc_trunc; // no response expected if tc is truncated
+
+ SCLogDebug!("SMB: TX TREECONNECT created: ID {} NAME {}",
+ tx.id, String::from_utf8_lossy(&name));
+ self.transactions.push(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<u8>,
+ disposition: u32, del: bool, dir: bool,
+ hdr: SMBCommonHdr)
+ -> &mut SMBTransaction
+ {
+ let mut tx = self.new_tx();
+ tx.hdr = hdr;
+ tx.type_data = Some(SMBTransactionTypeData::CREATE(
+ SMBTransactionCreate::new(
+ file_name.to_vec(), disposition,
+ del, dir)));
+ tx.request_done = true;
+ tx.response_done = self.tc_trunc; // no response expected if tc is truncated
+
+ self.transactions.push(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<u8>;
+ //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<u8>;
+ // 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<SMBState> = 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
+}
--- /dev/null
+/* 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<SMBEvent> = 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<u8>> = 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<SMBEvent> = 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);
+ },
+ }
+}
--- /dev/null
+/* 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<Smb1WriteRequestRecord>,
+ 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<Smb1WriteRequestRecord>,
+ 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<Smb1NegotiateProtocolResponseRecord>,
+ 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<Smb1NegotiateProtocolRecord>,
+ 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<Smb1ResponseRecordTreeConnectAndX>,
+ 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<SmbRecordTreeConnectAndX>,
+ 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<SmbPipeProtocolRecord<'a>>,
+ 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<SmbPipeProtocolRecord>,
+ 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<SmbPipeProtocolRecord>)>,
+ 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<SmbRecordTransResponse>,
+ do_parse!(
+ wct: le_u8
+ >> bcc: le_u16
+ >> (SmbRecordTransResponse {
+ data_cnt:0,
+ bcc:bcc,
+ data:&[],
+ }))
+);
+
+named!(pub parse_smb_trans_response_regular_record<SmbRecordTransResponse>,
+ 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<SmbRecordTransResponse>,
+ 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<SmbRecordSetupAndX>,
+ 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<SmbRequestReadAndXRecord>,
+ 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<SmbResponseReadAndXRecord>,
+ 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<SmbRequestCreateAndXRecord>,
+ 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<SmbResponseCreateAndXRecord>,
+ 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<SmbRequestCloseRecord>,
+ 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<SmbVersion>,
+ 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<SmbRecord>,
+ 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,
+ })
+));
--- /dev/null
+/* 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<SMBEvent> = 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<u8>> = 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<SMBEvent> = 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
+ },
+ };
+ }
+}
--- /dev/null
+/* 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<Smb2SecBlobRecord>,
+ 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<Smb2Record>,
+ 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<u16>,
+}
+
+named!(pub parse_smb2_request_negotiate_protocol<Smb2NegotiateProtocolRequestRecord>,
+ 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<Smb2NegotiateProtocolResponseRecord>,
+ 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<Smb2SessionSetupRequestRecord>,
+ 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<Smb2TreeConnectRequestRecord>,
+ 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<Smb2TreeConnectResponseRecord>,
+ 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<Smb2CreateRequestRecord>,
+ 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<Smb2IOCtlRequestRecord>,
+ 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<Smb2IOCtlResponseRecord>,
+ 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<Smb2CloseRequestRecord>,
+ 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<Smb2WriteRequestRecord>,
+ 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<Smb2ReadRequestRecord>,
+ 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<Smb2ReadResponseRecord>,
+ 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<Smb2CreateResponseRecord>,
+ 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<Smb2WriteResponseRecord>,
+ do_parse!(
+ skip1: take!(4)
+ >> wr_cnt: le_u32
+ >> skip2: take!(6)
+ >> (Smb2WriteResponseRecord {
+ wr_cnt : wr_cnt,
+ })
+));
+
+named!(pub parse_smb2_response_record<Smb2Record>,
+ 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<Smb2RecordPostGap>,
+ 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);
+}
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 \
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 \
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 \
--- /dev/null
+/* 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 */
--- /dev/null
+/* 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__ */
#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 {
UtRegisterTest("SMBParserTest10", SMBParserTest10);
#endif
}
+#endif /* HAVE_RUST */
#include "app-layer-dcerpc-common.h"
#include "app-layer-dcerpc.h"
+#ifndef HAVE_RUST
+
typedef struct SMBHdr_ {
uint8_t protocol[4];
uint8_t command;
#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__ */
#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;
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);
{
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;
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
}
}
-#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.
*
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");
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).
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) {
#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;
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);
{
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;
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);
}
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).
return -1;
}
- if (DetectSignatureSetAppProto(s, ALPROTO_DCERPC) != 0)
- return -1;
+ //if (DetectSignatureSetAppProto(s, ALPROTO_DCERPC) != 0)
+ // return -1;
dod = DetectDceOpnumArgParse(arg);
if (dod == NULL) {
#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"
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);
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,
{
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;
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;
#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"
DetectDceIfaceRegister();
DetectDceOpnumRegister();
DetectDceStubDataRegister();
+ DetectSmbNamedPipeRegister();
+ DetectSmbShareRegister();
DetectTlsRegister();
DetectTlsValidityRegister();
DetectTlsVersionRegister();
DETECT_DCE_IFACE,
DETECT_DCE_OPNUM,
DETECT_DCE_STUB_DATA,
+ DETECT_SMB_NAMED_PIPE,
+ DETECT_SMB_SHARE,
DETECT_ASN1,
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,
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");
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;
}
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");
--- /dev/null
+/* 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 <victor@inliniac.net>
+ *
+ */
+
+#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
--- /dev/null
+/* 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 <victor@inliniac.net>
+ */
+
+#ifndef __DETECT_SMB_NAMED_PIPE_H__
+#define __DETECT_SMB_NAMED_PIPE_H__
+
+void DetectSmbNamedPipeRegister(void);
+void DetectSmbShareRegister(void);
+
+#endif /* __DETECT_SMB_NAMED_PIPE_H__ */
#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"
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) {
--- /dev/null
+/* 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 <victor@inliniac.net>
+ *
+ * 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 */
--- /dev/null
+/* 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 <victor@inliniac.net>
+ */
+
+#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__ */
#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"
JsonNFSLogRegister();
/* TFTP JSON logger. */
JsonTFTPLogRegister();
+ /* SMB JSON logger. */
+ JsonSMBLogRegister();
+
/* Template JSON logger. */
JsonTemplateLogRegister();
}
/** Opaque Rust types. */
typedef struct NFState_ NFSState;
typedef struct NFSTransaction_ NFSTransaction;
+typedef struct SMBState_ SMBState;
+typedef struct SMBTransaction_ SMBTransaction;
#endif /* !__RUST_H__ */
LOGGER_JSON_DNP3_TS,
LOGGER_JSON_DNP3_TC,
LOGGER_JSON_SSH,
+ LOGGER_JSON_SMB,
LOGGER_JSON_TEMPLATE,
LOGGER_ALERT_DEBUG,
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);
}
SC_ERR_PF_RING_VLAN,
SC_ERR_CREATE_DIRECTORY,
SC_WARN_FLOWBIT,
+ SC_ERR_SMB_CONFIG,
SC_ERR_MAX,
} SCError;
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);
# Include top level metadata. Default yes.
#metadata: no
+ pcap-file: false
+
types:
- alert:
# payload: yes # enable dumping payload in Base64
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: