diff --git a/scripts/base/init-bare.bro b/scripts/base/init-bare.bro index 5f7a93ee34..d3c539bba0 100644 --- a/scripts/base/init-bare.bro +++ b/scripts/base/init-bare.bro @@ -2689,6 +2689,34 @@ export { security_blob : string &optional; }; + type SMB1::Find_First2_Request_Args: record { + ## File attributes to apply as a constraint to the search + search_attrs : count; + ## Max search results + search_count : count; + ## Misc. flags for how the server should manage the transaction + ## once results are returned + flags : count; + ## How detailed the information returned in the results should be + info_level : count; + ## Specify whether to search for directories or files + search_storage_type : count; + ## The string to serch for (note: may contain wildcards) + file_name : string; + }; + + type SMB1::Find_First2_Response_Args: record { + ## The server generated search identifier + sid : count; + ## Number of results returned by the search + search_count : count; + ## Whether or not the search can be continued using + ## the TRANS2_FIND_NEXT2 transaction + end_of_search : bool; + ## An extended attribute name that couldn't be retrieved + ext_attr_error : string &optional; + }; + } diff --git a/scripts/base/protocols/smb/consts.bro b/scripts/base/protocols/smb/consts.bro index 681fcb3c17..86c470024c 100644 --- a/scripts/base/protocols/smb/consts.bro +++ b/scripts/base/protocols/smb/consts.bro @@ -9,6 +9,113 @@ export { const statuses: table[count] of StatusCode = { [0x00000000] = [$id="SUCCESS", $desc="The operation completed successfully."], } &redef &default=function(i: count):StatusCode { local unknown=fmt("unknown-%d", i); return [$id=unknown, $desc=unknown]; }; + + ## These are files names that are used for special + ## cases by the file system and would not be + ## considered "normal" files. + const pipe_names: set[string] = { + "\\netdfs", + "\\spoolss", + "\\NETLOGON", + "\\winreg", + "\\lsarpc", + "\\samr", + "\\srvsvc", + "srvsvc", + "MsFteWds", + "\\wkssvc", + }; + + ## The UUIDs used by the various RPC endpoints + const rpc_uuids: table[string] of string = { + ["4b324fc8-1670-01d3-1278-5a47bf6ee188"] = "Server Service", + ["6bffd098-a112-3610-9833-46c3f87e345a"] = "Workstation Service", + } &redef &default=function(i: string):string { return fmt("unknown-uuid-%s", i); }; + + ## Server service sub commands + const srv_cmds: table[count] of string = { + [8] = "NetrConnectionEnum", + [9] = "NetrFileEnum", + [10] = "NetrFileGetInfo", + [11] = "NetrFileClose", + [12] = "NetrSessionEnum", + [13] = "NetrSessionDel", + [14] = "NetrShareAdd", + [15] = "NetrShareEnum", + [16] = "NetrShareGetInfo", + [17] = "NetrShareSetInfo", + [18] = "NetrShareDel", + [19] = "NetrShareDelSticky", + [20] = "NetrShareCheck", + [21] = "NetrServerGetInfo", + [22] = "NetrServerSetInfo", + [23] = "NetrServerDiskEnum", + [24] = "NetrServerStatisticsGet", + [25] = "NetrServerTransportAdd", + [26] = "NetrServerTransportEnum", + [27] = "NetrServerTransportDel", + [28] = "NetrRemoteTOD", + [30] = "NetprPathType", + [31] = "NetprPathCanonicalize", + [32] = "NetprPathCompare", + [33] = "NetprNameValidate", + [34] = "NetprNameCanonicalize", + [35] = "NetprNameCompare", + [36] = "NetrShareEnumSticky", + [37] = "NetrShareDelStart", + [38] = "NetrShareDelCommit", + [39] = "NetrGetFileSecurity", + [40] = "NetrSetFileSecurity", + [41] = "NetrServerTransportAddEx", + [43] = "NetrDfsGetVersion", + [44] = "NetrDfsCreateLocalPartition", + [45] = "NetrDfsDeleteLocalPartition", + [46] = "NetrDfsSetLocalVolumeState", + [48] = "NetrDfsCreateExitPoint", + [49] = "NetrDfsDeleteExitPoint", + [50] = "NetrDfsModifyPrefix", + [51] = "NetrDfsFixLocalVolume", + [52] = "NetrDfsManagerReportSiteInfo", + [53] = "NetrServerTransportDelEx", + [54] = "NetrServerAliasAdd", + [55] = "NetrServerAliasEnum", + [56] = "NetrServerAliasDel", + [57] = "NetrShareDelEx", + } &redef &default=function(i: count):string { return fmt("unknown-srv-command-%d", i); }; + + ## Workstation service sub commands + const wksta_cmds: table[count] of string = { + [0] = "NetrWkstaGetInfo", + [1] = "NetrWkstaSetInfo", + [2] = "NetrWkstaUserEnum", + [5] = "NetrWkstaTransportEnum", + [6] = "NetrWkstaTransportAdd", + [7] = "NetrWkstaTransportDel", + [8] = "NetrUseAdd", + [9] = "NetrUseGetInfo", + [10] = "NetrUseDel", + [11] = "NetrUseEnum", + [13] = "NetrWorkstationStatisticsGet", + [20] = "NetrGetJoinInformation", + [22] = "NetrJoinDomain2", + [23] = "NetrUnjoinDomain2", + [24] = "NetrRenameMachineInDomain2", + [25] = "NetrValidateName2", + [26] = "NetrGetJoinableOUs2", + [27] = "NetrAddAlternateComputerName", + [28] = "NetrRemoveAlternateComputerName", + [29] = "NetrSetPrimaryComputerName", + [30] = "NetrEnumerateComputerNames", + } &redef &default=function(i: count):string { return fmt("unknown-wksta-command-%d", i); }; + + type rpc_cmd_table: table[count] of string; + + ## The subcommands for RPC endpoints + const rpc_sub_cmds: table[string] of rpc_cmd_table = { + ["4b324fc8-1670-01d3-1278-5a47bf6ee188"] = srv_cmds, + ["6bffd098-a112-3610-9833-46c3f87e345a"] = wksta_cmds, + } &redef &default=function(i: string):rpc_cmd_table { return table() &default=function(j: string):string { return fmt("unknown-uuid-%s", j); }; }; + } module SMB1; @@ -88,6 +195,40 @@ export { [0xD9] = "WRITE_BULK", [0xDA] = "WRITE_BULK_DATA", } &default=function(i: count):string { return fmt("unknown-%d", i); }; + + const trans2_sub_commands: table[count] of string = { + [0x00] = "OPEN2", + [0x01] = "FIND_FIRST2", + [0x02] = "FIND_NEXT2", + [0x03] = "QUERY_FS_INFORMATION", + [0x04] = "SET_FS_INFORMATION", + [0x05] = "QUERY_PATH_INFORMATION", + [0x06] = "SET_PATH_INFORMATION", + [0x07] = "QUERY_FILE_INFORMATION", + [0x08] = "SET_FILE_INFORMATION", + [0x09] = "FSCTL", + [0x0A] = "IOCTL", + [0x0B] = "FIND_NOTIFY_FIRST", + [0x0C] = "FIND_NOTIFY_NEXT", + [0x0D] = "CREATE_DIRECTORY", + [0x0E] = "SESSION_SETUP", + [0x10] = "GET_DFS_REFERRAL", + [0x11] = "REPORT_DFS_INCONSISTENCY", + } &default=function(i: count):string { return fmt("unknown-trans2-sub-cmd-%d", i); }; + + const trans_sub_commands: table[count] of string = { + [0x01] = "SET_NMPIPE_STATE", + [0x11] = "RAW_READ_NMPIPE", + [0x21] = "QUERY_NMPIPE_STATE", + [0x22] = "QUERY_NMPIPE_INFO", + [0x23] = "PEEK_NMPIPE", + [0x26] = "TRANSACT_NMPIPE", + [0x31] = "RAW_WRITE_NMPIPE", + [0x36] = "READ_NMPIPE", + [0x37] = "WRITE_NMPIPE", + [0x53] = "WAIT_NMPIPE", + [0x54] = "CALL_NMPIPE", + } &default=function(i: count):string { return fmt("unknown-trans-sub-cmd-%d", i); }; } module SMB2; diff --git a/scripts/base/protocols/smb/files.bro b/scripts/base/protocols/smb/files.bro index 8c4eb083e3..e58e124307 100644 --- a/scripts/base/protocols/smb/files.bro +++ b/scripts/base/protocols/smb/files.bro @@ -12,15 +12,15 @@ export { function get_file_handle(c: connection, is_orig: bool): string { - if ( ! (c$smb?$current_file && - ((c$smb$current_file?$name && c$smb$current_file$name !in pipe_names) || - c$smb$current_file?$path)) ) + if ( ! (c$smb_state?$current_file && + ((c$smb_state$current_file?$name && c$smb_state$current_file$name !in pipe_names) || + c$smb_state$current_file?$path)) ) { - # TODO: figure out what are the cases where this happens. + # TODO - figure out what are the cases where this happens. return ""; } - local current_file = c$smb$current_file; + local current_file = c$smb_state$current_file; local path_name = current_file?$path ? current_file$path : ""; local file_name = current_file?$name ? current_file$name : ""; # Include last_mod time if available because if a file has been modified it @@ -38,8 +38,8 @@ function describe_file(f: fa_file): string for ( cid in f$conns ) { local info = f$conns[cid]; - if ( info?$smb && info$smb?$current_file && info$smb$current_file?$name ) - return info$smb$current_file$name; + if ( info?$smb_state && info$smb_state?$current_file && info$smb_state$current_file?$name ) + return info$smb_state$current_file$name; } return ""; } @@ -53,14 +53,14 @@ event bro_init() &priority=5 event file_over_new_connection(f: fa_file, c: connection, is_orig: bool) &priority=5 { - if ( c?$smb && c$smb?$current_file ) + if ( c?$smb_state && c$smb_state?$current_file ) { - c$smb$current_file$fuid = f$id; + c$smb_state$current_file$fuid = f$id; - if ( c$smb$current_file$size > 0 ) - f$total_bytes = c$smb$current_file$size; + if ( c$smb_state$current_file$size > 0 ) + f$total_bytes = c$smb_state$current_file$size; - if ( c$smb$current_file?$name ) - f$info$filename = c$smb$current_file$name; + if ( c$smb_state$current_file?$name ) + f$info$filename = c$smb_state$current_file$name; } } diff --git a/scripts/base/protocols/smb/main.bro b/scripts/base/protocols/smb/main.bro index d13f9f9f64..e054ad15fe 100644 --- a/scripts/base/protocols/smb/main.bro +++ b/scripts/base/protocols/smb/main.bro @@ -1,3 +1,5 @@ +@load ./consts + module SMB; export { @@ -16,141 +18,154 @@ export { FILE_UNKNOWN, }; + ## The file actions which are logged. const logged_file_actions: set[FileAction] = { FILE_OPEN, FILE_READ, FILE_WRITE, - }; - - ## These are files names that are used for special - ## cases by the file system and would not be - ## considered "normal" files. - const pipe_names: set[string] = { - "\\netdfs", - "\\spoolss", - "\\NETLOGON", - "\\winreg", - "\\lsarpc", - "\\samr", - "\\srvsvc", - "srvsvc", - "MsFteWds", - "\\wkssvc", - }; + } &redef; + ## The server response statuses which are *not* logged. + const ignored_command_statuses: set[string] = { + "MORE_PROCESSING_REQUIRED", + } &redef; + + ## This record is for the smb_files.log type FileInfo: record { ## Time when the file was first discovered. - ts : time &log; - uid : string &log; - id : conn_id &log; - fuid : string &log; - + ts : time &log; + ## Unique ID of the connection the file was sent over. + uid : string &log; + ## ID of the connection the file was sent over. + id : conn_id &log; + ## Unique ID of the file. + fuid : string &log &optional; + ## Action this log record represents. - action : FileAction &log &default=FILE_UNKNOWN; - + action : FileAction &log &default=FILE_UNKNOWN; ## Path pulled from the tree this file was transferred to or from. - path : string &log &optional; + path : string &log &optional; ## Filename if one was seen. - name : string &log &optional; - + name : string &log &optional; ## Total size of the file. - size : count &log &default=0; + size : count &log &default=0; ## Last time this file was modified. - times : SMB::MACTimes &log &optional; + times : SMB::MACTimes &log &optional; }; - + + ## This record is for the smb_mapping.log type TreeInfo: record { ## Time when the tree was mapped. - ts : time &log &optional; - - uid : string &log; - id : conn_id &log; + ts : time &log &optional; + ## Unique ID of the connection the tree was mapped over. + uid : string &log; + ## ID of the connection the tree was mapped over. + id : conn_id &log; ## Name of the tree path. - path : string &log &optional; - service : string &log &optional; - native_file_system : string &log &optional; - + path : string &log &optional; + ## The type of resource of the tree (disk share, printer share, named pipe, etc.) + service : string &log &optional; + ## File system of the tree. + native_file_system : string &log &optional; ## If this is SMB2, a share type will be included. - share_type : string &log &optional; + share_type : string &log &optional; }; - + + ## This record is for the smb_cmd.log type CmdInfo: record { - ## The command. - command : string &optional; - - ## If the command referenced a file, store it here. - referenced_file : FileInfo &optional; - ## If the command referenced a tree, store it here. - referenced_tree : TreeInfo &optional; - }; - - type Info: record { - ts: time &log; - uid: string &log; - id: conn_id &log; - - ## Version of SMB for the command. - version: string &log; - - ## Command sent by the client. - command: string &log &optional; - + ## Timestamp of the command request + ts : time &log; + ## Unique ID of the connection the request was sent over + uid : string &log; + ## ID of the connection the request was sent over + id : conn_id &log; + + ## The command sent by the client + command : string &log; + ## The subcommand sent by the client, if present + sub_command : string &log &optional; + ## Command argument sent by the client, if any + argument : string &log &optional; + ## Server reply to the client's command - status: string &log &optional; + status : string &log &optional; + ## Round trip time from the request to the response. + rtt : interval &log &optional; + ## Version of SMB for the command + version : string &log; + + ## Authenticated username, if available + username : string &log &optional; ## If this is related to a tree, this is the tree - ## that was used for the current command. - tree: string &log &optional; - - ## The negotiated dialect for the connection. - dialect: string &log &optional; - - ## Round trip time from the request to the response. - rtt: interval &log &optional; + ## that was used for the current command. + tree : string &log &optional; + ## The type of tree (disk share, printer share, named pipe, etc.) + tree_service : string &log &optional; + + ## If the command referenced a file, store it here. + referenced_file : FileInfo &optional; + ## If the command referenced a tree, store it here. + referenced_tree : TreeInfo &optional; + }; + ## This record stores the SMB state of in-flight commands, + ## the file and tree map of the connection. + type State: record { ## A reference to the current command. current_cmd : CmdInfo &optional; - ## A reference to the current file. current_file : FileInfo &optional; - ## A reference to the current tree. current_tree : TreeInfo &optional; ## Indexed on MID to map responses to requests. - pending_cmds : table[count] of CmdInfo &optional; + pending_cmds: table[count] of CmdInfo &optional; ## File map to retrieve file information based on the file ID. - fid_map : table[count] of FileInfo &optional; + fid_map : table[count] of FileInfo &optional; ## Tree map to retrieve tree information based on the tree ID. - tid_map : table[count] of TreeInfo &optional; + tid_map : table[count] of TreeInfo &optional; + ## User map to retrieve user name based on the user ID. + uid_map : table[count] of string &optional; + ## Pipe map to retrieve UUID based on the file ID of a pipe. + pipe_map : table[count] of string &optional; }; redef record connection += { - smb : Info &optional; + smb_state : State &optional; }; + + ## Internal use only + ## Some commands shouldn't be logged by the smb1_message event + const deferred_logging_cmds: set[string] = { + "NEGOTIATE", + "READ_ANDX", + "SESSION_SETUP_ANDX", + "TREE_CONNECT_ANDX", + }; + ## Optionally write out the SMB commands log. This is ## primarily useful for debugging so is disabled by default. const write_cmd_log = F &redef; ## This is an internally used function. - const set_current_file: function(smb: Info, file_id: count) &redef; + const set_current_file: function(smb_state: State, file_id: count) &redef; ## This is an internally used function. const write_file_log: function(f: FileInfo) &redef; } -redef record connection += { - smb_pending_cmds : table[count, count] of Info &default=table(); -}; - redef record FileInfo += { ## ID referencing this file. - fid : count &optional; + fid : count &optional; ## Maintain a reference to the file record. - f : fa_file &optional; + f : fa_file &optional; + + ## UUID referencing this file if DCE/RPC + uuid: string &optional; }; const ports = { 139/tcp, 445/tcp }; @@ -158,22 +173,22 @@ redef likely_server_ports += { ports }; event bro_init() &priority=5 { - Log::create_stream(CMD_LOG, [$columns=SMB::Info]); + Log::create_stream(CMD_LOG, [$columns=SMB::CmdInfo]); Log::create_stream(FILES_LOG, [$columns=SMB::FileInfo]); Log::create_stream(MAPPING_LOG, [$columns=SMB::TreeInfo]); Analyzer::register_for_ports(Analyzer::ANALYZER_SMB, ports); } -function set_current_file(smb: Info, file_id: count) +function set_current_file(smb_state: State, file_id: count) { - if ( file_id !in smb$fid_map ) + if ( file_id !in smb_state$fid_map ) { - smb$fid_map[file_id] = smb$current_cmd$referenced_file; - smb$fid_map[file_id]$fid = file_id; + smb_state$fid_map[file_id] = smb_state$current_cmd$referenced_file; + smb_state$fid_map[file_id]$fid = file_id; } - smb$current_file = smb$fid_map[file_id]; + smb_state$current_file = smb_state$fid_map[file_id]; } function write_file_log(f: FileInfo) @@ -194,9 +209,9 @@ event file_state_remove(f: fa_file) &priority=-5 for ( id in f$conns ) { local c = f$conns[id]; - if ( c?$smb && c$smb?$current_file) + if ( c?$smb_state && c$smb_state?$current_file) { - write_file_log(c$smb$current_file); + write_file_log(c$smb_state$current_file); } return; } diff --git a/scripts/base/protocols/smb/smb1-main.bro b/scripts/base/protocols/smb/smb1-main.bro index 78a203b0c3..6c8694e04c 100644 --- a/scripts/base/protocols/smb/smb1-main.bro +++ b/scripts/base/protocols/smb/smb1-main.bro @@ -1,197 +1,355 @@ module SMB1; -redef record SMB::Info += { +redef record SMB::CmdInfo += { + ## Dialects offered by the client smb1_offered_dialects: string_vec &optional; }; event smb1_message(c: connection, hdr: SMB1::Header, is_orig: bool) &priority=5 { - if ( ! c?$smb ) + if ( ! c?$smb_state ) { - local info: SMB::Info = [$ts=network_time(), $uid=c$uid, $id=c$id, $version="SMB1"]; - info$fid_map = table(); - info$tid_map = table(); - info$pending_cmds = table(); - c$smb = info; + local state: SMB::State; + state$fid_map = table(); + state$tid_map = table(); + state$uid_map = table(); + state$pipe_map = table(); + state$pending_cmds = table(); + c$smb_state = state; } - - local smb = c$smb; + + local smb_state = c$smb_state; local tid = hdr$tid; - local pid = hdr$pid; local uid = hdr$uid; + local pid = hdr$pid; local mid = hdr$mid; - - if ( tid !in smb$tid_map ) + + if ( uid in smb_state$uid_map ) + { + smb_state$current_cmd$username = smb_state$uid_map[uid]; + } + + if ( tid !in smb_state$tid_map ) { local tmp_tree: SMB::TreeInfo = [$uid=c$uid, $id=c$id]; - smb$tid_map[tid] = tmp_tree; + smb_state$tid_map[tid] = tmp_tree; } - smb$current_tree = smb$tid_map[tid]; - - if ( mid !in smb$pending_cmds ) + smb_state$current_tree = smb_state$tid_map[tid]; + if ( smb_state$current_tree?$path ) { - local tmp_cmd: SMB::CmdInfo; - tmp_cmd$command = SMB1::commands[hdr$command]; - - local tmp_file: SMB::FileInfo; - tmp_file$ts = network_time(); - tmp_file$id = c$id; - tmp_file$uid = c$uid; - tmp_cmd$referenced_file = tmp_file; - tmp_cmd$referenced_tree = smb$current_tree; + smb_state$current_cmd$tree = smb_state$current_tree$path; + } - smb$pending_cmds[mid] = tmp_cmd; + if ( smb_state$current_tree?$service ) + { + smb_state$current_cmd$tree_service = smb_state$current_tree$service; } - smb$current_cmd = smb$pending_cmds[mid]; - smb$command = smb$current_cmd$command; + if ( mid !in smb_state$pending_cmds ) + { + local tmp_cmd: SMB::CmdInfo = [$ts=network_time(), $uid=c$uid, $id=c$id, $version="SMB1", $command = SMB1::commands[hdr$command]]; - if ( is_orig ) - { - smb$ts = network_time(); + local tmp_file: SMB::FileInfo = [$ts=network_time(), $uid=c$uid, $id=c$id]; + tmp_cmd$referenced_file = tmp_file; + tmp_cmd$referenced_tree = smb_state$current_tree; + + smb_state$pending_cmds[mid] = tmp_cmd; } - else + + smb_state$current_cmd = smb_state$pending_cmds[mid]; + + if ( !is_orig ) { - smb$rtt = network_time() - smb$ts; - smb$status = SMB::statuses[hdr$status]$id; + smb_state$current_cmd$rtt = network_time() - smb_state$current_cmd$ts; + smb_state$current_cmd$status = SMB::statuses[hdr$status]$id; } } event smb1_message(c: connection, hdr: SMB1::Header, is_orig: bool) &priority=-5 { + # Is this a response? if ( !is_orig ) - # This is a response and the command is no longer pending - # so let's get rid of it. - delete c$smb$pending_cmds[hdr$mid]; + { + if ( SMB::write_cmd_log && + c$smb_state$current_cmd$status !in SMB::ignored_command_statuses && + c$smb_state$current_cmd$command !in SMB::deferred_logging_cmds ) + { + Log::write(SMB::CMD_LOG, c$smb_state$current_cmd); + } + delete c$smb_state$pending_cmds[hdr$mid]; + } + } - if ( SMB::write_cmd_log && c?$smb ) - Log::write(SMB::CMD_LOG, c$smb); + +event smb1_transaction2_request(c: connection, hdr: SMB1::Header, sub_cmd: count) + { + c$smb_state$current_cmd$sub_command = SMB1::trans2_sub_commands[sub_cmd]; } event smb1_negotiate_request(c: connection, hdr: SMB1::Header, dialects: string_vec) &priority=5 { - c$smb$smb1_offered_dialects = dialects; + c$smb_state$current_cmd$smb1_offered_dialects = dialects; } event smb1_negotiate_response(c: connection, hdr: SMB1::Header, response: SMB1::NegotiateResponse) &priority=5 { - if ( c$smb?$smb1_offered_dialects ) + if ( c$smb_state$current_cmd?$smb1_offered_dialects ) { if ( response?$ntlm ) - c$smb$dialect = c$smb$smb1_offered_dialects[response$ntlm$dialect_index]; - delete c$smb$smb1_offered_dialects; + { + c$smb_state$current_cmd$argument = c$smb_state$current_cmd$smb1_offered_dialects[response$ntlm$dialect_index]; + } + + delete c$smb_state$current_cmd$smb1_offered_dialects; } } - + +event smb1_negotiate_response(c: connection, hdr: SMB1::Header, response: SMB1::NegotiateResponse) &priority=-5 + { + if ( c$smb_state$current_cmd$status !in SMB::ignored_command_statuses ) + { + Log::write(SMB::CMD_LOG, c$smb_state$current_cmd); + } + } + event smb1_tree_connect_andx_request(c: connection, hdr: SMB1::Header, path: string, service: string) &priority=5 { - c$smb$current_cmd$referenced_tree$path = path; - c$smb$current_cmd$referenced_tree$service = service; - c$smb$current_tree$ts=network_time(); + local tmp_tree: SMB::TreeInfo = [$ts=network_time(), $uid=c$uid, $id=c$id, $path=path, $service=service]; + + c$smb_state$current_cmd$referenced_tree = tmp_tree; + c$smb_state$current_cmd$argument = path; } event smb1_tree_connect_andx_response(c: connection, hdr: SMB1::Header, service: string, native_file_system: string) &priority=5 { - c$smb$current_cmd$referenced_tree$native_file_system = native_file_system; - c$smb$current_tree = c$smb$current_cmd$referenced_tree; - c$smb$tid_map[hdr$tid] = c$smb$current_tree; + c$smb_state$current_cmd$referenced_tree$service = service; + c$smb_state$current_cmd$tree_service = service; + + c$smb_state$current_cmd$referenced_tree$native_file_system = native_file_system; + + c$smb_state$current_tree = c$smb_state$current_cmd$referenced_tree; + c$smb_state$tid_map[hdr$tid] = c$smb_state$current_tree; } event smb1_tree_connect_andx_response(c: connection, hdr: SMB1::Header, service: string, native_file_system: string) &priority=-5 { - Log::write(SMB::MAPPING_LOG, c$smb$current_tree); + Log::write(SMB::MAPPING_LOG, c$smb_state$current_tree); + + if ( c$smb_state$current_cmd$status !in SMB::ignored_command_statuses ) + { + Log::write(SMB::CMD_LOG, c$smb_state$current_cmd); + } } event smb1_nt_create_andx_request(c: connection, hdr: SMB1::Header, name: string) &priority=5 { - c$smb$current_cmd$referenced_file$name = name; - c$smb$current_file = c$smb$current_cmd$referenced_file; - c$smb$current_file$action = SMB::FILE_OPEN; + local tmp_file: SMB::FileInfo = [$ts=network_time(), $uid=c$uid, $id=c$id]; + c$smb_state$current_cmd$referenced_file = tmp_file; + c$smb_state$current_cmd$referenced_file$name = name; + c$smb_state$current_cmd$referenced_file$action = SMB::FILE_OPEN; + c$smb_state$current_file = c$smb_state$current_cmd$referenced_file; + c$smb_state$current_cmd$argument = name; } event smb1_nt_create_andx_response(c: connection, hdr: SMB1::Header, file_id: count, file_size: count, times: SMB::MACTimes) &priority=5 { - if ( ! c$smb?$current_file ) - { - c$smb$current_file = c$smb$current_cmd$referenced_file; - c$smb$current_file$action = SMB::FILE_OPEN; - } - c$smb$current_file$fid = file_id; - c$smb$current_file$size = file_size; + c$smb_state$current_cmd$referenced_file$action = SMB::FILE_OPEN; + c$smb_state$current_cmd$referenced_file$fid = file_id; + c$smb_state$current_cmd$referenced_file$size = file_size; # I'm seeing negative data from IPC tree transfers if ( time_to_double(times$modified) > 0.0 ) - c$smb$current_file$times = times; + c$smb_state$current_cmd$referenced_file$times = times; # We can identify the file by its file id now so let's stick it # in the file map. - c$smb$fid_map[file_id] = c$smb$current_file; - } + c$smb_state$fid_map[file_id] = c$smb_state$current_cmd$referenced_file; -event smb1_nt_create_andx_response(c: connection, hdr: SMB1::Header, file_id: count, file_size: count, times: SMB::MACTimes) &priority=-5 - { - SMB::write_file_log(c$smb$current_file); + c$smb_state$current_file = c$smb_state$fid_map[file_id]; + + + SMB::write_file_log(c$smb_state$current_file); } event smb1_read_andx_request(c: connection, hdr: SMB1::Header, file_id: count, offset: count, length: count) &priority=5 { - SMB::set_current_file(c$smb, file_id); - c$smb$current_file$action = SMB::FILE_READ; + SMB::set_current_file(c$smb_state, file_id); + c$smb_state$current_file$action = SMB::FILE_READ; + c$smb_state$current_cmd$argument = c$smb_state$current_file$name; + } + +event smb1_read_andx_request(c: connection, hdr: SMB1::Header, file_id: count, offset: count, length: count) &priority=-5 + { + if ( c$smb_state$current_tree?$path && !c$smb_state$current_file?$path ) + c$smb_state$current_file$path = c$smb_state$current_tree$path; - if ( c$smb$current_tree?$path && !c$smb$current_file?$path ) - c$smb$current_file$path = c$smb$current_tree$path; - - #write_file_log(c$smb$current_file); + # TODO - Why is this commented out? + #write_file_log(c$smb_state$current_file); } event smb1_read_andx_response(c: connection, hdr: SMB1::Header, data_len: count) &priority=5 { - #print "read andx response!"; + if ( c$smb_state$current_cmd$status !in SMB::ignored_command_statuses ) + { + Log::write(SMB::CMD_LOG, c$smb_state$current_cmd); + } } event smb1_write_andx_request(c: connection, hdr: SMB1::Header, file_id: count, offset: count, data_len: count) &priority=5 { - SMB::set_current_file(c$smb, file_id); - c$smb$current_file$action = SMB::FILE_WRITE; + SMB::set_current_file(c$smb_state, file_id); + c$smb_state$current_file$action = SMB::FILE_WRITE; + if ( !c$smb_state$current_cmd?$argument ) + c$smb_state$current_cmd$argument = c$smb_state$current_file$name; } event smb1_write_andx_request(c: connection, hdr: SMB1::Header, file_id: count, offset: count, data_len: count) &priority=-5 { - if ( c$smb$current_tree?$path && !c$smb$current_file?$path ) - c$smb$current_file$path = c$smb$current_tree$path; + if ( c$smb_state$current_tree?$path && !c$smb_state$current_file?$path ) + c$smb_state$current_file$path = c$smb_state$current_tree$path; - #write_file_log(c$smb$current_file); + # TODO - Why is this commented out? + #write_file_log(c$smb_state$current_file); } #event smb1_write_andx_response(c: connection, hdr: SMB1::Header, written_bytes: count) &priority=5 # { -# # Do i really need to do anything here? Maybe do a weird if the number of bytes written is odd? +# # TODO - determine what to do here # } event smb1_close_request(c: connection, hdr: SMB1::Header, file_id: count) &priority=5 { - SMB::set_current_file(c$smb, file_id); - c$smb$current_file$action = SMB::FILE_CLOSE; + SMB::set_current_file(c$smb_state, file_id); + c$smb_state$current_file$action = SMB::FILE_CLOSE; } event smb1_close_request(c: connection, hdr: SMB1::Header, file_id: count) &priority=-5 { - if ( file_id in c$smb$fid_map ) + if ( file_id in c$smb_state$fid_map ) { - local fl = c$smb$fid_map[file_id]; - fl$uid = c$uid; - fl$id = c$id; + local fl = c$smb_state$fid_map[file_id]; # Need to check for existence of path in case tree connect message wasn't seen. - if ( c$smb$current_tree?$path ) - fl$path = c$smb$current_tree$path; - delete c$smb$fid_map[file_id]; + if ( c$smb_state$current_tree?$path ) + fl$path = c$smb_state$current_tree$path; + + c$smb_state$current_cmd$argument = fl$name; + + delete c$smb_state$fid_map[file_id]; SMB::write_file_log(fl); } else { + # TODO - Determine correct action # A reporter message is not right... #Reporter::warning("attempting to close an unknown file!"); } } + +event smb1_trans2_get_dfs_referral_request(c: connection, hdr: SMB1::Header, file_name: string, max_referral_level: count) + { + c$smb_state$current_cmd$argument = file_name; + } + +event smb1_trans2_query_path_info_request(c: connection, hdr: SMB1::Header, file_name: string, level_of_interets: count) + { + c$smb_state$current_cmd$argument = file_name; + } + +event smb1_trans2_find_first2_request(c: connection, hdr: SMB1::Header, args: SMB1::Find_First2_Request_Args) + { + c$smb_state$current_cmd$argument = args$file_name; + } + +event smb1_session_setup_andx_response(c: connection, hdr: SMB1::Header, response: SMB1::SessionSetupAndXResponse) &priority=-5 + { + if ( c$smb_state$current_cmd$status !in SMB::ignored_command_statuses ) + { + Log::write(SMB::CMD_LOG, c$smb_state$current_cmd); + } + } + +event smb_ntlm_negotiate(c: connection, hdr: SMB1::Header, request: SMB::NTLMNegotiate) + { + c$smb_state$current_cmd$sub_command = "NTLMSSP_NEGOTIATE"; + } + +event smb1_error(c: connection, hdr: SMB1::Header, is_orig: bool) + { + if ( ! is_orig ) + { + # This is for deferred commands only. + # The more specific messages won't fire for errors + if ( ( c$smb_state$current_cmd$status !in SMB::ignored_command_statuses ) && + ( c$smb_state$current_cmd$command in SMB::deferred_logging_cmds ) ) + { + Log::write(SMB::CMD_LOG, c$smb_state$current_cmd); + } + } + } + +event smb_ntlm_authenticate(c: connection, hdr: SMB1::Header, request: SMB::NTLMAuthenticate) + { + c$smb_state$current_cmd$sub_command = "NTLMSSP_AUTHENTICATE"; + + local user: string = ""; + if ( ( request?$domain_name && request$domain_name != "" ) && ( request?$user_name && request$user_name != "" ) ) + user = fmt("%s\\%s", request$domain_name, request$user_name); + else if ( ( request?$workstation && request$workstation != "" ) && ( request?$user_name && request$user_name != "" ) ) + user = fmt("%s\\%s", request$workstation, request$user_name); + else if ( request?$user_name && request$user_name != "" ) + user = request$user_name; + else if ( request?$domain_name && request$domain_name != "" ) + user = fmt("%s\\", request$domain_name); + else if ( request?$workstation && request$workstation != "" ) + user = fmt("%s", request$workstation); + + if ( user != "" ) + { + c$smb_state$current_cmd$argument = user; + } + + if ( hdr$uid !in c$smb_state$uid_map ) + { + c$smb_state$uid_map[hdr$uid] = user; + } + } + +event smb1_transaction_request(c: connection, hdr: SMB1::Header, name: string, sub_cmd: count) + { + c$smb_state$current_cmd$sub_command = SMB1::trans_sub_commands[sub_cmd]; + } + +event smb1_write_andx_request(c: connection, hdr: SMB1::Header, file_id: count, offset: count, data_len: count) + { + c$smb_state$pipe_map[file_id] = c$smb_state$current_file$uuid; + } + +event smb_pipe_bind_ack_response(c: connection, hdr: SMB1::Header) + { + c$smb_state$current_cmd$sub_command = "RPC_BIND_ACK"; + c$smb_state$current_cmd$argument = SMB::rpc_uuids[c$smb_state$current_file$uuid]; + } + +event smb_pipe_bind_request(c: connection, hdr: SMB1::Header, uuid: string, version: string) + { + c$smb_state$current_cmd$sub_command = "RPC_BIND"; + c$smb_state$current_file$uuid = uuid; + c$smb_state$current_cmd$argument = fmt("%s v%s", SMB::rpc_uuids[uuid], version); + } + +event smb_pipe_request(c: connection, hdr: SMB1::Header, op_num: count) + { + c$smb_state$current_cmd$argument = fmt("%s: %s", SMB::rpc_uuids[c$smb_state$current_file$uuid], + SMB::rpc_sub_cmds[c$smb_state$current_file$uuid][op_num]); + } + +#event smb1_transaction_setup(c: connection, hdr: SMB1::Header, op_code: count, file_id: count) +# { +# local uuid = SMB::rpc_uuids[c$smb_state$pipe_map[file_id]]; +# if ( uuid in SMB::rpc_uuids ) +# { +# print fmt("smb1_transaction_setup %s", SMB::rap_cmds[op_code]); +# } +# } \ No newline at end of file diff --git a/scripts/base/protocols/smb/smb2-main.bro b/scripts/base/protocols/smb/smb2-main.bro index 7380784192..a27d6a87d0 100644 --- a/scripts/base/protocols/smb/smb2-main.bro +++ b/scripts/base/protocols/smb/smb2-main.bro @@ -1,141 +1,143 @@ module SMB2; -redef record SMB::Info += { +redef record SMB::CmdInfo += { + ## Dialects offered by the client smb2_offered_dialects: index_vec &optional; }; event smb2_message(c: connection, hdr: SMB2::Header, is_orig: bool) &priority=5 { - if ( ! c?$smb ) + if ( ! c?$smb_state ) { - local info: SMB::Info = [$ts=network_time(), $uid=c$uid, $id=c$id, $version="SMB2"]; - info$fid_map = table(); - info$tid_map = table(); - info$pending_cmds = table(); - c$smb = info; + local state: SMB::State; + state$fid_map = table(); + state$tid_map = table(); + state$pending_cmds = table(); + c$smb_state = state; } - local smb = c$smb; + local smb_state = c$smb_state; local tid = hdr$tree_id; local pid = hdr$process_id; local mid = hdr$message_id; local sid = hdr$session_id; - if ( tid !in smb$tid_map ) + if ( tid !in smb_state$tid_map ) { local tmp_tree: SMB::TreeInfo = [$uid=c$uid, $id=c$id]; - smb$tid_map[tid] = tmp_tree; + smb_state$tid_map[tid] = tmp_tree; } - smb$current_tree = smb$tid_map[tid]; + smb_state$current_tree = smb_state$tid_map[tid]; - if ( mid !in smb$pending_cmds ) + if ( mid !in smb_state$pending_cmds ) { - local tmp_cmd: SMB::CmdInfo; - tmp_cmd$command = SMB2::commands[hdr$command]; + local tmp_cmd: SMB::CmdInfo = [$ts=network_time(), $uid=c$uid, $id=c$id, $version="SMB2", $command = SMB2::commands[hdr$command]]; - local tmp_file: SMB::FileInfo; - tmp_file$ts = network_time(); - tmp_file$id = c$id; - tmp_file$uid = c$uid; + local tmp_file: SMB::FileInfo = [$ts=network_time(), $uid=c$uid, $id=c$id]; tmp_cmd$referenced_file = tmp_file; - tmp_cmd$referenced_tree = smb$current_tree; + tmp_cmd$referenced_tree = smb_state$current_tree; - smb$pending_cmds[mid] = tmp_cmd; + smb_state$pending_cmds[mid] = tmp_cmd; } - smb$current_cmd = smb$pending_cmds[mid]; - smb$command = smb$current_cmd$command; + smb_state$current_cmd = smb_state$pending_cmds[mid]; - if ( is_orig ) + if ( !is_orig ) { - smb$ts = network_time(); - } - else - { - smb$rtt = network_time() - smb$ts; - smb$status = SMB::statuses[hdr$status]$id; + smb_state$current_cmd$rtt = network_time() - smb_state$current_cmd$ts; + smb_state$current_cmd$status = SMB::statuses[hdr$status]$id; } } event smb2_message(c: connection, hdr: SMB2::Header, is_orig: bool) &priority=-5 { + # Is this a response? if ( !is_orig ) - # This is a response and the command is no longer pending - # so let's get rid of it. - delete c$smb$pending_cmds[hdr$message_id]; - - if ( SMB::write_cmd_log && c?$smb ) - Log::write(SMB::CMD_LOG, c$smb); + { + if ( SMB::write_cmd_log && + c$smb_state$current_cmd$status !in SMB::ignored_command_statuses && + c$smb_state$current_cmd$command !in SMB::deferred_logging_cmds ) + { + Log::write(SMB::CMD_LOG, c$smb_state$current_cmd); + } + delete c$smb_state$pending_cmds[hdr$message_id]; + } } event smb2_negotiate_request(c: connection, hdr: SMB2::Header, dialects: index_vec) &priority=5 { - c$smb$smb2_offered_dialects = dialects; + c$smb_state$current_cmd$smb2_offered_dialects = dialects; } -event smb2_negotiate_response(c: connection, hdr: SMB2::Header, response: SMB2::NegotiateResponse) +event smb2_negotiate_response(c: connection, hdr: SMB2::Header, response: SMB2::NegotiateResponse) &priority=5 { - if ( c$smb?$smb2_offered_dialects ) + if ( c$smb_state$current_cmd?$smb2_offered_dialects ) { - for ( i in c$smb$smb2_offered_dialects ) + for ( i in c$smb_state$current_cmd$smb2_offered_dialects ) { - if ( response$dialect_revision == c$smb$smb2_offered_dialects[i] ) + if ( response$dialect_revision == c$smb_state$current_cmd$smb2_offered_dialects[i] ) { - c$smb$dialect = SMB2::dialects[response$dialect_revision]; + c$smb_state$current_cmd$argument = SMB2::dialects[response$dialect_revision]; break; } } - delete c$smb$smb2_offered_dialects; + delete c$smb_state$current_cmd$smb2_offered_dialects; } } +event smb2_negotiate_response(c: connection, hdr: SMB2::Header, response: SMB2::NegotiateResponse) &priority=5 + { + if ( c$smb_state$current_cmd$status !in SMB::ignored_command_statuses ) + { + Log::write(SMB::CMD_LOG, c$smb_state$current_cmd); + } + } + event smb2_tree_connect_request(c: connection, hdr: SMB2::Header, path: string) &priority=5 { - c$smb$current_cmd$referenced_tree$path = path; - c$smb$current_tree$ts=network_time(); + local tmp_tree: SMB::TreeInfo = [$ts=network_time(), $uid=c$uid, $id=c$id, $path=path]; + + c$smb_state$current_cmd$referenced_tree = tmp_tree; } event smb2_tree_connect_response(c: connection, hdr: SMB2::Header, response: SMB2::TreeConnectResponse) &priority=5 { - c$smb$current_tree = c$smb$current_cmd$referenced_tree; - c$smb$current_tree$share_type = SMB2::share_types[response$share_type]; - c$smb$tid_map[hdr$tree_id] = c$smb$current_tree; + c$smb_state$current_cmd$referenced_tree$share_type = SMB2::share_types[response$share_type]; + c$smb_state$current_tree = c$smb_state$current_cmd$referenced_tree; + c$smb_state$tid_map[hdr$tree_id] = c$smb_state$current_tree; } event smb2_tree_connect_response(c: connection, hdr: SMB2::Header, response: SMB2::TreeConnectResponse) &priority=-5 { - Log::write(SMB::MAPPING_LOG, c$smb$current_tree); + Log::write(SMB::MAPPING_LOG, c$smb_state$current_tree); } event smb2_create_request(c: connection, hdr: SMB2::Header, name: string) &priority=5 { - c$smb$current_cmd$referenced_file$name = name; - c$smb$current_file = c$smb$current_cmd$referenced_file; - c$smb$current_file$action = SMB::FILE_OPEN; + local tmp_file: SMB::FileInfo = [$ts=network_time(), $uid=c$uid, $id=c$id]; + c$smb_state$current_cmd$referenced_file = tmp_file; + c$smb_state$current_cmd$referenced_file$name = name; + c$smb_state$current_cmd$referenced_file$action = SMB::FILE_OPEN; + c$smb_state$current_file = c$smb_state$current_cmd$referenced_file; } event smb2_create_response(c: connection, hdr: SMB2::Header, file_id: SMB2::GUID, file_size: count, times: SMB::MACTimes, attrs: SMB2::FileAttrs) &priority=5 { - if ( ! c$smb?$current_file ) - { - c$smb$current_file = c$smb$current_cmd$referenced_file; - c$smb$current_file$action = SMB::FILE_OPEN; - } - c$smb$current_file$fid = file_id$persistent+file_id$volatile; - c$smb$current_file$size = file_size; + c$smb_state$current_cmd$referenced_file$action = SMB::FILE_OPEN; + c$smb_state$current_cmd$referenced_file$fid = file_id$persistent+file_id$volatile; + c$smb_state$current_cmd$referenced_file$size = file_size; # I'm seeing negative data from IPC tree transfers if ( time_to_double(times$modified) > 0.0 ) - c$smb$current_file$times = times; + c$smb_state$current_cmd$referenced_file$times = times; # We can identify the file by its file id now so let's stick it # in the file map. - c$smb$fid_map[file_id$persistent+file_id$volatile] = c$smb$current_file; - } - -event smb2_create_response(c: connection, hdr: SMB2::Header, file_id: SMB2::GUID, file_size: count, times: SMB::MACTimes, attrs: SMB2::FileAttrs) &priority=-5 - { - SMB::write_file_log(c$smb$current_file); + c$smb_state$fid_map[file_id$persistent+file_id$volatile] = c$smb_state$current_cmd$referenced_file; + + c$smb_state$current_file = c$smb_state$fid_map[file_id$persistent+file_id$volatile]; + + SMB::write_file_log(c$smb_state$current_file); } event smb2_set_info_request(c: connection, hdr: SMB2::Header, request: SMB2::SetInfoRequest) &priority=5 @@ -145,46 +147,55 @@ event smb2_set_info_request(c: connection, hdr: SMB2::Header, request: SMB2::Set event smb2_read_request(c: connection, hdr: SMB2::Header, file_id: SMB2::GUID, offset: count, length: count) &priority=5 { - SMB::set_current_file(c$smb, file_id$persistent+file_id$volatile); - c$smb$current_file$action = SMB::FILE_READ; + SMB::set_current_file(c$smb_state, file_id$persistent+file_id$volatile); + c$smb_state$current_file$action = SMB::FILE_READ; + } - if ( c$smb$current_tree?$path && !c$smb$current_file?$path ) - c$smb$current_file$path = c$smb$current_tree$path; +event smb2_read_request(c: connection, hdr: SMB2::Header, file_id: SMB2::GUID, offset: count, length: count) &priority=-5 + { + if ( c$smb_state$current_tree?$path && !c$smb_state$current_file?$path ) + c$smb_state$current_file$path = c$smb_state$current_tree$path; - #write_file_log(c$smb$current_file); + # TODO - Why is this commented out? + #write_file_log(c$smb_state$current_file); } event smb2_write_request(c: connection, hdr: SMB2::Header, file_id: SMB2::GUID, offset: count, length: count) &priority=5 { - SMB::set_current_file(c$smb, file_id$persistent+file_id$volatile); - c$smb$current_file$action = SMB::FILE_WRITE; + SMB::set_current_file(c$smb_state, file_id$persistent+file_id$volatile); + c$smb_state$current_file$action = SMB::FILE_WRITE; + } - if ( c$smb$current_tree?$path && ! c$smb$current_file?$path ) - c$smb$current_file$path = c$smb$current_tree$path; +event smb2_write_request(c: connection, hdr: SMB2::Header, file_id: SMB2::GUID, offset: count, length: count) &priority=-5 + { + if ( c$smb_state$current_tree?$path && ! c$smb_state$current_file?$path ) + c$smb_state$current_file$path = c$smb_state$current_tree$path; + + # TODO - Why is this commented out? + #write_file_log(c$smb_state$current_file); } event smb2_close_request(c: connection, hdr: SMB2::Header, file_id: SMB2::GUID) &priority=5 { - SMB::set_current_file(c$smb, file_id$persistent+file_id$volatile); - c$smb$current_file$action = SMB::FILE_CLOSE; + SMB::set_current_file(c$smb_state, file_id$persistent+file_id$volatile); + c$smb_state$current_file$action = SMB::FILE_CLOSE; } event smb2_close_request(c: connection, hdr: SMB2::Header, file_id: SMB2::GUID) &priority=-5 { - if ( file_id$persistent+file_id$volatile in c$smb$fid_map ) + if ( file_id$persistent+file_id$volatile in c$smb_state$fid_map ) { - local fl = c$smb$fid_map[file_id$persistent+file_id$volatile]; - fl$uid = c$uid; - fl$id = c$id; + local fl = c$smb_state$fid_map[file_id$persistent+file_id$volatile]; # Need to check for existence of path in case tree connect message wasn't seen. - if ( c$smb$current_tree?$path ) - fl$path = c$smb$current_tree$path; - delete c$smb$fid_map[file_id$persistent+file_id$volatile]; + if ( c$smb_state$current_tree?$path ) + fl$path = c$smb_state$current_tree$path; + delete c$smb_state$fid_map[file_id$persistent+file_id$volatile]; SMB::write_file_log(fl); } else { + # TODO - Determine correct action # A reporter message is not right... #Reporter::warning("attempting to close an unknown file!"); } diff --git a/src/analyzer/protocol/smb/CMakeLists.txt b/src/analyzer/protocol/smb/CMakeLists.txt index 4dbb4d1d31..305e1191f6 100644 --- a/src/analyzer/protocol/smb/CMakeLists.txt +++ b/src/analyzer/protocol/smb/CMakeLists.txt @@ -20,6 +20,8 @@ bro_plugin_bif( smb1_com_query_information.bif smb1_com_read_andx.bif smb1_com_session_setup_andx.bif + smb1_com_transaction.bif + smb1_com_transaction2.bif smb1_com_tree_connect_andx.bif smb1_com_tree_disconnect.bif smb1_com_write_andx.bif diff --git a/src/analyzer/protocol/smb/DCE_RPC.cc b/src/analyzer/protocol/smb/DCE_RPC.cc new file mode 100644 index 0000000000..dd31cfa8a7 --- /dev/null +++ b/src/analyzer/protocol/smb/DCE_RPC.cc @@ -0,0 +1,588 @@ +// See the file "COPYING" in the main distribution directory for copyright. + +#include "config.h" + +#include +#include +#include + +using namespace std; + +#include "DCE_RPC.h" +#include "Sessions.h" + +#include "analyzer/Manager.h" + +#include "events.bif.h" + +using namespace analyzer::dce_rpc; + +#define xbyte(b, n) (((const u_char*) (b))[n]) + +#define extract_uint16(little_endian, bytes) \ + ((little_endian) ? \ + uint16(xbyte(bytes, 0)) | ((uint16(xbyte(bytes, 1))) << 8) : \ + uint16(xbyte(bytes, 1)) | ((uint16(xbyte(bytes, 0))) << 8)) + +static int uuid_index[] = { + 3, 2, 1, 0, + 5, 4, 7, 6, + 8, 9, 10, 11, + 12, 13, 14, 15 +}; + +const char* analyzer::dce_rpc::uuid_to_string(const u_char* uuid_data) + { + static char s[1024]; + char* sp = s; + + for ( int i = 0; i < 16; ++i ) + { + if ( i == 4 || i == 6 || i == 8 || i == 10 ) + sp += snprintf(sp, s + sizeof(s) - sp, "-"); + + int j = uuid_index[i]; + sp += snprintf(sp, s + sizeof(s) - sp, "%02x", uuid_data[j]); + } + + return s; + } + +UUID::UUID() + { + memset(data, 0, 16); + s = uuid_to_string(data); + } + +UUID::UUID(const u_char d[16]) + { + memcpy(data, d, 16); + s = uuid_to_string(data); + } + +UUID::UUID(const binpac::bytestring& uuid) + { + if ( uuid.length() != 16 ) + reporter->InternalError("UUID length error"); + memcpy(data, uuid.begin(), 16); + s = uuid_to_string(data); + } + +UUID::UUID(const char* str) + { + s = string(str); + const char* sp = str; + int i; + for ( i = 0; i < 16; ++i ) + { + if ( *sp == '-' ) + ++sp; + if ( ! *sp || ! *(sp+1) ) + break; + + data[uuid_index[i]] = + (u_char) (decode_hex(*sp) * 16 + decode_hex(*(sp+1))); + } + + if ( i != 16 ) + reporter->InternalError("invalid UUID string: %s", str); + } + +typedef map uuid_map_t; + +static uuid_map_t& well_known_uuid_map() + { + static uuid_map_t the_map; + static bool initialized = false; + + if ( initialized ) + return the_map; + + using namespace BifEnum; + + the_map[UUID("e1af8308-5d1f-11c9-91a4-08002b14a0fa")] = DCE_RPC_epmapper; + + the_map[UUID("afa8bd80-7d8a-11c9-bef4-08002b102989")] = DCE_RPC_mgmt; + + // It's said that the following interfaces are merely aliases. + the_map[UUID("12345778-1234-abcd-ef00-0123456789ab")] = DCE_RPC_lsarpc; + the_map[UUID("12345678-1234-abcd-ef00-01234567cffb")] = DCE_RPC_netlogon; + the_map[UUID("12345778-1234-abcd-ef00-0123456789ac")] = DCE_RPC_samr; + + // The next group of aliases. + the_map[UUID("4b324fc8-1670-01d3-1278-5a47bf6ee188")] = DCE_RPC_srvsvc; + the_map[UUID("12345678-1234-abcd-ef00-0123456789ab")] = DCE_RPC_spoolss; + the_map[UUID("45f52c28-7f9f-101a-b52b-08002b2efabe")] = DCE_RPC_winspipe; + the_map[UUID("6bffd098-a112-3610-9833-46c3f87e345a")] = DCE_RPC_wkssvc; + + // DRS - NT directory replication service. + the_map[UUID("e3514235-4b06-11d1-ab04-00c04fc2dcd2")] = DCE_RPC_drs; + + // "The IOXIDResolver RPC interface (formerly known as + // IObjectExporter) is remotely used to reach the local object + // resolver (OR)." + the_map[UUID("99fcfec4-5260-101b-bbcb-00aa0021347a")] = DCE_RPC_oxid; + + the_map[UUID("3919286a-b10c-11d0-9ba8-00c04fd92ef5")] = DCE_RPC_lsa_ds; + + the_map[UUID("000001a0-0000-0000-c000-000000000046")] = DCE_RPC_ISCMActivator; + + initialized = true; + return the_map; + } + +// Used to remember mapped DCE/RPC endpoints and parse the follow-up +// connections as DCE/RPC sessions. +map dce_rpc_endpoints; + +static bool is_mapped_dce_rpc_endpoint(const dce_rpc_endpoint_addr& addr) + { + return dce_rpc_endpoints.find(addr) != dce_rpc_endpoints.end(); + } + +bool is_mapped_dce_rpc_endpoint(const ConnID* id, TransportProto proto) + { + if ( id->dst_addr.GetFamily() == IPv6 ) + // TODO: Does the protocol support v6 addresses? #773 + return false; + + dce_rpc_endpoint_addr addr; + addr.addr = id->dst_addr; + addr.port = ntohs(id->dst_port); + addr.proto = proto; + + return is_mapped_dce_rpc_endpoint(addr); + } + +static void add_dce_rpc_endpoint(const dce_rpc_endpoint_addr& addr, + const UUID& uuid) + { + DEBUG_MSG("Adding endpoint %s @ %s\n", + uuid.to_string(), addr.to_string().c_str()); + dce_rpc_endpoints[addr] = uuid; + + // FIXME: Once we can pass the cookie to the analyzer, we can get rid + // of the dce_rpc_endpoints table. + // FIXME: Don't hard-code the timeout. + + analyzer_mgr->ScheduleAnalyzer(IPAddr(), addr.addr, addr.port, addr.proto, + "DCE_RPC", 5 * 60); + } + +DCE_RPC_Header::DCE_RPC_Header(analyzer::Analyzer* a, const u_char* b) + { + analyzer = a; + bytes = b; + + // This checks whether it's both the first fragment *and* + // the last fragment. + if ( (bytes[3] & 0x3) != 0x3 ) + { + fragmented = 1; + Weird("Fragmented DCE/RPC message"); + } + else + fragmented = 0; + + ptype = (BifEnum::dce_rpc_ptype) bytes[2]; + frag_len = extract_uint16(LittleEndian(), bytes + 8); + } + +DCE_RPC_Session::DCE_RPC_Session(analyzer::Analyzer* a) +: analyzer(a), + if_uuid("00000000-0000-0000-0000-000000000000"), + if_id(BifEnum::DCE_RPC_unknown_if) + { + opnum = -1; + } + +bool DCE_RPC_Session::LooksLikeRPC(int len, const u_char* msg) + { + // if ( ! is_IPC ) + // return false; + + try + { + binpac::DCE_RPC_Simple::DCE_RPC_Header h; + h.Parse(msg, msg + len); + if ( h.rpc_vers() == 5 && h.rpc_vers_minor() == 0 ) + { + if ( h.frag_length() == len ) + return true; + else + { + DEBUG_MSG("length mismatch: %d != %d\n", + h.frag_length(), len); + return false; + } + } + } + catch ( const binpac::Exception& ) + { + // do nothing + } + + return false; + } + +void DCE_RPC_Session::DeliverPDU(int is_orig, int len, const u_char* data) + { + if ( dce_rpc_message ) + { + val_list* vl = new val_list; + vl->append(analyzer->BuildConnVal()); + vl->append(new Val(is_orig, TYPE_BOOL)); + vl->append(new EnumVal(data[2], BifType::Enum::dce_rpc_ptype)); + vl->append(new StringVal(len, (const char*) data)); + + analyzer->ConnectionEvent(dce_rpc_message, vl); + } + + try + { + // TODO: handle incremental input + binpac::DCE_RPC_Simple::DCE_RPC_PDU pdu; + pdu.Parse(data, data + len); + + switch ( pdu.header()->PTYPE() ) { + case binpac::DCE_RPC_Simple::DCE_RPC_BIND: + case binpac::DCE_RPC_Simple::DCE_RPC_ALTER_CONTEXT: + DeliverBind(&pdu); + break; + + case binpac::DCE_RPC_Simple::DCE_RPC_REQUEST: + DeliverRequest(&pdu); + break; + + case binpac::DCE_RPC_Simple::DCE_RPC_RESPONSE: + DeliverResponse(&pdu); + break; + } + } + catch ( const binpac::Exception& e ) + { + analyzer->Weird(e.msg().c_str()); + } + } + +void DCE_RPC_Session::DeliverBind(const binpac::DCE_RPC_Simple::DCE_RPC_PDU* pdu) + { + binpac::DCE_RPC_Simple::DCE_RPC_Bind* bind = pdu->body()->bind(); + + for ( int i = 0; i < bind->p_context_elem()->n_context_elem(); ++i ) + { + binpac::DCE_RPC_Simple::p_cont_elem_t* elem = + (*bind->p_context_elem()->p_cont_elem())[i]; + + if_uuid = UUID(elem->abstract_syntax()->if_uuid().begin()); + uuid_map_t::const_iterator uuid_it = + well_known_uuid_map().find(if_uuid); + + if ( uuid_it == well_known_uuid_map().end() ) + { +#ifdef DEBUG + // conn->Weird(fmt("Unknown DCE_RPC interface %s", + // if_uuid.to_string())); +#endif + if_id = BifEnum::DCE_RPC_unknown_if; + } + else + if_id = uuid_it->second; + + if ( dce_rpc_bind ) + { + val_list* vl = new val_list; + vl->append(analyzer->BuildConnVal()); + vl->append(new StringVal(if_uuid.to_string())); + // vl->append(new EnumVal(if_id, BifType::Enum::dce_rpc_if_id)); + + analyzer->ConnectionEvent(dce_rpc_bind, vl); + } + } + } + +void DCE_RPC_Session::DeliverRequest(const binpac::DCE_RPC_Simple::DCE_RPC_PDU* pdu) + { + binpac::DCE_RPC_Simple::DCE_RPC_Request* req = pdu->body()->request(); + + opnum = req->opnum(); + + if ( dce_rpc_request ) + { + val_list* vl = new val_list; + vl->append(analyzer->BuildConnVal()); + vl->append(new Val(opnum, TYPE_COUNT)); + vl->append(new StringVal(req->stub().length(), + (const char*) req->stub().begin())); + + analyzer->ConnectionEvent(dce_rpc_request, vl); + } + + switch ( if_id ) { + case BifEnum::DCE_RPC_epmapper: + DeliverEpmapperRequest(pdu, req); + break; + + default: + break; + } + } + +void DCE_RPC_Session::DeliverResponse(const binpac::DCE_RPC_Simple::DCE_RPC_PDU* pdu) + { + binpac::DCE_RPC_Simple::DCE_RPC_Response* resp = pdu->body()->response(); + + if ( dce_rpc_response ) + { + val_list* vl = new val_list; + vl->append(analyzer->BuildConnVal()); + vl->append(new Val(opnum, TYPE_COUNT)); + vl->append(new StringVal(resp->stub().length(), + (const char*) resp->stub().begin())); + analyzer->ConnectionEvent(dce_rpc_response, vl); + } + + switch ( if_id ) { + case BifEnum::DCE_RPC_epmapper: + DeliverEpmapperResponse(pdu, resp); + break; + + default: + break; + } + } + +void DCE_RPC_Session::DeliverEpmapperRequest( + const binpac::DCE_RPC_Simple::DCE_RPC_PDU* /* pdu */, + const binpac::DCE_RPC_Simple::DCE_RPC_Request* /* req */) + { + // DEBUG_MSG("Epmapper request opnum = %d\n", req->opnum()); + // ### TODO(rpang): generate an event on epmapper request + } + +void DCE_RPC_Session::DeliverEpmapperResponse( + const binpac::DCE_RPC_Simple::DCE_RPC_PDU* pdu, + const binpac::DCE_RPC_Simple::DCE_RPC_Response* resp) + { + // DEBUG_MSG("Epmapper request opnum = %d\n", req->opnum()); + switch ( opnum ) { + case 3: // Map + DeliverEpmapperMapResponse(pdu, resp); + break; + } + } + + +void DCE_RPC_Session::DeliverEpmapperMapResponse( + const binpac::DCE_RPC_Simple::DCE_RPC_PDU* pdu, + const binpac::DCE_RPC_Simple::DCE_RPC_Response* resp) + { + try + { + binpac::DCE_RPC_Simple::epmapper_map_resp epm_resp; + + epm_resp.Parse(resp->stub().begin(), resp->stub().end(), + pdu->byteorder()); + + for ( unsigned int twr_i = 0; + twr_i < epm_resp.towers()->actual_count(); ++twr_i ) + { + binpac::DCE_RPC_Simple::epm_tower* twr = + (*epm_resp.towers()->towers())[twr_i]->tower(); + + mapped.addr = dce_rpc_endpoint_addr(); + mapped.uuid = UUID(); + + for ( int floor_i = 0; floor_i < twr->num_floors(); + ++floor_i ) + { + binpac::DCE_RPC_Simple::epm_floor* floor = + (*twr->floors())[floor_i]; + + switch ( floor->protocol() ) { + case binpac::DCE_RPC_Simple::EPM_PROTOCOL_UUID: + if ( floor_i == 0 ) + mapped.uuid = UUID(floor->lhs()->data()->uuid()->if_uuid()); + break; + + case binpac::DCE_RPC_Simple::EPM_PROTOCOL_TCP: + mapped.addr.port = + floor->rhs()->data()->tcp(); + mapped.addr.proto = TRANSPORT_TCP; + break; + + case binpac::DCE_RPC_Simple::EPM_PROTOCOL_UDP: + mapped.addr.port = + floor->rhs()->data()->udp(); + mapped.addr.proto = TRANSPORT_UDP; + break; + + case binpac::DCE_RPC_Simple::EPM_PROTOCOL_IP: + uint32 hostip = floor->rhs()->data()->ip(); + mapped.addr.addr = IPAddr(IPv4, &hostip, IPAddr::Host); + break; + } + } + + if ( mapped.addr.is_valid_addr() ) + add_dce_rpc_endpoint(mapped.addr, mapped.uuid); + + if ( epm_map_response ) + { + val_list* vl = new val_list; + vl->append(analyzer->BuildConnVal()); + vl->append(new StringVal(mapped.uuid.to_string())); + vl->append(new PortVal(mapped.addr.port, mapped.addr.proto)); + vl->append(new AddrVal(mapped.addr.addr)); + + analyzer->ConnectionEvent(epm_map_response, vl); + } + } + } + catch ( const binpac::Exception& e ) + { + analyzer->Weird(e.msg().c_str()); + } + } + +Contents_DCE_RPC_Analyzer::Contents_DCE_RPC_Analyzer(Connection* conn, + bool orig, DCE_RPC_Session* arg_session, bool speculative) +: tcp::TCP_SupportAnalyzer("CONTENTS_DCE_RPC", conn, orig) + { + session = arg_session; + msg_buf = 0; + buf_len = 0; + speculation = speculative ? 0 : 1; + + InitState(); + } + +void Contents_DCE_RPC_Analyzer::InitState() + { + // Allocate space for header. + if ( ! msg_buf ) + { + buf_len = DCE_RPC_HEADER_LENGTH; + msg_buf = new u_char[buf_len]; + } + + buf_n = 0; + msg_len = 0; + hdr = 0; + } + +Contents_DCE_RPC_Analyzer::~Contents_DCE_RPC_Analyzer() + { + delete [] msg_buf; + delete hdr; + } + +void Contents_DCE_RPC_Analyzer::DeliverStream(int len, const u_char* data, bool orig) + { + tcp::TCP_SupportAnalyzer::DeliverStream(len, data, orig); + + tcp::TCP_Analyzer* tcp = + static_cast(Parent())->TCP(); + + if ( tcp->HadGap(orig) || tcp->IsPartial() ) + return; + + if ( speculation == 0 ) // undecided + { + if ( ! DCE_RPC_Session::LooksLikeRPC(len, data) ) + speculation = -1; + else + speculation = 1; + } + + if ( speculation < 0 ) + return; + + ASSERT(buf_len >= DCE_RPC_HEADER_LENGTH); + while ( len > 0 ) + { + if ( buf_n < DCE_RPC_HEADER_LENGTH ) + { + while ( buf_n < DCE_RPC_HEADER_LENGTH && len > 0 ) + { + msg_buf[buf_n] = *data; + ++buf_n; ++data; --len; + } + + if ( buf_n < DCE_RPC_HEADER_LENGTH ) + break; + else + { + if ( ! ParseHeader() ) + return; + } + } + + while ( buf_n < msg_len && len > 0 ) + { + msg_buf[buf_n] = *data; + ++buf_n; ++data; --len; + } + + if ( buf_n < msg_len ) + break; + else + { + if ( msg_len > 0 ) + DeliverPDU(msg_len, msg_buf); + // Reset for next message + InitState(); + } + } + } + +void Contents_DCE_RPC_Analyzer::DeliverPDU(int len, const u_char* data) + { + session->DeliverPDU(IsOrig(), len, data); + } + +bool Contents_DCE_RPC_Analyzer::ParseHeader() + { + delete hdr; + hdr = 0; + + if ( msg_buf[0] != 5 ) // DCE/RPC version + { + Conn()->Weird("DCE/RPC_version_error (non-DCE/RPC?)"); + Conn()->SetSkip(1); + msg_len = 0; + return false; + } + + hdr = new DCE_RPC_Header(this, msg_buf); + + msg_len = hdr->FragLen(); + if ( msg_len > buf_len ) + { + u_char* new_msg_buf = new u_char[msg_len]; + memcpy(new_msg_buf, msg_buf, buf_n); + delete [] msg_buf; + buf_len = msg_len; + msg_buf = new_msg_buf; + hdr->SetBytes(new_msg_buf); + } + + return true; + } + +DCE_RPC_Analyzer::DCE_RPC_Analyzer(Connection* conn, bool arg_speculative) +: tcp::TCP_ApplicationAnalyzer("DCE_RPC", conn) + { + session = new DCE_RPC_Session(this); + speculative = arg_speculative; + + AddSupportAnalyzer(new Contents_DCE_RPC_Analyzer(conn, true, session, + speculative)); + AddSupportAnalyzer(new Contents_DCE_RPC_Analyzer(conn, false, session, + speculative)); + } + +DCE_RPC_Analyzer::~DCE_RPC_Analyzer() + { + delete session; + } diff --git a/src/analyzer/protocol/smb/DCE_RPC.h b/src/analyzer/protocol/smb/DCE_RPC.h new file mode 100644 index 0000000000..cd3910bf42 --- /dev/null +++ b/src/analyzer/protocol/smb/DCE_RPC.h @@ -0,0 +1,191 @@ +// See the file "COPYING" in the main distribution directory for copyright. + +#ifndef ANALYZER_PROTOCOL_DCE_RPC_DCE_RPC_H +#define ANALYZER_PROTOCOL_DCE_RPC_DCE_RPC_H + +// NOTE: This is a somewhat crude analyzer for DCE/RPC (used on Microsoft +// Windows systems) and shouldn't be considered as stable. + +#include "NetVar.h" +#include "analyzer/protocol/tcp/TCP.h" +#include "analyzer/protocol/dce-rpc/events.bif.h" +#include "IPAddr.h" + +#include "dce_rpc_simple_pac.h" + + +namespace analyzer { namespace dce_rpc { + +class UUID { +public: + UUID(); + UUID(const u_char data[16]); + UUID(const binpac::bytestring &uuid); + UUID(const char* s); + + const char* to_string() const { return s.c_str(); } + const string& str() const { return s; } + bool operator==(const UUID& u) const + { return s == u.str(); } + bool operator<(const UUID& u) const + { return s < u.str(); } + +protected: + u_char data[16]; + string s; +}; + +const char* uuid_to_string(const u_char* uuid_data); + +struct dce_rpc_endpoint_addr { + // All fields are in host byteorder. + IPAddr addr; + u_short port; + TransportProto proto; + + dce_rpc_endpoint_addr() + { + addr = IPAddr(); + port = 0; + proto = TRANSPORT_UNKNOWN; + } + + bool is_valid_addr() const + { return addr != IPAddr() && port != 0 && proto != TRANSPORT_UNKNOWN; } + + bool operator<(dce_rpc_endpoint_addr const &e) const + { + if ( addr != e.addr ) + return addr < e.addr; + if ( proto != e.proto ) + return proto < e.proto; + if ( port != e.port ) + return port < e.port; + + return false; + } + + string to_string() const + { + static char buf[128]; + snprintf(buf, sizeof(buf), "%s/%d/%s", + addr.AsString().c_str(), port, + proto == TRANSPORT_TCP ? "tcp" : + (proto == TRANSPORT_UDP ? "udp" : "?")); + + return string(buf); + } +}; + +/* +enum DCE_RPC_PTYPE { + DCE_RPC_REQUEST, DCE_RPC_PING, DCE_RPC_RESPONSE, DCE_RPC_FAULT, + DCE_RPC_WORKING, DCE_RPC_NOCALL, DCE_RPC_REJECT, DCE_RPC_ACK, + DCE_RPC_CL_CANCEL, DCE_RPC_FACK, DCE_RPC_CANCEL_ACK, DCE_RPC_BIND, + DCE_RPC_BIND_ACK, DCE_RPC_BIND_NAK, DCE_RPC_ALTER_CONTEXT, + DCE_RPC_ALTER_CONTEXT_RESP, DCE_RPC_SHUTDOWN, DCE_RPC_CO_CANCEL, + DCE_RPC_ORPHANED, +}; +*/ + +#define DCE_RPC_HEADER_LENGTH 16 + +class DCE_RPC_Header { +public: + DCE_RPC_Header(analyzer::Analyzer* a, const u_char* bytes); + + BifEnum::dce_rpc_ptype PTYPE() const { return ptype; } + int FragLen() const { return frag_len; } + int LittleEndian() const { return bytes[4] >> 4; } + bool Fragmented() const { return fragmented; } + + void Weird(const char* msg) { analyzer->Weird(msg); } + void SetBytes(const u_char* b) { bytes = b; } + +protected: + analyzer::Analyzer* analyzer; + const u_char* bytes; + BifEnum::dce_rpc_ptype ptype; + int frag_len; + bool fragmented; +}; + +// Create a general DCE_RPC_Session class so that it can be used in +// case the RPC conversation is tunneled through other connections, +// e.g. through an SMB session. + +class DCE_RPC_Session { +public: + DCE_RPC_Session(analyzer::Analyzer* a); + virtual ~DCE_RPC_Session() {} + virtual void DeliverPDU(int is_orig, int len, const u_char* data); + + static bool LooksLikeRPC(int len, const u_char* msg); + static bool any_dce_rpc_event() + { return dce_rpc_message || dce_rpc_bind || dce_rpc_request; } + +protected: + void DeliverBind(const binpac::DCE_RPC_Simple::DCE_RPC_PDU* pdu); + void DeliverRequest(const binpac::DCE_RPC_Simple::DCE_RPC_PDU* pdu); + void DeliverResponse(const binpac::DCE_RPC_Simple::DCE_RPC_PDU* pdu); + + void DeliverEpmapperRequest( + const binpac::DCE_RPC_Simple::DCE_RPC_PDU* pdu, + const binpac::DCE_RPC_Simple::DCE_RPC_Request* req); + void DeliverEpmapperResponse( + const binpac::DCE_RPC_Simple::DCE_RPC_PDU* pdu, + const binpac::DCE_RPC_Simple::DCE_RPC_Response* resp); + void DeliverEpmapperMapResponse( + const binpac::DCE_RPC_Simple::DCE_RPC_PDU* pdu, + const binpac::DCE_RPC_Simple::DCE_RPC_Response* resp); + + analyzer::Analyzer* analyzer; + UUID if_uuid; + BifEnum::dce_rpc_if_id if_id; + int opnum; + struct { + dce_rpc_endpoint_addr addr; + UUID uuid; + } mapped; +}; + +class Contents_DCE_RPC_Analyzer : public tcp::TCP_SupportAnalyzer { +public: + Contents_DCE_RPC_Analyzer(Connection* conn, bool orig, DCE_RPC_Session* session, + bool speculative); + ~Contents_DCE_RPC_Analyzer(); + +protected: + virtual void DeliverStream(int len, const u_char* data, bool orig); + virtual void DeliverPDU(int len, const u_char* data); + + void InitState(); + + int speculation; + u_char* msg_buf; + int msg_len; + int buf_n; // number of bytes in msg_buf + int buf_len; // size off msg_buf + DCE_RPC_Header* hdr; + + bool ParseHeader(); + + DCE_RPC_Session* session; +}; + +class DCE_RPC_Analyzer : public tcp::TCP_ApplicationAnalyzer { +public: + DCE_RPC_Analyzer(Connection* conn, bool speculative = false); + ~DCE_RPC_Analyzer(); + + static analyzer::Analyzer* Instantiate(Connection* conn) + { return new DCE_RPC_Analyzer(conn); } + +protected: + DCE_RPC_Session* session; + bool speculative; +}; + +} } // namespace analyzer::* + +#endif /* dce_rpc_h */ diff --git a/src/analyzer/protocol/smb/dce_rpc-protocol.pac b/src/analyzer/protocol/smb/dce_rpc-protocol.pac index 5e0ccc8cab..20b7edc1ef 100644 --- a/src/analyzer/protocol/smb/dce_rpc-protocol.pac +++ b/src/analyzer/protocol/smb/dce_rpc-protocol.pac @@ -88,6 +88,16 @@ type DCE_RPC_Bind = record { p_context_elem : p_cont_list_t; }; +type DCE_RPC_Bind_Ack = record { + max_xmit_frag : uint16; + max_recv_frag : uint16; + assoc_group_id : uint32; + sec_addr_length : uint16; + sec_addr : bytestring &length=sec_addr_length; + pad : padding align 4; + p_context_elem : p_cont_list_t; +}; + type DCE_RPC_AlterContext = record { max_xmit_frag : uint16; max_recv_frag : uint16; @@ -115,6 +125,7 @@ type DCE_RPC_Response = record { type DCE_RPC_Body(header: DCE_RPC_Header) = case header.PTYPE of { DCE_RPC_BIND -> bind : DCE_RPC_Bind; + DCE_RPC_BIND_ACK -> bind_ack: DCE_RPC_Bind_Ack; DCE_RPC_REQUEST -> request : DCE_RPC_Request; DCE_RPC_RESPONSE -> response : DCE_RPC_Response; default -> other : bytestring &restofdata; diff --git a/src/analyzer/protocol/smb/smb-ntlmssp-asn1.pac b/src/analyzer/protocol/smb/smb-ntlmssp-asn1.pac new file mode 100644 index 0000000000..0cb459d26c --- /dev/null +++ b/src/analyzer/protocol/smb/smb-ntlmssp-asn1.pac @@ -0,0 +1,57 @@ +# Supporting types for ASN.1 +# +# From the Kerberos analyzer +# +# TODO: Figure out a way to include this code only once. + +type ASN1Encoding = record { + meta: ASN1EncodingMeta; + content: bytestring &length = meta.length; +}; + +type ASN1EncodingMeta = record { + tag: uint8; + len: uint8; + more_len: bytestring &length = long_len ? len & 0x7f : 0; +} &let { + long_len: bool = len & 0x80; + length: uint64 = long_len ? binary_to_int64(more_len) : len & 0x7f; + index: uint8 = tag - 160; +}; + +type ASN1Integer = record { + encoding: ASN1Encoding; +}; + +type ASN1OctetString = record { + encoding: ASN1Encoding; +}; + +type SequenceElement(grab_content: bool) = record { + index_meta: ASN1EncodingMeta; + have_content: case grab_content of { + true -> data: ASN1Encoding; + false -> meta: ASN1EncodingMeta; + }; +} &let { + index: uint8 = index_meta.index; + length: uint64 = index_meta.length; +}; + +type Array = record { + array_meta: ASN1EncodingMeta; + data: ASN1Encoding[]; +}; + +function binary_to_int64(bs: bytestring): int64 + %{ + int64 rval = 0; + + for ( int i = 0; i < bs.length(); ++i ) + { + uint64 byte = bs[i]; + rval |= byte << (8 * (bs.length() - (i + 1))); + } + + return rval; + %} \ No newline at end of file diff --git a/src/analyzer/protocol/smb/smb-ntlmssp.pac b/src/analyzer/protocol/smb/smb-ntlmssp.pac index d95177c5a3..bb1c353281 100644 --- a/src/analyzer/protocol/smb/smb-ntlmssp.pac +++ b/src/analyzer/protocol/smb/smb-ntlmssp.pac @@ -16,32 +16,32 @@ refine connection SMB_Conn += { function build_negotiate_flag_record(val: SMB_NTLM_Negotiate_Flags): BroVal %{ RecordVal* flags = new RecordVal(BifType::Record::SMB::NTLMNegotiateFlags); - flags->Assign(0, new Val(${val.negotiate_56}, TYPE_BOOL)); - flags->Assign(1, new Val(${val.negotiate_key_exch}, TYPE_BOOL)); - flags->Assign(2, new Val(${val.negotiate_128}, TYPE_BOOL)); - flags->Assign(3, new Val(${val.negotiate_version}, TYPE_BOOL)); - flags->Assign(4, new Val(${val.negotiate_target_info}, TYPE_BOOL)); + flags->Assign(0, new Val(${val.negotiate_56}, TYPE_BOOL)); + flags->Assign(1, new Val(${val.negotiate_key_exch}, TYPE_BOOL)); + flags->Assign(2, new Val(${val.negotiate_128}, TYPE_BOOL)); + flags->Assign(3, new Val(${val.negotiate_version}, TYPE_BOOL)); + flags->Assign(4, new Val(${val.negotiate_target_info}, TYPE_BOOL)); - flags->Assign(5, new Val(${val.request_non_nt_session_key}, TYPE_BOOL)); - flags->Assign(6, new Val(${val.negotiate_identify}, TYPE_BOOL)); - flags->Assign(7, new Val(${val.negotiate_extended_sessionsecurity}, TYPE_BOOL)); - flags->Assign(8, new Val(${val.target_type_server}, TYPE_BOOL)); - flags->Assign(9, new Val(${val.target_type_domain}, TYPE_BOOL)); + flags->Assign(5, new Val(${val.request_non_nt_session_key}, TYPE_BOOL)); + flags->Assign(6, new Val(${val.negotiate_identify}, TYPE_BOOL)); + flags->Assign(7, new Val(${val.negotiate_extended_sessionsecurity}, TYPE_BOOL)); + flags->Assign(8, new Val(${val.target_type_server}, TYPE_BOOL)); + flags->Assign(9, new Val(${val.target_type_domain}, TYPE_BOOL)); - flags->Assign(10, new Val(${val.negotiate_always_sign}, TYPE_BOOL)); - flags->Assign(11, new Val(${val.negotiate_oem_workstation_supplied}, TYPE_BOOL)); - flags->Assign(12, new Val(${val.negotiate_oem_domain_supplied}, TYPE_BOOL)); - flags->Assign(13, new Val(${val.negotiate_anonymous_connection}, TYPE_BOOL)); - flags->Assign(14, new Val(${val.negotiate_ntlm}, TYPE_BOOL)); + flags->Assign(10, new Val(${val.negotiate_always_sign}, TYPE_BOOL)); + flags->Assign(11, new Val(${val.negotiate_oem_workstation_supplied}, TYPE_BOOL)); + flags->Assign(12, new Val(${val.negotiate_oem_domain_supplied}, TYPE_BOOL)); + flags->Assign(13, new Val(${val.negotiate_anonymous_connection}, TYPE_BOOL)); + flags->Assign(14, new Val(${val.negotiate_ntlm}, TYPE_BOOL)); - flags->Assign(15, new Val(${val.negotiate_lm_key}, TYPE_BOOL)); - flags->Assign(16, new Val(${val.negotiate_datagram}, TYPE_BOOL)); - flags->Assign(17, new Val(${val.negotiate_seal}, TYPE_BOOL)); - flags->Assign(18, new Val(${val.negotiate_sign}, TYPE_BOOL)); - flags->Assign(19, new Val(${val.request_target}, TYPE_BOOL)); + flags->Assign(15, new Val(${val.negotiate_lm_key}, TYPE_BOOL)); + flags->Assign(16, new Val(${val.negotiate_datagram}, TYPE_BOOL)); + flags->Assign(17, new Val(${val.negotiate_seal}, TYPE_BOOL)); + flags->Assign(18, new Val(${val.negotiate_sign}, TYPE_BOOL)); + flags->Assign(19, new Val(${val.request_target}, TYPE_BOOL)); - flags->Assign(20, new Val(${val.negotiate_oem}, TYPE_BOOL)); - flags->Assign(21, new Val(${val.negotiate_unicode}, TYPE_BOOL)); + flags->Assign(20, new Val(${val.negotiate_oem}, TYPE_BOOL)); + flags->Assign(21, new Val(${val.negotiate_unicode}, TYPE_BOOL)); return flags; %} @@ -51,7 +51,7 @@ refine connection SMB_Conn += { RecordVal* result = new RecordVal(BifType::Record::SMB::NTLMVersion); result->Assign(0, new Val(${val.major_version}, TYPE_COUNT)); result->Assign(1, new Val(${val.minor_version}, TYPE_COUNT)); - result->Assign(2, new Val(${val.build_number}, TYPE_COUNT)); + result->Assign(2, new Val(${val.build_number}, TYPE_COUNT)); result->Assign(3, new Val(${val.ntlm_revision}, TYPE_COUNT)); return result; @@ -94,9 +94,22 @@ refine connection SMB_Conn += { return result; %} - function proc_smb_ntlm_accept(header: SMB_Header): bool + function proc_smb_ntlm_ssp(header: SMB_Header, val:SMB_NTLM_SSP): bool %{ - BifEvent::generate_smb_ntlm_accepted(bro_analyzer(), bro_analyzer()->Conn(), BuildHeaderVal(header)); + if ( ${val.gssapi.is_init} ) + return true; + for ( uint i = 0; i < ${val.gssapi.resp.args}->size(); ++i ) + { + switch ( ${val.gssapi.resp.args[i].seq_meta.index} ) + { + case 0: + if ( ${val.gssapi.resp.args[i].args.neg_state} == 0 ) + BifEvent::generate_smb_ntlm_accepted(bro_analyzer(), bro_analyzer()->Conn(), BuildHeaderVal(header)); + break; + default: + break; + } + } return true; %} @@ -104,9 +117,15 @@ refine connection SMB_Conn += { %{ RecordVal* result = new RecordVal(BifType::Record::SMB::NTLMNegotiate); result->Assign(0, build_negotiate_flag_record(${val.flags})); - if ( ${val.flags.negotiate_oem_domain_supplied} ) result->Assign(1, bytestring_to_val(${val.domain_name.string.data})); - if ( ${val.flags.negotiate_oem_workstation_supplied} ) result->Assign(2, bytestring_to_val(${val.workstation.string.data})); - if ( ${val.flags.negotiate_version} ) result->Assign(3, build_version_record(${val.version})); + + if ( ${val.flags.negotiate_oem_domain_supplied} ) + result->Assign(1, bytestring_to_val(${val.domain_name.string.data})); + + if ( ${val.flags.negotiate_oem_workstation_supplied} ) + result->Assign(2, bytestring_to_val(${val.workstation.string.data})); + + if ( ${val.flags.negotiate_version} ) + result->Assign(3, build_version_record(${val.version})); BifEvent::generate_smb_ntlm_negotiate(bro_analyzer(), bro_analyzer()->Conn(), BuildHeaderVal(header), result); @@ -117,9 +136,15 @@ refine connection SMB_Conn += { %{ RecordVal* result = new RecordVal(BifType::Record::SMB::NTLMChallenge); result->Assign(0, build_negotiate_flag_record(${val.flags})); - if ( ${val.flags.request_target} ) result->Assign(1, bytestring_to_val(${val.target_name.string.data})); - if ( ${val.flags.negotiate_version} ) result->Assign(2, build_version_record(${val.version})); - if ( ${val.flags.negotiate_target_info} ) result->Assign(3, build_av_record(${val.target_info})); + + if ( ${val.flags.request_target} ) + result->Assign(1, bytestring_to_val(${val.target_name.string.data})); + + if ( ${val.flags.negotiate_version} ) + result->Assign(2, build_version_record(${val.version})); + + if ( ${val.flags.negotiate_target_info} ) + result->Assign(3, build_av_record(${val.target_info})); BifEvent::generate_smb_ntlm_challenge(bro_analyzer(), bro_analyzer()->Conn(), BuildHeaderVal(header), result); @@ -130,154 +155,161 @@ refine connection SMB_Conn += { %{ RecordVal* result = new RecordVal(BifType::Record::SMB::NTLMAuthenticate); result->Assign(0, build_negotiate_flag_record(${val.flags})); - result->Assign(1, bytestring_to_val(${val.domain_name.string.data})); - result->Assign(2, bytestring_to_val(${val.user_name.string.data})); - result->Assign(3, bytestring_to_val(${val.workstation.string.data})); - if ( ${val.flags.negotiate_version} ) result->Assign(4, build_version_record(${val.version})); + + if ( ${val.domain_name_fields.length} > 0 ) + result->Assign(1, bytestring_to_val(${val.domain_name.string.data})); + + if ( ${val.user_name_fields.length} > 0 ) + result->Assign(2, bytestring_to_val(${val.user_name.string.data})); + + if ( ${val.workstation_fields.length} > 0 ) + result->Assign(3, bytestring_to_val(${val.workstation.string.data})); + + if ( ${val.flags.negotiate_version} ) + result->Assign(4, build_version_record(${val.version})); BifEvent::generate_smb_ntlm_authenticate(bro_analyzer(), bro_analyzer()->Conn(), BuildHeaderVal(header), result); return true; %} - function convert_der_num(is_long: bool, first: uint8, fields: bytestring): uint64 - %{ - if (!is_long) return first; - - int result; - result = 0; - - for (uint i = 0; i < fields.length(); i++) - { - result << 8; - result += ${fields[i]}; - } - return result; - %} - - function test_accepted(m: SMB_NTLM_SSP): bool - %{ - if ( ${m.gssapi}->size() < 4 ) - return false; - return ( ${m.gssapi[3].tag} == 10 && ${m.gssapi[3].value} == "\x00" ); - %} - }; -type DER_Length = record { - first : uint8; - rest : bytestring &length=size; +type GSSAPI_NEG_TOKEN(header: SMB_Header) = record { + wrapper : ASN1EncodingMeta; + have_oid : case is_init of { + true -> oid: ASN1Encoding; + false -> no_oid: empty; + }; + have_init_wrapper: case is_init of { + true -> init_wrapper: ASN1EncodingMeta; + false -> no_init_wrapper: empty; + }; + msg_type : case is_init of { + true -> init: GSSAPI_NEG_TOKEN_INIT(header); + false -> resp: GSSAPI_NEG_TOKEN_RESP(header); + }; } &let { - is_long_form : bool = (first >= 128); - size : uint8 = is_long_form ? first % 128 : 0; - value : uint64 = $context.connection.convert_der_num(is_long_form, first, rest); + is_init: bool = wrapper.tag == 0x60; }; -type DER_ASN = record { - tag: uint8; - length: DER_Length; - skip_constructed: case (is_primitive && (tag != 0x04) ) of { - true -> value: bytestring &length=length.value; - false -> nothing: empty; - } &requires(is_primitive); -} &let { - is_primitive: bool = (tag & 0x20) == 0; - last: bool = tag == 0x04; +type GSSAPI_NEG_TOKEN_INIT(header: SMB_Header) = record { + seq_meta: ASN1EncodingMeta; + args : GSSAPI_NEG_TOKEN_INIT_Arg(header)[]; +}; + +type GSSAPI_NEG_TOKEN_INIT_Arg(header: SMB_Header) = record { + seq_meta: ASN1EncodingMeta; + args : GSSAPI_NEG_TOKEN_INIT_Arg_Data(header, seq_meta.index) &length=seq_meta.length; +}; + +type GSSAPI_NEG_TOKEN_INIT_Arg_Data(header: SMB_Header, index: uint8) = case index of { + 0 -> mech_type_list : ASN1Encoding; + 1 -> req_flags : ASN1Encoding; + 2 -> mech_token : SMB_NTLM_SSP_Token(header); + 3 -> mech_list_mic : ASN1OctetString; +}; + +type GSSAPI_NEG_TOKEN_RESP(header: SMB_Header) = record { + seq_meta: ASN1EncodingMeta; + args : GSSAPI_NEG_TOKEN_RESP_Arg(header)[]; +}; + +type GSSAPI_NEG_TOKEN_RESP_Arg(header: SMB_Header) = record { + seq_meta: ASN1EncodingMeta; + args : GSSAPI_NEG_TOKEN_RESP_Arg_Data(header, seq_meta.index) &length=seq_meta.length; +}; + +type GSSAPI_NEG_TOKEN_RESP_Arg_Data(header: SMB_Header, index: uint8) = case index of { + 0 -> neg_state : ASN1Integer; + 1 -> supported_mech : ASN1Encoding; + 2 -> response_token : SMB_NTLM_SSP_Token(header); + 3 -> mech_list_mic : ASN1OctetString; }; type SMB_NTLM_SSP(header: SMB_Header) = record { - gssapi : DER_ASN[] &until ($element.last); - skip_accepted: case ( is_accepted ) of { - true -> nothing: empty; - false -> token: SMB_NTLM_Neg_Token(header); - } &requires(is_accepted); + gssapi: GSSAPI_NEG_TOKEN(header); } &let { - is_accepted: bool = $context.connection.test_accepted(this); - proc: bool = $context.connection.proc_smb_ntlm_accept(header) &if(is_accepted); + proc: bool = $context.connection.proc_smb_ntlm_ssp(header, this); }; -type SMB_NTLM_Neg_Token(header: SMB_Header) = record { - identifier : bytestring &length=8; - msg_type : uint32; - msg : case msg_type of { - 0 -> accept : empty; - 1 -> negotiate : SMB_NTLM_Negotiate(header, 12); - 2 -> challenge : SMB_NTLM_Challenge(header, 12); - 3 -> authenticate : SMB_NTLM_Authenticate(header, 12); - }; -} &let { - is_accept : bool = (msg_type == 0); - is_negotiate : bool = (msg_type == 1); - is_challenge : bool = (msg_type == 2); - is_authenticate : bool = (msg_type == 3); - proc : bool = $context.connection.proc_smb_ntlm_accept(header) &if is_accept; +type SMB_NTLM_SSP_Token(header: SMB_Header) = record { + meta : ASN1EncodingMeta; + signature : bytestring &length=8; + msg_type : uint32; + msg : case msg_type of { + 1 -> negotiate : SMB_NTLM_Negotiate(header, offsetof(msg) - offsetof(signature)); + 2 -> challenge : SMB_NTLM_Challenge(header, offsetof(msg) - offsetof(signature)); + 3 -> authenticate : SMB_NTLM_Authenticate(header, offsetof(msg) - offsetof(signature)); + }; }; type SMB_NTLM_Negotiate(header: SMB_Header, offset: uint16) = record { - flags : SMB_NTLM_Negotiate_Flags; - domain_name_fields : SMB_NTLM_StringData; - workstation_fields : SMB_NTLM_StringData; - version_present : case flags.negotiate_version of { - true -> version: SMB_NTLM_Version; - false -> no_version: empty; + flags : SMB_NTLM_Negotiate_Flags; + domain_name_fields : SMB_NTLM_StringData; + workstation_fields : SMB_NTLM_StringData; + version_present : case flags.negotiate_version of { + true -> version : SMB_NTLM_Version; + false -> no_version : empty; }; - payload : bytestring &restofdata; + payload : bytestring &restofdata; } &let { - absolute_offset : uint16 = offsetof(payload) + offset; - domain_name : SMB_NTLM_String(domain_name_fields, absolute_offset, flags.negotiate_unicode) withinput payload &if(flags.negotiate_oem_domain_supplied); - workstation : SMB_NTLM_String(workstation_fields, absolute_offset, flags.negotiate_unicode) withinput payload &if(flags.negotiate_oem_workstation_supplied); - proc : bool = $context.connection.proc_smb_ntlm_negotiate(header, this); + absolute_offset : uint16 = offsetof(payload) + offset; + domain_name : SMB_NTLM_String(domain_name_fields, absolute_offset, flags.negotiate_unicode) withinput payload &if(flags.negotiate_oem_domain_supplied); + workstation : SMB_NTLM_String(workstation_fields, absolute_offset, flags.negotiate_unicode) withinput payload &if(flags.negotiate_oem_workstation_supplied); + proc : bool = $context.connection.proc_smb_ntlm_negotiate(header, this); }; type SMB_NTLM_Challenge(header: SMB_Header, offset: uint16) = record { - target_name_fields : SMB_NTLM_StringData; - flags : SMB_NTLM_Negotiate_Flags; - challenge : uint64; - reserved : padding[8]; - target_info_fields : SMB_NTLM_StringData; - version_present : case flags.negotiate_version of { - true -> version: SMB_NTLM_Version; - false -> no_version: empty; + target_name_fields : SMB_NTLM_StringData; + flags : SMB_NTLM_Negotiate_Flags; + challenge : uint64; + reserved : padding[8]; + target_info_fields : SMB_NTLM_StringData; + version_present : case flags.negotiate_version of { + true -> version : SMB_NTLM_Version; + false -> no_version : empty; }; - payload : bytestring &restofdata; + payload : bytestring &restofdata; } &let { - absolute_offset : uint16 = offsetof(payload) + offset; - target_name : SMB_NTLM_String(target_name_fields, absolute_offset, flags.negotiate_unicode) withinput payload &if(flags.request_target); - target_info : SMB_NTLM_AV_Pair_Sequence(target_info_fields.offset - absolute_offset) withinput payload &if(flags.negotiate_target_info); - proc : bool = $context.connection.proc_smb_ntlm_challenge(header, this); + absolute_offset : uint16 = offsetof(payload) + offset; + target_name : SMB_NTLM_String(target_name_fields, absolute_offset, flags.negotiate_unicode) withinput payload &if(flags.request_target); + target_info : SMB_NTLM_AV_Pair_Sequence(target_info_fields.offset - absolute_offset) withinput payload &if(flags.negotiate_target_info); + proc : bool = $context.connection.proc_smb_ntlm_challenge(header, this); }; type SMB_NTLM_Authenticate(header: SMB_Header, offset: uint16) = record { - lm_challenge_response_fields : SMB_NTLM_StringData; - nt_challenge_response_fields : SMB_NTLM_StringData; - domain_name_fields : SMB_NTLM_StringData; - user_name_fields : SMB_NTLM_StringData; - workstation_fields : SMB_NTLM_StringData; - encrypted_session_key_fields : SMB_NTLM_StringData; - flags : SMB_NTLM_Negotiate_Flags; - version_present : case flags.negotiate_version of { - true -> version: SMB_NTLM_Version; - false -> no_version: empty; + lm_challenge_response_fields: SMB_NTLM_StringData; + nt_challenge_response_fields: SMB_NTLM_StringData; + domain_name_fields : SMB_NTLM_StringData; + user_name_fields : SMB_NTLM_StringData; + workstation_fields : SMB_NTLM_StringData; + encrypted_session_key_fields: SMB_NTLM_StringData; + flags : SMB_NTLM_Negotiate_Flags; + version_present : case flags.negotiate_version of { + true -> version : SMB_NTLM_Version; + false -> no_version : empty; }; - mic : bytestring &length=16; - payload : bytestring &restofdata; + +# Windows NT, 2000, XP, and 2003 don't have the MIC field +# TODO - figure out how to parse this for those that do have it +# mic : bytestring &length=16; + + payload : bytestring &restofdata; } &let { - absolute_offset : uint16 = offsetof(payload) + offset; - domain_name : SMB_NTLM_String(domain_name_fields, absolute_offset, flags.negotiate_unicode) withinput payload; - user_name : SMB_NTLM_String(user_name_fields, absolute_offset, flags.negotiate_unicode) withinput payload; - workstation : SMB_NTLM_String(workstation_fields, absolute_offset , flags.negotiate_unicode) withinput payload; - encrypted_session_key : SMB_NTLM_String(workstation_fields, absolute_offset, flags.negotiate_unicode) withinput payload &if(flags.negotiate_key_exch); -# lm_response : SMB_LM_Response(lm_challenge_response_fields.offset - absolute_offset) withinput payload &if(lm_challenge_response_fields.length > 0); -# ntlm_response : SMB_NTLM_Response(nt_challenge_response_fields.offset - absolute_offset) withinput payload &if(nt_challenge_response_fields.length == 24); -# ntlmv2_response : SMB_NTLM_Response(nt_challenge_response_fields.offset - absolute_offset) withinput payload &if(nt_challenge_response_fields.length > 24); - proc : bool = $context.connection.proc_smb_ntlm_authenticate(header, this); + absolute_offset : uint16 = offsetof(payload) + offset; + domain_name : SMB_NTLM_String(domain_name_fields, absolute_offset, flags.negotiate_unicode) withinput payload &if(domain_name_fields.length > 0); + user_name : SMB_NTLM_String(user_name_fields, absolute_offset, flags.negotiate_unicode) withinput payload &if(user_name_fields.length > 0); + workstation : SMB_NTLM_String(workstation_fields, absolute_offset , flags.negotiate_unicode) withinput payload &if(workstation_fields.length > 0); + encrypted_session_key : SMB_NTLM_String(encrypted_session_key_fields, absolute_offset, flags.negotiate_unicode) withinput payload &if(flags.negotiate_key_exch); + proc : bool = $context.connection.proc_smb_ntlm_authenticate(header, this); }; type SMB_NTLM_Version = record { major_version : uint8; minor_version : uint8; build_number : uint16; - reserved : padding[3]; + reserved : padding[3]; ntlm_revision : uint8; }; @@ -320,80 +352,88 @@ type SMB_NTLM_AV_Pair = record { 0x000a -> channel_bindings : uint16; }; } &let { - last : bool = ( id == 0x0000); + last : bool = ( id == 0x0000); # av_flags refinement - constrained_auth : bool = (av_flags & 0x00000001) > 0 &if ( id == 0x0006); - mic_present : bool = (av_flags & 0x00000002) > 0 &if ( id == 0x0006); - untrusted_source : bool = (av_flags & 0x00000004) > 0 &if ( id == 0x0006); + constrained_auth: bool = (av_flags & 0x00000001) > 0 &if ( id == 0x0006); + mic_present : bool = (av_flags & 0x00000002) > 0 &if ( id == 0x0006); + untrusted_source: bool = (av_flags & 0x00000004) > 0 &if ( id == 0x0006); }; type SMB_NTLM_Single_Host = record { - size : uint32; - padpad : padding[4]; - data_present : uint32; - optional : case custom_data_present of { + size : uint32; + padpad : padding[4]; + data_present: uint32; + optional : case custom_data_present of { true -> custom_data : bytestring &length=4; false -> nothing : empty; }; - machine_id : uint32; + machine_id : uint32; } &let { - custom_data_present : bool = (data_present & 0x00000001) > 0; + custom_data_present: bool = (data_present & 0x00000001) > 0; }; type SMB_LM_Response(offset: uint16) = record { # This can be either LM (24 byte response) or # LMv2 (16 byte response + 8 byte client challenge. No way to # know for sure. - padpad : padding to offset; - response : bytestring &length=24; + padpad : padding to offset; + response: bytestring &length=24; }; type SMB_NTLM_Response(offset: uint16) = record { - padpad : padding to offset; - response : bytestring &length=24; + padpad : padding to offset; + response: bytestring &length=24; }; type SMB_NTLMv2_Response(flags: SMB_NTLM_Negotiate_Flags, offset: uint16) = record { - padpad : padding to offset; - response : bytestring &length=16; - client_challenge : SMB_NTLMv2_Client_Challenge(flags); + padpad : padding to offset; + response : bytestring &length=16; + client_challenge: SMB_NTLMv2_Client_Challenge(flags); }; type SMB_NTLMv2_Client_Challenge(flags: SMB_NTLM_Negotiate_Flags) = record { - resp_type : uint8; - max_resp_type : uint8; - reserved : padding[6]; - timestamp : uint64; - client_challenge : bytestring &length=8; - reserved2 : padding[4]; - av_pairs : SMB_NTLM_AV_Pair_Sequence(0); + resp_type : uint8; + max_resp_type : uint8; + reserved : padding[6]; + timestamp : uint64; + client_challenge: bytestring &length=8; + reserved2 : padding[4]; + av_pairs : SMB_NTLM_AV_Pair_Sequence(0); }; type SMB_NTLM_Negotiate_Flags = record { - flags : uint32; + flags: uint32; } &let { - negotiate_56 : bool = (flags & 0x80000000) > 0; - negotiate_key_exch : bool = (flags & 0x40000000) > 0; - negotiate_128 : bool = (flags & 0x20000000) > 0; - negotiate_version : bool = (flags & 0x02000000) > 0; - negotiate_target_info : bool = (flags & 0x00800000) > 0; - request_non_nt_session_key : bool = (flags & 0x00400000) > 0; - negotiate_identify : bool = (flags & 0x00100000) > 0; - negotiate_extended_sessionsecurity : bool = (flags & 0x00040000) > 0; - target_type_server : bool = (flags & 0x00020000) > 0; - target_type_domain : bool = (flags & 0x00010000) > 0; - negotiate_always_sign : bool = (flags & 0x00008000) > 0; - negotiate_oem_workstation_supplied : bool = (flags & 0x00002000) > 0; - negotiate_oem_domain_supplied : bool = (flags & 0x00001000) > 0; - negotiate_anonymous_connection : bool = (flags & 0x00000400) > 0; - negotiate_ntlm : bool = (flags & 0x00000100) > 0; - negotiate_lm_key : bool = (flags & 0x00000080) > 0; - negotiate_datagram : bool = (flags & 0x00000040) > 0; - negotiate_seal : bool = (flags & 0x00000020) > 0; - negotiate_sign : bool = (flags & 0x00000008) > 0; - request_target : bool = (flags & 0x00000004) > 0; - negotiate_oem : bool = (flags & 0x00000002) > 0; - negotiate_unicode : bool = (flags & 0x00000001) > 0; - is_oem : bool = !negotiate_unicode && negotiate_oem; - is_invalid : bool = !negotiate_unicode && !negotiate_oem; + negotiate_56 : bool = (flags & 0x80000000) > 0; + negotiate_key_exch : bool = (flags & 0x40000000) > 0; + negotiate_128 : bool = (flags & 0x20000000) > 0; + + negotiate_version : bool = (flags & 0x02000000) > 0; + + negotiate_target_info : bool = (flags & 0x00800000) > 0; + request_non_nt_session_key : bool = (flags & 0x00400000) > 0; + negotiate_identify : bool = (flags & 0x00100000) > 0; + + negotiate_extended_sessionsecurity : bool = (flags & 0x00040000) > 0; + target_type_server : bool = (flags & 0x00020000) > 0; + target_type_domain : bool = (flags & 0x00010000) > 0; + + negotiate_always_sign : bool = (flags & 0x00008000) > 0; + negotiate_oem_workstation_supplied : bool = (flags & 0x00002000) > 0; + negotiate_oem_domain_supplied : bool = (flags & 0x00001000) > 0; + + negotiate_anonymous_connection : bool = (flags & 0x00000400) > 0; + negotiate_ntlm : bool = (flags & 0x00000100) > 0; + + negotiate_lm_key : bool = (flags & 0x00000080) > 0; + negotiate_datagram : bool = (flags & 0x00000040) > 0; + negotiate_seal : bool = (flags & 0x00000020) > 0; + + negotiate_sign : bool = (flags & 0x00000008) > 0; + request_target : bool = (flags & 0x00000004) > 0; + negotiate_oem : bool = (flags & 0x00000002) > 0; + negotiate_unicode : bool = (flags & 0x00000001) > 0; + + is_oem : bool = !negotiate_unicode && negotiate_oem; + is_invalid : bool = !negotiate_unicode && !negotiate_oem; }; diff --git a/src/analyzer/protocol/smb/smb-pipe.pac b/src/analyzer/protocol/smb/smb-pipe.pac index e2e957be06..46d589a140 100644 --- a/src/analyzer/protocol/smb/smb-pipe.pac +++ b/src/analyzer/protocol/smb/smb-pipe.pac @@ -3,44 +3,67 @@ %include dce_rpc-protocol.pac +%extern{ + #include "DCE_RPC.h" +%} + refine connection SMB_Conn += { - function proc_smb_atsvc_job_add(val: AT_SVC_NetrJobAdd): bool - %{ - if ( smb_atsvc_job_add ) - { - BifEvent::generate_smb_atsvc_job_add(bro_analyzer(), bro_analyzer()->Conn(), smb_string2stringval(${val.server.string}), smb_string2stringval(${val.command.string})); - } - return true; - %} - function proc_smb_atsvc_job_id(val: AT_SVC_JobID): bool - %{ - if ( smb_atsvc_job_id ) - { - BifEvent::generate_smb_atsvc_job_id(bro_analyzer(), bro_analyzer()->Conn(), ${val.id}, ${val.status}); - } - return true; - %} - - function determine_pipe_msg_type(hdr: DCE_RPC_Header, opnum: uint8): uint8 + function get_tree_is_pipe(tree_id: uint16): bool %{ - if ( !is_atsvc ) return 0; - if ( ${hdr.PTYPE} == 0 && ${opnum} == 0 ) return 1; - if ( ${hdr.PTYPE} == 2 && ${opnum} == 0 ) return 2; - return 0; + if ( tree_is_pipe_map.count(tree_id) == 0 ) + return false; + return tree_is_pipe_map[tree_id]; %} + function set_tree_is_pipe(tree_id: uint16, is_pipe: bool): bool + %{ + tree_is_pipe_map[tree_id] = is_pipe; + return true; + %} + + %member{ + map tree_is_pipe_map; + %} + + function proc_smb_pipe_message(val: SMB_Pipe_message, header: SMB_Header): bool + %{ + switch ( ${val.rpc_header.PTYPE} ) { + case DCE_RPC_REQUEST: + if ( smb_pipe_request ) + BifEvent::generate_smb_pipe_request(bro_analyzer(), bro_analyzer()->Conn(), BuildHeaderVal(header), \ + ${val.rpc_body.request.opnum}); + break; + case DCE_RPC_RESPONSE: + if ( smb_pipe_response ) + BifEvent::generate_smb_pipe_response(bro_analyzer(), bro_analyzer()->Conn(), BuildHeaderVal(header)); + break; + case DCE_RPC_BIND_ACK: + if ( smb_pipe_bind_ack_response ) + BifEvent::generate_smb_pipe_bind_ack_response(bro_analyzer(), bro_analyzer()->Conn(), BuildHeaderVal(header)); + break; + case DCE_RPC_BIND: + if ( smb_pipe_bind_request ) + // TODO - the version number needs to be calculated properly + BifEvent::generate_smb_pipe_bind_request(bro_analyzer(), bro_analyzer()->Conn(), BuildHeaderVal(header), \ + new StringVal(analyzer::dce_rpc::uuid_to_string(bytestring_to_val(${val.rpc_body.bind.p_context_elem.p_cont_elem[0].abstract_syntax.if_uuid})->Bytes())), new StringVal(fmt("%d.0", ${val.rpc_body.bind.p_context_elem.p_cont_elem[0].abstract_syntax.if_version}))); + break; + } + + return true; + %} }; -type SMB_Pipe_message( unicode: bool, byte_count: uint16, sub_cmd: uint16 ) = record { - rpc : DCE_RPC_Header; - todo : padding[6]; # These fields are currently missing from DCE/RPC for some reason. - opnum : uint8; - pipe_type: case $context.connection.determine_pipe_msg_type(rpc, opnum) of { - 1 -> atsvc_request : AT_SVC_Request(unicode, opnum); - 2 -> atsvc_reply : AT_SVC_Reply(unicode, opnum); - default -> unknown : bytestring &restofdata; - }; +type SMB_Pipe_message(header: SMB_Header, byte_count: uint16) = record { + rpc_header : DCE_RPC_Header; + rpc_body : DCE_RPC_Body(rpc_header); +# pipe_type: case $context.connection.determine_pipe_msg_type(rpc, opnum) of { +# 1 -> atsvc_request : AT_SVC_Request(unicode, opnum); +# 2 -> atsvc_reply : AT_SVC_Reply(unicode, opnum); +# default -> unknown : bytestring &restofdata; +# }; +} &let { + proc: bool = $context.connection.proc_smb_pipe_message(this, header); } &byteorder = littleendian; type SMB_RAP_message( unicode: bool, byte_count: uint16 ) = record { @@ -77,8 +100,6 @@ type AT_SVC_NetrJobAdd(unicode: bool) = record { flags : uint8; unknown2 : padding[2]; command : AT_SVC_String_Pointer(unicode); -} &let { - proc: bool = $context.connection.proc_smb_atsvc_job_add(this); }; type AT_SVC_Reply(unicode: bool, opnum: uint16) = record { @@ -91,6 +112,4 @@ type AT_SVC_Reply(unicode: bool, opnum: uint16) = record { type AT_SVC_JobID(unicode: bool) = record { id: uint32; status: uint32; -} &let { - proc: bool = $context.connection.proc_smb_atsvc_job_id(this); }; diff --git a/src/analyzer/protocol/smb/smb-protocol.pac b/src/analyzer/protocol/smb/smb-protocol.pac index 2a53ca9d7f..5ee96bf31f 100644 --- a/src/analyzer/protocol/smb/smb-protocol.pac +++ b/src/analyzer/protocol/smb/smb-protocol.pac @@ -379,8 +379,8 @@ type SMB_transaction_data(unicode: bool, count: uint16, sub_cmd: uint16, SMB_MAILSLOT_BROWSE -> mailslot : SMB_MailSlot_message(unicode, count); SMB_MAILSLOT_LANMAN -> lanman : SMB_MailSlot_message(unicode, count); - SMB_RAP -> rap : SMB_Pipe_message(unicode, count, sub_cmd); - SMB_PIPE -> pipe : SMB_Pipe_message(unicode, count, sub_cmd); + SMB_RAP -> rap : SMB_Pipe_message(unicode, count); + SMB_PIPE -> pipe : SMB_Pipe_message(unicode, count); SMB_UNKNOWN -> unknown : bytestring &restofdata; default -> data : bytestring &restofdata; @@ -416,7 +416,7 @@ type SMB_transaction(trans_type: int, unicode: bool) = record { determine_transaction_type( setup_count, name_string( this ))); } &let { # does this work? - sub_cmd : uint16 = setup_count ? setup[0] : 0; + sub_cmd : uint16 = setup_count ? setup.op_code : 0; } &byteorder = littleendian; diff --git a/src/analyzer/protocol/smb/smb-strings.pac b/src/analyzer/protocol/smb/smb-strings.pac index 6496af51cb..f72225752b 100644 --- a/src/analyzer/protocol/smb/smb-strings.pac +++ b/src/analyzer/protocol/smb/smb-strings.pac @@ -47,7 +47,6 @@ function uint8s_to_stringval(s: uint8[]): StringVal // If the last 2 bytes are nulls, cut them with the length. length = length-2; } - StringVal *output = new StringVal(length, buf); delete [] buf; return output; @@ -71,7 +70,7 @@ function extract_string(s: SMB_string) : StringVal buf[i] = t; } - if ( length > 0 && buf[length] == 0x00 ) + if ( length > 0 && buf[length-1] == 0x00 ) length--; StringVal *ret = new StringVal(length, buf); diff --git a/src/analyzer/protocol/smb/smb.pac b/src/analyzer/protocol/smb/smb.pac index 802b5be123..918fd98169 100644 --- a/src/analyzer/protocol/smb/smb.pac +++ b/src/analyzer/protocol/smb/smb.pac @@ -21,6 +21,8 @@ #include "smb1_com_query_information.bif.h" #include "smb1_com_read_andx.bif.h" #include "smb1_com_session_setup_andx.bif.h" +#include "smb1_com_transaction.bif.h" +#include "smb1_com_transaction2.bif.h" #include "smb1_com_tree_connect_andx.bif.h" #include "smb1_com_tree_disconnect.bif.h" #include "smb1_com_write_andx.bif.h" @@ -50,6 +52,7 @@ connection SMB_Conn(bro_analyzer: BroAnalyzer) { %include smb-common.pac %include smb-time.pac +%include smb-ntlmssp-asn1.pac %include smb-ntlmssp.pac # SMB1 Commands diff --git a/src/analyzer/protocol/smb/smb1-com-close.pac b/src/analyzer/protocol/smb/smb1-com-close.pac index d609c48849..aafc31addc 100644 --- a/src/analyzer/protocol/smb/smb1-com-close.pac +++ b/src/analyzer/protocol/smb/smb1-com-close.pac @@ -2,8 +2,6 @@ refine connection SMB_Conn += { function proc_smb1_close_request(h: SMB_Header, val: SMB1_close_request): bool %{ - is_atsvc = false; - if ( smb1_close_request ) BifEvent::generate_smb1_close_request(bro_analyzer(), bro_analyzer()->Conn(), diff --git a/src/analyzer/protocol/smb/smb1-com-nt-create-andx.pac b/src/analyzer/protocol/smb/smb1-com-nt-create-andx.pac index 81e3429d1b..cd3367ef7e 100644 --- a/src/analyzer/protocol/smb/smb1-com-nt-create-andx.pac +++ b/src/analyzer/protocol/smb/smb1-com-nt-create-andx.pac @@ -1,23 +1,6 @@ refine connection SMB_Conn += { - - %member{ - bool is_atsvc; - %} - - %init{ - is_atsvc = false; - %} - - function isATSVC(): bool - %{ - return is_atsvc; - %} - function proc_smb1_nt_create_andx_request(header: SMB_Header, val: SMB1_nt_create_andx_request): bool %{ - if ( ${val.filename.u.s}->size() == 14 && ${val.filename.u.s[0]} == '\\' && ${val.filename.u.s[2]} == 'a' && ${val.filename.u.s[4]} == 't' && ${val.filename.u.s[6]} == 's' && ${val.filename.u.s[8]} == 'v' && ${val.filename.u.s[10]} == 'c' ) - is_atsvc = true; - if ( smb1_nt_create_andx_request ) { // name_length : uint16; diff --git a/src/analyzer/protocol/smb/smb1-com-open-andx.pcap b/src/analyzer/protocol/smb/smb1-com-open-andx.pcap new file mode 100644 index 0000000000..5c8eaeb66a --- /dev/null +++ b/src/analyzer/protocol/smb/smb1-com-open-andx.pcap @@ -0,0 +1,79 @@ +# Copyright (c) Broala LLC. All Rights Reserved. No use or distribution without permission. + +refine connection SMB_Conn += { + + function proc_smb1_open_andx_request(h: SMB_Header, val: SMB1_open_andx_request): bool + %{ + if ( smb1_open_andx_request ) + BifEvent::generate_smb1_open_andx_request(bro_analyzer(), + bro_analyzer()->Conn(), + BuildHeaderVal(h), + ${val.flags}, + ${val.access_mode}, + ${val.search_attrs}, + ${val.file_attrs}, + ${val.creation_time}, + ${val.open_mode}, + ${val.allocation_size}, + ${val.timeout}, + smb_string2stringval(${val.filename})); + + return true; + %} + + function proc_smb1_open_andx_response(h: SMB_Header, val: SMB1_open_andx_response): bool + %{ + if ( smb1_open_andx_response ) + BifEvent::generate_smb1_open_andx_response(bro_analyzer(), + bro_analyzer()->Conn(), + BuildHeaderVal(h), + ${val.fid}, + ${val.file_attrs}, + ${val.last_write_time}, + ${val.file_data_size}, + ${val.access_rights}, + ${val.resource_type}, + ${val.nm_pipe_status}, + ${val.open_results}); + + return true; + %} + +}; + + + +type SMB1_open_andx_request(header: SMB_Header) = record { + word_count : uint8; + andx : SMB_andx; + flags : uint16; + access_mode : uint16; + search_attrs : uint16; + file_attrs : uint16; + creation_time : uint32; + open_mode : uint16; + allocation_size : uint32; + timeout : uint32; + reserved : padding[2]; + byte_count : uint16; + filename : SMB_string(header.unicode, offsetof(filename); +} &let { + proc : bool = $context.connection.proc_smb1_open_andx_request(header, this); +} &byteorder=littleendian; + +type SMB1_open_andx_response(header: SMB_Header) = record { + word_count : uint8; + andx : SMB_andx; + fid : uint16; + file_attrs : uint16; + last_write_time : uint32; + file_data_size : uint32; + access_rights : uint16; + resource_type : uint16; + nm_pipe_status : uint16; + open_results : uint16; + reserved : padding[3]; + byte_count : uint16; +} &let { + proc : bool = $context.connection.proc_smb1_open_andx_response(header, this); +} &byteorder=littleendian; diff --git a/src/analyzer/protocol/smb/smb1-com-read-andx.pac b/src/analyzer/protocol/smb/smb1-com-read-andx.pac index cbd9881feb..5f9e564ee0 100644 --- a/src/analyzer/protocol/smb/smb1-com-read-andx.pac +++ b/src/analyzer/protocol/smb/smb1-com-read-andx.pac @@ -28,7 +28,7 @@ refine connection SMB_Conn += { BuildHeaderVal(h), ${val.data_len}); - if ( ${val.data_len} > 0 ) + if ( !get_tree_is_pipe(${h.tid}) && ( ${val.data_len} > 0 ) ) { uint64 offset = read_offsets[${h.mid}]; read_offsets.erase(${h.mid}); @@ -80,7 +80,10 @@ type SMB1_read_andx_response(header: SMB_Header) = record { byte_count : uint16; pad : padding to data_offset - SMB_Header_length; - data : bytestring &length=data_len; + is_pipe : case $context.connection.get_tree_is_pipe(header.tid) of { + true -> pipe_data : SMB_Pipe_message(header, byte_count) &length=data_len; + default -> data : bytestring &length=data_len; + } &requires(data_len); } &let { padding_len : uint8 = (header.unicode == 1) ? 1 : 0; data_len : uint32 = (data_len_high << 16) + data_len_low; diff --git a/src/analyzer/protocol/smb/smb1-com-session-setup-andx.pac b/src/analyzer/protocol/smb/smb1-com-session-setup-andx.pac index ec83465700..acf4bfc20a 100644 --- a/src/analyzer/protocol/smb/smb1-com-session-setup-andx.pac +++ b/src/analyzer/protocol/smb/smb1-com-session-setup-andx.pac @@ -103,7 +103,7 @@ refine connection SMB_Conn += { response->Assign(4, smb_string2stringval(${val.ntlm.primary_domain})); //response->Assign(5, bytestring_to_val(${val.ntlm.security_blob})); break; - case 0: // Error! + default: // Error! break; } diff --git a/src/analyzer/protocol/smb/smb1-com-transaction.pac b/src/analyzer/protocol/smb/smb1-com-transaction.pac index 822675be09..b5c19a332a 100644 --- a/src/analyzer/protocol/smb/smb1-com-transaction.pac +++ b/src/analyzer/protocol/smb/smb1-com-transaction.pac @@ -9,15 +9,26 @@ refine connection SMB_Conn += { function proc_smb1_transaction_request(header: SMB_Header, val: SMB1_transaction_request): bool %{ - //printf("transaction_request\n"); + if ( smb1_transaction_request ) + BifEvent::generate_smb1_transaction_request(bro_analyzer(), bro_analyzer()->Conn(), BuildHeaderVal(header), \ + smb_string2stringval(${val.name}), ${val.sub_cmd}); return true; %} + function proc_smb1_transaction_response(header: SMB_Header, val: SMB1_transaction_response): bool %{ //printf("transaction_response\n"); return true; %} + function proc_smb1_transaction_setup(header: SMB_Header, val: SMB1_transaction_setup): bool + %{ + if ( smb1_transaction_setup ) + BifEvent::generate_smb1_transaction_setup(bro_analyzer(), bro_analyzer()->Conn(), BuildHeaderVal(header), \ + ${val.op_code}, ${val.file_id}); + return true; + %} + }; @@ -25,12 +36,19 @@ type SMB1_transaction_data(header: SMB_Header, count: uint16, sub_cmd: uint16, trans_type: TransactionType ) = case trans_type of { # SMB_MAILSLOT_BROWSE -> mailslot : SMB_MailSlot_message(header.unicode, count); # SMB_MAILSLOT_LANMAN -> lanman : SMB_MailSlot_message(header.unicode, count); -# SMB_RAP -> rap : SMB_Pipe_message(header.unicode, count, sub_cmd); - SMB_PIPE -> pipe : SMB_Pipe_message(header.unicode, count, sub_cmd); -# SMB_UNKNOWN -> unknown : bytestring &restofdata; +# SMB_RAP -> rap : SMB_Pipe_message(header.unicode, count); + SMB_PIPE -> pipe : SMB_Pipe_message(header, count); + SMB_UNKNOWN -> unknown : bytestring &restofdata; # default -> data : bytestring &restofdata; }; +type SMB1_transaction_setup(header: SMB_Header) = record { + op_code : uint16; + file_id : uint16; +} &let { + proc: bool = $context.connection.proc_smb1_transaction_setup(header, this); +} + type SMB1_transaction_request(header: SMB_Header) = record { word_count : uint8; total_param_count : uint16; @@ -48,7 +66,7 @@ type SMB1_transaction_request(header: SMB_Header) = record { data_offset : uint16; setup_count : uint8; reserved3 : uint8; - setup : uint16[setup_count]; + setup : SMB1_transaction_setup(header); byte_count : uint16; name : SMB_string(header.unicode, offsetof(name)); @@ -57,7 +75,7 @@ type SMB1_transaction_request(header: SMB_Header) = record { pad2 : padding to data_offset - SMB_Header_length; data : SMB1_transaction_data(header, data_count, sub_cmd, determine_transaction_type(setup_count, name)); } &let { - sub_cmd : uint16 = setup_count ? setup[0] : 0; + sub_cmd : uint16 = setup_count ? setup.op_code : 0; proc : bool = $context.connection.proc_smb1_transaction_request(header, this); }; @@ -81,9 +99,9 @@ type SMB1_transaction_response(header: SMB_Header) = record { pad0 : padding to param_offset - SMB_Header_length; parameters : bytestring &length = param_count; pad1 : padding to data_offset - SMB_Header_length; - handle_response : case $context.connection.isATSVC() of { + handle_response : case $context.connection.get_tree_is_pipe(header.tid) of { true -> pipe_data : SMB1_transaction_data(header, data_count, 0, SMB_PIPE); -# false -> unk_data : SMB1_transaction_data(header, data_count, 0, SMB_UNKNOWN); + false -> unk_data : SMB1_transaction_data(header, data_count, 0, SMB_UNKNOWN); }; } &let { proc : bool = $context.connection.proc_smb1_transaction_response(header, this); diff --git a/src/analyzer/protocol/smb/smb1-com-transaction2.pac b/src/analyzer/protocol/smb/smb1-com-transaction2.pac index cff496c054..561f879cf9 100644 --- a/src/analyzer/protocol/smb/smb1-com-transaction2.pac +++ b/src/analyzer/protocol/smb/smb1-com-transaction2.pac @@ -22,13 +22,16 @@ refine connection SMB_Conn += { function proc_smb1_transaction2_request(header: SMB_Header, val: SMB1_transaction2_request): bool %{ - //printf("transaction2_request sub command: %d\n", ${val.sub_cmd}); + if ( smb1_transaction2_request ) + BifEvent::generate_smb1_transaction2_request(bro_analyzer(), bro_analyzer()->Conn(), BuildHeaderVal(header), ${val.sub_cmd}); + return true; %} function proc_smb1_transaction2_response(header: SMB_Header, val: SMB1_transaction2_response): bool %{ - //printf("transaction2_response sub command: %d\n", ${val.sub_cmd}); +// if ( smb1_transaction2_response ) +// BifEvent::generate_smb1_transaction2_response(bro_analyzer(), bro_analyzer()->Conn(), BuildHeaderVal(header), new Val(${val.sub_cmd}, TYPE_COUNT)); return true; %} @@ -106,8 +109,19 @@ refine connection SMB_Conn += { function proc_trans2_find_first2_request(header: SMB_Header, val: trans2_find_first2_request): bool %{ - // TODO: implement this. - //printf("trans2_find_first2 request!\n"); + if ( smb1_trans2_find_first2_request ) + { + RecordVal* result = new RecordVal(BifType::Record::SMB1::Find_First2_Request_Args); + result->Assign(0, new Val(${val.search_attrs}, TYPE_COUNT)); + result->Assign(1, new Val(${val.search_count}, TYPE_COUNT)); + result->Assign(2, new Val(${val.flags}, TYPE_COUNT)); + result->Assign(3, new Val(${val.info_level}, TYPE_COUNT)); + result->Assign(4, new Val(${val.search_storage_type}, TYPE_COUNT)); + result->Assign(5, smb_string2stringval(${val.file_name})); + BifEvent::generate_smb1_trans2_find_first2_request(bro_analyzer(), bro_analyzer()->Conn(), \ + BuildHeaderVal(header), result); + + } return true; %} @@ -179,8 +193,14 @@ refine connection SMB_Conn += { function proc_trans2_query_path_info_request(header: SMB_Header, val: trans2_query_path_info_request): bool %{ - // TODO: implement this. - //printf("trans2_query_path_info request!\n"); + if ( smb1_trans2_query_path_info_request ) + { + BifEvent::generate_smb1_trans2_query_path_info_request(bro_analyzer(), bro_analyzer()->Conn(), \ + BuildHeaderVal(header), \ + smb_string2stringval(${val.file_name}),\ + ${val.level_of_interest}); + + } return true; %} @@ -278,8 +298,13 @@ refine connection SMB_Conn += { function proc_trans2_get_dfs_referral_request(header: SMB_Header, val: trans2_get_dfs_referral_request): bool %{ - // TODO: implement this. - //printf("trans2_get_dfs_referral request!\n"); + if ( smb1_trans2_get_dfs_referral_request ) + { + BifEvent::generate_smb1_trans2_get_dfs_referral_request(bro_analyzer(), bro_analyzer()->Conn(), \ + BuildHeaderVal(header), \ + smb_string2stringval(${val.file_name}),\ + ${val.max_referral_level}); + } return true; %} diff --git a/src/analyzer/protocol/smb/smb1-com-tree-connect-andx.pac b/src/analyzer/protocol/smb/smb1-com-tree-connect-andx.pac index 25ddfc59f5..cb7f63d548 100644 --- a/src/analyzer/protocol/smb/smb1-com-tree-connect-andx.pac +++ b/src/analyzer/protocol/smb/smb1-com-tree-connect-andx.pac @@ -13,6 +13,7 @@ refine connection SMB_Conn += { function proc_smb1_tree_connect_andx_response(header: SMB_Header, val: SMB1_tree_connect_andx_response): bool %{ + set_tree_is_pipe(${header.tid}, strcmp((const char*) smb_string2stringval(${val.service})->Bytes(), "IPC") == 0); if ( smb1_tree_connect_andx_response ) BifEvent::generate_smb1_tree_connect_andx_response(bro_analyzer(), bro_analyzer()->Conn(), diff --git a/src/analyzer/protocol/smb/smb1-com-write-andx.pac b/src/analyzer/protocol/smb/smb1-com-write-andx.pac index bf3063c2ef..f508a62e68 100644 --- a/src/analyzer/protocol/smb/smb1-com-write-andx.pac +++ b/src/analyzer/protocol/smb/smb1-com-write-andx.pac @@ -52,7 +52,10 @@ type SMB1_write_andx_request(header: SMB_Header) = record { byte_count : uint16; pad : padding to data_offset - SMB_Header_length; - data : bytestring &length=data_len; + is_pipe : case $context.connection.get_tree_is_pipe(header.tid) of { + true -> pipe_data : SMB_Pipe_message(header, byte_count) &length=data_len; + default -> data : bytestring &length=data_len; + } &requires(data_len); } &let { data_len : uint32 = (data_len_high << 16) + data_len_low; offset_high : uint32 = (word_count == 0x0E) ? offset_high_tmp : 0; diff --git a/src/analyzer/protocol/smb/smb1-protocol.pac b/src/analyzer/protocol/smb/smb1-protocol.pac index 341c6abbf8..19afc626b6 100644 --- a/src/analyzer/protocol/smb/smb1-protocol.pac +++ b/src/analyzer/protocol/smb/smb1-protocol.pac @@ -68,7 +68,7 @@ refine connection SMB_Conn += { { BifEvent::generate_smb1_error(bro_analyzer(), bro_analyzer()->Conn(), - BuildHeaderVal(h)); + BuildHeaderVal(h), is_orig); } return true; %} diff --git a/src/analyzer/protocol/smb/smb1_com_transaction.bif b/src/analyzer/protocol/smb/smb1_com_transaction.bif new file mode 100644 index 0000000000..3ab02a51e6 --- /dev/null +++ b/src/analyzer/protocol/smb/smb1_com_transaction.bif @@ -0,0 +1,5 @@ +# TODO - Description +event smb1_transaction_request%(c: connection, hdr: SMB1::Header, name: string, sub_cmd: count%); + +# TODO - Description +event smb1_transaction_setup%(c: connection, hdr: SMB1::Header, op_code: count, file_id: count%); \ No newline at end of file diff --git a/src/analyzer/protocol/smb/smb1_com_transaction2.bif b/src/analyzer/protocol/smb/smb1_com_transaction2.bif new file mode 100644 index 0000000000..1430098f00 --- /dev/null +++ b/src/analyzer/protocol/smb/smb1_com_transaction2.bif @@ -0,0 +1,53 @@ +### Requests + + +# TODO - Description +event smb1_transaction2_request%(c: connection, hdr: SMB1::Header, sub_cmd: count%); + +# TODO - Description +event smb1_trans2_find_first2_request%(c: connection, hdr: SMB1::Header, args: SMB1::Find_First2_Request_Args%); + +# TODO - Implementation +# event smb1_trans2_fs_info_request%(c: connection, hdr: SMB1::Header, ??? %); + +# TODO - Description +event smb1_trans2_query_path_info_request%(c: connection, hdr: SMB1::Header, file_name: string, level_of_interest: count%); + +# TODO - Description +event smb1_trans2_query_file_info_request%(c: connection, hdr: SMB1::Header, file_id: count, level_of_interest: count%); + +# TODO - Implementation +# event smb1_trans2_set_file_info_request(c: connection, hdr: SMB1::Header, ??? %); + +# TODO - Description +event smb1_trans2_get_dfs_referral_request%(c: connection, hdr: SMB1::Header, file_name: string, max_referral_level: count%); + +### Responses + + +# TODO - Description +event smb1_transaction2_response%(c: connection, hdr: SMB1::Header, sub_cmd: count%); + +# TODO - Description +event smb1_trans2_find_first2_response%(c: connection, hdr: SMB1::Header, args: SMB1::Find_First2_Response_Args%); + +# TODO - Implementation +# event smb1_trans2_fs_info_response%(c: connection, hdr: SMB1::Header, ??? %); + +# TODO - Implementation +# event smb1_trans2_query_path_info_response%(c: connection, hdr: SMB1::Header, ??? %); + +# TODO - Implementation +# event smb1_trans2_query_file_info_response%(c: connection, hdr: SMB1::Header, ??? %); + +# TODO - Implementation +# event smb1_trans2_set_file_info_response%(c: connection, hdr: SMB1::Header, ??? %); + +# TODO - Implementation +# event smb1_trans2_get_dfs_referral_response%(c: connection, hdr: SMB1::Header, ??? %); + + +### Types + +type SMB1::Find_First2_Request_Args: record; +type SMB1::Find_First2_Response_Args: record; \ No newline at end of file diff --git a/src/analyzer/protocol/smb/smb1_events.bif b/src/analyzer/protocol/smb/smb1_events.bif index e2e1c1572e..1de3e777f6 100644 --- a/src/analyzer/protocol/smb/smb1_events.bif +++ b/src/analyzer/protocol/smb/smb1_events.bif @@ -25,5 +25,8 @@ event smb1_empty_response%(c: connection, hdr: SMB1::Header%); ## c: The connection. ## ## hdr: The parsed header of the SMB message. -event smb1_error%(c: connection, hdr: SMB1::Header%); +## +## is_orig: True if the message was sent by the originator of the underlying +## transport-level connection. +event smb1_error%(c: connection, hdr: SMB1::Header, is_orig: bool%); diff --git a/src/analyzer/protocol/smb/smb_pipe.bif b/src/analyzer/protocol/smb/smb_pipe.bif index b4116781b8..9bc65166f7 100644 --- a/src/analyzer/protocol/smb/smb_pipe.bif +++ b/src/analyzer/protocol/smb/smb_pipe.bif @@ -1,6 +1,12 @@ -## TODO -event smb_atsvc_job_add%(c: connection, server: string, job: string%); +## TODO - Description +event smb_pipe_bind_request%(c: connection, hdr: SMB1::Header, uuid: string, version: string%); -## TODO -event smb_atsvc_job_id%(c: connection, id: count, status: count%); +## TODO - Description +event smb_pipe_bind_ack_response%(c: connection, hdr: SMB1::Header%); + +## TODO - Description +event smb_pipe_request%(c: connection, hdr: SMB1::Header, op_num: count%); + +## TODO - Description +event smb_pipe_response%(c: connection, hdr: SMB1::Header%);