mirror of
https://github.com/zeek/zeek.git
synced 2025-10-02 06:38:20 +00:00
306 lines
7.5 KiB
Text
306 lines
7.5 KiB
Text
# $Id: icmp.bro 6883 2009-08-19 21:08:09Z vern $
|
|
|
|
@load hot
|
|
@load weird
|
|
@load conn
|
|
@load scan
|
|
|
|
global icmp_file = open_log_file("icmp");
|
|
|
|
redef capture_filters += { ["icmp"] = "icmp" };
|
|
|
|
module ICMP;
|
|
|
|
export {
|
|
|
|
redef enum Notice += {
|
|
ICMPAsymPayload, # payload in echo req-resp not the same
|
|
ICMPConnectionPair, # too many ICMPs between hosts
|
|
ICMPAddressScan,
|
|
|
|
# The following isn't presently sufficiently useful due
|
|
# to cold start and packet drops.
|
|
# ICMPUnpairedEchoReply, # no EchoRequest seen for EchoReply
|
|
};
|
|
|
|
# Whether to log detailed information icmp.log.
|
|
const log_details = T &redef;
|
|
|
|
# ICMP scan detection.
|
|
const detect_scans = T &redef;
|
|
const scan_threshold = 25 &redef;
|
|
|
|
# Analysis of connection pairs.
|
|
const detect_conn_pairs = F &redef; # switch for connection pair
|
|
const detect_payload_asym = F &redef; # switch for echo payload
|
|
const conn_pair_threshold = 200 &redef;
|
|
}
|
|
|
|
global conn_pair:table[addr] of set[addr] &create_expire = 1 day;
|
|
global conn_pair_thresh_reached: table[addr] of bool &default=F;
|
|
|
|
|
|
|
|
type flow_id: record {
|
|
orig_h: addr;
|
|
resp_h: addr;
|
|
id: count;
|
|
};
|
|
|
|
type flow_info: record {
|
|
start_time: time;
|
|
last_time: time;
|
|
orig_bytes: count;
|
|
resp_bytes: count;
|
|
payload: string;
|
|
};
|
|
|
|
const names: table[count] of string = {
|
|
[0] = "echo_reply",
|
|
[3] = "unreach",
|
|
[4] = "quench",
|
|
[5] = "redirect",
|
|
[8] = "echo_req",
|
|
[9] = "router_adv",
|
|
[10] = "router_sol",
|
|
[11] = "time_xcd",
|
|
[12] = "param_prob",
|
|
[13] = "tstamp_req",
|
|
[14] = "tstamp_reply",
|
|
[15] = "info_req",
|
|
[16] = "info_reply",
|
|
[17] = "mask_req",
|
|
[18] = "mask_reply",
|
|
} &default = function(n: count): string { return fmt("icmp-%d", n); };
|
|
|
|
|
|
# Map IP protocol number to the protocol's name.
|
|
const IP_proto_name: table[count] of string = {
|
|
[1] = "ICMP",
|
|
[2] = "IGMP",
|
|
[6] = "TCP",
|
|
[17] = "UDP",
|
|
[41] = "IPV6",
|
|
} &default = function(n: count): string { return fmt("%s", n); }
|
|
&redef;
|
|
|
|
# Print a report for the given ICMP flow.
|
|
function generate_flow_summary(flow: flow_id, fi: flow_info)
|
|
{
|
|
local local_init = is_local_addr(flow$orig_h);
|
|
local local_addr = local_init ? flow$orig_h : flow$resp_h;
|
|
local remote_addr = local_init ? flow$resp_h : flow$orig_h;
|
|
local flags = local_init ? "L" : "";
|
|
|
|
local state: string;
|
|
if ( fi$orig_bytes > 0 )
|
|
{
|
|
if ( fi$resp_bytes > 0 )
|
|
state = "SF";
|
|
else
|
|
state = "SH";
|
|
}
|
|
else if ( fi$resp_bytes > 0 )
|
|
state = "SHR";
|
|
else
|
|
state = "OTH";
|
|
|
|
print icmp_file, fmt("%.6f %.6f %s %s %s %s %s %s %s",
|
|
fi$start_time, fi$last_time - fi$start_time,
|
|
flow$orig_h, flow$resp_h, "icmp_echo",
|
|
fi$orig_bytes, fi$resp_bytes, state, flags);
|
|
}
|
|
|
|
# Called when a flow is expired in order to generate a report for it.
|
|
function flush_flow(ft: table[flow_id] of flow_info, fi: flow_id): interval
|
|
{
|
|
generate_flow_summary(fi, ft[fi]);
|
|
return 0 sec;
|
|
}
|
|
|
|
# Table to track each active flow.
|
|
global flows: table[flow_id] of flow_info
|
|
&read_expire = 45 sec
|
|
&expire_func = flush_flow;
|
|
|
|
event icmp_sent(c: connection, icmp: icmp_conn)
|
|
{
|
|
print icmp_file, fmt("%.6f %.6f %s %s %s %s %s %s %s %s %s",
|
|
network_time(), 0.0, icmp$orig_h, icmp$resp_h,
|
|
names[icmp$itype], icmp$itype, icmp$icode, "icmp",
|
|
icmp$len, "0", "SH");
|
|
}
|
|
|
|
event flow_summary(flow: flow_id, last_time: time)
|
|
{
|
|
if ( flow !in flows )
|
|
return;
|
|
|
|
local fi = flows[flow];
|
|
|
|
if ( fi$last_time == last_time )
|
|
{
|
|
generate_flow_summary(flow, fi);
|
|
delete flows[flow];
|
|
}
|
|
}
|
|
|
|
function update_flow(icmp: icmp_conn, id: count, is_orig: bool, payload: string)
|
|
{
|
|
local fid: flow_id;
|
|
fid$orig_h = is_orig ? icmp$orig_h : icmp$resp_h;
|
|
fid$resp_h = is_orig ? icmp$resp_h : icmp$orig_h;
|
|
fid$id = id;
|
|
|
|
if ( fid !in flows )
|
|
{
|
|
local info: flow_info;
|
|
info$start_time = network_time();
|
|
info$orig_bytes = info$resp_bytes = 0;
|
|
info$payload = payload; # checked in icmp_echo_reply
|
|
flows[fid] = info;
|
|
}
|
|
|
|
local fi = flows[fid];
|
|
|
|
fi$last_time = network_time();
|
|
|
|
if ( is_orig )
|
|
fi$orig_bytes = fi$orig_bytes + byte_len(payload);
|
|
else
|
|
fi$resp_bytes = fi$resp_bytes + byte_len(payload);
|
|
|
|
schedule +30sec { flow_summary(fid, fi$last_time) };
|
|
}
|
|
|
|
event icmp_echo_request(c: connection, icmp: icmp_conn, id: count, seq: count, payload: string)
|
|
{
|
|
update_flow(icmp, id, T, payload);
|
|
|
|
local orig = icmp$orig_h;
|
|
local resp = icmp$resp_h;
|
|
|
|
# Simple ping scan detector.
|
|
if ( detect_scans &&
|
|
(orig !in Scan::distinct_peers ||
|
|
resp !in Scan::distinct_peers[orig]) )
|
|
{
|
|
if ( orig !in Scan::distinct_peers )
|
|
{
|
|
local empty_peer_set: set[addr] &mergeable;
|
|
Scan::distinct_peers[orig] = empty_peer_set;
|
|
}
|
|
|
|
if ( resp !in Scan::distinct_peers[orig] )
|
|
add Scan::distinct_peers[orig][resp];
|
|
|
|
if ( ! Scan::shut_down_thresh_reached[orig] &&
|
|
orig !in Scan::skip_scan_sources &&
|
|
orig !in Scan::skip_scan_nets &&
|
|
|Scan::distinct_peers[orig]| >= scan_threshold )
|
|
{
|
|
NOTICE([$note=ICMPAddressScan, $src=orig,
|
|
$n=scan_threshold,
|
|
$msg=fmt("%s has icmp echo scanned %s hosts",
|
|
orig, scan_threshold)]);
|
|
|
|
Scan::shut_down_thresh_reached[orig] = T;
|
|
}
|
|
}
|
|
|
|
if ( detect_conn_pairs )
|
|
{
|
|
if ( orig !in conn_pair )
|
|
{
|
|
local empty_peer_set2: set[addr] &mergeable;
|
|
conn_pair[orig] = empty_peer_set2;
|
|
}
|
|
|
|
if ( resp !in conn_pair[orig] )
|
|
add conn_pair[orig][resp];
|
|
|
|
if ( ! conn_pair_thresh_reached[orig] &&
|
|
|conn_pair[orig]| >= conn_pair_threshold )
|
|
{
|
|
NOTICE([$note=ICMPConnectionPair,
|
|
$msg=fmt("ICMP connection threshold exceeded : %s -> %s",
|
|
orig, resp)]);
|
|
conn_pair_thresh_reached[orig] = T;
|
|
}
|
|
}
|
|
}
|
|
|
|
event icmp_echo_reply(c: connection, icmp: icmp_conn, id: count,
|
|
seq: count, payload: string)
|
|
{
|
|
# Check payload with the associated flow.
|
|
|
|
local fid: flow_id;
|
|
fid$orig_h = icmp$resp_h; # We know the expected results since
|
|
fid$resp_h = icmp$orig_h; # it's an echo reply.
|
|
fid$id = id;
|
|
|
|
if ( fid !in flows )
|
|
{
|
|
# NOTICE([$note=ICMPUnpairedEchoReply,
|
|
# $msg=fmt("ICMP echo reply w/o request: %s -> %s",
|
|
# icmp$orig_h, icmp$resp_h)]);
|
|
}
|
|
else
|
|
{
|
|
if ( detect_payload_asym )
|
|
{
|
|
local fi = flows[fid];
|
|
local pl = fi$payload;
|
|
|
|
if ( pl != payload )
|
|
{
|
|
NOTICE([$note=ICMPAsymPayload,
|
|
$msg=fmt("ICMP payload inconsistancy: %s(%s) -> %s(%s)",
|
|
icmp$orig_h, byte_len(fi$payload),
|
|
icmp$resp_h, byte_len(payload))]);
|
|
}
|
|
}
|
|
}
|
|
|
|
update_flow(icmp, id, F, payload);
|
|
}
|
|
|
|
event icmp_unreachable(c: connection, icmp: icmp_conn, code: count,
|
|
context: icmp_context)
|
|
{
|
|
if ( active_connection(context$id) )
|
|
{
|
|
# This section allows Bro to act on ICMP-unreachable packets
|
|
# that happen in the context of an active connection. It is
|
|
# not currently used.
|
|
local c2 = connection_record(context$id);
|
|
local os = c2$orig$state;
|
|
local rs = c2$resp$state;
|
|
local is_attempt =
|
|
is_tcp_port(c2$id$orig_p) ?
|
|
(os == TCP_SYN_SENT && rs == TCP_INACTIVE) :
|
|
(os == UDP_ACTIVE && rs == UDP_INACTIVE);
|
|
|
|
# Insert action here.
|
|
}
|
|
|
|
if ( log_details )
|
|
{
|
|
# ICMP unreachable packets are the only ones currently
|
|
# logged. Due to the connection data contained *within*
|
|
# them, each log line will contain two connections' worth
|
|
# of data. The initial ICMP connection info is the same
|
|
# as logged for connections.
|
|
print icmp_file, fmt("%.6f %.6f %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s",
|
|
network_time(), 0.0, icmp$orig_h, icmp$resp_h,
|
|
names[icmp$itype], icmp$itype, icmp$icode, "icmp",
|
|
icmp$len, "0", "EncapPkt:",
|
|
# This is the encapsulated packet:
|
|
context$id$orig_h, context$id$orig_p,
|
|
context$id$resp_h, context$id$resp_p,
|
|
context$len, IP_proto_name[context$proto],
|
|
context$len, context$bad_hdr_len,
|
|
context$bad_checksum);
|
|
}
|
|
}
|