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).
This commit is contained in:
Seth Hall 2016-10-24 03:22:20 -04:00
parent bd0a374c87
commit e4b620673b
7 changed files with 76 additions and 13 deletions

View file

@ -2,6 +2,16 @@
module DCE_RPC; module DCE_RPC;
export { 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 = { const uuid_endpoint_map: table[string] of string = {
["367abb81-9844-35f1-ad32-98f038001003"] = "svcctl", ["367abb81-9844-35f1-ad32-98f038001003"] = "svcctl",
["86d35949-83c9-4044-b424-db363231fd0c"] = "ITaskSchedulerService", ["86d35949-83c9-4044-b424-db363231fd0c"] = "ITaskSchedulerService",

View file

@ -5,7 +5,7 @@ include_directories(BEFORE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DI
bro_plugin_begin(Bro DCE_RPC) bro_plugin_begin(Bro DCE_RPC)
bro_plugin_cc(DCE_RPC.cc Plugin.cc) 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( bro_plugin_pac(
dce_rpc.pac dce_rpc.pac
dce_rpc-protocol.pac dce_rpc-protocol.pac

View file

@ -16,6 +16,7 @@ using namespace analyzer::dce_rpc;
DCE_RPC_Analyzer::DCE_RPC_Analyzer(Connection *conn) DCE_RPC_Analyzer::DCE_RPC_Analyzer(Connection *conn)
: tcp::TCP_ApplicationAnalyzer("DCE_RPC", conn) : tcp::TCP_ApplicationAnalyzer("DCE_RPC", conn)
{ {
had_gap = false;
interp = new binpac::DCE_RPC::DCE_RPC_Conn(this); 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) void DCE_RPC_Analyzer::Undelivered(uint64 seq, int len, bool orig)
{ {
TCP_ApplicationAnalyzer::Undelivered(seq, len, orig); TCP_ApplicationAnalyzer::Undelivered(seq, len, orig);
had_gap = true;
interp->NewGap(orig, len); 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); TCP_ApplicationAnalyzer::DeliverStream(len, data, orig);
assert(TCP()); 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 try
{ {
interp->NewData(orig, data, data + len); interp->NewData(orig, data, data + len);

View file

@ -29,6 +29,7 @@ public:
{ return new DCE_RPC_Analyzer(conn); } { return new DCE_RPC_Analyzer(conn); }
protected: protected:
bool had_gap;
binpac::DCE_RPC::DCE_RPC_Conn* interp; binpac::DCE_RPC::DCE_RPC_Conn* interp;
}; };

View file

@ -0,0 +1,2 @@
const DCE_RPC::max_cmd_reassembly: count;
const DCE_RPC::max_frag_data: count;

View file

@ -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. # 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); 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); 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; } &byteorder = header.byteorder, &length = header.frag_length;
type NDR_Format = record { type NDR_Format = record {
@ -174,23 +174,65 @@ flow DCE_RPC_Flow(is_orig: bool) {
flowunit = DCE_RPC_PDU(is_orig) withcontext(connection, this); flowunit = DCE_RPC_PDU(is_orig) withcontext(connection, this);
%member{ %member{
std::map<uint32, FlowBuffer*> fb; std::map<uint32, std::unique_ptr<FlowBuffer>> fb;
%} %}
# Fragment reassembly. # Fragment reassembly.
function reassemble_fragment(header: DCE_RPC_Header, frag: bytestring): bool function reassemble_fragment(header: DCE_RPC_Header, frag: bytestring): bool
%{ %{
if ( ${header.firstfrag} && !${header.lastfrag} && if ( ${header.firstfrag} )
fb.count(${header.call_id}) == 0 ) {
fb[${header.call_id}] = new FlowBuffer(); 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<FlowBuffer>(new FlowBuffer());
fb[${header.call_id}]->NewFrame(0, true);
fb[${header.call_id}]->BufferData(frag.begin(), frag.end());
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();
}
if ( fb.count(${header.call_id}) == 0 )
return false; 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());
auto frag_reassembler_ = fb[${header.call_id}]; if ( fb[${header.call_id}]->data_length() > BifConst::DCE_RPC::max_frag_data )
frag_reassembler_->BufferData(frag.begin(), frag.end()); {
reporter->Weird(connection()->bro_analyzer()->Conn(),
"too_much_dce_rpc_fragment_data");
connection()->bro_analyzer()->Remove();
}
return (!${header.firstfrag} && ${header.lastfrag}); return ${header.lastfrag};
}
else
{
// no flow buffer and not a first frag, ignore it.
return false;
}
// can't reach here.
return false;
%} %}
function reassembled_body(h: DCE_RPC_Header, body: bytestring): const_bytestring function reassembled_body(h: DCE_RPC_Header, body: bytestring): const_bytestring
@ -200,7 +242,6 @@ flow DCE_RPC_Flow(is_orig: bool) {
if ( fb.count(${h.call_id}) > 0 ) if ( fb.count(${h.call_id}) > 0 )
{ {
bd = const_bytestring(fb[${h.call_id}]->begin(), fb[${h.call_id}]->end()); bd = const_bytestring(fb[${h.call_id}]->begin(), fb[${h.call_id}]->end());
delete fb[${h.call_id}];
fb.erase(${h.call_id}); fb.erase(${h.call_id});
} }

View file

@ -2,6 +2,7 @@
%include bro.pac %include bro.pac
%extern{ %extern{
#include "consts.bif.h"
#include "types.bif.h" #include "types.bif.h"
#include "events.bif.h" #include "events.bif.h"
%} %}