From e4b620673b50469c72f794e174aa43c46d5751e5 Mon Sep 17 00:00:00 2001 From: Seth Hall Date: Mon, 24 Oct 2016 03:22:20 -0400 Subject: [PATCH] More DCE_RPC improvements. - The logic for fragment handling has been rewritten and should be correct now. - There are now tunables for fragment handling overflow situations. - DCE_RPC::max_cmd_reassembly and DCE_RPC::max_frag_data - They result in weirds and analyzer removal. - Memory leak fixed by unique_ptr auto cleanup. - DCE_RPC is now intolerate of content gaps and will stop analyzing traffic if content gaps happen (like most other analyzers currently). --- scripts/base/protocols/dce-rpc/consts.bro | 10 +++ src/analyzer/protocol/dce-rpc/CMakeLists.txt | 2 +- src/analyzer/protocol/dce-rpc/DCE_RPC.cc | 8 +++ src/analyzer/protocol/dce-rpc/DCE_RPC.h | 1 + src/analyzer/protocol/dce-rpc/consts.bif | 2 + .../protocol/dce-rpc/dce_rpc-protocol.pac | 65 +++++++++++++++---- src/analyzer/protocol/dce-rpc/dce_rpc.pac | 1 + 7 files changed, 76 insertions(+), 13 deletions(-) create mode 100644 src/analyzer/protocol/dce-rpc/consts.bif diff --git a/scripts/base/protocols/dce-rpc/consts.bro b/scripts/base/protocols/dce-rpc/consts.bro index 0cbb8a30b6..545828d776 100644 --- a/scripts/base/protocols/dce-rpc/consts.bro +++ b/scripts/base/protocols/dce-rpc/consts.bro @@ -2,6 +2,16 @@ module DCE_RPC; export { + ## The maximum number of simultaneous fragmented commands that + ## the analyzer will tolerate before the analyzer will generate + ## a weird and remove itself from the connection. + const max_cmd_reassembly = 20 &redef; + + ## The maximum number of fragmented bytes that will be tolerated + ## on a command before the analyzer will generate a weird and + ## remove itself from the connection. + const max_frag_data = 30000 &redef; + const uuid_endpoint_map: table[string] of string = { ["367abb81-9844-35f1-ad32-98f038001003"] = "svcctl", ["86d35949-83c9-4044-b424-db363231fd0c"] = "ITaskSchedulerService", diff --git a/src/analyzer/protocol/dce-rpc/CMakeLists.txt b/src/analyzer/protocol/dce-rpc/CMakeLists.txt index 79ec16ada6..c7f9a940e0 100644 --- a/src/analyzer/protocol/dce-rpc/CMakeLists.txt +++ b/src/analyzer/protocol/dce-rpc/CMakeLists.txt @@ -5,7 +5,7 @@ include_directories(BEFORE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DI bro_plugin_begin(Bro DCE_RPC) bro_plugin_cc(DCE_RPC.cc Plugin.cc) -bro_plugin_bif(types.bif events.bif) +bro_plugin_bif(consts.bif types.bif events.bif) bro_plugin_pac( dce_rpc.pac dce_rpc-protocol.pac diff --git a/src/analyzer/protocol/dce-rpc/DCE_RPC.cc b/src/analyzer/protocol/dce-rpc/DCE_RPC.cc index e93a2541f7..f7a96fbb6e 100644 --- a/src/analyzer/protocol/dce-rpc/DCE_RPC.cc +++ b/src/analyzer/protocol/dce-rpc/DCE_RPC.cc @@ -16,6 +16,7 @@ using namespace analyzer::dce_rpc; DCE_RPC_Analyzer::DCE_RPC_Analyzer(Connection *conn) : tcp::TCP_ApplicationAnalyzer("DCE_RPC", conn) { + had_gap = false; interp = new binpac::DCE_RPC::DCE_RPC_Conn(this); } @@ -41,6 +42,7 @@ void DCE_RPC_Analyzer::EndpointEOF(bool is_orig) void DCE_RPC_Analyzer::Undelivered(uint64 seq, int len, bool orig) { TCP_ApplicationAnalyzer::Undelivered(seq, len, orig); + had_gap = true; interp->NewGap(orig, len); } @@ -49,6 +51,12 @@ void DCE_RPC_Analyzer::DeliverStream(int len, const u_char* data, bool orig) TCP_ApplicationAnalyzer::DeliverStream(len, data, orig); assert(TCP()); + + if ( had_gap ) + // If only one side had a content gap, we could still try to + // deliver data to the other side if the script layer can handle this. + return; + try { interp->NewData(orig, data, data + len); diff --git a/src/analyzer/protocol/dce-rpc/DCE_RPC.h b/src/analyzer/protocol/dce-rpc/DCE_RPC.h index 714607f5e2..498e055e0a 100644 --- a/src/analyzer/protocol/dce-rpc/DCE_RPC.h +++ b/src/analyzer/protocol/dce-rpc/DCE_RPC.h @@ -29,6 +29,7 @@ public: { return new DCE_RPC_Analyzer(conn); } protected: + bool had_gap; binpac::DCE_RPC::DCE_RPC_Conn* interp; }; diff --git a/src/analyzer/protocol/dce-rpc/consts.bif b/src/analyzer/protocol/dce-rpc/consts.bif new file mode 100644 index 0000000000..68b052d84b --- /dev/null +++ b/src/analyzer/protocol/dce-rpc/consts.bif @@ -0,0 +1,2 @@ +const DCE_RPC::max_cmd_reassembly: count; +const DCE_RPC::max_frag_data: count; \ No newline at end of file diff --git a/src/analyzer/protocol/dce-rpc/dce_rpc-protocol.pac b/src/analyzer/protocol/dce-rpc/dce_rpc-protocol.pac index 5fe9380422..14f8a6d8a5 100644 --- a/src/analyzer/protocol/dce-rpc/dce_rpc-protocol.pac +++ b/src/analyzer/protocol/dce-rpc/dce_rpc-protocol.pac @@ -37,7 +37,7 @@ type DCE_RPC_PDU(is_orig: bool) = record { # Subtract an extra 8 when there is an auth section because we have some "auth header" fields in that structure. body_length : int = header.frag_length - sizeof(header) - header.auth_length - (header.auth_length > 0 ? 8 : 0); frag_reassembled : bool = $context.flow.reassemble_fragment(header, frag); - body : DCE_RPC_Body(header) withinput $context.flow.reassembled_body(header, frag) &if(header.lastfrag); + body : DCE_RPC_Body(header) withinput $context.flow.reassembled_body(header, frag) &if(frag_reassembled); } &byteorder = header.byteorder, &length = header.frag_length; type NDR_Format = record { @@ -174,23 +174,65 @@ flow DCE_RPC_Flow(is_orig: bool) { flowunit = DCE_RPC_PDU(is_orig) withcontext(connection, this); %member{ - std::map fb; + std::map> fb; %} # Fragment reassembly. function reassemble_fragment(header: DCE_RPC_Header, frag: bytestring): bool %{ - if ( ${header.firstfrag} && !${header.lastfrag} && - fb.count(${header.call_id}) == 0 ) - fb[${header.call_id}] = new FlowBuffer(); + if ( ${header.firstfrag} ) + { + if ( ${header.lastfrag} ) + { + // all-in-one packet + return true; + } + else + { + // first frag, but not last so we start a flowbuffer + fb[${header.call_id}] = std::unique_ptr(new FlowBuffer()); + fb[${header.call_id}]->NewFrame(0, true); + fb[${header.call_id}]->BufferData(frag.begin(), frag.end()); - if ( fb.count(${header.call_id}) == 0 ) + if ( fb.size() > BifConst::DCE_RPC::max_cmd_reassembly ) + { + reporter->Weird(connection()->bro_analyzer()->Conn(), + "too_many_dce_rpc_msgs_in_reassembly"); + connection()->bro_analyzer()->Remove(); + } + + if ( fb[${header.call_id}]->data_length() > BifConst::DCE_RPC::max_frag_data ) + { + reporter->Weird(connection()->bro_analyzer()->Conn(), + "too_much_dce_rpc_fragment_data"); + connection()->bro_analyzer()->Remove(); + } + + return false; + } + } + else if ( fb.count(${header.call_id}) > 0 ) + { + // not the first frag, but we have a flow buffer so add to it + fb[${header.call_id}]->BufferData(frag.begin(), frag.end()); + + if ( fb[${header.call_id}]->data_length() > BifConst::DCE_RPC::max_frag_data ) + { + reporter->Weird(connection()->bro_analyzer()->Conn(), + "too_much_dce_rpc_fragment_data"); + connection()->bro_analyzer()->Remove(); + } + + return ${header.lastfrag}; + } + else + { + // no flow buffer and not a first frag, ignore it. return false; + } - auto frag_reassembler_ = fb[${header.call_id}]; - frag_reassembler_->BufferData(frag.begin(), frag.end()); - - return (!${header.firstfrag} && ${header.lastfrag}); + // can't reach here. + return false; %} function reassembled_body(h: DCE_RPC_Header, body: bytestring): const_bytestring @@ -200,10 +242,9 @@ flow DCE_RPC_Flow(is_orig: bool) { if ( fb.count(${h.call_id}) > 0 ) { bd = const_bytestring(fb[${h.call_id}]->begin(), fb[${h.call_id}]->end()); - delete fb[${h.call_id}]; fb.erase(${h.call_id}); } - + return bd; %} }; diff --git a/src/analyzer/protocol/dce-rpc/dce_rpc.pac b/src/analyzer/protocol/dce-rpc/dce_rpc.pac index f4a54a1e62..87070e6216 100644 --- a/src/analyzer/protocol/dce-rpc/dce_rpc.pac +++ b/src/analyzer/protocol/dce-rpc/dce_rpc.pac @@ -2,6 +2,7 @@ %include bro.pac %extern{ +#include "consts.bif.h" #include "types.bif.h" #include "events.bif.h" %}