mirror of
https://github.com/zeek/zeek.git
synced 2025-10-02 06:38:20 +00:00

SMB error handling improved. The analyzer isn't destroyed when a problem is encoutered anymore. The flowbuffer in the parser is now flushed and the analyzer is set to resync against an SMB command. This was needed because there is some state about open files that is kept within the parser itself which was being destroyed and that was causing analysis after content gaps or parse errors to be faulty. The new mechanism doesn't detroy the parser so parsing after gaps is improved. DCE_RPC handling in SMB is improved in the edge case where a drive mapping isn't seen. There is a new const named SMB::pipe_filenames which is used as a heuristic for identifying "files" opened on named pipe shares. If the share mapping type isn't known and a filename in this set is found, the share type will change to "PIPE" by generating an event named "smb_pipe_connect_heuristic". Reads and writes to that file will be sent to the DCE_RPC analyzer instead of to the files framework. The concept of "unknown" share types has been removed due to the new heuristic detection of share types. Some general clean up of how the SMB cmd log is written and when.
209 lines
5.7 KiB
Text
209 lines
5.7 KiB
Text
@load ./consts
|
|
@load base/frameworks/dpd
|
|
|
|
module DCE_RPC;
|
|
|
|
export {
|
|
redef enum Log::ID += { LOG };
|
|
|
|
type Info: record {
|
|
## Timestamp for when the event happened.
|
|
ts : time &log;
|
|
## Unique ID for the connection.
|
|
uid : string &log;
|
|
## The connection's 4-tuple of endpoint addresses/ports.
|
|
id : conn_id &log;
|
|
## Round trip time from the request to the response.
|
|
## If either the request or response wasn't seen,
|
|
## this will be null.
|
|
rtt : interval &log &optional;
|
|
|
|
## Remote pipe name.
|
|
named_pipe : string &log &optional;
|
|
## Endpoint name looked up from the uuid.
|
|
endpoint : string &log &optional;
|
|
## Operation seen in the call.
|
|
operation : string &log &optional;
|
|
};
|
|
|
|
## These are DCE-RPC operations that are ignored, typically due
|
|
## the operations being noisy and low valueon most networks.
|
|
const ignored_operations: table[string] of set[string] = {
|
|
["winreg"] = set("BaseRegCloseKey", "BaseRegGetVersion", "BaseRegOpenKey", "BaseRegQueryValue", "BaseRegDeleteKeyEx", "OpenLocalMachine", "BaseRegEnumKey", "OpenClassesRoot"),
|
|
["spoolss"] = set("RpcSplOpenPrinter", "RpcClosePrinter"),
|
|
["wkssvc"] = set("NetrWkstaGetInfo"),
|
|
} &redef;
|
|
}
|
|
|
|
redef DPD::ignore_violations += { Analyzer::ANALYZER_DCE_RPC };
|
|
|
|
type State: record {
|
|
uuid : string &optional;
|
|
named_pipe : string &optional;
|
|
};
|
|
|
|
# This is to store the log and state information
|
|
# for multiple DCE/RPC bindings over a single TCP connection (named pipes).
|
|
type BackingState: record {
|
|
info: Info;
|
|
state: State;
|
|
};
|
|
|
|
redef record connection += {
|
|
dce_rpc: Info &optional;
|
|
dce_rpc_state: State &optional;
|
|
dce_rpc_backing: table[count] of BackingState &optional;
|
|
};
|
|
|
|
const ports = { 135/tcp };
|
|
redef likely_server_ports += { ports };
|
|
|
|
event bro_init() &priority=5
|
|
{
|
|
Log::create_stream(DCE_RPC::LOG, [$columns=Info, $path="dce_rpc"]);
|
|
Analyzer::register_for_ports(Analyzer::ANALYZER_DCE_RPC, ports);
|
|
}
|
|
|
|
function normalize_named_pipe_name(pn: string): string
|
|
{
|
|
local parts = split_string(pn, /\\[pP][iI][pP][eE]\\/);
|
|
if ( 1 in parts )
|
|
return to_lower(parts[1]);
|
|
else
|
|
return to_lower(pn);
|
|
}
|
|
|
|
function set_state(c: connection, state_x: BackingState)
|
|
{
|
|
c$dce_rpc = state_x$info;
|
|
c$dce_rpc_state = state_x$state;
|
|
|
|
if ( c$dce_rpc_state?$uuid )
|
|
c$dce_rpc$endpoint = uuid_endpoint_map[c$dce_rpc_state$uuid];
|
|
if ( c$dce_rpc_state?$named_pipe )
|
|
c$dce_rpc$named_pipe = c$dce_rpc_state$named_pipe;
|
|
}
|
|
|
|
function set_session(c: connection, fid: count)
|
|
{
|
|
if ( ! c?$dce_rpc_backing )
|
|
{
|
|
c$dce_rpc_backing = table();
|
|
}
|
|
if ( fid !in c$dce_rpc_backing )
|
|
{
|
|
local info = Info($ts=network_time(),$id=c$id,$uid=c$uid);
|
|
c$dce_rpc_backing[fid] = BackingState($info=info, $state=State());
|
|
}
|
|
|
|
local state_x = c$dce_rpc_backing[fid];
|
|
set_state(c, state_x);
|
|
}
|
|
|
|
event dce_rpc_bind(c: connection, fid: count, uuid: string, ver_major: count, ver_minor: count) &priority=5
|
|
{
|
|
set_session(c, fid);
|
|
|
|
local uuid_str = uuid_to_string(uuid);
|
|
c$dce_rpc_state$uuid = uuid_str;
|
|
c$dce_rpc$endpoint = uuid_endpoint_map[uuid_str];
|
|
}
|
|
|
|
event dce_rpc_bind_ack(c: connection, fid: count, sec_addr: string) &priority=5
|
|
{
|
|
set_session(c, fid);
|
|
|
|
if ( sec_addr != "" )
|
|
{
|
|
c$dce_rpc_state$named_pipe = sec_addr;
|
|
c$dce_rpc$named_pipe = sec_addr;
|
|
}
|
|
}
|
|
|
|
event dce_rpc_request(c: connection, fid: count, opnum: count, stub_len: count) &priority=5
|
|
{
|
|
set_session(c, fid);
|
|
|
|
if ( c?$dce_rpc )
|
|
{
|
|
c$dce_rpc$ts = network_time();
|
|
}
|
|
}
|
|
|
|
event dce_rpc_response(c: connection, fid: count, opnum: count, stub_len: count) &priority=5
|
|
{
|
|
set_session(c, fid);
|
|
|
|
# In the event that the binding wasn't seen, but the pipe
|
|
# name is known, go ahead and see if we have a pipe name to
|
|
# uuid mapping...
|
|
if ( ! c$dce_rpc?$endpoint && c$dce_rpc?$named_pipe )
|
|
{
|
|
local npn = normalize_named_pipe_name(c$dce_rpc$named_pipe);
|
|
if ( npn in pipe_name_to_common_uuid )
|
|
{
|
|
c$dce_rpc_state$uuid = pipe_name_to_common_uuid[npn];
|
|
}
|
|
}
|
|
|
|
if ( c?$dce_rpc && c$dce_rpc?$endpoint )
|
|
{
|
|
c$dce_rpc$operation = operations[c$dce_rpc_state$uuid, opnum];
|
|
if ( c$dce_rpc$ts != network_time() )
|
|
c$dce_rpc$rtt = network_time() - c$dce_rpc$ts;
|
|
}
|
|
}
|
|
|
|
event dce_rpc_response(c: connection, fid: count, opnum: count, stub_len: count) &priority=-5
|
|
{
|
|
if ( c?$dce_rpc )
|
|
{
|
|
# If there is not an endpoint, there isn't much reason to log.
|
|
# This can happen if the request isn't seen.
|
|
if ( (c$dce_rpc?$endpoint && c$dce_rpc?$operation &&
|
|
c$dce_rpc$endpoint !in ignored_operations)
|
|
||
|
|
(c$dce_rpc?$endpoint && c$dce_rpc?$operation &&
|
|
c$dce_rpc$operation !in ignored_operations[c$dce_rpc$endpoint] &&
|
|
"*" !in ignored_operations[c$dce_rpc$endpoint]) )
|
|
{
|
|
Log::write(LOG, c$dce_rpc);
|
|
}
|
|
delete c$dce_rpc;
|
|
}
|
|
}
|
|
|
|
event connection_state_remove(c: connection)
|
|
{
|
|
if ( ! c?$dce_rpc )
|
|
return;
|
|
|
|
# TODO: Go through any remaining dce_rpc requests that haven't been processed with replies.
|
|
for ( i in c$dce_rpc_backing )
|
|
{
|
|
local x = c$dce_rpc_backing[i];
|
|
set_state(c, x);
|
|
|
|
# In the event that the binding wasn't seen, but the pipe
|
|
# name is known, go ahead and see if we have a pipe name to
|
|
# uuid mapping...
|
|
if ( ! c$dce_rpc?$endpoint && c$dce_rpc?$named_pipe )
|
|
{
|
|
local npn = normalize_named_pipe_name(c$dce_rpc$named_pipe);
|
|
if ( npn in pipe_name_to_common_uuid )
|
|
{
|
|
c$dce_rpc_state$uuid = pipe_name_to_common_uuid[npn];
|
|
}
|
|
}
|
|
|
|
if ( (c$dce_rpc?$endpoint && c$dce_rpc?$operation &&
|
|
c$dce_rpc$endpoint !in ignored_operations)
|
|
||
|
|
(c$dce_rpc?$endpoint && c$dce_rpc?$operation &&
|
|
c$dce_rpc$operation !in ignored_operations[c$dce_rpc$endpoint] &&
|
|
"*" !in ignored_operations[c$dce_rpc$endpoint]) )
|
|
{
|
|
Log::write(LOG, c$dce_rpc);
|
|
}
|
|
}
|
|
}
|