# $Id: worm.bro,v 1.12 2003/09/04 06:02:41 vern Exp $

@load alert
@load site

redef enum Alert += {
	LocalWorm,		# worm seen in local host
	RemoteWorm,		# worm seen in remote host
};

# redef capture_filters += { ["worm"] = "tcp dst port 80" };

global worm_log = open_log_file("worm") &redef;

# Maps types of worms to URI patterns.
const worm_types: table[string] of pattern = {
	["Code Red 1"] = /\.id[aq]\?.*NNNNNNNNNNNNN/,
	["Code Red 2"] = /\.id[aq]\?.*XXXXXXXXXXXXX/,
	["Nimda"] = /\/scripts\/root\.exe\?\/c\+tftp/ |
			/\/MSADC\/root.exe\?\/c\+dir/ |
			/cool\.dll.*httpodbc\.dll/,	# 29Oct01 Nimda variant
} &redef;

# Maps signatures to worm types.
const worm_sigs: table[string] of string = {
    ["slammer"] = "Slammer",
    ["nimda"] = "Nimda"
};

# Indexed by infectee.
global worm_list: table[addr] of count &default=0;

# Indexed by infectee and type of worm.
global worm_type_list: table[addr, string] of count &default=0;

# Invoked each time a new infectee (or a new type of worm for an existing
# infectee) is seen.  For the first instance of any type for a new infectee,
# two events will be generated, one with worm_type of "first instance",
# and another with the particular worm type.
global worm_infectee_seen: event(infectee: addr, is_local: bool, worm_type: string);

# Invoked whenever connection c has included a URI of worm type "worm_type".
event worm_instance(c: connection, worm_type: string)
	{
	local id = c$id;
	local src = id$orig_h;
	local is_local = is_local_addr(src);

	if ( ++worm_list[src] == 1 )
		event worm_infectee_seen(src, is_local, "first instance");

	if ( ++worm_type_list[src, worm_type] == 1 )
		event worm_infectee_seen(src, is_local, worm_type);
	}

event worm_infectee_seen(infectee: addr, is_local: bool, worm_type: string)
	{
	if ( worm_type == "first instance" )
		return;	# just do the reporting for the specific type

	local where = is_local ? "local" : "remote";
	local msg = fmt("%s %s worm source: %s", where, worm_type, infectee);

	if ( is_local )
		ALERT([$alert=LocalWorm, $src=infectee, $msg=msg]);
	else
		ALERT([$alert=RemoteWorm, $src=infectee, $msg=msg]);

	print worm_log, fmt("%.6f %s", network_time(), msg);
	}

event http_request(c: connection, method: string,
		   original_URI: string, unescaped_URI: string, version: string)
	{
	# It's a pity to do this as a loop.  Better would be if Bro could
	# search the patterns as one large RE and note which matched.

	for ( wt in worm_types )
		if ( worm_types[wt] in unescaped_URI )
			event worm_instance(c, wt);
	}

event signature_match(state: signature_state, msg: string, data: string)
	{
	if ( state$id in worm_sigs )
		event worm_instance(state$conn, worm_sigs[state$id]);
	}

# Ignore "weird" events, we get some due to the capture_filter above that
# only captures the client side of an HTTP session.
event conn_weird(name: string, c: connection)
	{
	}

function is_worm_infectee( ip: addr ): bool
	{
	return ip in worm_list;
	}

