diff --git a/src/analyzer/protocol/gssapi/CMakeLists.txt b/src/analyzer/protocol/gssapi/CMakeLists.txt new file mode 100644 index 0000000000..5338f04952 --- /dev/null +++ b/src/analyzer/protocol/gssapi/CMakeLists.txt @@ -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() + diff --git a/src/analyzer/protocol/gssapi/GSSAPI.cc b/src/analyzer/protocol/gssapi/GSSAPI.cc new file mode 100644 index 0000000000..079eebe8d7 --- /dev/null +++ b/src/analyzer/protocol/gssapi/GSSAPI.cc @@ -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); + } diff --git a/src/analyzer/protocol/gssapi/GSSAPI.h b/src/analyzer/protocol/gssapi/GSSAPI.h new file mode 100644 index 0000000000..3cb39c6536 --- /dev/null +++ b/src/analyzer/protocol/gssapi/GSSAPI.h @@ -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 diff --git a/src/analyzer/protocol/gssapi/Plugin.cc b/src/analyzer/protocol/gssapi/Plugin.cc new file mode 100644 index 0000000000..3765d9b79d --- /dev/null +++ b/src/analyzer/protocol/gssapi/Plugin.cc @@ -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; + +} +} diff --git a/src/analyzer/protocol/gssapi/events.bif b/src/analyzer/protocol/gssapi/events.bif new file mode 100644 index 0000000000..4b648f3c9a --- /dev/null +++ b/src/analyzer/protocol/gssapi/events.bif @@ -0,0 +1,5 @@ +## Generated for GSSAPI messages of type *accept-completed*. +## +## c: The connection. +## +event gssapi_accepted%(c: connection%); diff --git a/src/analyzer/protocol/gssapi/gssapi-analyzer.pac b/src/analyzer/protocol/gssapi/gssapi-analyzer.pac new file mode 100644 index 0000000000..6c3b5d30c4 --- /dev/null +++ b/src/analyzer/protocol/gssapi/gssapi-analyzer.pac @@ -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); +}; diff --git a/src/analyzer/protocol/gssapi/gssapi-protocol.pac b/src/analyzer/protocol/gssapi/gssapi-protocol.pac new file mode 100644 index 0000000000..cbaee0572f --- /dev/null +++ b/src/analyzer/protocol/gssapi/gssapi-protocol.pac @@ -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); +}; diff --git a/src/analyzer/protocol/gssapi/gssapi.pac b/src/analyzer/protocol/gssapi/gssapi.pac new file mode 100644 index 0000000000..07759e8daa --- /dev/null +++ b/src/analyzer/protocol/gssapi/gssapi.pac @@ -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 diff --git a/src/analyzer/protocol/gssapi/types.bif b/src/analyzer/protocol/gssapi/types.bif new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/analyzer/protocol/ntlm/CMakeLists.txt b/src/analyzer/protocol/ntlm/CMakeLists.txt new file mode 100644 index 0000000000..fe2d4115e9 --- /dev/null +++ b/src/analyzer/protocol/ntlm/CMakeLists.txt @@ -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() + diff --git a/src/analyzer/protocol/ntlm/NTLM.cc b/src/analyzer/protocol/ntlm/NTLM.cc new file mode 100644 index 0000000000..cc7bd04d1d --- /dev/null +++ b/src/analyzer/protocol/ntlm/NTLM.cc @@ -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); + } diff --git a/src/analyzer/protocol/ntlm/NTLM.h b/src/analyzer/protocol/ntlm/NTLM.h new file mode 100644 index 0000000000..77a56eb94f --- /dev/null +++ b/src/analyzer/protocol/ntlm/NTLM.h @@ -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 diff --git a/src/analyzer/protocol/ntlm/Plugin.cc b/src/analyzer/protocol/ntlm/Plugin.cc new file mode 100644 index 0000000000..a9450537b5 --- /dev/null +++ b/src/analyzer/protocol/ntlm/Plugin.cc @@ -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; + +} +} diff --git a/src/analyzer/protocol/ntlm/events.bif b/src/analyzer/protocol/ntlm/events.bif new file mode 100644 index 0000000000..4c99fc561e --- /dev/null +++ b/src/analyzer/protocol/ntlm/events.bif @@ -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%); diff --git a/src/analyzer/protocol/ntlm/ntlm-analyzer.pac b/src/analyzer/protocol/ntlm/ntlm-analyzer.pac new file mode 100644 index 0000000000..555c4dbe61 --- /dev/null +++ b/src/analyzer/protocol/ntlm/ntlm-analyzer.pac @@ -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(utf16_copy) + utf16.length(); + const UTF16* sourcestart = utf16_copy; + const UTF16* sourceend = reinterpret_cast(utf16_copy_end); + + UTF8* targetstart = reinterpret_cast(&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); +}; + diff --git a/src/analyzer/protocol/ntlm/ntlm-protocol.pac b/src/analyzer/protocol/ntlm/ntlm-protocol.pac new file mode 100644 index 0000000000..6f5fef29b9 --- /dev/null +++ b/src/analyzer/protocol/ntlm/ntlm-protocol.pac @@ -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; +}; diff --git a/src/analyzer/protocol/ntlm/ntlm.pac b/src/analyzer/protocol/ntlm/ntlm.pac new file mode 100644 index 0000000000..ee5d33b688 --- /dev/null +++ b/src/analyzer/protocol/ntlm/ntlm.pac @@ -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 \ No newline at end of file diff --git a/src/analyzer/protocol/ntlm/types.bif b/src/analyzer/protocol/ntlm/types.bif new file mode 100644 index 0000000000..c76c0d425e --- /dev/null +++ b/src/analyzer/protocol/ntlm/types.bif @@ -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; \ No newline at end of file