From: Maya Dagon (mdagon) Date: Fri, 4 Aug 2023 14:04:59 +0000 (+0000) Subject: Pull request #3940: wizard: refactoring - split curses to multiple files by protocol X-Git-Tag: 3.1.69.0~19 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=8d46920df74f96ec532acc5b57b54264fa73dfa2;p=thirdparty%2Fsnort3.git Pull request #3940: wizard: refactoring - split curses to multiple files by protocol Merge in SNORT/snort3 from ~MDAGON/snort3:wizard to master Squashed commit of the following: commit ad41e68e63256944ec6a6ffb1d1074f2fd891250 Author: maya dagon Date: Mon Jul 31 14:51:01 2023 -0400 wizard: refactoring - split curses to multiple files by protocol --- diff --git a/src/service_inspectors/wizard/CMakeLists.txt b/src/service_inspectors/wizard/CMakeLists.txt index 33ab8c803..90f3b0611 100644 --- a/src/service_inspectors/wizard/CMakeLists.txt +++ b/src/service_inspectors/wizard/CMakeLists.txt @@ -1,11 +1,17 @@ set(FILE_LIST - curses.cc - curses.h + curse_book.cc + curse_book.h + dce_curse.cc + dce_curse.h magic.cc magic.h + mms_curse.cc mms_curse.h + s7commplus_curse.cc s7commplus_curse.h + ssl_curse.cc + ssl_curse.h hexes.cc spells.cc wizard.cc @@ -24,5 +30,5 @@ endif (STATIC_INSPECTORS) add_catch_test(curses_test NO_TEST_SOURCE SOURCES - curses.cc + ssl_curse.cc ) diff --git a/src/service_inspectors/wizard/curse_book.cc b/src/service_inspectors/wizard/curse_book.cc new file mode 100644 index 000000000..721933702 --- /dev/null +++ b/src/service_inspectors/wizard/curse_book.cc @@ -0,0 +1,62 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2023-2023 Cisco and/or its affiliates. All rights reserved. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- +// curse_book.cc author Maya Dagon +// Based on curses.cc + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "curse_book.h" + +using namespace std; + +// map between service and curse details +vector CurseBook::curse_map = +{ + // name service alg is_tcp + { "dce_udp" , "dcerpc" , CurseBook::dce_udp_curse , false }, + { "dce_tcp" , "dcerpc" , CurseBook::dce_tcp_curse , true }, + { "mms" , "mms" , CurseBook::mms_curse , true }, + { "s7commplus", "s7commplus" , CurseBook::s7commplus_curse, true }, + { "dce_smb" , "netbios-ssn", CurseBook::dce_smb_curse , true }, + { "sslv2" , "ssl" , CurseBook::ssl_v2_curse , true } +}; + +bool CurseBook::add_curse(const char* key) +{ + for ( const CurseDetails& curse : curse_map ) + { + if ( curse.name == key ) + { + if ( curse.is_tcp ) + tcp_curses.emplace_back(&curse); + else + non_tcp_curses.emplace_back(&curse); + + return true; + } + } + + return false; +} + +const vector& CurseBook::get_curses(bool tcp) const +{ + return tcp ? tcp_curses : non_tcp_curses; +} diff --git a/src/service_inspectors/wizard/curses.h b/src/service_inspectors/wizard/curse_book.h similarity index 50% rename from src/service_inspectors/wizard/curses.h rename to src/service_inspectors/wizard/curse_book.h index 1a0764d0f..8960c4bc8 100644 --- a/src/service_inspectors/wizard/curses.h +++ b/src/service_inspectors/wizard/curse_book.h @@ -1,5 +1,5 @@ //-------------------------------------------------------------------------- -// Copyright (C) 2016-2023 Cisco and/or its affiliates. All rights reserved. +// Copyright (C) 2023-2023 Cisco and/or its affiliates. All rights reserved. // // This program is free software; you can redistribute it and/or modify it // under the terms of the GNU General Public License Version 2 as published @@ -15,91 +15,28 @@ // with this program; if not, write to the Free Software Foundation, Inc., // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. //-------------------------------------------------------------------------- -// curses.h author Maya Dagon +// curse_book.h author Maya Dagon +// Refactored from curse.h -#ifndef CURSES_H -#define CURSES_H +#ifndef CURSE_BOOK_H +#define CURSE_BOOK_H #include #include #include +#include "dce_curse.h" #include "mms_curse.h" #include "s7commplus_curse.h" - -enum DCE_State -{ - STATE_0 = 0, - STATE_1, - STATE_2, - STATE_3, - STATE_4, - STATE_5, - STATE_6, - STATE_7, - STATE_8, - STATE_9, - STATE_10 -}; - -enum SSL_State -{ - BYTE_0_LEN_MSB = 0, - BYTE_1_LEN_LSB, - BYTE_2_CLIENT_HELLO, - BYTE_3_MAX_MINOR_VER, - BYTE_4_V3_MAJOR, - BYTE_5_SPECS_LEN_MSB, - BYTE_6_SPECS_LEN_LSB, - BYTE_7_SSNID_LEN_MSB, - BYTE_8_SSNID_LEN_LSB, - BYTE_9_CHLNG_LEN_MSB, - BYTE_10_CHLNG_LEN_LSB, - SSL_FOUND, - SSL_NOT_FOUND -}; +#include "ssl_curse.h" class CurseTracker { public: - struct DCE - { - DCE_State state; - uint32_t helper; - } dce; - - struct MMS - { - MMS_State state; - MMS_State last_state; - } mms; - - struct S7COMMPLUS - { - S7commplus_State state; - S7commplus_State last_state; - uint16_t func; - } s7commplus; - - struct SSL - { - SSL_State state; - unsigned total_len; - unsigned ssnid_len; - unsigned specs_len; - unsigned chlng_len; - } ssl; - - CurseTracker() - { - dce.state = DCE_State::STATE_0; - mms.state = MMS_State::MMS_STATE__TPKT_VER; - mms.last_state = mms.state; - s7commplus.state = S7commplus_State::S7COMMPLUS_STATE__TPKT_VER; - s7commplus.last_state = s7commplus.state; - s7commplus.func = 0; - ssl.state = SSL_State::BYTE_0_LEN_MSB; - } + DceTracker dce; + MmsTracker mms; + S7commplusTracker s7commplus; + SslTracker ssl; }; typedef bool (* curse_alg)(const uint8_t* data, unsigned len, CurseTracker*); @@ -121,7 +58,17 @@ public: private: std::vector tcp_curses; std::vector non_tcp_curses; + static std::vector curse_map; + + static bool dce_udp_curse(const uint8_t* data, unsigned len, CurseTracker*); + static bool dce_tcp_curse(const uint8_t* data, unsigned len, CurseTracker*); + static bool dce_smb_curse(const uint8_t* data, unsigned len, CurseTracker*); + static bool mms_curse(const uint8_t* data, unsigned len, CurseTracker*); + static bool s7commplus_curse(const uint8_t* data, unsigned len, CurseTracker*); +#ifdef CATCH_TEST_BUILD +public: +#endif + static bool ssl_v2_curse(const uint8_t* data, unsigned len, CurseTracker*); }; #endif - diff --git a/src/service_inspectors/wizard/curses.cc b/src/service_inspectors/wizard/curses.cc deleted file mode 100644 index e91708a86..000000000 --- a/src/service_inspectors/wizard/curses.cc +++ /dev/null @@ -1,1040 +0,0 @@ -//-------------------------------------------------------------------------- -// Copyright (C) 2016-2023 Cisco and/or its affiliates. All rights reserved. -// -// This program is free software; you can redistribute it and/or modify it -// under the terms of the GNU General Public License Version 2 as published -// by the Free Software Foundation. You may not use, modify or distribute -// this program under any other version of the GNU General Public License. -// -// This program is distributed in the hope that it will be useful, but -// WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// General Public License for more details. -// -// You should have received a copy of the GNU General Public License along -// with this program; if not, write to the Free Software Foundation, Inc., -// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -//-------------------------------------------------------------------------- -// curses.cc author Maya Dagon -// mms_curse and s7commplus_curse author Jared Rittle - -#ifdef HAVE_CONFIG_H -#include "config.h" -#endif - -#include "curses.h" - -#include - -using namespace std; - -enum DceRpcPduType -{ - DCERPC_PDU_TYPE__REQUEST = 0, - DCERPC_PDU_TYPE__PING, - DCERPC_PDU_TYPE__RESPONSE, - DCERPC_PDU_TYPE__FAULT, - DCERPC_PDU_TYPE__WORKING, - DCERPC_PDU_TYPE__NOCALL, - DCERPC_PDU_TYPE__REJECT, - DCERPC_PDU_TYPE__ACK, - DCERPC_PDU_TYPE__CL_CANCEL, - DCERPC_PDU_TYPE__FACK, - DCERPC_PDU_TYPE__CANCEL_ACK, - DCERPC_PDU_TYPE__BIND, - DCERPC_PDU_TYPE__BIND_ACK, - DCERPC_PDU_TYPE__BIND_NACK, - DCERPC_PDU_TYPE__ALTER_CONTEXT, - DCERPC_PDU_TYPE__ALTER_CONTEXT_RESP, - DCERPC_PDU_TYPE__AUTH3, - DCERPC_PDU_TYPE__SHUTDOWN, - DCERPC_PDU_TYPE__CO_CANCEL, - DCERPC_PDU_TYPE__ORPHANED, - DCERPC_PDU_TYPE__MICROSOFT_PROPRIETARY_OUTLOOK2003_RPC_OVER_HTTP, - DCERPC_PDU_TYPE__MAX -}; - -/* Version 4 is for Connectionless - * Version 5 is for Connection oriented */ -enum DceRpcProtoMajorVers -{ - DCERPC_PROTO_MAJOR_VERS__4 = 4, - DCERPC_PROTO_MAJOR_VERS__5 = 5 -}; - -enum DceRpcProtoMinorVers -{ - DCERPC_PROTO_MINOR_VERS__0 = 0, - DCERPC_PROTO_MINOR_VERS__1 = 1 -}; - -static bool dce_udp_curse(const uint8_t* data, unsigned len, CurseTracker*) -{ - const uint8_t dcerpc_cl_hdr_len = 80; - const uint8_t cl_len_offset = 74; - - if ( len >= dcerpc_cl_hdr_len ) - { - uint8_t version = data[0]; - uint8_t pdu_type = data[1]; - bool little_endian = ((data[4] & 0x10) >> 4) ? true : false; - uint16_t cl_len; - -#ifdef WORDS_BIGENDIAN - if ( !little_endian ) -#else - if ( little_endian ) -#endif /* WORDS_BIGENDIAN */ - cl_len = (data[cl_len_offset+1] << 8) | data[cl_len_offset]; - else - cl_len = (data[cl_len_offset] << 8) | data[cl_len_offset+1]; - - if ( (version == DCERPC_PROTO_MAJOR_VERS__4) and - ((pdu_type == DCERPC_PDU_TYPE__REQUEST) or - (pdu_type == DCERPC_PDU_TYPE__RESPONSE) or - (pdu_type == DCERPC_PDU_TYPE__FAULT) or - (pdu_type == DCERPC_PDU_TYPE__REJECT) or - (pdu_type == DCERPC_PDU_TYPE__FACK)) and - ((cl_len != 0) and - (cl_len + (unsigned)dcerpc_cl_hdr_len) <= len) ) - return true; - } - - return false; -} - -static bool dce_tcp_curse(const uint8_t* data, unsigned len, CurseTracker* tracker) -{ - const uint8_t dce_rpc_co_hdr_len = 16; - CurseTracker::DCE& dce = tracker->dce; - - uint32_t n = 0; - while ( n < len ) - { - switch ( dce.state ) - { - case STATE_0: // check major version - if ( data[n] != DCERPC_PROTO_MAJOR_VERS__5 ) - { - // go to bad state - dce.state = STATE_10; - - return false; - } - - dce.state = (DCE_State)((int)dce.state + 1); - break; - - case STATE_1: // check minor version - if ( data[n] != DCERPC_PROTO_MINOR_VERS__0 ) - { - // go to bad state - dce.state = STATE_10; - - return false; - } - - dce.state = (DCE_State)((int)dce.state + 1); - break; - - case STATE_2: // pdu_type - { - uint8_t pdu_type = data[n]; - - if ( (pdu_type != DCERPC_PDU_TYPE__BIND) and - (pdu_type != DCERPC_PDU_TYPE__BIND_ACK) ) - { - // go to bad state - dce.state = STATE_10; - - return false; - } - - dce.state = (DCE_State)((int)dce.state + 1); - break; - } - - case STATE_4: //little endian - dce.helper = (data[n] & 0x10) << 20; - dce.state = (DCE_State)((int)dce.state + 1); - break; - case STATE_8: - dce.helper |= data[n]; - dce.state = (DCE_State)((int)dce.state + 1); - break; - case STATE_9: -#ifdef WORDS_BIGENDIAN - if ( !(dce.helper >> 24) ) -#else - if ( dce.helper >> 24 ) -#endif /* WORDS_BIGENDIAN */ - dce.helper = (data[n] << 8) | (dce.helper & 0XFF); - else - { - dce.helper <<=8; - dce.helper |= data[n]; - } - - if ( dce.helper >= dce_rpc_co_hdr_len ) - return true; - - dce.state = STATE_10; - break; - - case STATE_10: - // no match - return false; - default: - dce.state = (DCE_State)((int)dce.state + 1); - break; - } - - n++; - } - - return false; -} - -static bool dce_smb_curse(const uint8_t* data, unsigned len, CurseTracker* tracker) -{ - const uint32_t dce_smb_id = 0xff534d42; /* \xffSMB */ - const uint32_t dce_smb2_id = 0xfe534d42; /* \xfeSMB */ - const uint8_t session_request = 0x81, session_response = 0x82, session_message = 0x00; - CurseTracker::DCE& dce = tracker->dce; - - uint32_t n = 0; - while ( n < len ) - { - switch ( dce.state ) - { - case STATE_0: - if ( data[n] == session_message ) - { - dce.state = (DCE_State)((int)dce.state + 2); - break; - } - - if ( data[n] == session_request or data[n] == session_response ) - { - dce.state = (DCE_State)((int)dce.state + 1); - - return false; - } - - dce.state = STATE_9; - - return false; - - case STATE_1: - if ( data[n] == session_message ) - { - dce.state = (DCE_State)((int)dce.state + 1); - break; - } - - dce.state = STATE_9; - - return false; - - case STATE_5: - dce.helper = data[n]; - dce.state = (DCE_State)((int)dce.state + 1); - break; - - case STATE_6: - case STATE_7: - dce.helper <<= 8; - dce.helper |= data[n]; - dce.state = (DCE_State)((int)dce.state + 1); - break; - - case STATE_8: - dce.helper <<= 8; - dce.helper |= data[n]; - - if ( (dce.helper == dce_smb_id) or (dce.helper == dce_smb2_id) ) - return true; - - dce.state = (DCE_State)((int)dce.state + 1); - break; - - case STATE_9: - // no match - return false; - - default: - dce.state = (DCE_State)((int)dce.state + 1); - break; - } - - n++; - } - - return false; -} - - -static bool mms_curse(const uint8_t* data, unsigned len, CurseTracker* tracker) -{ - // peg the tracker to MMS - CurseTracker::MMS& mms = tracker->mms; - - // if the state is set to MMS_STATE__SEARCH it means we most likely - // have a split pipelined message coming through and will need to - // reset the state - if ( mms.state == MMS_STATE__SEARCH ) - { - mms.state = mms.last_state; - } - - // define all known MMS tags to check for - enum - { - MMS_CONFIRMED_REQUEST_TAG = 0xA0, - MMS_CONFIRMED_RESPONSE_TAG = 0xA1, - MMS_CONFIRMED_ERROR_TAG = 0xA2, - MMS_UNCONFIRMED_TAG = 0xA3, - MMS_REJECT_TAG = 0xA4, - MMS_CANCEL_REQUEST_TAG = 0x85, - MMS_CANCEL_RESPONSE_TAG = 0x86, - MMS_CANCEL_ERROR_TAG = 0xA7, - MMS_INITIATE_REQUEST_TAG = 0xA8, - MMS_INITIATE_RESPONSE_TAG = 0xA9, - MMS_INITIATE_ERROR_TAG = 0xAA, - MMS_CONCLUDE_REQUEST_TAG = 0x8B, - MMS_CONCLUDE_RESPONSE_TAG = 0x8C, - MMS_CONCLUDE_ERROR_TAG = 0xAD, - }; - - uint32_t idx = 0; - while ( idx < len ) - { - switch ( mms.state ) - { - case MMS_STATE__TPKT_VER: - { - mms.state = MMS_STATE__TPKT_RES; - break; - } - - case MMS_STATE__TPKT_RES: - { - mms.state = MMS_STATE__TPKT_LEN1; - break; - } - - case MMS_STATE__TPKT_LEN1: - { - mms.state = MMS_STATE__TPKT_LEN2; - break; - } - - case MMS_STATE__TPKT_LEN2: - { - mms.state = MMS_STATE__COTP_LEN; - break; - } - - case MMS_STATE__COTP_LEN: - { - mms.state = MMS_STATE__COTP_PDU; - break; - } - - case MMS_STATE__COTP_PDU: - { - // 7 6 5 4 3 2 1 0 - // --------------- - // . . . . x x x x Destination Reference - // x x x x . . . . PDU Type - const uint32_t MMS_COTP_PDU_DT_DATA = 0x0F; - - if ( data[idx] >> 0x04 != MMS_COTP_PDU_DT_DATA ) - { - mms.state = MMS_STATE__NOT_FOUND; - break; - } - - mms.state = MMS_STATE__COTP_TPDU_NUM; - break; - } - - case MMS_STATE__COTP_TPDU_NUM: - { - mms.state = MMS_STATE__OSI_SESSION_SPDU; - break; - } - - case MMS_STATE__OSI_SESSION_SPDU: - { - // define all known OSI Session layer SPDU tags to check - enum - { - MMS_OSI_SESSION_SPDU_GT_DT = 0x01, - MMS_OSI_SESSION_SPDU_CN = 0x0D, - MMS_OSI_SESSION_SPDU_AC = 0x0E, - }; - - switch ( data[idx] ) - { - // check for a known MMS message tag in the event Session/Pres/ACSE aren't used - case MMS_CONFIRMED_REQUEST_TAG: // fallthrough intentional - case MMS_CONFIRMED_RESPONSE_TAG: // fallthrough intentional - case MMS_CONFIRMED_ERROR_TAG: // fallthrough intentional - case MMS_UNCONFIRMED_TAG: // fallthrough intentional - case MMS_REJECT_TAG: // fallthrough intentional - case MMS_CANCEL_REQUEST_TAG: // fallthrough intentional - case MMS_CANCEL_RESPONSE_TAG: // fallthrough intentional - case MMS_CANCEL_ERROR_TAG: // fallthrough intentional - case MMS_INITIATE_REQUEST_TAG: // fallthrough intentional - case MMS_INITIATE_RESPONSE_TAG: // fallthrough intentional - case MMS_INITIATE_ERROR_TAG: // fallthrough intentional - case MMS_CONCLUDE_REQUEST_TAG: // fallthrough intentional - case MMS_CONCLUDE_RESPONSE_TAG: // fallthrough intentional - case MMS_CONCLUDE_ERROR_TAG: - { - // if an MMS tag exists in the remaining data, - // hand off to the MMS service inspector - mms.state = MMS_STATE__FOUND; - break; - } - - // if mms isn't found, search for an OSI Session layer - case MMS_OSI_SESSION_SPDU_GT_DT: // fallthrough intentional - case MMS_OSI_SESSION_SPDU_CN: // fallthrough intentional - case MMS_OSI_SESSION_SPDU_AC: - { - mms.state = MMS_STATE__MMS; - break; - } - - // if neither are found, it is most likely not MMS - default: - { - mms.state = MMS_STATE__NOT_FOUND; - } - } - - break; - } - - case MMS_STATE__MMS: - { - // loop through the remaining bytes in the buffer checking for known MMS tags - for ( uint32_t i=idx; i < len; i++ ) - { - // for each remaining byte check to see if it is in the known tag map - switch ( data[i] ) - { - case MMS_CONFIRMED_REQUEST_TAG: // fallthrough intentional - case MMS_CONFIRMED_RESPONSE_TAG: // fallthrough intentional - case MMS_CONFIRMED_ERROR_TAG: // fallthrough intentional - case MMS_UNCONFIRMED_TAG: // fallthrough intentional - case MMS_REJECT_TAG: // fallthrough intentional - case MMS_CANCEL_REQUEST_TAG: // fallthrough intentional - case MMS_CANCEL_RESPONSE_TAG: // fallthrough intentional - case MMS_CANCEL_ERROR_TAG: // fallthrough intentional - case MMS_INITIATE_REQUEST_TAG: // fallthrough intentional - case MMS_INITIATE_RESPONSE_TAG: // fallthrough intentional - case MMS_INITIATE_ERROR_TAG: // fallthrough intentional - case MMS_CONCLUDE_REQUEST_TAG: // fallthrough intentional - case MMS_CONCLUDE_RESPONSE_TAG: // fallthrough intentional - case MMS_CONCLUDE_ERROR_TAG: - { - // if an MMS tag exists in the remaining data, - // hand off to the MMS service inspector - mms.state = MMS_STATE__FOUND; - break; - } - // no default here as it we don't know when we would hit - // the first MMS tag without doing full parsing - } - - // exit the loop when a state has been determined - if ( mms.state == MMS_STATE__NOT_FOUND - or mms.state == MMS_STATE__SEARCH - or mms.state == MMS_STATE__FOUND ) - { - break; - } - } - - break; - } - - case MMS_STATE__FOUND: - { - mms.state = MMS_STATE__TPKT_VER; - - return true; - } - - case MMS_STATE__NOT_FOUND: - { - mms.state = MMS_STATE__TPKT_VER; - - return false; - } - - default: - { - mms.state = MMS_STATE__NOT_FOUND; - assert(false); - break; - } - } - - idx++; - } - - mms.last_state = mms.state; - mms.state = MMS_STATE__SEARCH; - - return false; -} - - -static bool s7commplus_curse(const uint8_t* data, unsigned len, CurseTracker* tracker) -{ - // peg the tracker to s7commplus - CurseTracker::S7COMMPLUS& s7commplus = tracker->s7commplus; - - // if the state is set to S7COMMPLUS_STATE__SEARCH it means we most likely - // have a split pipelined message coming through and will need to - // reset the state - if ( s7commplus.state == S7COMMPLUS_STATE__SEARCH ) - { - s7commplus.state = s7commplus.last_state; - } - - uint32_t idx = 0; - while ( idx < len ) - { - switch ( s7commplus.state ) - { - case S7COMMPLUS_STATE__TPKT_VER: - { - s7commplus.state = S7COMMPLUS_STATE__TPKT_RES; - break; - } - - case S7COMMPLUS_STATE__TPKT_RES: - { - s7commplus.state = S7COMMPLUS_STATE__TPKT_LEN1; - break; - } - - case S7COMMPLUS_STATE__TPKT_LEN1: - { - s7commplus.state = S7COMMPLUS_STATE__TPKT_LEN2; - break; - } - - case S7COMMPLUS_STATE__TPKT_LEN2: - { - s7commplus.state = S7COMMPLUS_STATE__COTP_LEN; - break; - } - - case S7COMMPLUS_STATE__COTP_LEN: - { - s7commplus.state = S7COMMPLUS_STATE__COTP_PDU; - break; - } - - case S7COMMPLUS_STATE__COTP_PDU: - { - // 7 6 5 4 3 2 1 0 - // --------------- - // . . . . x x x x Destination Reference - // x x x x . . . . PDU Type - const uint32_t S7COMMPLUS_COTP_PDU_DT_DATA = 0x0F; - - if ( data[idx] >> 0x04 != S7COMMPLUS_COTP_PDU_DT_DATA ) - { - s7commplus.state = S7COMMPLUS_STATE__NOT_FOUND; - break; - } - - s7commplus.state = S7COMMPLUS_STATE__COTP_TPDU_NUM; - break; - } - - case S7COMMPLUS_STATE__COTP_TPDU_NUM: - { - s7commplus.state = S7COMMPLUS_STATE__PROTO_ID; - break; - } - - case S7COMMPLUS_STATE__PROTO_ID: - { - // there are two possible protocol identifiers - 0x32 and 0x72 - // 0x32 indicates the original s7comm protocol - // * the original protocol is not supported within the inspector - // so just catching and considering it a no match for now - // 0x72 indicates the s7commplus protocol - // * this is the protocol on which the existing inspector focuses - if ( data[idx] == S7COMMPLUS_PROTOCOL_IDENTIFIER__S7COMMPLUS ) - { - s7commplus.state = S7COMMPLUS_STATE__PDU_TYPE; - } - else - { - s7commplus.state = S7COMMPLUS_STATE__NOT_FOUND; - } - break; - } - - case S7COMMPLUS_STATE__PDU_TYPE: - { - switch ( data[idx] ) - { - case S7COMMPLUS_PDU_TYPE__CONNECT: // fallthrough intentional - case S7COMMPLUS_PDU_TYPE__DATA: // fallthrough intentional - case S7COMMPLUS_PDU_TYPE__DATA2: // fallthrough intentional - case S7COMMPLUS_PDU_TYPE__KEEPALIVE: - { - s7commplus.state = S7COMMPLUS_STATE__DATALENGTH_1; - break; - } - - default: - { - s7commplus.state = S7COMMPLUS_STATE__NOT_FOUND; - break; - } - } - - break; - } - - case S7COMMPLUS_STATE__DATALENGTH_1: - { - s7commplus.state = S7COMMPLUS_STATE__DATALENGTH_2; - break; - } - - case S7COMMPLUS_STATE__DATALENGTH_2: - { - s7commplus.state = S7COMMPLUS_STATE__OPCODE; - break; - } - - case S7COMMPLUS_STATE__OPCODE: - { - switch ( data[idx] ) - { - case S7COMMPLUS_OPCODE__REQ: // fallthrough intentional - case S7COMMPLUS_OPCODE__RES: // fallthrough intentional - case S7COMMPLUS_OPCODE__NOTIFICATION: // fallthrough intentional - case S7COMMPLUS_OPCODE__RES2: - { - s7commplus.state = S7COMMPLUS_STATE__RES_1; - break; - } - - default: - { - s7commplus.state = S7COMMPLUS_STATE__NOT_FOUND; - break; - } - } - - break; - } - - case S7COMMPLUS_STATE__RES_1: - { - s7commplus.state = S7COMMPLUS_STATE__RES_2; - break; - } - - case S7COMMPLUS_STATE__RES_2: - { - s7commplus.state = S7COMMPLUS_STATE__FUNCTION_1; - break; - } - - case S7COMMPLUS_STATE__FUNCTION_1: - { - // make sure the function code is zeroed out before building - s7commplus.func = 0; - - // get the high byte of the function code - s7commplus.func = data[idx] << 0x08; - - // move on to the low byte - s7commplus.state = S7COMMPLUS_STATE__FUNCTION_2; - break; - } - - case S7COMMPLUS_STATE__FUNCTION_2: - { - // get the low byte of the function code - s7commplus.func |= data[idx]; - - switch ( s7commplus.func ) - { - case S7COMMPLUS_FUNCTION__EXPLORE: // fallthrough intentional - case S7COMMPLUS_FUNCTION__CREATEOBJECT: // fallthrough intentional - case S7COMMPLUS_FUNCTION__DELETEOBJECT: // fallthrough intentional - case S7COMMPLUS_FUNCTION__SETVARIABLE: // fallthrough intentional - case S7COMMPLUS_FUNCTION__GETLINK: // fallthrough intentional - case S7COMMPLUS_FUNCTION__SETMULTIVAR: // fallthrough intentional - case S7COMMPLUS_FUNCTION__GETMULTIVAR: // fallthrough intentional - case S7COMMPLUS_FUNCTION__BEGINSEQUENCE: // fallthrough intentional - case S7COMMPLUS_FUNCTION__ENDSEQUENCE: // fallthrough intentional - case S7COMMPLUS_FUNCTION__INVOKE: // fallthrough intentional - case S7COMMPLUS_FUNCTION__GETVARSUBSTR: - { - s7commplus.state = S7COMMPLUS_STATE__FOUND; - break; - } - - default: - { - s7commplus.state = S7COMMPLUS_STATE__NOT_FOUND; - break; - } - } - - break; - } - - case S7COMMPLUS_STATE__FOUND: - { - s7commplus.state = S7COMMPLUS_STATE__TPKT_VER; - - return true; - } - - case S7COMMPLUS_STATE__NOT_FOUND: - { - s7commplus.state = S7COMMPLUS_STATE__TPKT_VER; - - return false; - } - - default: - { - s7commplus.state = S7COMMPLUS_STATE__NOT_FOUND; - assert(false); - break; - } - } - - idx++; - } - - s7commplus.last_state = s7commplus.state; - s7commplus.state = S7COMMPLUS_STATE__SEARCH; - - return false; -} - - -namespace SSL_Const -{ -static constexpr uint8_t hdr_len = 9; -static constexpr uint8_t sslv2_msb_set = 0x80; -static constexpr uint8_t client_hello = 0x01; -static constexpr uint8_t sslv3_major_ver = 0x03; -static constexpr uint8_t sslv3_max_minor_ver = 0x03; -} - -static bool ssl_v2_curse(const uint8_t* data, unsigned len, CurseTracker* tracker) -{ - CurseTracker::SSL& ssl = tracker->ssl; - - if ( ssl.state == SSL_State::SSL_NOT_FOUND ) - return false; - else if ( ssl.state == SSL_State::SSL_FOUND ) - return true; - - for ( unsigned i = 0; i < len; ++i ) - { - uint8_t val = data[i]; - - switch ( ssl.state ) - { - case SSL_State::BYTE_0_LEN_MSB: - if ( (val & SSL_Const::sslv2_msb_set) == 0 ) - { - ssl.state = SSL_State::SSL_NOT_FOUND; - - return false; - } - - ssl.total_len = (val & (~SSL_Const::sslv2_msb_set)) << 8; - ssl.state = SSL_State::BYTE_1_LEN_LSB; - break; - - case SSL_State::BYTE_1_LEN_LSB: - ssl.total_len |= val; - if ( ssl.total_len < SSL_Const::hdr_len ) - { - ssl.state = SSL_State::SSL_NOT_FOUND; - - return false; - } - - ssl.total_len -= SSL_Const::hdr_len; - ssl.state = SSL_State::BYTE_2_CLIENT_HELLO; - break; - - case SSL_State::BYTE_2_CLIENT_HELLO: - if ( val != SSL_Const::client_hello ) - { - ssl.state = SSL_State::SSL_NOT_FOUND; - - return false; - } - - ssl.state = SSL_State::BYTE_3_MAX_MINOR_VER; - break; - - case SSL_State::BYTE_3_MAX_MINOR_VER: - if ( val > SSL_Const::sslv3_max_minor_ver ) - { - ssl.state = SSL_State::SSL_NOT_FOUND; - - return false; - } - - ssl.state = SSL_State::BYTE_4_V3_MAJOR; - break; - - case SSL_State::BYTE_4_V3_MAJOR: - if ( val > SSL_Const::sslv3_major_ver ) - { - ssl.state = SSL_State::SSL_NOT_FOUND; - - return false; - } - - ssl.state = SSL_State::BYTE_5_SPECS_LEN_MSB; - break; - - case SSL_State::BYTE_5_SPECS_LEN_MSB: - ssl.specs_len = val << 8; - ssl.state = SSL_State::BYTE_6_SPECS_LEN_LSB; - break; - - case SSL_State::BYTE_6_SPECS_LEN_LSB: - ssl.specs_len |= val; - - if ( ssl.total_len < ssl.specs_len ) - { - ssl.state = SSL_State::SSL_NOT_FOUND; - - return false; - } - - ssl.total_len -= ssl.specs_len; - ssl.state = SSL_State::BYTE_7_SSNID_LEN_MSB; - break; - - case SSL_State::BYTE_7_SSNID_LEN_MSB: - ssl.ssnid_len = val << 8; - ssl.state = SSL_State::BYTE_8_SSNID_LEN_LSB; - break; - - case SSL_State::BYTE_8_SSNID_LEN_LSB: - ssl.ssnid_len |= val; - - if ( ssl.total_len < ssl.ssnid_len ) - { - ssl.state = SSL_State::SSL_NOT_FOUND; - - return false; - } - - ssl.total_len -= ssl.ssnid_len; - ssl.state = SSL_State::BYTE_9_CHLNG_LEN_MSB; - break; - - case SSL_State::BYTE_9_CHLNG_LEN_MSB: - ssl.chlng_len = val << 8; - ssl.state = SSL_State::BYTE_10_CHLNG_LEN_LSB; - break; - - case SSL_State::BYTE_10_CHLNG_LEN_LSB: - ssl.chlng_len |= val; - - if ( ssl.total_len < ssl.chlng_len ) - { - ssl.state = SSL_State::SSL_NOT_FOUND; - - return false; - } - - ssl.state = SSL_State::SSL_FOUND; - - return true; - - default: - return false; - } - } - - return false; -} - - -// map between service and curse details -static vector curse_map -{ - // name service alg is_tcp - { "dce_udp" , "dcerpc" , dce_udp_curse , false }, - { "dce_tcp" , "dcerpc" , dce_tcp_curse , true }, - { "mms" , "mms" , mms_curse , true }, - { "s7commplus", "s7commplus" , s7commplus_curse, true }, - { "dce_smb" , "netbios-ssn", dce_smb_curse , true }, - { "sslv2" , "ssl" , ssl_v2_curse , true } -}; - -bool CurseBook::add_curse(const char* key) -{ - for ( const CurseDetails& curse : curse_map ) - { - if ( curse.name == key ) - { - if ( curse.is_tcp ) - tcp_curses.emplace_back(&curse); - else - non_tcp_curses.emplace_back(&curse); - - return true; - } - } - - return false; -} - -const vector& CurseBook::get_curses(bool tcp) const -{ - if ( tcp ) - return tcp_curses; - - return non_tcp_curses; -} - -#ifdef CATCH_TEST_BUILD - -#include "catch/catch.hpp" -#include - -//client hello with v2 header advertising sslv2 -static const uint8_t ssl_v2_ch[] = -{ 0x80,0x59,0x01,0x00,0x02,0x00,0x30,0x00,0x00,0x00,0x20,0x00,0x00,0x39,0x00,0x00, - 0x38,0x00,0x00,0x35,0x00,0x00,0x16,0x00,0x00,0x13,0x00,0x00,0x0a,0x00,0x00,0x33, - 0x00,0x00,0x32,0x00,0x00,0x2f,0x00,0x00,0x07,0x00,0x00,0x05,0x00,0x00,0x04,0x00, - 0x00,0x15,0x00,0x00,0x12,0x00,0x00,0x09,0x00,0x00,0xff,0xda,0x86,0xfa,0xb4,0x73, - 0x5a,0x1e,0x11,0xd1,0xdb,0x58,0x4b,0x59,0xe1,0x07,0x51,0x5f,0x13,0x46,0xa2,0xdd, - 0xee,0xda,0xc1,0x9d,0xdc,0xd7,0xb8,0x86,0x51,0x10,0x5a }; - -//client hello with v2 header advertising tls 1.0 -static const uint8_t ssl_v2_v3_ch[] = -{ 0x80,0x59,0x01,0x03,0x01,0x00,0x30,0x00,0x00,0x00,0x20,0x00,0x00,0x39,0x00,0x00, - 0x38,0x00,0x00,0x35,0x00,0x00,0x16,0x00,0x00,0x13,0x00,0x00,0x0a,0x00,0x00,0x33, - 0x00,0x00,0x32,0x00,0x00,0x2f,0x00,0x00,0x07,0x00,0x00,0x05,0x00,0x00,0x04,0x00, - 0x00,0x15,0x00,0x00,0x12,0x00,0x00,0x09,0x00,0x00,0xff,0xda,0x86,0xfa,0xb4,0x73, - 0x5a,0x1e,0x11,0xd1,0xdb,0x58,0x4b,0x59,0xe1,0x07,0x51,0x5f,0x13,0x46,0xa2,0xdd, - 0xee,0xda,0xc1,0x9d,0xdc,0xd7,0xb8,0x86,0x51,0x10,0x5a }; - -TEST_CASE("sslv2 detect", "[SslV2Curse]") -{ - uint32_t max_detect = static_cast(SSL_State::BYTE_10_CHLNG_LEN_LSB); - CurseTracker tracker{ }; - - auto test = [&](uint32_t incr_by,const uint8_t* ch) - { - uint32_t i = 0; - while ( i <= max_detect ) - { - if ( (i + incr_by - 1) < max_detect ) - { - CHECK(tracker.ssl.state == static_cast(i)); - CHECK_FALSE(ssl_v2_curse(&ch[i],sizeof(uint8_t) * incr_by,&tracker)); - } - else - { - CHECK(ssl_v2_curse(&ch[i],sizeof(uint8_t) * incr_by,&tracker)); - CHECK(tracker.ssl.state == SSL_State::SSL_FOUND); - } - - i += incr_by; - } - //subsequent checks must return found - CHECK(ssl_v2_curse(&ch[max_detect + 1],sizeof(uint8_t),&tracker)); - CHECK(tracker.ssl.state == SSL_State::SSL_FOUND); - }; - - //sslv2 with ssl version 2 - SECTION("1 byte v2"){ test(1,ssl_v2_ch); } - SECTION("2 bytes v2"){ test(2,ssl_v2_ch); } - SECTION("3 bytes v2"){ test(3,ssl_v2_ch); } - SECTION("4 bytes v2"){ test(4,ssl_v2_ch); } - SECTION("5 bytes v2"){ test(5,ssl_v2_ch); } - SECTION("6 bytes v2"){ test(6,ssl_v2_ch); } - SECTION("7 bytes v2"){ test(7,ssl_v2_ch); } - SECTION("8 bytes v2"){ test(8,ssl_v2_ch); } - SECTION("9 bytes v2"){ test(9,ssl_v2_ch); } - SECTION("10 bytes v2"){ test(10,ssl_v2_ch); } - SECTION("11 bytes v2"){ test(11,ssl_v2_ch);} - - //sslv2 with tls version 1.0 - SECTION("1 byte v2_v3"){ test(1,ssl_v2_v3_ch); } - SECTION("2 bytes v2_v3"){ test(2,ssl_v2_v3_ch); } - SECTION("3 bytes v2_v3"){ test(3,ssl_v2_v3_ch); } - SECTION("4 bytes v2_v3"){ test(4,ssl_v2_v3_ch); } - SECTION("5 bytes v2_v3"){ test(5,ssl_v2_v3_ch); } - SECTION("6 bytes v2_v3"){ test(6,ssl_v2_v3_ch); } - SECTION("7 bytes v2_v3"){ test(7,ssl_v2_v3_ch); } - SECTION("8 bytes v2_v3"){ test(8,ssl_v2_v3_ch); } - SECTION("9 bytes v2_v3"){ test(9,ssl_v2_v3_ch); } - SECTION("10 bytes v2_v3"){ test(10,ssl_v2_v3_ch); } - SECTION("11 bytes v2_v3"){ test(11,ssl_v2_v3_ch); } -} - -TEST_CASE("sslv2 not found", "[SslV2Curse]") -{ - uint32_t max_detect = static_cast(SSL_State::BYTE_10_CHLNG_LEN_LSB); - CurseTracker tracker{}; - uint8_t bad_data[] = {0x00,0x08,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff}; - auto test = [&](uint32_t fail_at_byte) - { - uint8_t ch_data[sizeof(ssl_v2_ch)]; - memcpy(ch_data,ssl_v2_ch,sizeof(ssl_v2_ch)); - - ch_data[fail_at_byte] = bad_data[fail_at_byte]; - - for ( uint32_t i = 0; i <= fail_at_byte; i++ ) - { - if ( i < fail_at_byte ) - { - CHECK(tracker.ssl.state == static_cast(i)); - CHECK_FALSE(ssl_v2_curse(&ch_data[i],sizeof(uint8_t),&tracker)); - } - else - { - CHECK_FALSE(ssl_v2_curse(&ch_data[i],sizeof(uint8_t),&tracker)); - CHECK(tracker.ssl.state == SSL_State::SSL_NOT_FOUND); - } - } - //subsequent checks must return ssl not found - CHECK_FALSE(ssl_v2_curse(&ch_data[max_detect + 1],sizeof(uint8_t),&tracker)); - CHECK(tracker.ssl.state == SSL_State::SSL_NOT_FOUND); - }; - - SECTION("byte 0"){ test(0);} - SECTION("byte 1"){ test(1);} - SECTION("byte 2"){ test(2);} - SECTION("byte 3"){ test(3);} - SECTION("byte 4"){ test(4);} - SECTION("byte 6"){ test(6);} - SECTION("byte 8"){ test(8);} - SECTION("byte 10"){ test(10);} -} - -#endif diff --git a/src/service_inspectors/wizard/dce_curse.cc b/src/service_inspectors/wizard/dce_curse.cc new file mode 100644 index 000000000..6e0374a29 --- /dev/null +++ b/src/service_inspectors/wizard/dce_curse.cc @@ -0,0 +1,271 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2023-2023 Cisco and/or its affiliates. All rights reserved. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- +// dce_curses.cc author Maya Dagon +// Refactored from curses.cc + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "dce_curse.h" +#include "curse_book.h" + +enum DceRpcPduType +{ + DCERPC_PDU_TYPE__REQUEST = 0, + DCERPC_PDU_TYPE__PING, + DCERPC_PDU_TYPE__RESPONSE, + DCERPC_PDU_TYPE__FAULT, + DCERPC_PDU_TYPE__WORKING, + DCERPC_PDU_TYPE__NOCALL, + DCERPC_PDU_TYPE__REJECT, + DCERPC_PDU_TYPE__ACK, + DCERPC_PDU_TYPE__CL_CANCEL, + DCERPC_PDU_TYPE__FACK, + DCERPC_PDU_TYPE__CANCEL_ACK, + DCERPC_PDU_TYPE__BIND, + DCERPC_PDU_TYPE__BIND_ACK, + DCERPC_PDU_TYPE__BIND_NACK, + DCERPC_PDU_TYPE__ALTER_CONTEXT, + DCERPC_PDU_TYPE__ALTER_CONTEXT_RESP, + DCERPC_PDU_TYPE__AUTH3, + DCERPC_PDU_TYPE__SHUTDOWN, + DCERPC_PDU_TYPE__CO_CANCEL, + DCERPC_PDU_TYPE__ORPHANED, + DCERPC_PDU_TYPE__MICROSOFT_PROPRIETARY_OUTLOOK2003_RPC_OVER_HTTP, + DCERPC_PDU_TYPE__MAX +}; + +/* Version 4 is for Connectionless + * Version 5 is for Connection oriented */ +enum DceRpcProtoMajorVers +{ + DCERPC_PROTO_MAJOR_VERS__4 = 4, + DCERPC_PROTO_MAJOR_VERS__5 = 5 +}; + +enum DceRpcProtoMinorVers +{ + DCERPC_PROTO_MINOR_VERS__0 = 0, + DCERPC_PROTO_MINOR_VERS__1 = 1 +}; + +bool CurseBook::dce_udp_curse(const uint8_t* data, unsigned len, CurseTracker*) +{ + const uint8_t dcerpc_cl_hdr_len = 80; + const uint8_t cl_len_offset = 74; + + if ( len >= dcerpc_cl_hdr_len ) + { + uint8_t version = data[0]; + uint8_t pdu_type = data[1]; + bool little_endian = ((data[4] & 0x10) >> 4) ? true : false; + uint16_t cl_len; + +#ifdef WORDS_BIGENDIAN + if ( !little_endian ) +#else + if ( little_endian ) +#endif /* WORDS_BIGENDIAN */ + cl_len = (data[cl_len_offset+1] << 8) | data[cl_len_offset]; + else + cl_len = (data[cl_len_offset] << 8) | data[cl_len_offset+1]; + + if ( (version == DCERPC_PROTO_MAJOR_VERS__4) and + ((pdu_type == DCERPC_PDU_TYPE__REQUEST) or + (pdu_type == DCERPC_PDU_TYPE__RESPONSE) or + (pdu_type == DCERPC_PDU_TYPE__FAULT) or + (pdu_type == DCERPC_PDU_TYPE__REJECT) or + (pdu_type == DCERPC_PDU_TYPE__FACK)) and + ((cl_len != 0) and + (cl_len + (unsigned)dcerpc_cl_hdr_len) <= len) ) + return true; + } + + return false; +} + +bool CurseBook::dce_tcp_curse(const uint8_t* data, unsigned len, CurseTracker* tracker) +{ + const uint8_t dce_rpc_co_hdr_len = 16; + DceTracker& dce = tracker->dce; + + uint32_t n = 0; + while ( n < len ) + { + switch ( dce.state ) + { + case DCE_STATE__0: // check major version + if ( data[n] != DCERPC_PROTO_MAJOR_VERS__5 ) + { + // go to bad state + dce.state = DCE_STATE__10; + + return false; + } + + dce.state = (DCE_State)((int)dce.state + 1); + break; + + case DCE_STATE__1: // check minor version + if ( data[n] != DCERPC_PROTO_MINOR_VERS__0 ) + { + // go to bad state + dce.state = DCE_STATE__10; + + return false; + } + + dce.state = (DCE_State)((int)dce.state + 1); + break; + + case DCE_STATE__2: // pdu_type + { + uint8_t pdu_type = data[n]; + + if ( (pdu_type != DCERPC_PDU_TYPE__BIND) and + (pdu_type != DCERPC_PDU_TYPE__BIND_ACK) ) + { + // go to bad state + dce.state = DCE_STATE__10; + + return false; + } + + dce.state = (DCE_State)((int)dce.state + 1); + break; + } + + case DCE_STATE__4: //little endian + dce.helper = (data[n] & 0x10) << 20; + dce.state = (DCE_State)((int)dce.state + 1); + break; + case DCE_STATE__8: + dce.helper |= data[n]; + dce.state = (DCE_State)((int)dce.state + 1); + break; + case DCE_STATE__9: +#ifdef WORDS_BIGENDIAN + if ( !(dce.helper >> 24) ) +#else + if ( dce.helper >> 24 ) +#endif /* WORDS_BIGENDIAN */ + dce.helper = (data[n] << 8) | (dce.helper & 0XFF); + else + { + dce.helper <<=8; + dce.helper |= data[n]; + } + + if ( dce.helper >= dce_rpc_co_hdr_len ) + return true; + + dce.state = DCE_STATE__10; + break; + + case DCE_STATE__10: + // no match + return false; + default: + dce.state = (DCE_State)((int)dce.state + 1); + break; + } + + n++; + } + + return false; +} + +bool CurseBook::dce_smb_curse(const uint8_t* data, unsigned len, CurseTracker* tracker) +{ + const uint32_t dce_smb_id = 0xff534d42; /* \xffSMB */ + const uint32_t dce_smb2_id = 0xfe534d42; /* \xfeSMB */ + const uint8_t session_request = 0x81, session_response = 0x82, session_message = 0x00; + DceTracker& dce = tracker->dce; + + uint32_t n = 0; + while ( n < len ) + { + switch ( dce.state ) + { + case DCE_STATE__0: + if ( data[n] == session_message ) + { + dce.state = (DCE_State)((int)dce.state + 2); + break; + } + + if ( data[n] == session_request or data[n] == session_response ) + { + dce.state = (DCE_State)((int)dce.state + 1); + + return false; + } + + dce.state = DCE_STATE__9; + + return false; + + case DCE_STATE__1: + if ( data[n] == session_message ) + { + dce.state = (DCE_State)((int)dce.state + 1); + break; + } + + dce.state = DCE_STATE__9; + + return false; + + case DCE_STATE__5: + dce.helper = data[n]; + dce.state = (DCE_State)((int)dce.state + 1); + break; + + case DCE_STATE__6: + case DCE_STATE__7: + dce.helper <<= 8; + dce.helper |= data[n]; + dce.state = (DCE_State)((int)dce.state + 1); + break; + + case DCE_STATE__8: + dce.helper <<= 8; + dce.helper |= data[n]; + + if ( (dce.helper == dce_smb_id) or (dce.helper == dce_smb2_id) ) + return true; + + dce.state = (DCE_State)((int)dce.state + 1); + break; + + case DCE_STATE__9: + // no match + return false; + + default: + dce.state = (DCE_State)((int)dce.state + 1); + break; + } + + n++; + } + + return false; +} diff --git a/src/service_inspectors/wizard/dce_curse.h b/src/service_inspectors/wizard/dce_curse.h new file mode 100644 index 000000000..a3ee3a950 --- /dev/null +++ b/src/service_inspectors/wizard/dce_curse.h @@ -0,0 +1,50 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2023-2023 Cisco and/or its affiliates. All rights reserved. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- +// dce_curse.h author Maya Dagon +// Refactored from curses.h + +#ifndef DCE_CURSE_H +#define DCE_CURSE_H + +// DCE curse helps determine if the traffic being processed is DCERPC + +#include + +enum DCE_State +{ + DCE_STATE__0 = 0, + DCE_STATE__1, + DCE_STATE__2, + DCE_STATE__3, + DCE_STATE__4, + DCE_STATE__5, + DCE_STATE__6, + DCE_STATE__7, + DCE_STATE__8, + DCE_STATE__9, + DCE_STATE__10 +}; + +class DceTracker +{ +public: + DCE_State state = DCE_State::DCE_STATE__0; + uint32_t helper; +}; + +#endif diff --git a/src/service_inspectors/wizard/mms_curse.cc b/src/service_inspectors/wizard/mms_curse.cc new file mode 100644 index 000000000..7bbaeb60f --- /dev/null +++ b/src/service_inspectors/wizard/mms_curse.cc @@ -0,0 +1,247 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2023-2023 Cisco and/or its affiliates. All rights reserved. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- +// mms_curses.cc author Jared Rittle +// Moved from curses.cc + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "mms_curse.h" +#include "curse_book.h" + +#include + +bool CurseBook::mms_curse(const uint8_t* data, unsigned len, CurseTracker* tracker) +{ + // peg the tracker to MMS + MmsTracker& mms = tracker->mms; + + // if the state is set to MMS_STATE__SEARCH it means we most likely + // have a split pipelined message coming through and will need to + // reset the state + if ( mms.state == MMS_STATE__SEARCH ) + { + mms.state = mms.last_state; + } + + // define all known MMS tags to check for + enum + { + MMS_CONFIRMED_REQUEST_TAG = 0xA0, + MMS_CONFIRMED_RESPONSE_TAG = 0xA1, + MMS_CONFIRMED_ERROR_TAG = 0xA2, + MMS_UNCONFIRMED_TAG = 0xA3, + MMS_REJECT_TAG = 0xA4, + MMS_CANCEL_REQUEST_TAG = 0x85, + MMS_CANCEL_RESPONSE_TAG = 0x86, + MMS_CANCEL_ERROR_TAG = 0xA7, + MMS_INITIATE_REQUEST_TAG = 0xA8, + MMS_INITIATE_RESPONSE_TAG = 0xA9, + MMS_INITIATE_ERROR_TAG = 0xAA, + MMS_CONCLUDE_REQUEST_TAG = 0x8B, + MMS_CONCLUDE_RESPONSE_TAG = 0x8C, + MMS_CONCLUDE_ERROR_TAG = 0xAD, + }; + + uint32_t idx = 0; + while ( idx < len ) + { + switch ( mms.state ) + { + case MMS_STATE__TPKT_VER: + { + mms.state = MMS_STATE__TPKT_RES; + break; + } + + case MMS_STATE__TPKT_RES: + { + mms.state = MMS_STATE__TPKT_LEN1; + break; + } + + case MMS_STATE__TPKT_LEN1: + { + mms.state = MMS_STATE__TPKT_LEN2; + break; + } + + case MMS_STATE__TPKT_LEN2: + { + mms.state = MMS_STATE__COTP_LEN; + break; + } + + case MMS_STATE__COTP_LEN: + { + mms.state = MMS_STATE__COTP_PDU; + break; + } + + case MMS_STATE__COTP_PDU: + { + // 7 6 5 4 3 2 1 0 + // --------------- + // . . . . x x x x Destination Reference + // x x x x . . . . PDU Type + const uint32_t MMS_COTP_PDU_DT_DATA = 0x0F; + + if ( data[idx] >> 0x04 != MMS_COTP_PDU_DT_DATA ) + { + mms.state = MMS_STATE__NOT_FOUND; + break; + } + + mms.state = MMS_STATE__COTP_TPDU_NUM; + break; + } + + case MMS_STATE__COTP_TPDU_NUM: + { + mms.state = MMS_STATE__OSI_SESSION_SPDU; + break; + } + + case MMS_STATE__OSI_SESSION_SPDU: + { + // define all known OSI Session layer SPDU tags to check + enum + { + MMS_OSI_SESSION_SPDU_GT_DT = 0x01, + MMS_OSI_SESSION_SPDU_CN = 0x0D, + MMS_OSI_SESSION_SPDU_AC = 0x0E, + }; + + switch ( data[idx] ) + { + // check for a known MMS message tag in the event Session/Pres/ACSE aren't used + case MMS_CONFIRMED_REQUEST_TAG: // fallthrough intentional + case MMS_CONFIRMED_RESPONSE_TAG: // fallthrough intentional + case MMS_CONFIRMED_ERROR_TAG: // fallthrough intentional + case MMS_UNCONFIRMED_TAG: // fallthrough intentional + case MMS_REJECT_TAG: // fallthrough intentional + case MMS_CANCEL_REQUEST_TAG: // fallthrough intentional + case MMS_CANCEL_RESPONSE_TAG: // fallthrough intentional + case MMS_CANCEL_ERROR_TAG: // fallthrough intentional + case MMS_INITIATE_REQUEST_TAG: // fallthrough intentional + case MMS_INITIATE_RESPONSE_TAG: // fallthrough intentional + case MMS_INITIATE_ERROR_TAG: // fallthrough intentional + case MMS_CONCLUDE_REQUEST_TAG: // fallthrough intentional + case MMS_CONCLUDE_RESPONSE_TAG: // fallthrough intentional + case MMS_CONCLUDE_ERROR_TAG: + { + // if an MMS tag exists in the remaining data, + // hand off to the MMS service inspector + mms.state = MMS_STATE__FOUND; + break; + } + + // if mms isn't found, search for an OSI Session layer + case MMS_OSI_SESSION_SPDU_GT_DT: // fallthrough intentional + case MMS_OSI_SESSION_SPDU_CN: // fallthrough intentional + case MMS_OSI_SESSION_SPDU_AC: + { + mms.state = MMS_STATE__MMS; + break; + } + + // if neither are found, it is most likely not MMS + default: + { + mms.state = MMS_STATE__NOT_FOUND; + } + } + + break; + } + + case MMS_STATE__MMS: + { + // loop through the remaining bytes in the buffer checking for known MMS tags + for ( uint32_t i=idx; i < len; i++ ) + { + // for each remaining byte check to see if it is in the known tag map + switch ( data[i] ) + { + case MMS_CONFIRMED_REQUEST_TAG: // fallthrough intentional + case MMS_CONFIRMED_RESPONSE_TAG: // fallthrough intentional + case MMS_CONFIRMED_ERROR_TAG: // fallthrough intentional + case MMS_UNCONFIRMED_TAG: // fallthrough intentional + case MMS_REJECT_TAG: // fallthrough intentional + case MMS_CANCEL_REQUEST_TAG: // fallthrough intentional + case MMS_CANCEL_RESPONSE_TAG: // fallthrough intentional + case MMS_CANCEL_ERROR_TAG: // fallthrough intentional + case MMS_INITIATE_REQUEST_TAG: // fallthrough intentional + case MMS_INITIATE_RESPONSE_TAG: // fallthrough intentional + case MMS_INITIATE_ERROR_TAG: // fallthrough intentional + case MMS_CONCLUDE_REQUEST_TAG: // fallthrough intentional + case MMS_CONCLUDE_RESPONSE_TAG: // fallthrough intentional + case MMS_CONCLUDE_ERROR_TAG: + { + // if an MMS tag exists in the remaining data, + // hand off to the MMS service inspector + mms.state = MMS_STATE__FOUND; + break; + } + // no default here as it we don't know when we would hit + // the first MMS tag without doing full parsing + } + + // exit the loop when a state has been determined + if ( mms.state == MMS_STATE__NOT_FOUND + or mms.state == MMS_STATE__SEARCH + or mms.state == MMS_STATE__FOUND ) + { + break; + } + } + + break; + } + + case MMS_STATE__FOUND: + { + mms.state = MMS_STATE__TPKT_VER; + + return true; + } + + case MMS_STATE__NOT_FOUND: + { + mms.state = MMS_STATE__TPKT_VER; + + return false; + } + + default: + { + mms.state = MMS_STATE__NOT_FOUND; + assert(false); + break; + } + } + + idx++; + } + + mms.last_state = mms.state; + mms.state = MMS_STATE__SEARCH; + + return false; +} diff --git a/src/service_inspectors/wizard/mms_curse.h b/src/service_inspectors/wizard/mms_curse.h index 61f56c210..09ff7d102 100644 --- a/src/service_inspectors/wizard/mms_curse.h +++ b/src/service_inspectors/wizard/mms_curse.h @@ -24,8 +24,6 @@ // conforms to the Manufacturing Message Specification (MMS) traffic defined // within the IEC-61850 family of protocols -#include "curses.h" - enum MMS_State { MMS_STATE__TPKT_VER = 0, @@ -42,5 +40,12 @@ enum MMS_State MMS_STATE__NOT_FOUND, }; +class MmsTracker +{ +public: + MMS_State state = MMS_State::MMS_STATE__TPKT_VER; + MMS_State last_state = MMS_State::MMS_STATE__TPKT_VER; +}; + #endif diff --git a/src/service_inspectors/wizard/s7commplus_curse.cc b/src/service_inspectors/wizard/s7commplus_curse.cc new file mode 100644 index 000000000..88ef696d1 --- /dev/null +++ b/src/service_inspectors/wizard/s7commplus_curse.cc @@ -0,0 +1,302 @@ +// Copyright (C) 2023-2023 Cisco and/or its affiliates. All rights reserved. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- +// s7commplus_curse.cc author Jared Rittle +// Moved from curses.cc + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include "s7commplus_curse.h" +#include "curse_book.h" + +enum S7commplus_Protocol_Identifier +{ + S7COMMPLUS_PROTOCOL_IDENTIFIER__S7COMM = 0x32, + S7COMMPLUS_PROTOCOL_IDENTIFIER__S7COMMPLUS = 0x72, +}; + +enum S7commplus_Pdu_Type +{ + S7COMMPLUS_PDU_TYPE__CONNECT = 0x01, + S7COMMPLUS_PDU_TYPE__DATA = 0x02, + S7COMMPLUS_PDU_TYPE__DATA2 = 0x03, + S7COMMPLUS_PDU_TYPE__KEEPALIVE = 0xFF, +}; + +enum S7commplus_Opcode +{ + S7COMMPLUS_OPCODE__REQ = 0x31, + S7COMMPLUS_OPCODE__RES = 0x32, + S7COMMPLUS_OPCODE__NOTIFICATION = 0x33, + S7COMMPLUS_OPCODE__RES2 = 0x02, +}; + +enum S7commplus_Function +{ + S7COMMPLUS_FUNCTION__EXPLORE = 0x04BB, + S7COMMPLUS_FUNCTION__CREATEOBJECT = 0x04CA, + S7COMMPLUS_FUNCTION__DELETEOBJECT = 0x04D4, + S7COMMPLUS_FUNCTION__SETVARIABLE = 0x04F2, + S7COMMPLUS_FUNCTION__GETLINK = 0x0524, + S7COMMPLUS_FUNCTION__SETMULTIVAR = 0x0542, + S7COMMPLUS_FUNCTION__GETMULTIVAR = 0x054C, + S7COMMPLUS_FUNCTION__BEGINSEQUENCE = 0x0556, + S7COMMPLUS_FUNCTION__ENDSEQUENCE = 0x0560, + S7COMMPLUS_FUNCTION__INVOKE = 0x056B, + S7COMMPLUS_FUNCTION__GETVARSUBSTR = 0x0586, +}; + +bool CurseBook::s7commplus_curse(const uint8_t* data, unsigned len, CurseTracker* tracker) +{ + // peg the tracker to s7commplus + S7commplusTracker& s7commplus = tracker->s7commplus; + + // if the state is set to S7COMMPLUS_STATE__SEARCH it means we most likely + // have a split pipelined message coming through and will need to + // reset the state + if ( s7commplus.state == S7COMMPLUS_STATE__SEARCH ) + { + s7commplus.state = s7commplus.last_state; + } + + uint32_t idx = 0; + while ( idx < len ) + { + switch ( s7commplus.state ) + { + case S7COMMPLUS_STATE__TPKT_VER: + { + s7commplus.state = S7COMMPLUS_STATE__TPKT_RES; + break; + } + + case S7COMMPLUS_STATE__TPKT_RES: + { + s7commplus.state = S7COMMPLUS_STATE__TPKT_LEN1; + break; + } + + case S7COMMPLUS_STATE__TPKT_LEN1: + { + s7commplus.state = S7COMMPLUS_STATE__TPKT_LEN2; + break; + } + + case S7COMMPLUS_STATE__TPKT_LEN2: + { + s7commplus.state = S7COMMPLUS_STATE__COTP_LEN; + break; + } + + case S7COMMPLUS_STATE__COTP_LEN: + { + s7commplus.state = S7COMMPLUS_STATE__COTP_PDU; + break; + } + + case S7COMMPLUS_STATE__COTP_PDU: + { + // 7 6 5 4 3 2 1 0 + // --------------- + // . . . . x x x x Destination Reference + // x x x x . . . . PDU Type + const uint32_t S7COMMPLUS_COTP_PDU_DT_DATA = 0x0F; + + if ( data[idx] >> 0x04 != S7COMMPLUS_COTP_PDU_DT_DATA ) + { + s7commplus.state = S7COMMPLUS_STATE__NOT_FOUND; + break; + } + + s7commplus.state = S7COMMPLUS_STATE__COTP_TPDU_NUM; + break; + } + + case S7COMMPLUS_STATE__COTP_TPDU_NUM: + { + s7commplus.state = S7COMMPLUS_STATE__PROTO_ID; + break; + } + + case S7COMMPLUS_STATE__PROTO_ID: + { + // there are two possible protocol identifiers - 0x32 and 0x72 + // 0x32 indicates the original s7comm protocol + // * the original protocol is not supported within the inspector + // so just catching and considering it a no match for now + // 0x72 indicates the s7commplus protocol + // * this is the protocol on which the existing inspector focuses + if ( data[idx] == S7COMMPLUS_PROTOCOL_IDENTIFIER__S7COMMPLUS ) + { + s7commplus.state = S7COMMPLUS_STATE__PDU_TYPE; + } + else + { + s7commplus.state = S7COMMPLUS_STATE__NOT_FOUND; + } + break; + } + + case S7COMMPLUS_STATE__PDU_TYPE: + { + switch ( data[idx] ) + { + case S7COMMPLUS_PDU_TYPE__CONNECT: // fallthrough intentional + case S7COMMPLUS_PDU_TYPE__DATA: // fallthrough intentional + case S7COMMPLUS_PDU_TYPE__DATA2: // fallthrough intentional + case S7COMMPLUS_PDU_TYPE__KEEPALIVE: + { + s7commplus.state = S7COMMPLUS_STATE__DATALENGTH_1; + break; + } + + default: + { + s7commplus.state = S7COMMPLUS_STATE__NOT_FOUND; + break; + } + } + + break; + } + + case S7COMMPLUS_STATE__DATALENGTH_1: + { + s7commplus.state = S7COMMPLUS_STATE__DATALENGTH_2; + break; + } + + case S7COMMPLUS_STATE__DATALENGTH_2: + { + s7commplus.state = S7COMMPLUS_STATE__OPCODE; + break; + } + + case S7COMMPLUS_STATE__OPCODE: + { + switch ( data[idx] ) + { + case S7COMMPLUS_OPCODE__REQ: // fallthrough intentional + case S7COMMPLUS_OPCODE__RES: // fallthrough intentional + case S7COMMPLUS_OPCODE__NOTIFICATION: // fallthrough intentional + case S7COMMPLUS_OPCODE__RES2: + { + s7commplus.state = S7COMMPLUS_STATE__RES_1; + break; + } + + default: + { + s7commplus.state = S7COMMPLUS_STATE__NOT_FOUND; + break; + } + } + + break; + } + + case S7COMMPLUS_STATE__RES_1: + { + s7commplus.state = S7COMMPLUS_STATE__RES_2; + break; + } + + case S7COMMPLUS_STATE__RES_2: + { + s7commplus.state = S7COMMPLUS_STATE__FUNCTION_1; + break; + } + + case S7COMMPLUS_STATE__FUNCTION_1: + { + // make sure the function code is zeroed out before building + s7commplus.func = 0; + + // get the high byte of the function code + s7commplus.func = data[idx] << 0x08; + + // move on to the low byte + s7commplus.state = S7COMMPLUS_STATE__FUNCTION_2; + break; + } + + case S7COMMPLUS_STATE__FUNCTION_2: + { + // get the low byte of the function code + s7commplus.func |= data[idx]; + + switch ( s7commplus.func ) + { + case S7COMMPLUS_FUNCTION__EXPLORE: // fallthrough intentional + case S7COMMPLUS_FUNCTION__CREATEOBJECT: // fallthrough intentional + case S7COMMPLUS_FUNCTION__DELETEOBJECT: // fallthrough intentional + case S7COMMPLUS_FUNCTION__SETVARIABLE: // fallthrough intentional + case S7COMMPLUS_FUNCTION__GETLINK: // fallthrough intentional + case S7COMMPLUS_FUNCTION__SETMULTIVAR: // fallthrough intentional + case S7COMMPLUS_FUNCTION__GETMULTIVAR: // fallthrough intentional + case S7COMMPLUS_FUNCTION__BEGINSEQUENCE: // fallthrough intentional + case S7COMMPLUS_FUNCTION__ENDSEQUENCE: // fallthrough intentional + case S7COMMPLUS_FUNCTION__INVOKE: // fallthrough intentional + case S7COMMPLUS_FUNCTION__GETVARSUBSTR: + { + s7commplus.state = S7COMMPLUS_STATE__FOUND; + break; + } + + default: + { + s7commplus.state = S7COMMPLUS_STATE__NOT_FOUND; + break; + } + } + + break; + } + + case S7COMMPLUS_STATE__FOUND: + { + s7commplus.state = S7COMMPLUS_STATE__TPKT_VER; + + return true; + } + + case S7COMMPLUS_STATE__NOT_FOUND: + { + s7commplus.state = S7COMMPLUS_STATE__TPKT_VER; + + return false; + } + + default: + { + s7commplus.state = S7COMMPLUS_STATE__NOT_FOUND; + assert(false); + break; + } + } + + idx++; + } + + s7commplus.last_state = s7commplus.state; + s7commplus.state = S7COMMPLUS_STATE__SEARCH; + + return false; +} diff --git a/src/service_inspectors/wizard/s7commplus_curse.h b/src/service_inspectors/wizard/s7commplus_curse.h index f9863d3d2..ed44490eb 100644 --- a/src/service_inspectors/wizard/s7commplus_curse.h +++ b/src/service_inspectors/wizard/s7commplus_curse.h @@ -23,48 +23,7 @@ // s7commplus_curse provides the ability to determine if the traffic being processed // conforms to the S7CommPlus protocol used in select Siemens devices -#include "curses.h" - -enum S7commplus_Protocol_Identifier -{ - S7COMMPLUS_PROTOCOL_IDENTIFIER__S7COMM = 0x32, - S7COMMPLUS_PROTOCOL_IDENTIFIER__S7COMMPLUS = 0x72, -}; - - -enum S7commplus_Pdu_Type -{ - S7COMMPLUS_PDU_TYPE__CONNECT = 0x01, - S7COMMPLUS_PDU_TYPE__DATA = 0x02, - S7COMMPLUS_PDU_TYPE__DATA2 = 0x03, - S7COMMPLUS_PDU_TYPE__KEEPALIVE = 0xFF, -}; - - -enum S7commplus_Opcode -{ - S7COMMPLUS_OPCODE__REQ = 0x31, - S7COMMPLUS_OPCODE__RES = 0x32, - S7COMMPLUS_OPCODE__NOTIFICATION = 0x33, - S7COMMPLUS_OPCODE__RES2 = 0x02, -}; - - -enum S7commplus_Function -{ - S7COMMPLUS_FUNCTION__EXPLORE = 0x04BB, - S7COMMPLUS_FUNCTION__CREATEOBJECT = 0x04CA, - S7COMMPLUS_FUNCTION__DELETEOBJECT = 0x04D4, - S7COMMPLUS_FUNCTION__SETVARIABLE = 0x04F2, - S7COMMPLUS_FUNCTION__GETLINK = 0x0524, - S7COMMPLUS_FUNCTION__SETMULTIVAR = 0x0542, - S7COMMPLUS_FUNCTION__GETMULTIVAR = 0x054C, - S7COMMPLUS_FUNCTION__BEGINSEQUENCE = 0x0556, - S7COMMPLUS_FUNCTION__ENDSEQUENCE = 0x0560, - S7COMMPLUS_FUNCTION__INVOKE = 0x056B, - S7COMMPLUS_FUNCTION__GETVARSUBSTR = 0x0586, -}; - +#include enum S7commplus_State { @@ -89,5 +48,13 @@ enum S7commplus_State S7COMMPLUS_STATE__NOT_FOUND, }; +class S7commplusTracker +{ +public: + S7commplus_State state = S7commplus_State::S7COMMPLUS_STATE__TPKT_VER; + S7commplus_State last_state = S7commplus_State::S7COMMPLUS_STATE__TPKT_VER; + uint16_t func = 0; +}; + #endif diff --git a/src/service_inspectors/wizard/ssl_curse.cc b/src/service_inspectors/wizard/ssl_curse.cc new file mode 100644 index 000000000..7c965706f --- /dev/null +++ b/src/service_inspectors/wizard/ssl_curse.cc @@ -0,0 +1,299 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2023-2023 Cisco and/or its affiliates. All rights reserved. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- +// ssl_curse.cc author Maya Dagon +// Refactored from curses.cc + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include "ssl_curse.h" +#include "curse_book.h" + +namespace SSL_Const +{ +static constexpr uint8_t hdr_len = 9; +static constexpr uint8_t sslv2_msb_set = 0x80; +static constexpr uint8_t client_hello = 0x01; +static constexpr uint8_t sslv3_major_ver = 0x03; +static constexpr uint8_t sslv3_max_minor_ver = 0x03; +} + +bool CurseBook::ssl_v2_curse(const uint8_t* data, unsigned len, CurseTracker* tracker) +{ + SslTracker& ssl = tracker->ssl; + + if ( ssl.state == SSL_STATE__SSL_NOT_FOUND ) + return false; + else if ( ssl.state == SSL_STATE__SSL_FOUND ) + return true; + + for ( unsigned i = 0; i < len; ++i ) + { + uint8_t val = data[i]; + + switch ( ssl.state ) + { + case SSL_STATE__BYTE_0_LEN_MSB: + if ( (val & SSL_Const::sslv2_msb_set) == 0 ) + { + ssl.state = SSL_STATE__SSL_NOT_FOUND; + + return false; + } + + ssl.total_len = (val & (~SSL_Const::sslv2_msb_set)) << 8; + ssl.state = SSL_STATE__BYTE_1_LEN_LSB; + break; + + case SSL_STATE__BYTE_1_LEN_LSB: + ssl.total_len |= val; + if ( ssl.total_len < SSL_Const::hdr_len ) + { + ssl.state = SSL_STATE__SSL_NOT_FOUND; + + return false; + } + + ssl.total_len -= SSL_Const::hdr_len; + ssl.state = SSL_STATE__BYTE_2_CLIENT_HELLO; + break; + + case SSL_STATE__BYTE_2_CLIENT_HELLO: + if ( val != SSL_Const::client_hello ) + { + ssl.state = SSL_STATE__SSL_NOT_FOUND; + + return false; + } + + ssl.state = SSL_STATE__BYTE_3_MAX_MINOR_VER; + break; + + case SSL_STATE__BYTE_3_MAX_MINOR_VER: + if ( val > SSL_Const::sslv3_max_minor_ver ) + { + ssl.state = SSL_STATE__SSL_NOT_FOUND; + + return false; + } + + ssl.state = SSL_STATE__BYTE_4_V3_MAJOR; + break; + + case SSL_STATE__BYTE_4_V3_MAJOR: + if ( val > SSL_Const::sslv3_major_ver ) + { + ssl.state = SSL_STATE__SSL_NOT_FOUND; + + return false; + } + + ssl.state = SSL_STATE__BYTE_5_SPECS_LEN_MSB; + break; + + case SSL_STATE__BYTE_5_SPECS_LEN_MSB: + ssl.specs_len = val << 8; + ssl.state = SSL_STATE__BYTE_6_SPECS_LEN_LSB; + break; + + case SSL_STATE__BYTE_6_SPECS_LEN_LSB: + ssl.specs_len |= val; + + if ( ssl.total_len < ssl.specs_len ) + { + ssl.state = SSL_STATE__SSL_NOT_FOUND; + + return false; + } + + ssl.total_len -= ssl.specs_len; + ssl.state = SSL_STATE__BYTE_7_SSNID_LEN_MSB; + break; + + case SSL_STATE__BYTE_7_SSNID_LEN_MSB: + ssl.ssnid_len = val << 8; + ssl.state = SSL_STATE__BYTE_8_SSNID_LEN_LSB; + break; + + case SSL_STATE__BYTE_8_SSNID_LEN_LSB: + ssl.ssnid_len |= val; + + if ( ssl.total_len < ssl.ssnid_len ) + { + ssl.state = SSL_STATE__SSL_NOT_FOUND; + + return false; + } + + ssl.total_len -= ssl.ssnid_len; + ssl.state = SSL_STATE__BYTE_9_CHLNG_LEN_MSB; + break; + + case SSL_STATE__BYTE_9_CHLNG_LEN_MSB: + ssl.chlng_len = val << 8; + ssl.state = SSL_STATE__BYTE_10_CHLNG_LEN_LSB; + break; + + case SSL_STATE__BYTE_10_CHLNG_LEN_LSB: + ssl.chlng_len |= val; + + if ( ssl.total_len < ssl.chlng_len ) + { + ssl.state = SSL_STATE__SSL_NOT_FOUND; + + return false; + } + + ssl.state = SSL_STATE__SSL_FOUND; + + return true; + + default: + return false; + } + } + + return false; +} + +#ifdef CATCH_TEST_BUILD + +#include "catch/catch.hpp" +#include + +// client hello with v2 header advertising sslv2 +static const uint8_t ssl_v2_ch[] = +{ + 0x80,0x59,0x01,0x00,0x02,0x00,0x30,0x00,0x00,0x00,0x20,0x00,0x00,0x39,0x00,0x00, + 0x38,0x00,0x00,0x35,0x00,0x00,0x16,0x00,0x00,0x13,0x00,0x00,0x0a,0x00,0x00,0x33, + 0x00,0x00,0x32,0x00,0x00,0x2f,0x00,0x00,0x07,0x00,0x00,0x05,0x00,0x00,0x04,0x00, + 0x00,0x15,0x00,0x00,0x12,0x00,0x00,0x09,0x00,0x00,0xff,0xda,0x86,0xfa,0xb4,0x73, + 0x5a,0x1e,0x11,0xd1,0xdb,0x58,0x4b,0x59,0xe1,0x07,0x51,0x5f,0x13,0x46,0xa2,0xdd, + 0xee,0xda,0xc1,0x9d,0xdc,0xd7,0xb8,0x86,0x51,0x10,0x5a +}; + +// client hello with v2 header advertising tls 1.0 +static const uint8_t ssl_v2_v3_ch[] = +{ + 0x80,0x59,0x01,0x03,0x01,0x00,0x30,0x00,0x00,0x00,0x20,0x00,0x00,0x39,0x00,0x00, + 0x38,0x00,0x00,0x35,0x00,0x00,0x16,0x00,0x00,0x13,0x00,0x00,0x0a,0x00,0x00,0x33, + 0x00,0x00,0x32,0x00,0x00,0x2f,0x00,0x00,0x07,0x00,0x00,0x05,0x00,0x00,0x04,0x00, + 0x00,0x15,0x00,0x00,0x12,0x00,0x00,0x09,0x00,0x00,0xff,0xda,0x86,0xfa,0xb4,0x73, + 0x5a,0x1e,0x11,0xd1,0xdb,0x58,0x4b,0x59,0xe1,0x07,0x51,0x5f,0x13,0x46,0xa2,0xdd, + 0xee,0xda,0xc1,0x9d,0xdc,0xd7,0xb8,0x86,0x51,0x10,0x5a +}; + +TEST_CASE("sslv2 detect", "[SslV2Curse]") +{ + uint32_t max_detect = static_cast(SSL_STATE__BYTE_10_CHLNG_LEN_LSB); + CurseTracker tracker{ }; + + auto test = [&](uint32_t incr_by,const uint8_t* ch) + { + uint32_t i = 0; + while ( i <= max_detect ) + { + if ( (i + incr_by - 1) < max_detect ) + { + CHECK(tracker.ssl.state == static_cast(i)); + CHECK_FALSE(CurseBook::ssl_v2_curse(&ch[i],sizeof(uint8_t) * incr_by,&tracker)); + } + else + { + CHECK(CurseBook::ssl_v2_curse(&ch[i],sizeof(uint8_t) * incr_by,&tracker)); + CHECK(tracker.ssl.state == SSL_STATE__SSL_FOUND); + } + + i += incr_by; + } + // subsequent checks must return found + CHECK(CurseBook::ssl_v2_curse(&ch[max_detect + 1],sizeof(uint8_t),&tracker)); + CHECK(tracker.ssl.state == SSL_STATE__SSL_FOUND); + }; + + // sslv2 with ssl version 2 + SECTION("1 byte v2"){ test(1,ssl_v2_ch); } + SECTION("2 bytes v2"){ test(2,ssl_v2_ch); } + SECTION("3 bytes v2"){ test(3,ssl_v2_ch); } + SECTION("4 bytes v2"){ test(4,ssl_v2_ch); } + SECTION("5 bytes v2"){ test(5,ssl_v2_ch); } + SECTION("6 bytes v2"){ test(6,ssl_v2_ch); } + SECTION("7 bytes v2"){ test(7,ssl_v2_ch); } + SECTION("8 bytes v2"){ test(8,ssl_v2_ch); } + SECTION("9 bytes v2"){ test(9,ssl_v2_ch); } + SECTION("10 bytes v2"){ test(10,ssl_v2_ch); } + SECTION("11 bytes v2"){ test(11,ssl_v2_ch);} + + // sslv2 with tls version 1.0 + SECTION("1 byte v2_v3"){ test(1,ssl_v2_v3_ch); } + SECTION("2 bytes v2_v3"){ test(2,ssl_v2_v3_ch); } + SECTION("3 bytes v2_v3"){ test(3,ssl_v2_v3_ch); } + SECTION("4 bytes v2_v3"){ test(4,ssl_v2_v3_ch); } + SECTION("5 bytes v2_v3"){ test(5,ssl_v2_v3_ch); } + SECTION("6 bytes v2_v3"){ test(6,ssl_v2_v3_ch); } + SECTION("7 bytes v2_v3"){ test(7,ssl_v2_v3_ch); } + SECTION("8 bytes v2_v3"){ test(8,ssl_v2_v3_ch); } + SECTION("9 bytes v2_v3"){ test(9,ssl_v2_v3_ch); } + SECTION("10 bytes v2_v3"){ test(10,ssl_v2_v3_ch); } + SECTION("11 bytes v2_v3"){ test(11,ssl_v2_v3_ch); } +} + +TEST_CASE("sslv2 not found", "[SslV2Curse]") +{ + uint32_t max_detect = static_cast(SSL_STATE__BYTE_10_CHLNG_LEN_LSB); + CurseTracker tracker{}; + uint8_t bad_data[] = {0x00,0x08,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff}; + auto test = [&](uint32_t fail_at_byte) + { + uint8_t ch_data[sizeof(ssl_v2_ch)]; + memcpy(ch_data,ssl_v2_ch,sizeof(ssl_v2_ch)); + + ch_data[fail_at_byte] = bad_data[fail_at_byte]; + + for ( uint32_t i = 0; i <= fail_at_byte; i++ ) + { + if ( i < fail_at_byte ) + { + CHECK(tracker.ssl.state == static_cast(i)); + CHECK_FALSE(CurseBook::ssl_v2_curse(&ch_data[i],sizeof(uint8_t),&tracker)); + } + else + { + CHECK_FALSE(CurseBook::ssl_v2_curse(&ch_data[i],sizeof(uint8_t),&tracker)); + CHECK(tracker.ssl.state == SSL_STATE__SSL_NOT_FOUND); + } + } + // subsequent checks must return ssl not found + CHECK_FALSE(CurseBook::ssl_v2_curse(&ch_data[max_detect + 1],sizeof(uint8_t),&tracker)); + CHECK(tracker.ssl.state == SSL_STATE__SSL_NOT_FOUND); + }; + + SECTION("byte 0"){ test(0);} + SECTION("byte 1"){ test(1);} + SECTION("byte 2"){ test(2);} + SECTION("byte 3"){ test(3);} + SECTION("byte 4"){ test(4);} + SECTION("byte 6"){ test(6);} + SECTION("byte 8"){ test(8);} + SECTION("byte 10"){ test(10);} +} + +#endif diff --git a/src/service_inspectors/wizard/ssl_curse.h b/src/service_inspectors/wizard/ssl_curse.h new file mode 100644 index 000000000..cb83a5eab --- /dev/null +++ b/src/service_inspectors/wizard/ssl_curse.h @@ -0,0 +1,53 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2023-2023 Cisco and/or its affiliates. All rights reserved. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License Version 2 as published +// by the Free Software Foundation. You may not use, modify or distribute +// this program under any other version of the GNU General Public License. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +//-------------------------------------------------------------------------- +// ssl_curse.h author Maya Dagon +// Refactored from curses.h + +#ifndef SSL_CURSE_H +#define SSL_CURSE_H + +// SSL curse helps determine if the traffic being processed is SSL + +enum SSL_State +{ + SSL_STATE__BYTE_0_LEN_MSB = 0, + SSL_STATE__BYTE_1_LEN_LSB, + SSL_STATE__BYTE_2_CLIENT_HELLO, + SSL_STATE__BYTE_3_MAX_MINOR_VER, + SSL_STATE__BYTE_4_V3_MAJOR, + SSL_STATE__BYTE_5_SPECS_LEN_MSB, + SSL_STATE__BYTE_6_SPECS_LEN_LSB, + SSL_STATE__BYTE_7_SSNID_LEN_MSB, + SSL_STATE__BYTE_8_SSNID_LEN_LSB, + SSL_STATE__BYTE_9_CHLNG_LEN_MSB, + SSL_STATE__BYTE_10_CHLNG_LEN_LSB, + SSL_STATE__SSL_FOUND, + SSL_STATE__SSL_NOT_FOUND +}; + +class SslTracker +{ +public: + SSL_State state = SSL_STATE__BYTE_0_LEN_MSB; + unsigned total_len; + unsigned ssnid_len; + unsigned specs_len; + unsigned chlng_len; +}; + +#endif diff --git a/src/service_inspectors/wizard/wiz_module.cc b/src/service_inspectors/wizard/wiz_module.cc index 4b329ab7e..25bbd8d5e 100644 --- a/src/service_inspectors/wizard/wiz_module.cc +++ b/src/service_inspectors/wizard/wiz_module.cc @@ -27,7 +27,7 @@ #include "log/messages.h" #include "trace/trace.h" -#include "curses.h" +#include "curse_book.h" using namespace snort; using namespace std; diff --git a/src/service_inspectors/wizard/wizard.cc b/src/service_inspectors/wizard/wizard.cc index 3f173919a..7919f622c 100644 --- a/src/service_inspectors/wizard/wizard.cc +++ b/src/service_inspectors/wizard/wizard.cc @@ -29,7 +29,7 @@ #include "stream/stream_splitter.h" #include "trace/trace_api.h" -#include "curses.h" +#include "curse_book.h" #include "magic.h" #include "wiz_module.h"