// $Id: SMB.cc 6219 2008-10-01 05:39:07Z vern $ // // See the file "COPYING" in the main distribution directory for copyright. #include "NetVar.h" #include "SMB.h" #include "smb_pac.h" #include "Val.h" 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 { #define SMB_COMMAND(name, value) name = value, #include "SMB_COM.def" #undef SMB_COMMAND }; enum SMB_Transaction_Command { HOST_ANNOUNCEMENT = 1, ANNOUCEMENT_REQUEST = 2, REQUEST_ELECTION = 8, GET_BACKUP_LIST_REQUEST = 9, GET_BACKUP_LIST_RESPONSE = 10, BECOME_BACKUP_REQUEST = 11, DOMAIN_ANNOUNCEMENT = 12, MASTER_ANNOUNCEMENT = 13, RESET_BROWSER_STATE = 14, LOCAL_MASTER_ANNOUNCEMENT = 15, }; const char* SMB_command_name[256]; StringVal* SMB_command_str[256]; const char* SMB_trans_command_name[256]; StringVal* SMB_trans_command_str[256]; static void init_SMB_command_name() { static int initialized = 0; if ( initialized ) return; initialized = 1; for ( int i = 0; i < 256; ++i ) { SMB_command_name[i] = ""; SMB_command_str[i] = 0; } #define SMB_COMMAND(name, value) SMB_command_name[value] = #name; #include "SMB_COM.def" #undef SMB_COMMAND #define SMB_COMMAND(name, value) SMB_trans_command_name[value] = #name; SMB_COMMAND(HOST_ANNOUNCEMENT, 1) SMB_COMMAND(ANNOUCEMENT_REQUEST, 2) SMB_COMMAND(REQUEST_ELECTION, 8) SMB_COMMAND(GET_BACKUP_LIST_REQUEST, 9) SMB_COMMAND(GET_BACKUP_LIST_RESPONSE, 10) SMB_COMMAND(BECOME_BACKUP_REQUEST, 11) SMB_COMMAND(DOMAIN_ANNOUNCEMENT, 12) SMB_COMMAND(MASTER_ANNOUNCEMENT, 13) SMB_COMMAND(RESET_BROWSER_STATE, 14) SMB_COMMAND(LOCAL_MASTER_ANNOUNCEMENT, 15) } StringVal* get_SMB_command_str(int cmd) { if ( ! SMB_command_str[cmd] ) SMB_command_str[cmd] = new StringVal(SMB_command_name[cmd]); return SMB_command_str[cmd]; } // ### TODO: the list of IPC pipes needs a lot of expansion. static int lookup_IPC_name(BroString* name) { static const char* IPC_pipe_names[] = { "\\locator", "\\epmapper", "\\samr", "\\lsarpc", 0 }; for ( int i = 0; IPC_pipe_names[i]; ++i ) { if ( size_t(name->Len()) == strlen(IPC_pipe_names[i]) && strncmp((const char*) name->Bytes(), IPC_pipe_names[i], name->Len()) == 0 ) return i + 1; } return IPC_NONE; } SMB_Session::SMB_Session(Analyzer* arg_analyzer) { analyzer = arg_analyzer; dce_rpc_session = 0; init_SMB_command_name(); // Strangely, one does not have to connect to IPC$ before // making DCE/RPC calls. So we assume that it's always IPC // unless confirmed otherwise. is_IPC = true; IPC_pipe = IPC_NONE; transaction_name = 0; transaction_subcmd = 0; andx_[0] = andx_[1] = 0; set_andx(0, 0); set_andx(1, 0); } SMB_Session::~SMB_Session() { binpac::Unref(andx_[0]); binpac::Unref(andx_[1]); Unref(transaction_name); delete dce_rpc_session; } void SMB_Session::set_andx(int is_orig, binpac::SMB::SMB_andx* andx) { int ind = is_orig ? 1 : 0; if ( andx ) andx->Ref(); binpac::Unref(andx_[ind]); andx_[ind] = andx; } 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 { const u_char* data_start = data; const u_char* data_end = data + len; binpac::SMB::SMB_header hdr; int hdr_len = hdr.Parse(data, data_end); data += hdr_len; int next_command = hdr.command(); fprintf(stderr, "SMB command: 0x%02x %s (%d) len %-7d dur %.6lf\n", next_command, SMB_command_name[next_command], is_orig, 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); int next = AndxOffset(is_orig, next_command); if ( next <= 0 ) break; //Weird(fmt("ANDX! at %d", next)); const u_char* tmp = data_start + next; if ( data_start + next < data + body.length() ) { Weird(fmt("ANDX buffer overlapping: next = %d, buffer_end = %" PRIuPTR, next, data + body.length() - data_start)); break; } data = data_start + next; } fprintf(stderr, "ncmds %d\n", ncmds); } catch ( const binpac::Exception& e ) { analyzer->Weird(e.msg().c_str()); } } void SMB_Session::ParseMessage(int is_orig, int cmd, binpac::SMB::SMB_header const& hdr, SMB_Body const& body) { if ( smb_message ) { val_list* vl = new val_list; StringVal* cmd_str = get_SMB_command_str(cmd); Ref(cmd_str); vl->append(analyzer->BuildConnVal()); vl->append(BuildHeaderVal(hdr)); vl->append(new Val(is_orig, TYPE_BOOL)); vl->append(cmd_str); vl->append(new Val(body.length(), TYPE_COUNT)); vl->append(new StringVal(body.length(), (const char*) body.data())); analyzer->ConnectionEvent(smb_message, vl); } if ( is_orig ) req_cmd = cmd; // What if there's an error? // if ( hdr.status->status() || hdr.status->dos_error() ) // The command code in the header might be right, but // the response is probably mangled :-. int ci = hdr.status()->val_case_index(); unsigned int error = 0; switch ( ci ) { case 0: error = hdr.status()->dos_error()->error_class() << 24 || hdr.status()->dos_error()->error(); break; case 1: error = hdr.status()->status(); break; } if (error) { val_list* vl = new val_list; StringVal* cmd_str = get_SMB_command_str(cmd); Ref(cmd_str); vl->append(analyzer->BuildConnVal()); vl->append(BuildHeaderVal(hdr)); vl->append(new Val(cmd, TYPE_COUNT)); vl->append(cmd_str); vl->append(new Val(ci, TYPE_COUNT)); vl->append(new Val(error, TYPE_COUNT)); analyzer->ConnectionEvent(smb_error, vl); // Is this the right behavior? return; } int ret = 0; switch ( cmd ) { case SMB_COM_TREE_CONNECT_ANDX: if ( is_orig ) ret = ParseTreeConnectAndx(hdr, body); else ret = ParseAndx(is_orig, hdr, body); break; case SMB_COM_NT_CREATE_ANDX: if ( is_orig ) ret = ParseNtCreateAndx(hdr, body); else ret = ParseAndx(is_orig, hdr, body); break; case SMB_COM_TRANSACTION: case SMB_COM_TRANSACTION2: case SMB_COM_TRANSACTION_SECONDARY: case SMB_COM_TRANSACTION2_SECONDARY: ret = ParseTransaction(is_orig, cmd, hdr, body); break; case SMB_COM_READ_ANDX: if ( is_orig ) ret = ParseReadAndx(hdr, body); else ret = ParseReadAndxResponse(hdr, body); break; case SMB_COM_WRITE_ANDX: if ( is_orig ) ret = ParseWriteAndx(hdr, body); else ret = ParseWriteAndxResponse(hdr, body); break; case SMB_COM_NEGOTIATE: if ( is_orig ) ret = ParseNegotiate(hdr, body); else ret = ParseNegotiateResponse(hdr, body); break; case SMB_COM_CLOSE: ret = ParseClose(is_orig, hdr, body); break; case SMB_COM_TREE_DISCONNECT: ret = ParseTreeDisconnect(is_orig, hdr, body); break; case SMB_COM_LOGOFF_ANDX: if ( is_orig ) ret = ParseLogoffAndx(is_orig, hdr, body); else ret = ParseAndx(is_orig, hdr, body); break; case SMB_COM_SESSION_SETUP_ANDX: if ( is_orig ) ret = ParseSetupAndx(is_orig, hdr, body); else ret = ParseAndx(is_orig, hdr, body); break; default: Weird(fmt("unknown_SMB_command(0x%x)", cmd)); break; } if ( ret == -1 ) Weird("SMB_parsing_error"); } int SMB_Session::ParseNegotiate(binpac::SMB::SMB_header const& hdr, SMB_Body const& body) { binpac::SMB::SMB_negotiate msg; msg.Parse(body.data(), body.data() + body.length()); if ( smb_com_negotiate ) { TableVal* t = new TableVal(smb_negotiate); for ( int i = 0; i < int(msg.dialects()->size()); ++i ) { binpac::SMB::SMB_dialect* d = (*msg.dialects())[i]; BroString* tmp = ExtractString(d->dialectname()); t->Assign(new Val(i, TYPE_COUNT), new StringVal(tmp)); } val_list* vl = new val_list; vl->append(analyzer->BuildConnVal()); vl->append(BuildHeaderVal(hdr)); vl->append(t); analyzer->ConnectionEvent(smb_com_negotiate, vl); } return 0; } int SMB_Session::ParseNegotiateResponse(binpac::SMB::SMB_header const& hdr, SMB_Body const& body) { binpac::SMB::SMB_negotiate_response msg; msg.Parse(body.data(), body.data() + body.length()); if ( smb_com_negotiate_response ) { val_list* vl = new val_list; vl->append(analyzer->BuildConnVal()); vl->append(BuildHeaderVal(hdr)); vl->append(new Val(msg.dialect_index(), TYPE_COUNT)); analyzer->ConnectionEvent(smb_com_negotiate_response, vl); } return 0; } int SMB_Session::ParseSetupAndx(int is_orig, binpac::SMB::SMB_header const& hdr, SMB_Body const& body) { // The binpac type depends on the negotiated server settings - // possibly we can just pick the "right" format here, and use that? if ( hdr.flags2() && 0x0800 ) { binpac::SMB::SMB_setup_andx_ext msg(hdr.unicode()); msg.Parse(body.data(), body.data() + body.length()); set_andx(1, msg.andx()); } else { binpac::SMB::SMB_setup_andx_basic msg(hdr.unicode()); msg.Parse(body.data(), body.data() + body.length()); set_andx(1, msg.andx()); } if ( smb_com_setup_andx ) { val_list* vl = new val_list; vl->append(analyzer->BuildConnVal()); vl->append(BuildHeaderVal(hdr)); analyzer->ConnectionEvent(smb_com_setup_andx, vl); } return 0; } int SMB_Session::ParseClose(int is_orig, binpac::SMB::SMB_header const& hdr, SMB_Body const& body) { if ( smb_com_close ) { val_list* vl = new val_list; vl->append(analyzer->BuildConnVal()); vl->append(BuildHeaderVal(hdr)); analyzer->ConnectionEvent(smb_com_close, vl); } return 0; } int SMB_Session::ParseLogoffAndx(int is_orig, binpac::SMB::SMB_header const& hdr, SMB_Body const& body) { binpac::SMB::SMB_generic_andx msg; msg.Parse(body.data(), body.data() + body.length()); if ( msg.word_count() > 0 ) set_andx(is_orig, msg.andx()); if ( smb_com_logoff_andx ) { val_list* vl = new val_list; vl->append(analyzer->BuildConnVal()); vl->append(BuildHeaderVal(hdr)); analyzer->ConnectionEvent(smb_com_logoff_andx, vl); } return 0; } int SMB_Session::ParseAndx(int is_orig, binpac::SMB::SMB_header const& hdr, SMB_Body const& body) { // This is a generic ANDX event generator. It passes the header // and the ANDX data out to the policy. try { binpac::SMB::SMB_generic_andx msg; msg.Parse(body.data(), body.data() + body.length()); if ( msg.word_count() > 0 ) set_andx(is_orig, msg.andx()); if ( smb_com_generic_andx ) { val_list* vl = new val_list; vl->append(analyzer->BuildConnVal()); vl->append(BuildHeaderVal(hdr)); vl->append(new StringVal(msg.data().length(), (char *) msg.data().begin())); analyzer->ConnectionEvent(smb_com_generic_andx, vl); } } catch ( const binpac::Exception& ) { Weird("smb_andx_command_failed_to_parse"); } return 0; } int SMB_Session::ParseTreeConnectAndx(binpac::SMB::SMB_header const& hdr, SMB_Body const& body) { binpac::SMB::SMB_tree_connect_andx req(hdr.unicode()); req.Parse(body.data(), body.data() + body.length()); set_andx(1, req.andx()); BroString* path = ExtractString(req.path()); BroString* service = ExtractString(req.service()); // Replicate path. BroString* norm_path = new BroString(path->Bytes(), path->Len(), 1); norm_path->ToUpper(); RecordVal* r = new RecordVal(smb_tree_connect); r->Assign(0, new Val(req.flags(), TYPE_COUNT)); r->Assign(1, new StringVal(req.password_length(), (const char*) req.password())); r->Assign(2, new StringVal(path)); r->Assign(3, new StringVal(service)); if ( strstr_n(norm_path->Len(), norm_path->Bytes(), 5, (const u_char*) "\\IPC$") != -1 ) is_IPC = true; // TODO: change is_IPC to 0 on tree_disconnect else is_IPC = false; delete norm_path; if ( smb_com_tree_connect_andx ) { val_list* vl = new val_list; vl->append(analyzer->BuildConnVal()); vl->append(BuildHeaderVal(hdr)); vl->append(r); analyzer->ConnectionEvent(smb_com_tree_connect_andx, vl); } else { delete path; delete service; } return 0; } int SMB_Session::ParseTreeDisconnect(int is_orig, binpac::SMB::SMB_header const& hdr, SMB_Body const& body) { binpac::SMB::SMB_tree_disconnect msg(hdr.unicode()); msg.Parse(body.data(), body.data() + body.length()); if ( smb_com_nt_create_andx ) { val_list* vl = new val_list; vl->append(analyzer->BuildConnVal()); vl->append(BuildHeaderVal(hdr)); analyzer->ConnectionEvent(smb_com_tree_disconnect, vl); } return 0; } int SMB_Session::ParseNtCreateAndx(binpac::SMB::SMB_header const& hdr, SMB_Body const& body) { binpac::SMB::SMB_nt_create_andx req(hdr.unicode()); req.Parse(body.data(), body.data() + body.length()); set_andx(1, req.andx()); BroString* name = ExtractString(req.name()); IPC_pipe = (enum IPC_named_pipe) lookup_IPC_name(name); if ( smb_com_nt_create_andx ) { val_list* vl = new val_list; vl->append(analyzer->BuildConnVal()); vl->append(BuildHeaderVal(hdr)); vl->append(new StringVal(name)); analyzer->ConnectionEvent(smb_com_nt_create_andx, vl); } else delete name; return 0; } int SMB_Session::ParseReadAndx(binpac::SMB::SMB_header const& hdr, SMB_Body const& body) { binpac::SMB::SMB_read_andx req; req.Parse(body.data(), body.data() + body.length()); set_andx(1, req.andx()); if ( smb_com_read_andx ) { val_list* vl = new val_list; vl->append(analyzer->BuildConnVal()); vl->append(BuildHeaderVal(hdr)); vl->append(new Val(req.fid(), TYPE_COUNT)); //vl->append(new StringVal("")); analyzer->ConnectionEvent(smb_com_read_andx, vl); } return 0; } int SMB_Session::ParseReadAndxResponse(binpac::SMB::SMB_header const& hdr, SMB_Body const& body) { binpac::SMB::SMB_read_andx_response resp; resp.Parse(body.data(), body.data() + body.length()); set_andx(0, resp.andx()); uint32_t data_len = resp.data_len_high(); data_len = (data_len<<16) + resp.data_len(); const u_char* data = resp.data().begin(); if ( smb_com_read_andx_response ) { val_list* vl = new val_list; vl->append(analyzer->BuildConnVal()); vl->append(BuildHeaderVal(hdr)); vl->append(new Val(data_len, TYPE_COUNT)); //vl->append(new StringVal(data_count, (const char*) data)); analyzer->ConnectionEvent(smb_com_read_andx_response, vl); } CheckRPC(0, data_len, data); return 0; } int SMB_Session::ParseWriteAndx(binpac::SMB::SMB_header const& hdr, SMB_Body const& body) { binpac::SMB::SMB_write_andx req; req.Parse(body.data(), body.data() + body.length()); set_andx(1, req.andx()); uint32_t data_len = req.data_len_high(); data_len = (data_len<<16) + req.data_len(); const u_char* data = req.data().begin(); if ( smb_com_write_andx ) { val_list* vl = new val_list; vl->append(analyzer->BuildConnVal()); vl->append(BuildHeaderVal(hdr)); vl->append(new Val(req.fid(), TYPE_COUNT)); vl->append(new Val(data_len, TYPE_COUNT)); //vl->append(new StringVal(data_count, (const char*) data)); analyzer->ConnectionEvent(smb_com_write_andx, vl); } CheckRPC(1, data_len, data); return 0; } int SMB_Session::ParseWriteAndxResponse(binpac::SMB::SMB_header const& hdr, SMB_Body const& body) { binpac::SMB::SMB_write_andx_response resp; resp.Parse(body.data(), body.data() + body.length()); set_andx(0, resp.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("")); analyzer->ConnectionEvent(smb_com_write_andx_response, vl); } return 0; } int SMB_Session::TransactionEvent(EventHandlerPtr f, int is_orig, binpac::SMB::SMB_header const &hdr, binpac::SMB::SMB_transaction const &trans, int data_count, binpac::SMB::SMB_transaction_data* data) { if ( f ) { val_list* vl = new val_list; vl->append(analyzer->BuildConnVal()); vl->append(BuildHeaderVal(hdr)); vl->append(BuildTransactionVal(trans)); vl->append(BuildTransactionDataVal(data)); vl->append(new Val(is_orig, TYPE_BOOL)); analyzer->ConnectionEvent(f, vl); } else if ( smb_com_transaction ) { // generic transaction } return 0; } int SMB_Session::TransactionEvent(EventHandlerPtr f, int is_orig, binpac::SMB::SMB_header const &hdr, binpac::SMB::SMB_transaction_secondary const &trans, int data_count, binpac::SMB::SMB_transaction_data* data) { if ( f ) { val_list* vl = new val_list; vl->append(analyzer->BuildConnVal()); vl->append(BuildHeaderVal(hdr)); vl->append(BuildTransactionVal(trans)); vl->append(BuildTransactionDataVal(data)); vl->append(new Val(is_orig, TYPE_BOOL)); analyzer->ConnectionEvent(f, vl); } else if ( smb_com_transaction ) { // generic transaction } return 0; } int SMB_Session::TransactionEvent(EventHandlerPtr f, int is_orig, binpac::SMB::SMB_header const &hdr, binpac::SMB::SMB_transaction_response const &trans, int data_count, binpac::SMB::SMB_transaction_data* data) { if ( f ) { val_list* vl = new val_list; vl->append(analyzer->BuildConnVal()); vl->append(BuildHeaderVal(hdr)); vl->append(BuildTransactionVal(trans)); vl->append(BuildTransactionDataVal(data)); vl->append(new Val(is_orig, TYPE_BOOL)); analyzer->ConnectionEvent(f, vl); } else if ( smb_com_transaction ) { // generic transaction } return 0; } int SMB_Session::ParseTransaction(int is_orig, int cmd, binpac::SMB::SMB_header const& hdr, SMB_Body const& body) { switch ( cmd ) { case SMB_COM_TRANSACTION: case SMB_COM_TRANSACTION2: case SMB_COM_TRANSACTION_SECONDARY: case SMB_COM_TRANSACTION2_SECONDARY: break; default: internal_error("command mismatch for ParseTransaction"); } int ret; if ( is_orig ) { if ( cmd == SMB_COM_TRANSACTION || cmd == SMB_COM_TRANSACTION2 ) ret = ParseTransactionRequest(cmd, hdr, body); else if ( cmd == SMB_COM_TRANSACTION_SECONDARY || cmd == SMB_COM_TRANSACTION2_SECONDARY ) ret = ParseTransactionSecondaryRequest(cmd, hdr, body); else ret = 0; } else ret = ParseTransactionResponse(cmd, hdr, body); return ret; } int SMB_Session::ParseTransactionRequest(int cmd, binpac::SMB::SMB_header const& hdr, SMB_Body const& body) { binpac::SMB::SMB_transaction trans(cmd == SMB_COM_TRANSACTION ? 1 : 2, hdr.unicode()); trans.Parse(body.data(), body.data() + body.length()); if ( transaction_name ) { Unref(transaction_name); transaction_name = 0; } if ( cmd == SMB_COM_TRANSACTION ) { binpac::SMB::SMB_transaction_data* trans_data = trans.data(); //transaction_name = new StringVal(ExtractString(trans.name())); //if ( is_orig ) // Weird(fmt("smb_transaction subcmd: 0x%x", transaction_subcmd)); if ( trans_data->val_case_index() == binpac::SMB::SMB_MAILSLOT_BROWSE && trans_data->mailslot() ) { // Mailslot transaction event return TransactionEvent(smb_com_trans_mailslot, true, hdr, trans, trans.data_count(), trans.data()); } else if ( trans_data->val_case_index() == binpac::SMB::SMB_PIPE && trans_data->pipe() ) { // Pipe return TransactionEvent(smb_com_trans_pipe, true, hdr, trans, trans.data_count(), trans.data()); } else if ( trans_data->val_case_index() == binpac::SMB::SMB_RAP && trans_data->rap() ) { // Remote Administration Protocol return TransactionEvent(smb_com_trans_rap, true, hdr, trans, trans.data_count(), trans.data()); } else { // SOME UNKNOWN TRANSACTION TYPE - COULD BE RPC STILL! if ( trans.data_count() > 0 && trans.setup_count() == 2 ) { if ( CheckRPC(true, trans.data_count(), trans_data->pipe()->data().begin()) ) { if ( cmd != SMB_COM_TRANSACTION || transaction_subcmd != 0x26 ) Weird(fmt("RPC through unknown command: 0x%x/0x%x", cmd, transaction_subcmd)); } } } } if ( cmd == SMB_COM_TRANSACTION2 ) { switch ( transaction_subcmd ) { case 0x3: // QueryFSInfo case 0x5: // QueryPathInfo case 0x7: // QueryFileInfo case 0x8: // SetFileInfo break; case 0x10: // if ( is_orig ) return ParseGetDFSReferral(hdr, trans.param_count(), trans.parameters().begin()); default: // if ( is_orig ) Weird(fmt("Unknown smb_transaction2 subcmd: 0x%x", transaction_subcmd)); break; } } if ( smb_com_transaction ) return TransactionEvent(smb_com_transaction, true, hdr, trans, trans.data_count(), trans.data()); else return 0; #if 0 // TODO: LANMAN transaction uses the first u_short of // parameters as subcmd if ( trans.setup_count() > 0 ) transaction_subcmd = (*trans.setup())[0]; else if ( strncmp( transaction_name->CheckString(), "\\PIPE\\", 6 ) == 0 ) transaction_subcmd = 0; else if ( strncmp( transaction_name->CheckString(), "\\MAILSLOT\\", 10 ) == 0 ) transaction_subcmd = 0; else Weird("transaction_subcmd_missing"); #endif } int SMB_Session::ParseTransactionSecondaryRequest(int cmd, binpac::SMB::SMB_header const& hdr, SMB_Body const& body) { binpac::SMB::SMB_transaction_secondary trans(hdr.unicode()); trans.Parse(body.data(), body.data() + body.length()); return TransactionEvent(smb_com_transaction2, true, hdr, trans, trans.data_count(), trans.data()); } int SMB_Session::ParseTransactionResponse(int cmd, binpac::SMB::SMB_header const& hdr, SMB_Body const& body) { binpac::SMB::SMB_transaction_response trans(hdr.unicode()); trans.Parse(body.data(), body.data() + body.length()); if ( body.word_count() == 0 ) { // interim response // Does the transaction get parsed correctly?! return TransactionEvent(smb_com_transaction, false, hdr, trans, 0, NULL); } return TransactionEvent(smb_com_transaction, false, hdr, trans, trans.data_count(), trans.data()); } int SMB_Session::ParseGetDFSReferral(binpac::SMB::SMB_header const& hdr, int param_count, const u_char* param) { binpac::SMB::SMB_get_dfs_referral req(hdr.unicode()); req.Parse(param, param + param_count); if ( smb_get_dfs_referral ) { val_list* vl = new val_list; vl->append(analyzer->BuildConnVal()); vl->append(BuildHeaderVal(hdr)); vl->append(new Val(req.max_referral_level(), TYPE_COUNT)); vl->append(new StringVal(ExtractString(req.file_name()))); analyzer->ConnectionEvent(smb_get_dfs_referral, vl); } return 0; } int SMB_Session::AndxOffset(int is_orig, int& next_command) const { if ( ! andx(is_orig) ) return -1; next_command = andx(is_orig)->command(); if ( next_command != 0xff ) return andx(is_orig)->offset(); else return -1; } void SMB_Session::Weird(const char* msg) { analyzer->Weird(msg); } // Extract a NUL-terminated string from [data, data+len-1]. The // input can be in Unicode (little endian), and the returned string // will be in ASCII. Note, Unicode strings have NUL characters // at the end of them already. Adding an additional NUL byte at // the end leads to embedded-NUL warnings (CheckString() run_time error). BroString* SMB_Session::ExtractString(binpac::SMB::SMB_string const* s) { return s->unicode() ? ExtractString(s->u()) : ExtractString(s->a()); } BroString* SMB_Session::ExtractString(binpac::SMB::SMB_ascii_string const* s) { bool add_NUL = true; int n = s->size(); if ( n > 0 && (*s)[n - 1] == '\0' ) add_NUL = false; // already has a NUL if ( add_NUL ) ++n; u_char* b = new u_char[n]; int i; for ( i = 0; i < int(s->size()); ++i ) b[i] = (*s)[i]; if ( add_NUL ) b[i] = '\0'; return new BroString(1, b, n - 1); } BroString* SMB_Session::ExtractString(binpac::SMB::SMB_unicode_string const* s) { bool add_NUL = true; int n = s->s()->size(); if ( n > 0 && ((*s->s())[n - 1] & 0xff) == '\0' ) add_NUL = false; // already has a NUL if ( add_NUL ) ++n; u_char* b = new u_char[n]; int i; for ( i = 0; i < int(s->s()->size()); ++i ) { uint16 x = (*s->s())[i]; if ( x & 0xff00 ) Weird(fmt("unicode string confusion: 0x%04x", x)); b[i] = u_char(x & 0xff); } if ( add_NUL ) b[i] = '\0'; return new BroString(1, b, n - 1); } Val* SMB_Session::BuildHeaderVal(binpac::SMB::SMB_header const& hdr) { RecordVal* r = new RecordVal(smb_hdr); unsigned int status = 0; #if 0 try { // FIXME: does this work? We need to catch exceptions :-( // or use guard functions. status = hdr.status()->status() || hdr.status()->dos_error()->error_class() << 24 || hdr.status()->dos_error()->error(); } catch ( const binpac::Exception& ) { // do nothing } #endif uint32_t pid = hdr.pid_high(); pid = (pid<<16) + hdr.pid(); r->Assign(0, new Val(hdr.command(), TYPE_COUNT)); r->Assign(1, new Val(status, TYPE_COUNT)); r->Assign(2, new Val(hdr.flags(), TYPE_COUNT)); r->Assign(3, new Val(hdr.flags2(), TYPE_COUNT)); r->Assign(4, new Val(hdr.tid(), TYPE_COUNT)); r->Assign(5, new Val(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; } Val* SMB_Session::BuildTransactionVal(binpac::SMB::SMB_transaction const& trans) { RecordVal* r = new RecordVal(smb_trans); // r->Assign(0, new Val(variable, type)); return r; } Val* SMB_Session::BuildTransactionVal(binpac::SMB::SMB_transaction_secondary const& trans) { RecordVal* r = new RecordVal(smb_trans); // r->Assign(0, new Val(variable, type)); return r; } Val* SMB_Session::BuildTransactionVal(binpac::SMB::SMB_transaction_response const& trans) { RecordVal* r = new RecordVal(smb_trans); // r->Assign(0, new Val(variable, type)); return r; } Val* SMB_Session::BuildTransactionDataVal(binpac::SMB::SMB_transaction_data *data) { RecordVal* r = new RecordVal(smb_trans_data); // r->Assign(0, new Val(variable, type)); return r; } bool SMB_Session::LooksLikeRPC(int len, const u_char* msg) { try { binpac::DCE_RPC_Simple::DCE_RPC_Header h; h.Parse(msg, msg + len); if ( h.rpc_vers() == 5 && h.rpc_vers_minor() == 0 ) { unsigned short frag_len = h.frag_length(); if ( frag_len == len || BYTEORDER_SWAP16(frag_len) == len ) { if ( ! is_IPC && DEBUG_smb_ipc ) analyzer->Weird("TreeConnect to IPC missing"); return true; } else { analyzer->Weird(fmt("endianness %d", h.byteorder())); analyzer->Weird(fmt("length mismatch: %d != %d", h.frag_length(), len)); return false; } } } catch ( const binpac::Exception& ) { // do nothing } return false; } bool SMB_Session::CheckRPC(int is_orig, int data_count, const u_char *data) { if ( LooksLikeRPC(data_count, data) ) { if ( ! dce_rpc_session ) dce_rpc_session = new DCE_RPC_Session(analyzer); dce_rpc_session->DeliverPDU(is_orig, data_count, data); return true; } return false; } Contents_SMB::Contents_SMB(Connection* conn, bool orig, SMB_Session* s) : TCP_SupportAnalyzer(AnalyzerTag::Contents_SMB, conn, orig) { smb_session = s; state = WAIT_FOR_HDR; resync_state = INSYNC; first_time = last_time = 0.0; hdr_buf.Init(4,4); msg_len = 0; msg_type = 0; } void Contents_SMB::Init() { TCP_SupportAnalyzer::Init(); NeedResync(); } Contents_SMB::~Contents_SMB() { } void Contents_SMB::Undelivered(int seq, int len, bool orig) { TCP_SupportAnalyzer::Undelivered(seq, len, orig); NeedResync(); } 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 %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])); NeedResync(); } else smb_session->Deliver(IsOrig(), len, data, first_time, last_time); } bool Contents_SMB::CheckResync(int& len, const u_char*& data, bool orig) { if (resync_state == INSYNC) return true; // This is an attempt to re-synchronize the stream after a content gap. // Returns true if we are in sync. // Returns false otherwise (we are in resync mode) // // We try to look for the beginning of a SMB message, assuming // SMB messages start at packet boundaries (though they may span // over multiple packets) (note that the data* of DeliverStream() // usually starts at a packet boundrary). // // Now lets see whether data points to the beginning of a // SMB message. If the resync processs is successful, we should // be at the beginning of a frame. if ( len < 36 ) { // Ignore small chunks. // 4 byte NetBIOS header (or length field) + 32 Byte SMB header Conn()->Weird(fmt("SMB resync: discard %d bytes\n", len)); NeedResync(); return false; } const u_char *xdata = data; int xlen = len; bool discard_this_chunk = false; // Check if it's a data message if (xdata[0]!=0x00) discard_this_chunk = true; // Check if the flags / high-byte of the message length is < 1 if (xdata[1] > 1) discard_this_chunk = true; // check if the SMB header starts with \xFFSMB if (strncmp((const char*) (xdata+4), "\xffSMB", 4)!=0) discard_this_chunk = true; if (discard_this_chunk) { NeedResync(); return false; } resync_state = INSYNC; first_time = last_time = 0.0; hdr_buf.Init(4,4); msg_len = 0; msg_type = 0; fprintf(stderr, "Resync successful\n"); return true; } void Contents_SMB::DeliverStream(int len, const u_char* data, bool orig) { TCP_SupportAnalyzer::DeliverStream(len, data, orig); if (!CheckResync(len, data, orig)) return; // Not in sync yet. Still resyncing last_time = network_time; while ( len > 0 ) { switch (state) { case WAIT_FOR_HDR: { if (first_time < 1e-3) first_time = network_time; bool got_hdr = hdr_buf.ConsumeChunk(data, len); if (got_hdr) { // 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; } } break; case WAIT_FOR_DATA: { bool got_all_data = msg_buf.ConsumeChunk(data, len); if (got_all_data) { const u_char *dummy_p = msg_buf.GetBuf(); int dummy_len = (int) msg_buf.GetFill(); if (msg_type == 0x00 && dummy_len >= 32) DeliverSMB(dummy_len, dummy_p); else if (msg_type == 0x00) { Conn()->Weird(fmt("SMB too short: len=%d", msg_len)); NeedResync(); } else Conn()->Weird(fmt("SMB other msg type: %x", msg_type)); state = WAIT_FOR_HDR; first_time = 0.0; hdr_buf.Init(4,4); } } break; } // end switch } // end while } SMB_Analyzer::SMB_Analyzer(Connection* conn) : TCP_ApplicationAnalyzer(AnalyzerTag::SMB, conn) { smb_session = new SMB_Session(this); o_smb = new Contents_SMB(conn, true, smb_session); r_smb = new Contents_SMB(conn, false, smb_session); AddSupportAnalyzer(o_smb); AddSupportAnalyzer(r_smb); } SMB_Analyzer::~SMB_Analyzer() { delete smb_session; }