Pushing out the new NTLM and GSSAPI analyzers.

I accidentally left these out of the previous commit.
This commit is contained in:
Seth Hall 2016-04-03 04:18:45 -04:00
parent 5b5589e167
commit d6e01b7769
18 changed files with 894 additions and 0 deletions

View file

@ -0,0 +1,15 @@
include(BroPlugin)
include_directories(BEFORE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR})
bro_plugin_begin(Bro GSSAPI)
bro_plugin_cc(GSSAPI.cc Plugin.cc)
bro_plugin_bif(types.bif events.bif)
bro_plugin_pac(
gssapi.pac
gssapi-protocol.pac
gssapi-analyzer.pac
)
bro_plugin_end()

View file

@ -0,0 +1,56 @@
// See the file "COPYING" in the main distribution directory for copyright.
#include "GSSAPI.h"
#include "analyzer/protocol/tcp/TCP_Reassembler.h"
#include "Reporter.h"
#include "events.bif.h"
using namespace analyzer::gssapi;
GSSAPI_Analyzer::GSSAPI_Analyzer(Connection* c)
: tcp::TCP_ApplicationAnalyzer("GSSAPI", c)
{
interp = new binpac::GSSAPI::GSSAPI_Conn(this);
}
GSSAPI_Analyzer::~GSSAPI_Analyzer()
{
delete interp;
}
void GSSAPI_Analyzer::Done()
{
tcp::TCP_ApplicationAnalyzer::Done();
interp->FlowEOF(true);
interp->FlowEOF(false);
}
void GSSAPI_Analyzer::EndpointEOF(bool is_orig)
{
tcp::TCP_ApplicationAnalyzer::EndpointEOF(is_orig);
interp->FlowEOF(is_orig);
}
void GSSAPI_Analyzer::DeliverStream(int len, const u_char* data, bool orig)
{
tcp::TCP_ApplicationAnalyzer::DeliverStream(len, data, orig);
assert(TCP());
try
{
interp->NewData(orig, data, data + len);
ProtocolConfirmation();
}
catch ( const binpac::Exception& e )
{
ProtocolViolation(fmt("Binpac exception: %s", e.c_msg()));
}
}
void GSSAPI_Analyzer::Undelivered(uint64 seq, int len, bool orig)
{
tcp::TCP_ApplicationAnalyzer::Undelivered(seq, len, orig);
interp->NewGap(orig, len);
}

View file

@ -0,0 +1,39 @@
// See the file "COPYING" in the main distribution directory for copyright.
#ifndef ANALYZER_PROTOCOL_GSSAPI_GSSAPI_H
#define ANALYZER_PROTOCOL_GSSAPI_GSSAPI_H
#include "events.bif.h"
#include "analyzer/protocol/tcp/TCP.h"
#include "gssapi_pac.h"
namespace analyzer { namespace gssapi {
class GSSAPI_Analyzer
: public tcp::TCP_ApplicationAnalyzer {
public:
GSSAPI_Analyzer(Connection* conn);
virtual ~GSSAPI_Analyzer();
// Overriden from Analyzer.
virtual void Done();
virtual void DeliverStream(int len, const u_char* data, bool orig);
virtual void Undelivered(uint64 seq, int len, bool orig);
// Overriden from tcp::TCP_ApplicationAnalyzer.
virtual void EndpointEOF(bool is_orig);
static analyzer::Analyzer* Instantiate(Connection* conn)
{ return new GSSAPI_Analyzer(conn); }
protected:
binpac::GSSAPI::GSSAPI_Conn* interp;
};
} } // namespace analyzer::*
#endif

View file

@ -0,0 +1,24 @@
// See the file in the main distribution directory for copyright.
#include "plugin/Plugin.h"
#include "GSSAPI.h"
namespace plugin {
namespace Bro_GSSAPI {
class Plugin : public plugin::Plugin {
public:
plugin::Configuration Configure()
{
AddComponent(new ::analyzer::Component("GSSAPI", ::analyzer::gssapi::GSSAPI_Analyzer::Instantiate));
plugin::Configuration config;
config.name = "Bro::GSSAPI";
config.description = "GSSAPI analyzer";
return config;
}
} plugin;
}
}

View file

@ -0,0 +1,5 @@
## Generated for GSSAPI messages of type *accept-completed*.
##
## c: The connection.
##
event gssapi_accepted%(c: connection%);

View file

@ -0,0 +1,49 @@
refine connection GSSAPI_Conn += {
%member{
analyzer::Analyzer *ntlm;
%}
%init{
ntlm = analyzer_mgr->InstantiateAnalyzer("NTLM", bro_analyzer->Conn());
%}
%cleanup{
if ( ntlm )
delete ntlm;
%}
function forward_ntlm(data: bytestring, is_orig: bool): bool
%{
if ( ntlm )
ntlm->DeliverStream(${data}.length(), ${data}.begin(), is_orig);
return true;
%}
function proc_gssapi_neg_token(val: GSSAPI_NEG_TOKEN): bool
%{
if ( ${val.is_init} )
return true;
for ( uint i = 0; i < ${val.resp.args}->size(); ++i )
{
switch ( ${val.resp.args[i].seq_meta.index} )
{
case 0:
if ( ${val.resp.args[i].args.neg_state} == 0 )
{
BifEvent::generate_gssapi_accepted(bro_analyzer(),
bro_analyzer()->Conn());
}
break;
default:
break;
}
}
return true;
%}
}
refine typeattr GSSAPI_NEG_TOKEN += &let {
proc : bool = $context.connection.proc_gssapi_neg_token(this);
};

View file

@ -0,0 +1,56 @@
type GSSAPI_NEG_TOKEN(is_orig: bool) = record {
wrapper : ASN1EncodingMeta;
have_oid : case is_init of {
true -> oid : ASN1Encoding;
false -> no_oid : empty;
};
have_init_wrapper : case is_init of {
true -> init_wrapper : ASN1EncodingMeta;
false -> no_init_wrapper : empty;
};
msg_type : case is_init of {
true -> init : GSSAPI_NEG_TOKEN_INIT;
false -> resp : GSSAPI_NEG_TOKEN_RESP;
};
} &let {
is_init: bool = wrapper.tag == 0x60;
} &byteorder=littleendian;
type GSSAPI_NEG_TOKEN_INIT = record {
seq_meta : ASN1EncodingMeta;
args : GSSAPI_NEG_TOKEN_INIT_Arg[];
};
type GSSAPI_NEG_TOKEN_INIT_Arg = record {
seq_meta : ASN1EncodingMeta;
args : GSSAPI_NEG_TOKEN_INIT_Arg_Data(seq_meta.index) &length=seq_meta.length;
};
type GSSAPI_NEG_TOKEN_INIT_Arg_Data(index: uint8) = case index of {
0 -> mech_type_list : ASN1Encoding;
1 -> req_flags : ASN1Encoding;
2 -> mech_token : bytestring &restofdata;
3 -> mech_list_mic : ASN1OctetString;
} &let {
fwd: bool = $context.connection.forward_ntlm(mech_token, true) &if(index==2);
};
type GSSAPI_NEG_TOKEN_RESP = record {
seq_meta : ASN1EncodingMeta;
args : GSSAPI_NEG_TOKEN_RESP_Arg[];
};
type GSSAPI_NEG_TOKEN_RESP_Arg = record {
seq_meta : ASN1EncodingMeta;
args : GSSAPI_NEG_TOKEN_RESP_Arg_Data(seq_meta.index) &length=seq_meta.length;
};
type GSSAPI_NEG_TOKEN_RESP_Arg_Data(index: uint8) = case index of {
0 -> neg_state : ASN1Integer;
1 -> supported_mech : ASN1Encoding;
2 -> response_token : bytestring &restofdata;
3 -> mech_list_mic : ASN1OctetString;
} &let {
fwd: bool = $context.connection.forward_ntlm(response_token, false) &if(index==2);
};

View file

@ -0,0 +1,30 @@
%include binpac.pac
%include bro.pac
%extern{
#include "analyzer/Manager.h"
#include "analyzer/Analyzer.h"
#include "types.bif.h"
#include "events.bif.h"
%}
analyzer GSSAPI withcontext {
connection : GSSAPI_Conn;
flow : GSSAPI_Flow;
};
connection GSSAPI_Conn(bro_analyzer: BroAnalyzer) {
upflow = GSSAPI_Flow(true);
downflow = GSSAPI_Flow(false);
};
%include gssapi-protocol.pac
%include ../asn1/asn1.pac
# Now we define the flow:
flow GSSAPI_Flow(is_orig: bool) {
datagram = GSSAPI_NEG_TOKEN(is_orig) withcontext(connection, this);
};
%include gssapi-analyzer.pac

View file

View file

@ -0,0 +1,15 @@
include(BroPlugin)
include_directories(BEFORE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR})
bro_plugin_begin(Bro NTLM)
bro_plugin_cc(NTLM.cc Plugin.cc)
bro_plugin_bif(types.bif events.bif)
bro_plugin_pac(
ntlm.pac
ntlm-protocol.pac
ntlm-analyzer.pac
)
bro_plugin_end()

View file

@ -0,0 +1,56 @@
// See the file "COPYING" in the main distribution directory for copyright.
#include "NTLM.h"
#include "analyzer/protocol/tcp/TCP_Reassembler.h"
#include "Reporter.h"
#include "events.bif.h"
using namespace analyzer::ntlm;
NTLM_Analyzer::NTLM_Analyzer(Connection* c)
: tcp::TCP_ApplicationAnalyzer("NTLM", c)
{
interp = new binpac::NTLM::NTLM_Conn(this);
}
NTLM_Analyzer::~NTLM_Analyzer()
{
delete interp;
}
void NTLM_Analyzer::Done()
{
tcp::TCP_ApplicationAnalyzer::Done();
interp->FlowEOF(true);
interp->FlowEOF(false);
}
void NTLM_Analyzer::EndpointEOF(bool is_orig)
{
tcp::TCP_ApplicationAnalyzer::EndpointEOF(is_orig);
interp->FlowEOF(is_orig);
}
void NTLM_Analyzer::DeliverStream(int len, const u_char* data, bool orig)
{
tcp::TCP_ApplicationAnalyzer::DeliverStream(len, data, orig);
assert(TCP());
try
{
interp->NewData(orig, data, data + len);
ProtocolConfirmation();
}
catch ( const binpac::Exception& e )
{
ProtocolViolation(fmt("Binpac exception: %s", e.c_msg()));
}
}
void NTLM_Analyzer::Undelivered(uint64 seq, int len, bool orig)
{
tcp::TCP_ApplicationAnalyzer::Undelivered(seq, len, orig);
interp->NewGap(orig, len);
}

View file

@ -0,0 +1,39 @@
// See the file "COPYING" in the main distribution directory for copyright.
#ifndef ANALYZER_PROTOCOL_NTLM_NTLM_H
#define ANALYZER_PROTOCOL_NTLM_NTLM_H
#include "events.bif.h"
#include "analyzer/protocol/tcp/TCP.h"
#include "ntlm_pac.h"
namespace analyzer { namespace ntlm {
class NTLM_Analyzer
: public tcp::TCP_ApplicationAnalyzer {
public:
NTLM_Analyzer(Connection* conn);
virtual ~NTLM_Analyzer();
// Overriden from Analyzer.
virtual void Done();
virtual void DeliverStream(int len, const u_char* data, bool orig);
virtual void Undelivered(uint64 seq, int len, bool orig);
// Overriden from tcp::TCP_ApplicationAnalyzer.
virtual void EndpointEOF(bool is_orig);
static analyzer::Analyzer* Instantiate(Connection* conn)
{ return new NTLM_Analyzer(conn); }
protected:
binpac::NTLM::NTLM_Conn* interp;
};
} } // namespace analyzer::*
#endif

View file

@ -0,0 +1,24 @@
// See the file in the main distribution directory for copyright.
#include "plugin/Plugin.h"
#include "NTLM.h"
namespace plugin {
namespace Bro_NTLM {
class Plugin : public plugin::Plugin {
public:
plugin::Configuration Configure()
{
AddComponent(new ::analyzer::Component("NTLM", ::analyzer::ntlm::NTLM_Analyzer::Instantiate));
plugin::Configuration config;
config.name = "Bro::NTLM";
config.description = "NTLM analyzer";
return config;
}
} plugin;
}
}

View file

@ -0,0 +1,23 @@
## Generated for NTLM messages of type *negotiate*.
##
## c: The connection.
##
## negotiate: The parsed data of the NTLM message. See init-bare for more details.
##
event ntlm_negotiate%(c: connection, negotiate: NTLM::Negotiate%);
## Generated for NTLM messages of type *challenge*.
##
## c: The connection.
##
## negotiate: The parsed data of the NTLM message. See init-bare for more details.
##
event ntlm_challenge%(c: connection, challenge: NTLM::Challenge%);
## Generated for NTLM messages of type *authenticate*.
##
## c: The connection.
##
## request: The parsed data of the NTLM message. See init-bare for more details.
##
event ntlm_authenticate%(c: connection, request: NTLM::Authenticate%);

View file

@ -0,0 +1,223 @@
%extern{
#include "ConvertUTF.h"
%}
refine connection NTLM_Conn += {
# This is copied from the RDP analyzer :(
function utf16_to_utf8_val(utf16: bytestring): StringVal
%{
std::string resultstring;
size_t utf8size = (3 * utf16.length() + 1);
if ( utf8size > resultstring.max_size() )
{
bro_analyzer()->Weird("excessive_utf16_length");
// If the conversion didn't go well, return the original data.
return bytestring_to_val(utf16);
}
resultstring.resize(utf8size, '\0');
// We can't assume that the string data is properly aligned
// here, so make a copy.
UTF16 utf16_copy[utf16.length()]; // Twice as much memory than necessary.
memcpy(utf16_copy, utf16.begin(), utf16.length());
const char* utf16_copy_end = reinterpret_cast<const char*>(utf16_copy) + utf16.length();
const UTF16* sourcestart = utf16_copy;
const UTF16* sourceend = reinterpret_cast<const UTF16*>(utf16_copy_end);
UTF8* targetstart = reinterpret_cast<UTF8*>(&resultstring[0]);
UTF8* targetend = targetstart + utf8size;
ConversionResult res = ConvertUTF16toUTF8(&sourcestart,
sourceend,
&targetstart,
targetend,
lenientConversion);
if ( res != conversionOK )
{
bro_analyzer()->Weird("utf16_conversion_failed");
// If the conversion didn't go well, return the original data.
return bytestring_to_val(utf16);
}
*targetstart = 0;
// We're relying on no nulls being in the string.
//return new StringVal(resultstring.length(), (const char *) resultstring.data());
return new StringVal(resultstring.c_str());
%}
# This is replicated from the SMB analyzer. :(
function filetime2brotime(ts: uint64): Val
%{
double secs = (ts / 10000000.0);
// Bro can't support times back to the 1600's
// so we subtract a lot of seconds.
Val* bro_ts = new Val(secs - 11644473600.0, TYPE_TIME);
return bro_ts;
%}
function build_version_record(val: NTLM_Version): BroVal
%{
RecordVal* result = new RecordVal(BifType::Record::NTLM::Version);
result->Assign(0, new Val(${val.major_version}, TYPE_COUNT));
result->Assign(1, new Val(${val.minor_version}, TYPE_COUNT));
result->Assign(2, new Val(${val.build_number}, TYPE_COUNT));
result->Assign(3, new Val(${val.ntlm_revision}, TYPE_COUNT));
return result;
%}
function build_av_record(val: NTLM_AV_Pair_Sequence): BroVal
%{
RecordVal* result = new RecordVal(BifType::Record::NTLM::AVs);
for ( uint i = 0; ${val.pairs[i].id} != 0; i++ )
{
switch ( ${val.pairs[i].id} )
{
case 1:
result->Assign(0, utf16_to_utf8_val(${val.pairs[i].nb_computer_name.data}));
break;
case 2:
result->Assign(1, utf16_to_utf8_val(${val.pairs[i].nb_domain_name.data}));
break;
case 3:
result->Assign(2, utf16_to_utf8_val(${val.pairs[i].dns_computer_name.data}));
break;
case 4:
result->Assign(3, utf16_to_utf8_val(${val.pairs[i].dns_domain_name.data}));
break;
case 5:
result->Assign(4, utf16_to_utf8_val(${val.pairs[i].dns_tree_name.data}));
break;
case 6:
result->Assign(5, new Val(${val.pairs[i].constrained_auth}, TYPE_BOOL));
break;
case 7:
result->Assign(6, filetime2brotime(${val.pairs[i].timestamp}));
break;
case 8:
result->Assign(7, new Val(${val.pairs[i].single_host.machine_id}, TYPE_COUNT));
break;
case 9:
result->Assign(8, utf16_to_utf8_val(${val.pairs[i].target_name.data}));
break;
}
}
return result;
%}
function build_negotiate_flag_record(val: NTLM_Negotiate_Flags): BroVal
%{
RecordVal* flags = new RecordVal(BifType::Record::NTLM::NegotiateFlags);
flags->Assign(0, new Val(${val.negotiate_56}, TYPE_BOOL));
flags->Assign(1, new Val(${val.negotiate_key_exch}, TYPE_BOOL));
flags->Assign(2, new Val(${val.negotiate_128}, TYPE_BOOL));
flags->Assign(3, new Val(${val.negotiate_version}, TYPE_BOOL));
flags->Assign(4, new Val(${val.negotiate_target_info}, TYPE_BOOL));
flags->Assign(5, new Val(${val.request_non_nt_session_key}, TYPE_BOOL));
flags->Assign(6, new Val(${val.negotiate_identify}, TYPE_BOOL));
flags->Assign(7, new Val(${val.negotiate_extended_sessionsecurity}, TYPE_BOOL));
flags->Assign(8, new Val(${val.target_type_server}, TYPE_BOOL));
flags->Assign(9, new Val(${val.target_type_domain}, TYPE_BOOL));
flags->Assign(10, new Val(${val.negotiate_always_sign}, TYPE_BOOL));
flags->Assign(11, new Val(${val.negotiate_oem_workstation_supplied}, TYPE_BOOL));
flags->Assign(12, new Val(${val.negotiate_oem_domain_supplied}, TYPE_BOOL));
flags->Assign(13, new Val(${val.negotiate_anonymous_connection}, TYPE_BOOL));
flags->Assign(14, new Val(${val.negotiate_ntlm}, TYPE_BOOL));
flags->Assign(15, new Val(${val.negotiate_lm_key}, TYPE_BOOL));
flags->Assign(16, new Val(${val.negotiate_datagram}, TYPE_BOOL));
flags->Assign(17, new Val(${val.negotiate_seal}, TYPE_BOOL));
flags->Assign(18, new Val(${val.negotiate_sign}, TYPE_BOOL));
flags->Assign(19, new Val(${val.request_target}, TYPE_BOOL));
flags->Assign(20, new Val(${val.negotiate_oem}, TYPE_BOOL));
flags->Assign(21, new Val(${val.negotiate_unicode}, TYPE_BOOL));
return flags;
%}
function proc_ntlm_negotiate(val: NTLM_Negotiate): bool
%{
RecordVal* result = new RecordVal(BifType::Record::NTLM::Negotiate);
result->Assign(0, build_negotiate_flag_record(${val.flags}));
if ( ${val.flags.negotiate_oem_domain_supplied} )
result->Assign(1, utf16_to_utf8_val(${val.domain_name.string.data}));
if ( ${val.flags.negotiate_oem_workstation_supplied} )
result->Assign(2, utf16_to_utf8_val(${val.workstation.string.data}));
if ( ${val.flags.negotiate_version} )
result->Assign(3, build_version_record(${val.version}));
BifEvent::generate_ntlm_negotiate(bro_analyzer(),
bro_analyzer()->Conn(),
result);
return true;
%}
function proc_ntlm_challenge(val: NTLM_Challenge): bool
%{
RecordVal* result = new RecordVal(BifType::Record::NTLM::Challenge);
result->Assign(0, build_negotiate_flag_record(${val.flags}));
if ( ${val.flags.request_target} )
result->Assign(1, utf16_to_utf8_val(${val.target_name.string.data}));
if ( ${val.flags.negotiate_version} )
result->Assign(2, build_version_record(${val.version}));
if ( ${val.flags.negotiate_target_info} )
result->Assign(3, build_av_record(${val.target_info}));
BifEvent::generate_ntlm_challenge(bro_analyzer(),
bro_analyzer()->Conn(),
result);
return true;
%}
function proc_ntlm_authenticate(val: NTLM_Authenticate): bool
%{
RecordVal* result = new RecordVal(BifType::Record::NTLM::Authenticate);
result->Assign(0, build_negotiate_flag_record(${val.flags}));
if ( ${val.domain_name_fields.length} > 0 )
result->Assign(1, utf16_to_utf8_val(${val.domain_name.string.data}));
if ( ${val.user_name_fields.length} > 0 )
result->Assign(2, utf16_to_utf8_val(${val.user_name.string.data}));
if ( ${val.workstation_fields.length} > 0 )
result->Assign(3, utf16_to_utf8_val(${val.workstation.string.data}));
if ( ${val.flags.negotiate_version} )
result->Assign(4, build_version_record(${val.version}));
BifEvent::generate_ntlm_authenticate(bro_analyzer(),
bro_analyzer()->Conn(),
result);
return true;
%}
}
refine typeattr NTLM_Negotiate += &let {
proc = $context.connection.proc_ntlm_negotiate(this);
};
refine typeattr NTLM_Challenge += &let {
proc : bool = $context.connection.proc_ntlm_challenge(this);
};
refine typeattr NTLM_Authenticate += &let {
proc : bool = $context.connection.proc_ntlm_authenticate(this);
};

View file

@ -0,0 +1,201 @@
type NTLM_SSP_Token(is_orig: bool) = record {
meta : ASN1EncodingMeta;
signature : bytestring &length=8;
msg_type : uint32;
msg : case msg_type of {
1 -> negotiate : NTLM_Negotiate(offsetof(msg) - offsetof(signature));
2 -> challenge : NTLM_Challenge(offsetof(msg) - offsetof(signature));
3 -> authenticate : NTLM_Authenticate(offsetof(msg) - offsetof(signature));
default -> def : bytestring &restofdata &transient;
};
} &byteorder=littleendian;
type NTLM_Negotiate(offset: uint16) = record {
flags : NTLM_Negotiate_Flags;
domain_name_fields : NTLM_StringData;
workstation_fields : NTLM_StringData;
version_present : case flags.negotiate_version of {
true -> version : NTLM_Version;
false -> no_version : empty;
};
payload : bytestring &restofdata;
} &let {
absolute_offset : uint16 = offsetof(payload) + offset;
domain_name : NTLM_String(domain_name_fields, absolute_offset, flags.negotiate_unicode) withinput payload &if(flags.negotiate_oem_domain_supplied);
workstation : NTLM_String(workstation_fields, absolute_offset, flags.negotiate_unicode) withinput payload &if(flags.negotiate_oem_workstation_supplied);
};
type NTLM_Challenge(offset: uint16) = record {
target_name_fields : NTLM_StringData;
flags : NTLM_Negotiate_Flags;
challenge : uint64;
reserved : padding[8];
target_info_fields : NTLM_StringData;
version_present : case flags.negotiate_version of {
true -> version : NTLM_Version;
false -> no_version : empty;
};
payload : bytestring &restofdata;
} &let {
absolute_offset : uint16 = offsetof(payload) + offset;
target_name : NTLM_String(target_name_fields, absolute_offset, flags.negotiate_unicode) withinput payload &if(flags.request_target);
target_info : NTLM_AV_Pair_Sequence(target_info_fields.offset - absolute_offset) withinput payload &if(flags.negotiate_target_info);
};
type NTLM_Authenticate(offset: uint16) = record {
lm_challenge_response_fields : NTLM_StringData;
nt_challenge_response_fields : NTLM_StringData;
domain_name_fields : NTLM_StringData;
user_name_fields : NTLM_StringData;
workstation_fields : NTLM_StringData;
encrypted_session_key_fields : NTLM_StringData;
flags : NTLM_Negotiate_Flags;
version_present : case flags.negotiate_version of {
true -> version : NTLM_Version;
false -> no_version : empty;
};
# Windows NT, 2000, XP, and 2003 don't have the MIC field
# TODO - figure out how to parse this for those that do have it
# mic : bytestring &length=16;
payload : bytestring &restofdata;
} &let {
absolute_offset : uint16 = offsetof(payload) + offset;
domain_name : NTLM_String(domain_name_fields, absolute_offset, flags.negotiate_unicode) withinput payload &if(domain_name_fields.length > 0);
user_name : NTLM_String(user_name_fields, absolute_offset, flags.negotiate_unicode) withinput payload &if(user_name_fields.length > 0);
workstation : NTLM_String(workstation_fields, absolute_offset , flags.negotiate_unicode) withinput payload &if(workstation_fields.length > 0);
encrypted_session_key : NTLM_String(encrypted_session_key_fields, absolute_offset, flags.negotiate_unicode) withinput payload &if(flags.negotiate_key_exch);
};
type NTLM_Version = record {
major_version : uint8;
minor_version : uint8;
build_number : uint16;
reserved : padding[3];
ntlm_revision : uint8;
};
type NTLM_StringData = record {
length : uint16;
max_length : uint16;
offset : uint32;
};
type Fixed_Length_String(unicode: bool) = record {
data: bytestring &restofdata;
};
type NTLM_String(fields: NTLM_StringData, offset: uint16, unicode: bool) = record {
pad1 : padding to fields.offset - offset;
string : Fixed_Length_String(unicode) &length=fields.length;
};
type NTLM_AV_Pair_Sequence(offset: uint16) = record {
pad1 : padding to offset;
pairs : NTLM_AV_Pair[] &until($element.last);
};
type NTLM_AV_Pair = record {
id : uint16;
length : uint16;
value_case : case id of {
0x0000 -> av_eol : empty;
0x0001 -> nb_computer_name : Fixed_Length_String(true) &length=length;
0x0002 -> nb_domain_name : Fixed_Length_String(true) &length=length;
0x0003 -> dns_computer_name : Fixed_Length_String(true) &length=length;
0x0004 -> dns_domain_name : Fixed_Length_String(true) &length=length;
0x0005 -> dns_tree_name : Fixed_Length_String(true) &length=length;
0x0006 -> av_flags : uint32;
0x0007 -> timestamp : uint64;
0x0008 -> single_host : NTLM_Single_Host;
0x0009 -> target_name : Fixed_Length_String(true) &length=length;
0x000a -> channel_bindings : uint16;
};
} &let {
last : bool = (id == 0x0000);
# av_flags refinement
constrained_auth : bool = (av_flags & 0x00000001) > 0 &if(id == 0x0006);
mic_present : bool = (av_flags & 0x00000002) > 0 &if(id == 0x0006);
untrusted_source : bool = (av_flags & 0x00000004) > 0 &if(id == 0x0006);
};
type NTLM_Single_Host = record {
size : uint32;
padpad : padding[4];
data_present : uint32;
optional : case custom_data_present of {
true -> custom_data : bytestring &length=4;
false -> nothing : empty;
};
machine_id : uint32;
} &let {
custom_data_present: bool = (data_present & 0x00000001) > 0;
};
type LM_Response(offset: uint16) = record {
# This can be either LM (24 byte response) or
# LMv2 (16 byte response + 8 byte client challenge. No way to
# know for sure.
padpad : padding to offset;
response : bytestring &length=24;
};
type NTLM_Response(offset: uint16) = record {
padpad : padding to offset;
response : bytestring &length=24;
};
type NTLMv2_Response(flags: NTLM_Negotiate_Flags, offset: uint16) = record {
padpad : padding to offset;
response : bytestring &length=16;
client_challenge : NTLMv2_Client_Challenge(flags);
};
type NTLMv2_Client_Challenge(flags: NTLM_Negotiate_Flags) = record {
resp_type : uint8;
max_resp_type : uint8;
reserved : padding[6];
timestamp : uint64;
client_challenge : bytestring &length=8;
reserved2 : padding[4];
av_pairs : NTLM_AV_Pair_Sequence(0);
};
type NTLM_Negotiate_Flags = record {
flags: uint32;
} &let {
negotiate_56 : bool = (flags & 0x80000000) > 0;
negotiate_key_exch : bool = (flags & 0x40000000) > 0;
negotiate_128 : bool = (flags & 0x20000000) > 0;
negotiate_version : bool = (flags & 0x02000000) > 0;
negotiate_target_info : bool = (flags & 0x00800000) > 0;
request_non_nt_session_key : bool = (flags & 0x00400000) > 0;
negotiate_identify : bool = (flags & 0x00100000) > 0;
negotiate_extended_sessionsecurity : bool = (flags & 0x00040000) > 0;
target_type_server : bool = (flags & 0x00020000) > 0;
target_type_domain : bool = (flags & 0x00010000) > 0;
negotiate_always_sign : bool = (flags & 0x00008000) > 0;
negotiate_oem_workstation_supplied : bool = (flags & 0x00002000) > 0;
negotiate_oem_domain_supplied : bool = (flags & 0x00001000) > 0;
negotiate_anonymous_connection : bool = (flags & 0x00000400) > 0;
negotiate_ntlm : bool = (flags & 0x00000100) > 0;
negotiate_lm_key : bool = (flags & 0x00000080) > 0;
negotiate_datagram : bool = (flags & 0x00000040) > 0;
negotiate_seal : bool = (flags & 0x00000020) > 0;
negotiate_sign : bool = (flags & 0x00000008) > 0;
request_target : bool = (flags & 0x00000004) > 0;
negotiate_oem : bool = (flags & 0x00000002) > 0;
negotiate_unicode : bool = (flags & 0x00000001) > 0;
is_oem : bool = !negotiate_unicode && negotiate_oem;
is_invalid : bool = !negotiate_unicode && !negotiate_oem;
};

View file

@ -0,0 +1,30 @@
%include binpac.pac
%include bro.pac
%extern{
#include "analyzer/Manager.h"
#include "analyzer/Analyzer.h"
#include "types.bif.h"
#include "events.bif.h"
%}
analyzer NTLM withcontext {
connection : NTLM_Conn;
flow : NTLM_Flow;
};
connection NTLM_Conn(bro_analyzer: BroAnalyzer) {
upflow = NTLM_Flow(true);
downflow = NTLM_Flow(false);
};
%include ntlm-protocol.pac
%include ../asn1/asn1.pac
# Now we define the flow:
flow NTLM_Flow(is_orig: bool) {
datagram = NTLM_SSP_Token(is_orig) withcontext(connection, this);
};
%include ntlm-analyzer.pac

View file

@ -0,0 +1,9 @@
module NTLM;
type NTLM::Negotiate: record;
type NTLM::Challenge: record;
type NTLM::Authenticate: record;
type NTLM::NegotiateFlags: record;
type NTLM::Version: record;
type NTLM::AVs: record;