mirror of
https://github.com/zeek/zeek.git
synced 2025-10-02 06:38:20 +00:00
278 lines
7.1 KiB
Text
278 lines
7.1 KiB
Text
# $Id: adu.bro 5152 2007-12-04 21:48:56Z vern $
|
|
|
|
@load conn-id
|
|
|
|
module adu;
|
|
|
|
# This script parses application-layer data (ADU) units, or "messages",
|
|
# out of the packet streams. Since the analysis is generic, we define
|
|
# an ADU simply as all application-layer data in a 5-tuple flow going
|
|
# in one direction without any data going the other way. Once we see
|
|
# data in the other direction, we finish the current ADU and start
|
|
# a new one (going the other way). While this approach is only
|
|
# approximate, it can work well for both UDP and TCP.
|
|
#
|
|
# The script reports ADUs as strings, up to a configurable maximum size, and
|
|
# up to a configurable depth into the flow.
|
|
#
|
|
# Generated events:
|
|
#
|
|
# - adu_tx(c: connection, a: adu_state) reports an ADU seen from
|
|
# c's originator to its responder.
|
|
#
|
|
# - adu_rx(c: connection, a: adu_state) reports an ADU seen from
|
|
# c's responder to the originator.
|
|
#
|
|
# - adu_done(c: connection) indicates that no more ADUs will be seen
|
|
# on connection c. This is useful to know in case your statekeeping
|
|
# relies on event connection_state_remove(), which is also used by
|
|
# adu.bro.
|
|
#
|
|
|
|
# --- Input configuration -- which ports to look at --------------------
|
|
|
|
# Right now: everything!
|
|
#
|
|
redef tcp_content_deliver_all_orig = T;
|
|
redef tcp_content_deliver_all_resp = T;
|
|
redef udp_content_deliver_all_orig = T;
|
|
redef udp_content_deliver_all_resp = T;
|
|
|
|
# --- Debugging -- should really be a separate policy ------------------
|
|
|
|
# Comment out to disable debugging output:
|
|
#global adu_debug = T;
|
|
|
|
# Uncomment to enable tests:
|
|
#global adu_test = T;
|
|
|
|
@ifdef (adu_debug)
|
|
function DBG(msg: string) { print fmt("DBG[adu.bro]: %s", msg); }
|
|
@else
|
|
function DBG(msg: string) { }
|
|
@endif
|
|
|
|
export {
|
|
|
|
# --- Constants --------------------------------------------------------
|
|
|
|
# The maximum depth in bytes up to which we follow a flow.
|
|
# This is counting bytes seen in both directions.
|
|
const adu_conn_max_depth = 100000 &redef;
|
|
|
|
# The maximum message depth that we report.
|
|
const adu_max_depth = 3 &redef;
|
|
|
|
# The maximum message size in bytes that we report.
|
|
const adu_max_size = 1000 &redef;
|
|
|
|
# Whether ADUs are reported beyond content gaps.
|
|
const adu_gaps_ok = F &redef;
|
|
|
|
# --- Types ------------------------------------------------------------
|
|
|
|
# adu_state records contain the latest ADU and aditional flags to help
|
|
# the user identify the direction of the message, its depth in the flow,
|
|
# etc.
|
|
type adu_state: record {
|
|
adu: string &default = ""; # the current ADU
|
|
|
|
# Message counter (>= 1), orig->resp and resp->orig.
|
|
depth_tx: count &default = 1;
|
|
depth_rx: count &default = 1;
|
|
|
|
# TCP: seqno tracking to recognize gaps.
|
|
seen_tx: count &default = 0;
|
|
seen_rx: count &default = 0;
|
|
|
|
size: count &default = 0; # total connection size in bytes
|
|
is_orig: bool &default = F; # whether ADU is orig->resp
|
|
ignore: bool &default = F; # ignore future activity on conn
|
|
};
|
|
|
|
# Tell the ADU policy that you do not wish to receive further
|
|
# adu_tx/adu_rx events for a given connection. Other policies
|
|
# may continue to process the connection.
|
|
#
|
|
global adu_skip_further_processing: function(cid: conn_id);
|
|
}
|
|
|
|
|
|
# --- Globals ----------------------------------------------------------
|
|
|
|
# A global table that tracks each flow's messages.
|
|
global adu_conns: table[conn_id] of adu_state;
|
|
|
|
# Testing invokes the following events.
|
|
global adu_tx: event(c: connection, astate: adu_state);
|
|
global adu_rx: event(c: connection, astate: adu_state);
|
|
global adu_done: event(c: connection);
|
|
|
|
# --- Functions --------------------------------------------------------
|
|
|
|
function adu_skip_further_processing(cid: conn_id)
|
|
{
|
|
if ( cid !in adu_conns )
|
|
return;
|
|
|
|
adu_conns[cid]$ignore = T;
|
|
}
|
|
|
|
function flow_contents(c: connection, is_orig: bool, seq: count, contents: string)
|
|
{
|
|
local astate: adu_state;
|
|
|
|
DBG(fmt("contents %s, %s: %s", id_string(c$id), is_orig, contents));
|
|
|
|
# Ensure we track the given connection.
|
|
if ( c$id !in adu_conns )
|
|
adu_conns[c$id] = astate;
|
|
else
|
|
astate = adu_conns[c$id];
|
|
|
|
# Forget it if we've been asked to ignore.
|
|
#
|
|
if ( astate$ignore == T )
|
|
return;
|
|
|
|
# Don't report if flow is too big.
|
|
#
|
|
if ( astate$size >= adu_conn_max_depth )
|
|
return;
|
|
|
|
# If we have an assembled message, we may now have something
|
|
# to report.
|
|
if ( |astate$adu| > 0 )
|
|
{
|
|
# If application-layer data flow is switching
|
|
# from resp->orig to orig->resp, report the assembled
|
|
# message as a received ADU.
|
|
if ( is_orig && ! astate$is_orig )
|
|
{
|
|
event adu_rx(c, copy(astate));
|
|
astate$adu = "";
|
|
|
|
if ( ++astate$depth_rx > adu_max_depth )
|
|
adu_skip_further_processing(c$id);
|
|
}
|
|
|
|
# If application-layer data flow is switching
|
|
# from orig->resp to resp->orig, report the assembled
|
|
# message as a transmitted ADU.
|
|
#
|
|
if ( !is_orig && astate$is_orig )
|
|
{
|
|
event adu_tx(c, copy(astate));
|
|
astate$adu = "";
|
|
|
|
if ( ++astate$depth_tx > adu_max_depth )
|
|
adu_skip_further_processing(c$id);
|
|
}
|
|
}
|
|
|
|
# Check for content gaps. If we identify one, only continue
|
|
# if user allowed it.
|
|
#
|
|
if ( !adu_gaps_ok && seq > 0 )
|
|
{
|
|
if ( is_orig )
|
|
{
|
|
if ( seq > astate$seen_tx + 1 )
|
|
return;
|
|
else
|
|
astate$seen_tx += |contents|;
|
|
}
|
|
else
|
|
{
|
|
if ( seq > astate$seen_rx + 1 )
|
|
return;
|
|
else
|
|
astate$seen_rx += |contents|;
|
|
}
|
|
}
|
|
|
|
# Append the contents to the end of the currently
|
|
# assembled message, if the message hasn't already
|
|
# reached the maximum size.
|
|
#
|
|
if ( |astate$adu| < adu_max_size )
|
|
{
|
|
astate$adu += contents;
|
|
|
|
# As a precaution, clip the string to the maximum
|
|
# size. A long content string with astate$adu just
|
|
# below its maximum allowed size could exceed that
|
|
# limit by a lot.
|
|
### str_clip(astate$adu, adu_max_size);
|
|
}
|
|
|
|
|
|
# Note that this counter is bumped up even if we have
|
|
# exceeded the maximum size of an individual message.
|
|
#
|
|
astate$size += |contents|;
|
|
|
|
astate$is_orig = is_orig;
|
|
}
|
|
|
|
# --- Event Handlers ---------------------------------------------------
|
|
|
|
event tcp_contents(c: connection, is_orig: bool, seq: count, contents: string)
|
|
{
|
|
flow_contents(c, is_orig, seq, contents);
|
|
}
|
|
|
|
event udp_contents(u: connection, is_orig: bool, contents: string)
|
|
{
|
|
flow_contents(u, is_orig, 0, contents);
|
|
}
|
|
|
|
event connection_state_remove(c: connection)
|
|
{
|
|
if ( c$id !in adu_conns )
|
|
return;
|
|
|
|
local astate = adu_conns[c$id];
|
|
|
|
# Forget it if we've been asked to ignore.
|
|
#
|
|
if ( astate$ignore == T )
|
|
return;
|
|
|
|
# Report the remaining data now, if any.
|
|
#
|
|
if ( |astate$adu| > 0 ) {
|
|
if ( astate$is_orig )
|
|
{
|
|
if ( astate$depth_tx <= adu_max_depth )
|
|
event adu_tx(c, copy(astate));
|
|
}
|
|
else
|
|
{
|
|
if ( astate$depth_rx <= adu_max_depth )
|
|
event adu_rx(c, copy(astate));
|
|
}
|
|
}
|
|
|
|
delete adu_conns[c$id];
|
|
event adu_done(c);
|
|
}
|
|
|
|
|
|
# --- Tests ------------------------------------------------------------
|
|
|
|
@ifdef (adu_test)
|
|
|
|
event adu_tx(c: connection, astate: adu_state)
|
|
{
|
|
print fmt("%s ---- %s, %d -> ----", network_time(), id_string(c$id), astate$depth_tx);
|
|
# print astate$adu;
|
|
}
|
|
|
|
event adu_rx(c: connection, astate: adu_state)
|
|
{
|
|
print fmt("%s ---- %s, %d <- ----", network_time(), id_string(c$id), astate$depth_rx);
|
|
# print astate$adu;
|
|
}
|
|
|
|
@endif
|