#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; } }