mirror of
https://github.com/zeek/zeek.git
synced 2025-10-08 09:38:19 +00:00
482 lines
11 KiB
C++
482 lines
11 KiB
C++
#include "DPM.h"
|
|
#include "PIA.h"
|
|
#include "Hash.h"
|
|
#include "ICMP.h"
|
|
#include "UDP.h"
|
|
#include "TCP.h"
|
|
#include "Val.h"
|
|
#include "BackDoor.h"
|
|
#include "InterConn.h"
|
|
#include "SteppingStone.h"
|
|
#include "ConnSizeAnalyzer.h"
|
|
|
|
|
|
ExpectedConn::ExpectedConn(const uint32* _orig, const uint32* _resp,
|
|
uint16 _resp_p, uint16 _proto)
|
|
{
|
|
if ( orig )
|
|
copy_addr(_orig, orig);
|
|
else
|
|
{
|
|
for ( int i = 0; i < NUM_ADDR_WORDS; ++i )
|
|
orig[i] = 0;
|
|
}
|
|
|
|
copy_addr(_resp, resp);
|
|
|
|
resp_p = _resp_p;
|
|
proto = _proto;
|
|
}
|
|
|
|
ExpectedConn::ExpectedConn(uint32 _orig, uint32 _resp,
|
|
uint16 _resp_p, uint16 _proto)
|
|
{
|
|
#ifdef BROv6
|
|
// Use the IPv4-within-IPv6 convention, as this is what's
|
|
// needed when we mix uint32's (like in this construction)
|
|
// with addr_type's (for example, when looking up expected
|
|
// connections).
|
|
|
|
orig[0] = orig[1] = orig[2] = 0;
|
|
resp[0] = resp[1] = resp[2] = 0;
|
|
orig[3] = _orig;
|
|
resp[3] = _resp;
|
|
#else
|
|
orig[0] = _orig;
|
|
resp[0] = _resp;
|
|
#endif
|
|
resp_p = _resp_p;
|
|
proto = _proto;
|
|
}
|
|
|
|
ExpectedConn::ExpectedConn(const ExpectedConn& c)
|
|
{
|
|
copy_addr(c.orig, orig);
|
|
copy_addr(c.resp, resp);
|
|
resp_p = c.resp_p;
|
|
proto = c.proto;
|
|
}
|
|
|
|
|
|
DPM::DPM()
|
|
: expected_conns_queue(AssignedAnalyzer::compare)
|
|
{
|
|
}
|
|
|
|
DPM::~DPM()
|
|
{
|
|
delete [] active_analyzers;
|
|
}
|
|
|
|
void DPM::PreScriptInit()
|
|
{
|
|
for ( int i = 1; i < int(AnalyzerTag::LastAnalyzer); i++ )
|
|
{
|
|
// Create IDs ANALYZER_*.
|
|
ID* id = install_ID(fmt("ANALYZER_%s",
|
|
Analyzer::analyzer_configs[i].name),
|
|
GLOBAL_MODULE_NAME, true, false);
|
|
assert(id);
|
|
id->SetVal(new Val(i, TYPE_COUNT));
|
|
id->SetType(id->ID_Val()->Type()->Ref());
|
|
}
|
|
}
|
|
|
|
void DPM::PostScriptInit()
|
|
{
|
|
active_analyzers = new bool[int(AnalyzerTag::LastAnalyzer)];
|
|
|
|
for ( int i = 1; i < int(AnalyzerTag::LastAnalyzer); i++ )
|
|
{
|
|
if ( ! Analyzer::analyzer_configs[i].available )
|
|
continue;
|
|
|
|
active_analyzers[i] = Analyzer::analyzer_configs[i].available();
|
|
if ( active_analyzers[i] )
|
|
AddConfig(Analyzer::analyzer_configs[i]);
|
|
}
|
|
}
|
|
|
|
void DPM::AddConfig(const Analyzer::Config& cfg)
|
|
{
|
|
#ifdef USE_PERFTOOLS
|
|
HeapLeakChecker::Disabler disabler;
|
|
#endif
|
|
|
|
Val* index = new Val(cfg.tag, TYPE_COUNT);
|
|
Val* v = dpd_config->Lookup(index);
|
|
|
|
#ifdef DEBUG
|
|
ODesc desc;
|
|
#endif
|
|
if ( v )
|
|
{
|
|
RecordVal* cfg_record = v->AsRecordVal();
|
|
Val* ports = cfg_record->Lookup(0);
|
|
|
|
if ( ports )
|
|
{
|
|
ListVal* plist = ports->AsTableVal()->ConvertToPureList();
|
|
|
|
for ( int i = 0; i< plist->Length(); ++i )
|
|
{
|
|
PortVal* port = plist->Index(i)->AsPortVal();
|
|
|
|
analyzer_map* ports =
|
|
port->IsTCP() ? &tcp_ports : &udp_ports;
|
|
|
|
analyzer_map::iterator j =
|
|
ports->find(port->Port());
|
|
|
|
if ( j == ports->end() )
|
|
{
|
|
tag_list* analyzers = new tag_list;
|
|
analyzers->push_back(cfg.tag);
|
|
ports->insert(analyzer_map::value_type(port->Port(), analyzers));
|
|
}
|
|
else
|
|
j->second->push_back(cfg.tag);
|
|
|
|
#ifdef DEBUG
|
|
port->Describe(&desc);
|
|
desc.SP();
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
DBG_LOG(DBG_DPD, "%s analyzer active on port(s) %s", cfg.name, desc.Description());
|
|
|
|
Unref(index);
|
|
}
|
|
|
|
AnalyzerTag::Tag DPM::GetExpected(int proto, const Connection* conn)
|
|
{
|
|
if ( ! expected_conns.Length() )
|
|
return AnalyzerTag::Error;
|
|
|
|
ExpectedConn c(conn->OrigAddr(), conn->RespAddr(),
|
|
ntohs(conn->RespPort()), proto);
|
|
|
|
// Can't use sizeof(c) due to potential alignment issues.
|
|
// FIXME: I guess this is still not portable ...
|
|
HashKey key(&c, sizeof(c.orig) + sizeof(c.resp) +
|
|
sizeof(c.resp_p) + sizeof(c.proto));
|
|
|
|
AssignedAnalyzer* a = expected_conns.Lookup(&key);
|
|
|
|
if ( ! a )
|
|
{
|
|
// Wildcard for originator.
|
|
for ( int i = 0; i < NUM_ADDR_WORDS; ++i )
|
|
c.orig[i] = 0;
|
|
|
|
HashKey key(&c, sizeof(c.orig) + sizeof(c.resp) +
|
|
sizeof(c.resp_p) + sizeof(c.proto));
|
|
|
|
a = expected_conns.Lookup(&key);
|
|
}
|
|
|
|
if ( ! a )
|
|
return AnalyzerTag::Error;
|
|
|
|
// We don't delete it here. It will be expired eventually.
|
|
return a->analyzer;
|
|
}
|
|
|
|
bool DPM::BuildInitialAnalyzerTree(TransportProto proto, Connection* conn,
|
|
const u_char* data)
|
|
{
|
|
TCP_Analyzer* tcp = 0;
|
|
UDP_Analyzer* udp = 0;
|
|
ICMP_Analyzer* icmp = 0;
|
|
TransportLayerAnalyzer* root = 0;
|
|
AnalyzerTag::Tag expected = AnalyzerTag::Error;
|
|
analyzer_map* ports = 0;
|
|
PIA* pia = 0;
|
|
bool analyzed = false;
|
|
|
|
switch ( proto ) {
|
|
|
|
case TRANSPORT_TCP:
|
|
root = tcp = new TCP_Analyzer(conn);
|
|
pia = new PIA_TCP(conn);
|
|
expected = GetExpected(proto, conn);
|
|
ports = &tcp_ports;
|
|
DBG_DPD(conn, "activated TCP analyzer");
|
|
break;
|
|
|
|
case TRANSPORT_UDP:
|
|
root = udp = new UDP_Analyzer(conn);
|
|
pia = new PIA_UDP(conn);
|
|
expected = GetExpected(proto, conn);
|
|
ports = &udp_ports;
|
|
DBG_DPD(conn, "activated UDP analyzer");
|
|
break;
|
|
|
|
case TRANSPORT_ICMP: {
|
|
const struct icmp* icmpp = (const struct icmp *) data;
|
|
switch ( icmpp->icmp_type ) {
|
|
|
|
case ICMP_ECHO:
|
|
case ICMP_ECHOREPLY:
|
|
if ( ICMP_Echo_Analyzer::Available() )
|
|
{
|
|
root = icmp = new ICMP_Echo_Analyzer(conn);
|
|
DBG_DPD(conn, "activated ICMP Echo analyzer");
|
|
}
|
|
break;
|
|
|
|
case ICMP_REDIRECT:
|
|
if ( ICMP_Redir_Analyzer::Available() )
|
|
{
|
|
root = new ICMP_Redir_Analyzer(conn);
|
|
DBG_DPD(conn, "activated ICMP Redir analyzer");
|
|
}
|
|
break;
|
|
|
|
case ICMP_UNREACH:
|
|
if ( ICMP_Unreachable_Analyzer::Available() )
|
|
{
|
|
root = icmp = new ICMP_Unreachable_Analyzer(conn);
|
|
DBG_DPD(conn, "activated ICMP Unreachable analyzer");
|
|
}
|
|
break;
|
|
|
|
case ICMP_TIMXCEED:
|
|
if ( ICMP_TimeExceeded_Analyzer::Available() )
|
|
{
|
|
root = icmp = new ICMP_TimeExceeded_Analyzer(conn);
|
|
DBG_DPD(conn, "activated ICMP Time Exceeded analyzer");
|
|
}
|
|
break;
|
|
}
|
|
|
|
if ( ! root )
|
|
root = icmp = new ICMP_Analyzer(conn);
|
|
|
|
analyzed = true;
|
|
break;
|
|
}
|
|
|
|
default:
|
|
reporter->InternalError("unknown protocol");
|
|
}
|
|
|
|
if ( ! root )
|
|
{
|
|
DBG_DPD(conn, "cannot build analyzer tree");
|
|
return false;
|
|
}
|
|
|
|
// Any scheduled analyzer?
|
|
if ( expected != AnalyzerTag::Error )
|
|
{
|
|
Analyzer* analyzer =
|
|
Analyzer::InstantiateAnalyzer(expected, conn);
|
|
root->AddChildAnalyzer(analyzer, false);
|
|
DBG_DPD_ARGS(conn, "activated %s analyzer as scheduled",
|
|
Analyzer::GetTagName(expected));
|
|
|
|
// Hmm... Do we want *just* the expected analyzer, or all
|
|
// other potential analyzers as well? For now we only take
|
|
// the scheduled one.
|
|
}
|
|
|
|
else
|
|
{ // Let's see if it's a port we know.
|
|
if ( ports && ! dpd_ignore_ports )
|
|
{
|
|
analyzer_map::const_iterator i =
|
|
ports->find(ntohs(conn->RespPort()));
|
|
|
|
if ( i != ports->end() )
|
|
{
|
|
tag_list* analyzers = i->second;
|
|
for ( tag_list::const_iterator j = analyzers->begin();
|
|
j != analyzers->end(); j++ )
|
|
{
|
|
Analyzer* analyzer =
|
|
Analyzer::InstantiateAnalyzer(*j, conn);
|
|
|
|
root->AddChildAnalyzer(analyzer, false);
|
|
DBG_DPD_ARGS(conn, "activated %s analyzer due to port %d", Analyzer::GetTagName(*j), conn->RespPort());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( tcp )
|
|
{
|
|
// We have to decide whether to reassamble the stream.
|
|
// We turn it on right away if we already have an app-layer
|
|
// analyzer, reassemble_first_packets is true, or the user
|
|
// asks us to do so. In all other cases, reassembly may
|
|
// be turned on later by the TCP PIA.
|
|
|
|
bool reass = root->GetChildren().size() ||
|
|
dpd_reassemble_first_packets ||
|
|
tcp_content_deliver_all_orig ||
|
|
tcp_content_deliver_all_resp;
|
|
|
|
if ( tcp_contents && ! reass )
|
|
{
|
|
PortVal dport(ntohs(conn->RespPort()), TRANSPORT_TCP);
|
|
Val* result;
|
|
|
|
if ( ! reass )
|
|
reass = tcp_content_delivery_ports_orig->Lookup(&dport);
|
|
|
|
if ( ! reass )
|
|
reass = tcp_content_delivery_ports_resp->Lookup(&dport);
|
|
}
|
|
|
|
if ( reass )
|
|
tcp->EnableReassembly();
|
|
|
|
// Add a BackDoor analyzer if requested. This analyzer
|
|
// can handle both reassembled and non-reassembled input.
|
|
if ( BackDoor_Analyzer::Available() )
|
|
{
|
|
BackDoor_Analyzer* bd = new BackDoor_Analyzer(conn);
|
|
tcp->AddChildAnalyzer(bd, false);
|
|
}
|
|
|
|
// Add a InterConn analyzer if requested. This analyzer
|
|
// can handle both reassembled and non-reassembled input.
|
|
if ( InterConn_Analyzer::Available() )
|
|
{
|
|
InterConn_Analyzer* bd = new InterConn_Analyzer(conn);
|
|
tcp->AddChildAnalyzer(bd, false);
|
|
}
|
|
|
|
// Add a SteppingStone analyzer if requested. The port
|
|
// should really not be hardcoded here, but as it can
|
|
// handle non-reassembled data, it doesn't really fit into
|
|
// our general framing ... Better would be to turn it
|
|
// on *after* we discover we have interactive traffic.
|
|
uint16 resp_port = ntohs(conn->RespPort());
|
|
if ( SteppingStone_Analyzer::Available() &&
|
|
(resp_port == 22 || resp_port == 23 || resp_port == 513) )
|
|
{
|
|
AddrVal src(conn->OrigAddr());
|
|
if ( ! stp_skip_src->Lookup(&src) )
|
|
{
|
|
SteppingStone_Analyzer* bd =
|
|
new SteppingStone_Analyzer(conn);
|
|
tcp->AddChildAnalyzer(bd, false);
|
|
}
|
|
}
|
|
|
|
// Add TCPStats analyzer. This needs to see packets so
|
|
// we cannot add it as a normal child.
|
|
if ( TCPStats_Analyzer::Available() )
|
|
tcp->AddChildPacketAnalyzer(new TCPStats_Analyzer(conn));
|
|
|
|
// Add ConnSize analyzer. Needs to see packets, not stream.
|
|
if ( ConnSize_Analyzer::Available() )
|
|
tcp->AddChildPacketAnalyzer(new ConnSize_Analyzer(conn));
|
|
}
|
|
|
|
else
|
|
{
|
|
if ( ConnSize_Analyzer::Available() )
|
|
root->AddChildAnalyzer(new ConnSize_Analyzer(conn), false);
|
|
}
|
|
|
|
if ( pia )
|
|
root->AddChildAnalyzer(pia->AsAnalyzer(), false);
|
|
|
|
if ( root->GetChildren().size() )
|
|
analyzed = true;
|
|
|
|
conn->SetRootAnalyzer(root, pia);
|
|
root->Init();
|
|
root->InitChildren();
|
|
|
|
if ( ! analyzed )
|
|
conn->SetLifetime(non_analyzed_lifetime);
|
|
|
|
if ( expected != AnalyzerTag::Error )
|
|
conn->Event(expected_connection_seen, 0,
|
|
new Val(expected, TYPE_COUNT));
|
|
|
|
return true;
|
|
}
|
|
|
|
void DPM::ExpectConnection(addr_type orig, addr_type resp, uint16 resp_p,
|
|
TransportProto proto, AnalyzerTag::Tag analyzer,
|
|
double timeout, void* cookie)
|
|
{
|
|
// Use the chance to see if the oldest entry is already expired.
|
|
if ( expected_conns_queue.size() )
|
|
{
|
|
AssignedAnalyzer* a = expected_conns_queue.top();
|
|
if ( a->timeout < network_time )
|
|
{
|
|
if ( ! a->deleted )
|
|
{
|
|
HashKey* key = new HashKey(&a->conn,
|
|
sizeof(a->conn.orig) +
|
|
sizeof(a->conn.resp) +
|
|
sizeof(a->conn.resp_p) +
|
|
sizeof(a->conn.proto));
|
|
expected_conns.Remove(key);
|
|
delete key;
|
|
}
|
|
|
|
expected_conns_queue.pop();
|
|
|
|
DBG_LOG(DBG_DPD, "Expired expected %s analyzer for %s",
|
|
Analyzer::GetTagName(analyzer),
|
|
fmt_conn_id(a->conn.orig, 0,
|
|
a->conn.resp,
|
|
a->conn.resp_p));
|
|
|
|
delete a;
|
|
}
|
|
}
|
|
|
|
ExpectedConn c(orig, resp, resp_p, proto);
|
|
|
|
HashKey key(&c, sizeof(c.orig) + sizeof(c.resp) +
|
|
sizeof(c.resp_p) + sizeof(c.proto));
|
|
|
|
AssignedAnalyzer* a = expected_conns.Lookup(&key);
|
|
|
|
if ( a )
|
|
a->deleted = true;
|
|
|
|
a = new AssignedAnalyzer(c);
|
|
|
|
a->analyzer = analyzer;
|
|
a->cookie = cookie;
|
|
a->timeout = network_time + timeout;
|
|
a->deleted = false;
|
|
|
|
expected_conns.Insert(&key, a);
|
|
expected_conns_queue.push(a);
|
|
}
|
|
|
|
void DPM::Done()
|
|
{
|
|
// Clean up expected-connection table.
|
|
while ( expected_conns_queue.size() )
|
|
{
|
|
AssignedAnalyzer* a = expected_conns_queue.top();
|
|
if ( ! a->deleted )
|
|
{
|
|
HashKey* key = new HashKey(&a->conn,
|
|
sizeof(a->conn.orig) +
|
|
sizeof(a->conn.resp) +
|
|
sizeof(a->conn.resp_p) +
|
|
sizeof(a->conn.proto));
|
|
expected_conns.Remove(key);
|
|
delete key;
|
|
}
|
|
|
|
expected_conns_queue.pop();
|
|
delete a;
|
|
}
|
|
}
|
|
|