From 1b638eec0cd77cd573bcc0aedfb63b087a05e4b8 Mon Sep 17 00:00:00 2001 From: Gregor Maier Date: Wed, 1 Jun 2011 14:30:02 -0700 Subject: [PATCH] Hacking the SMB analyzer. Checkpoint. --- policy/bro.init | 2 + policy/smb.bro | 251 ++++++++++++++++++++++++++++++++++++++++++- src/NetbiosSSN.cc | 6 +- src/SMB.cc | 156 ++++++++++++--------------- src/SMB.h | 28 +++-- src/event.bif | 6 +- src/smb-protocol.pac | 9 +- 7 files changed, 353 insertions(+), 105 deletions(-) diff --git a/policy/bro.init b/policy/bro.init index 868cbb34cf..f2ddaa6cb1 100644 --- a/policy/bro.init +++ b/policy/bro.init @@ -931,6 +931,8 @@ type smb_hdr : record { pid: count; uid: count; mid: count; + first_time: time; + last_time: time; }; type smb_trans : record { diff --git a/policy/smb.bro b/policy/smb.bro index 4d31393a13..8854051420 100644 --- a/policy/smb.bro +++ b/policy/smb.bro @@ -1,8 +1,255 @@ # $Id:$ +# +@load conn-id + +# TODO: capture filter redef capture_filters += { ["smb"] = "port 445" }; -global smb_ports = { 445/tcp } &redef; +global smb_ports = { 139/tcp, 445/tcp } &redef; redef dpd_config += { [ANALYZER_SMB] = [$ports = smb_ports] }; -# No default implementation for events. +const smb_log = open_log_file("smb") &redef; + + +type smb_cmd_info: record { + pid: count; + mid: count; + cmd: count; + cmdstr: string; + + # hack: for operations involving file ids: note the file id. + # this is 16 bit, so we use 0x10000 to indicate that the fid is not + # valid + fid: count; + # for read/writes: number of bytes read/written + file_payload: count; + + req_first_time: time; + req_last_time: time; + req_body_len: count; + + rep_first_time: time; + rep_last_time: time; + rep_body_len: count; + + done: bool; +}; + +type smb_pending_cmds: table[count, count] of smb_cmd_info; +global smb_sessions: table[conn_id] of smb_pending_cmds; + +# it seems Bro has issues with anonymous records of the form [cid,count] +# so we just use a table of table +global fid_map: table[conn_id] of table[count] of string; +global next_fid = 0; + +# the commands in this set are handled by a more specific event handler +# that add additional information. I.e., the smb_message event still does +# request/reply matching, but the more specific event takes care of printing. +# It's all a hack.... +global more_specific_cmds: set[count]; +event bro_init() + { + add more_specific_cmds[0x2e]; # read_andx + add more_specific_cmds[0x2f]; # write_andx + } + +function smb_new_cmd_info(hdr: smb_hdr, body_len: count): smb_cmd_info + { + local info: smb_cmd_info; + + info$cmd = hdr$command; + info$pid = hdr$pid; + info$mid = hdr$mid; + info$cmdstr = ""; + + info$fid = 0x10000; + info$file_payload = 0; + + info$req_first_time = hdr$first_time; + info$req_last_time = hdr$last_time; + info$req_body_len = body_len; + + info$rep_first_time = double_to_time(0.0); + info$rep_last_time = double_to_time(0.0); + info$rep_body_len = 0; + + + info$done = F; + + return info; + } + +function get_fid(cid: conn_id, fid: count): string + { + if (cid !in fid_map) + fid_map[cid] = table(); + if ( fid !in fid_map[cid]) + { + if (fid >= 0x10000) + return "FIDxx"; + fid_map[cid][fid] = fmt("FID%d", next_fid); + ++next_fid; + } + return fid_map[cid][fid]; + } + + +function fmt_smb_hdr(hdr: smb_hdr): string + { + return fmt("%.6f %.6f %d %d %d %d %d", hdr$first_time, hdr$last_time, hdr$tid, + hdr$pid, hdr$mid, hdr$uid, hdr$status); + + } + +function fmt_msg_prefix(cid: conn_id, is_orig: bool, hdr: smb_hdr): string + { + return fmt("%s %d (%d) %s", id_string(cid), is_orig, hdr$command, + fmt_smb_hdr(hdr)); + } + +function smb_log_cmd(c: connection, info: smb_cmd_info) + { + local msg = ""; + msg = fmt("COMMAND %s (%d) %d:%d %.6f %.6f %d %.6f %.6f %d %s", + info$cmdstr, info$cmd, info$pid, info$mid, + info$req_first_time, info$req_last_time, info$req_body_len, + info$rep_first_time, info$rep_last_time, info$rep_body_len, + get_fid(c$id, info$fid)); + print smb_log, msg; + } + +function smb_log_cmd2(c: connection, hdr: smb_hdr) + { + if (c$id !in smb_sessions) + return; + local cur_session = smb_sessions[c$id]; + if ([hdr$pid,hdr$mid] !in cur_session) + return; + local info = cur_session[hdr$pid, hdr$mid]; + + smb_log_cmd(c, info); + } + +function mismatch_fmt_hdr(hdr: smb_hdr, cmd: string): string + { + return fmt("%s %d:%d", cmd, hdr$pid, hdr$mid); + } + +function mismatch_fmt_info(info: smb_cmd_info): string + { + return fmt("%s %d:%d", info$cmdstr, info$pid, info$mid); + } + +function smb_set_fid(cid: conn_id, hdr: smb_hdr, fid: count) + { + # smb_messge takes care of error / mismatch handling, so we can + # just punt here + if (cid !in smb_sessions) + return; + local cur_session = smb_sessions[cid]; + if ([hdr$pid,hdr$mid] !in cur_session) + return; + local info = cur_session[hdr$pid, hdr$mid]; + + info$fid = fid; + } + +function smb_set_file_payload(cid: conn_id, hdr: smb_hdr, payload_len: count) + { + # smb_messge takes care of error / mismatch handling, so we can + # just punt here + if (cid !in smb_sessions) + return; + local cur_session = smb_sessions[cid]; + if ([hdr$pid,hdr$mid] !in cur_session) + return; + local info = cur_session[hdr$pid, hdr$mid]; + + info$file_payload = payload_len; + } + +# note, the smb_message event is raised before and more specific ones, so +# we use it to match requests to replies. +# A hack, but it works +event smb_message(c: connection, hdr: smb_hdr, is_orig: bool, cmd: string, body_length: count, body: string) + { + ###print smb_log, fmt("%s %s %d", fmt_msg_prefix(c$id, is_orig, hdr), cmd, body_length); + if (c$id !in smb_sessions) + smb_sessions[c$id] = table(); + local cur_session = smb_sessions[c$id]; + + # cleanup and log + if ([hdr$pid,hdr$mid] in cur_session) + if (cur_session[hdr$pid,hdr$mid]$done) + delete cur_session[hdr$pid,hdr$mid]; + + if (is_orig) + { + if ([hdr$pid,hdr$mid] in cur_session) + print smb_log, fmt("Mismatch: got a request but already have request queued: %s %s", + mismatch_fmt_info(cur_session[hdr$pid,hdr$mid]), mismatch_fmt_hdr(hdr,cmd)); + cur_session[hdr$pid, hdr$mid] = smb_new_cmd_info(hdr, body_length); + cur_session[hdr$pid, hdr$mid]$cmdstr = cmd; + } + else + { + if ([hdr$pid,hdr$mid] !in cur_session) + print smb_log, fmt("Mismatch: got a reply but no request queued: %s", mismatch_fmt_hdr(hdr,cmd)); + else + { + local info = cur_session[hdr$pid, hdr$mid]; + if (info$cmd != hdr$command) + { + print smb_log, fmt("Mismatch: request and reply command don't match: %s %s", + mismatch_fmt_info(cur_session[hdr$pid,hdr$mid]), mismatch_fmt_hdr(hdr,cmd)); + delete cur_session[hdr$pid,hdr$mid]; + } + else if (info$mid != hdr$mid || info$pid != hdr$pid) + { + # This really should not happen + print smb_log, fmt("Mismatch: request and reply IDs don't match: %s %s", + mismatch_fmt_info(cur_session[hdr$pid,hdr$mid]), mismatch_fmt_hdr(hdr,cmd)); + delete cur_session[hdr$pid,hdr$mid]; + } + else + { + info$rep_first_time = hdr$first_time; + info$rep_last_time = hdr$last_time; + info$rep_body_len = body_length; + info$done = T; + if (hdr$command !in more_specific_cmds) + smb_log_cmd(c, info); + } + } + } + } + +event smb_com_read_andx(c: connection, hdr: smb_hdr, fid: count) + { + smb_set_fid(c$id, hdr, fid); + } + +event smb_com_read_andx_response(c: connection, hdr: smb_hdr, len: count) + { + smb_set_file_payload(c$id, hdr, len); + smb_log_cmd2(c, hdr); + } + +event smb_com_write_andx(c: connection, hdr: smb_hdr, fid: count, len: count) + { + smb_set_fid(c$id, hdr, fid); + smb_set_file_payload(c$id, hdr, len); + } + +event smb_com_write_andx_response(c: connection, hdr: smb_hdr) + { + smb_log_cmd2(c, hdr); + } + + +event connection_state_remove(c: connection) + { + delete smb_sessions[c$id]; + } diff --git a/src/NetbiosSSN.cc b/src/NetbiosSSN.cc index 0bb135f59d..5431d25288 100644 --- a/src/NetbiosSSN.cc +++ b/src/NetbiosSSN.cc @@ -106,7 +106,7 @@ int NetbiosSSN_Interpreter::ParseDatagram(const u_char* data, int len, { if ( smb_session ) { - smb_session->Deliver(is_query, len, data); + smb_session->Deliver(is_query, len, data, 0, 0); return 0; } @@ -129,7 +129,7 @@ int NetbiosSSN_Interpreter::ParseBroadcast(const u_char* data, int len, if ( smb_session ) { - smb_session->Deliver(is_query, len, data); + smb_session->Deliver(is_query, len, data, 0, 0); return 0; } @@ -188,7 +188,7 @@ int NetbiosSSN_Interpreter::ParseSessionMsg(const u_char* data, int len, if ( smb_session ) { - smb_session->Deliver(is_query, len, data); + smb_session->Deliver(is_query, len, data, 0, 0); return 0; } else diff --git a/src/SMB.cc b/src/SMB.cc index e33c7ac73a..30ac77f0a1 100644 --- a/src/SMB.cc +++ b/src/SMB.cc @@ -11,6 +11,8 @@ namespace { const bool DEBUG_smb_ipc = true; } +#define SMB_MAX_LEN (1<<17) + #define BYTEORDER_SWAP16(n) ((256 * ((n) & 0xff)) + ((n) >> 8)) enum SMB_Command { @@ -135,10 +137,13 @@ void SMB_Session::set_andx(int is_orig, binpac::SMB::SMB_andx* andx) andx_[ind] = andx; } -void SMB_Session::Deliver(int is_orig, int len, const u_char* data) +void SMB_Session::Deliver(int is_orig, int len, const u_char* data, + double arg_first_time, double arg_last_time) { if ( len == 0 ) return; + first_time = arg_first_time; + last_time = arg_last_time; try { @@ -152,8 +157,14 @@ void SMB_Session::Deliver(int is_orig, int len, const u_char* data) int next_command = hdr.command(); + fprintf(stderr, "SMB command: %02x %s len %-7d dur %.6lf\n", next_command, + SMB_command_name[next_command], len, + last_time-first_time); + int ncmds = 0; + while ( data < data_end ) { + ncmds++; SMB_Body body(data, data_end); set_andx(is_orig, 0); ParseMessage(is_orig, next_command, hdr, body); @@ -172,6 +183,7 @@ void SMB_Session::Deliver(int is_orig, int len, const u_char* data) data = data_start + next; } + fprintf(stderr, "ncmds %d\n", ncmds); } catch ( const binpac::Exception& e ) { @@ -566,7 +578,8 @@ int SMB_Session::ParseReadAndx(binpac::SMB::SMB_header const& hdr, val_list* vl = new val_list; vl->append(analyzer->BuildConnVal()); vl->append(BuildHeaderVal(hdr)); - vl->append(new StringVal("")); + vl->append(new Val(req.fid(), TYPE_COUNT)); + //vl->append(new StringVal("")); analyzer->ConnectionEvent(smb_com_read_andx, vl); } @@ -584,12 +597,13 @@ int SMB_Session::ParseReadAndxResponse(binpac::SMB::SMB_header const& hdr, int data_count = resp.data_length(); const u_char* data = resp.data().begin(); - if ( smb_com_read_andx ) + if ( smb_com_read_andx_response ) { val_list* vl = new val_list; vl->append(analyzer->BuildConnVal()); vl->append(BuildHeaderVal(hdr)); - vl->append(new StringVal(data_count, (const char*) data)); + vl->append(new Val((resp.data_len_high()<<16)+(resp.data_len()), TYPE_COUNT)); + //vl->append(new StringVal(data_count, (const char*) data)); analyzer->ConnectionEvent(smb_com_read_andx, vl); } @@ -614,7 +628,9 @@ int SMB_Session::ParseWriteAndx(binpac::SMB::SMB_header const& hdr, val_list* vl = new val_list; vl->append(analyzer->BuildConnVal()); vl->append(BuildHeaderVal(hdr)); - vl->append(new StringVal(data_count, (const char*) data)); + vl->append(new Val(req.fid(), TYPE_COUNT)); + vl->append(new Val((req.data_len_high()<<16)+(req.data_len()), TYPE_COUNT)); + //vl->append(new StringVal(data_count, (const char*) data)); analyzer->ConnectionEvent(smb_com_write_andx, vl); } @@ -631,14 +647,14 @@ int SMB_Session::ParseWriteAndxResponse(binpac::SMB::SMB_header const& hdr, resp.Parse(body.data(), body.data() + body.length()); set_andx(0, resp.andx()); - if ( smb_com_write_andx ) + if ( smb_com_write_andx_response ) { val_list* vl = new val_list; vl->append(analyzer->BuildConnVal()); vl->append(BuildHeaderVal(hdr)); - vl->append(new StringVal("")); + //vl->append(new StringVal("")); - analyzer->ConnectionEvent(smb_com_write_andx, vl); + analyzer->ConnectionEvent(smb_com_write_andx_response, vl); } return 0; @@ -1016,6 +1032,8 @@ Val* SMB_Session::BuildHeaderVal(binpac::SMB::SMB_header const& hdr) r->Assign(5, new Val(hdr.pid(), TYPE_COUNT)); r->Assign(6, new Val(hdr.uid(), TYPE_COUNT)); r->Assign(7, new Val(hdr.mid(), TYPE_COUNT)); + r->Assign(8, new Val(first_time, TYPE_TIME)); + r->Assign(9, new Val(last_time, TYPE_TIME)); return r; } @@ -1108,23 +1126,16 @@ Contents_SMB::Contents_SMB(Connection* conn, bool orig, SMB_Session* s) : TCP_SupportAnalyzer(AnalyzerTag::Contents_SMB, conn, orig) { smb_session = s; - msg_buf = 0; + state = WAIT_FOR_HDR; + first_time = last_time = 0.0; + hdr_buf.Init(4,4); msg_len = 0; - buf_len = 0; - buf_n = 0; + msg_type = 0; } -void Contents_SMB::InitMsgBuf() - { - delete [] msg_buf; - msg_buf = new u_char[msg_len]; - buf_len = msg_len; - buf_n = 0; - } Contents_SMB::~Contents_SMB() { - delete [] msg_buf; } void Contents_SMB::DeliverSMB(int len, const u_char* data) @@ -1132,93 +1143,68 @@ void Contents_SMB::DeliverSMB(int len, const u_char* data) // Check the 4-byte header. if ( strncmp((const char*) data, "\xffSMB", 4) ) { - Conn()->Weird(fmt("SMB-over-TCP header error: %02x%02x%02x%02x, \\x%02x%c%c%c", - dshdr[0], dshdr[1], dshdr[2], dshdr[3], + Conn()->Weird(fmt("SMB-over-TCP header error: %02x %05x, >>\\x%02x%c%c%c<<", + //dshdr[0], dshdr[1], dshdr[2], dshdr[3], + msg_type, msg_len, data[0], data[1], data[2], data[3])); SetSkip(1); } else - smb_session->Deliver(IsOrig(), len, data); + smb_session->Deliver(IsOrig(), len, data, first_time, last_time); - buf_n = 0; - msg_len = 0; } void Contents_SMB::DeliverStream(int len, const u_char* data, bool orig) { TCP_SupportAnalyzer::DeliverStream(len, data, orig); + if (Skipping()) + return; + last_time = network_time; while ( len > 0 ) { - if ( ! msg_len ) + switch (state) { + case WAIT_FOR_HDR: { - // Get the SMB-over-TCP header (4 bytes). - while ( buf_n < 4 && len > 0 ) + if (first_time < 1e-3) + first_time = network_time; + bool got_hdr = hdr_buf.ConsumeChunk(data, len); + if (got_hdr) { - dshdr[buf_n] = *data; - ++buf_n; ++data; --len; - } - - if ( buf_n < 4 ) - return; - - buf_n = 0; - for ( int i = 1; i < 4; ++i ) - msg_len = ( msg_len << 8 ) + dshdr[i]; - - if ( dshdr[0] != 0 ) - { - // Netbios header indicates this is NOT - // a session message ... - // 0x81 = session request - // 0x82 = positive response - // 0x83 = neg response - // 0x84 = retarget(?) - // 0x85 = keepalive - // Maybe we should just generate a Netbios - // event and die? - Conn()->Weird("SMB checked Netbios type and found != 0"); - SetSkip(1); - return; - } - - else if ( msg_len <= 4 ) - { - Conn()->Weird("SMB message length error"); - SetSkip(1); - return; + // We have the 4 bytes header now + const u_char *dummy = hdr_buf.GetBuf(); + if (dummy[1] > 1) + Conn()->Weird(fmt("NetBIOS session flags > 1: %d", dummy[1])); + msg_len = 0; + msg_type = dummy[0]; + for ( int i =1; i < 4; i++) + msg_len = ( msg_len << 8) + dummy[i]; + msg_buf.Init(SMB_MAX_LEN, msg_len); + state = WAIT_FOR_DATA; } } - - if ( buf_n == 0 && msg_len <= len ) + break; + case WAIT_FOR_DATA: { - // The fast lane: - // Keep msg_len -- it will be changed in DeliverSMB - int mlen = msg_len; - DeliverSMB(msg_len, data); - len -= mlen; - data += mlen; - } - - else - { - if ( buf_len < msg_len ) - InitMsgBuf(); - - while ( buf_n < msg_len && len > 0 ) + bool got_all_data = msg_buf.ConsumeChunk(data, len); + if (got_all_data) { - msg_buf[buf_n] = *data; - ++buf_n; - ++data; - --len; + const u_char *dummy_p = msg_buf.GetBuf(); + int dummy_len = (int) msg_buf.GetFill(); + if (msg_type == 0x00 && dummy_len >= 4) + DeliverSMB(dummy_len, dummy_p); + else if (msg_type == 0x00) + Conn()->Weird(fmt("SMB too short: len=%d", msg_len)); + else + Conn()->Weird(fmt("SMB other msg type: %x", msg_type)); + state = WAIT_FOR_HDR; + first_time = 0.0; + hdr_buf.Init(4,4); } - - if ( buf_n < msg_len ) - return; - - DeliverSMB(msg_len, msg_buf); } - } + break; + } // end switch + } // end while } SMB_Analyzer::SMB_Analyzer(Connection* conn) diff --git a/src/SMB.h b/src/SMB.h index 408fa91068..b77622b778 100644 --- a/src/SMB.h +++ b/src/SMB.h @@ -9,6 +9,7 @@ // Reference: http://www.snia.org/tech_activities/CIFS/CIFS-TR-1p00_FINAL.pdf #include "TCP.h" +#include "RPC.h" #include "DCE_RPC.h" #include "smb_pac.h" @@ -44,7 +45,8 @@ public: SMB_Session(Analyzer* analyzer); ~SMB_Session(); - void Deliver(int is_orig, int len, const u_char* msg); + void Deliver(int is_orig, int len, const u_char* msg, + double first_time, double last_time); static bool any_smb_event() { @@ -170,6 +172,8 @@ protected: bool smb_pipe_prot; StringVal* transaction_name; binpac::SMB::SMB_andx* andx_[2]; + double first_time; + double last_time; }; class Contents_SMB : public TCP_SupportAnalyzer { @@ -180,16 +184,21 @@ public: virtual void DeliverStream(int len, const u_char* data, bool orig); protected: - void InitMsgBuf(); - + typedef enum { + WAIT_FOR_HDR, + WAIT_FOR_DATA + } state_t; void DeliverSMB(int len, const u_char* data); SMB_Session* smb_session; - u_char dshdr[4]; - u_char* msg_buf; + + RPC_Reasm_Buffer hdr_buf; // Reassemlbes the NetBIOS length and glue + RPC_Reasm_Buffer msg_buf; // Reassembles the SMB message. int msg_len; - int buf_n; // number of bytes in msg_buf - int buf_len; // size off msg_buf + int msg_type; + double first_time; // timestamp of first packet of current message + double last_time; // timestamp of last pakcet of current message + state_t state; }; class SMB_Analyzer : public TCP_ApplicationAnalyzer { @@ -202,8 +211,9 @@ public: static bool Available() { - return SMB_Session::any_smb_event() || - DCE_RPC_Session::any_dce_rpc_event(); + return true; + //return SMB_Session::any_smb_event() || + // DCE_RPC_Session::any_dce_rpc_event(); } protected: diff --git a/src/event.bif b/src/event.bif index 90ce537230..6bd1f3df6d 100644 --- a/src/event.bif +++ b/src/event.bif @@ -221,8 +221,10 @@ event smb_com_transaction2%(c: connection, hdr: smb_hdr, trans: smb_trans, data: event smb_com_trans_mailslot%(c: connection, hdr: smb_hdr, trans: smb_trans, data: smb_trans_data, is_orig: bool%); event smb_com_trans_rap%(c: connection, hdr: smb_hdr, trans: smb_trans, data: smb_trans_data, is_orig: bool%); event smb_com_trans_pipe%(c: connection, hdr: smb_hdr, trans: smb_trans, data: smb_trans_data, is_orig: bool%); -event smb_com_read_andx%(c: connection, hdr: smb_hdr, data: string%); -event smb_com_write_andx%(c: connection, hdr: smb_hdr, data: string%); +event smb_com_read_andx%(c: connection, hdr: smb_hdr, fid: count%); +event smb_com_read_andx_response%(c: connection, hdr: smb_hdr, len: count%); +event smb_com_write_andx%(c: connection, hdr: smb_hdr, fid: count, len: count%); +event smb_com_write_andx_response%(c: connection, hdr: smb_hdr%); event smb_get_dfs_referral%(c: connection, hdr: smb_hdr, max_referral_level: count, file_name: string%); event smb_com_negotiate%(c: connection, hdr: smb_hdr%); event smb_com_negotiate_response%(c: connection, hdr: smb_hdr, dialect_index: count%); diff --git a/src/smb-protocol.pac b/src/smb-protocol.pac index b00613f16c..bdbbfd5085 100644 --- a/src/smb-protocol.pac +++ b/src/smb-protocol.pac @@ -337,12 +337,12 @@ type SMB_read_andx_response = record { reserved2 : uint16[4]; byte_count : uint16; pad : padding[padding_length]; - data : bytestring &length = data_length; + #data : bytestring &length = data_length; # Chris: the length here is causing problems - could we be having # issues with the packet format or is the data_length just not # right. The problem is that the padding isn't always filled right, # espeically when its not the first command in the packet. - #data : bytestring &restofdata; + data : bytestring &restofdata; } &let { data_length = data_len_high * 0x10000 + data_len; padding_length = byte_count - data_length; @@ -353,7 +353,7 @@ type SMB_write_andx = record { andx : SMB_andx; fid : uint16; offset : uint32; - reserved : uint32; + timeout : uint32; write_mode : uint16; remaining : uint16; data_len_high : uint16; @@ -362,7 +362,8 @@ type SMB_write_andx = record { rest_words : uint8[word_count * 2 - offsetof(rest_words) + 1]; byte_count : uint16; pad : padding to data_offset - smb_header_length; - data : bytestring &length = data_length; + #data : bytestring &length = data_length; + data : bytestring &restofdata; } &let { data_length = data_len_high * 0x10000 + data_len; } &byteorder = littleendian;