diff --git a/scripts/base/protocols/ssh/README b/scripts/base/protocols/ssh/README deleted file mode 100644 index c3f68d543f..0000000000 --- a/scripts/base/protocols/ssh/README +++ /dev/null @@ -1 +0,0 @@ -Support for Secure Shell (SSH) protocol analysis. diff --git a/scripts/base/protocols/ssh/__load__.bro b/scripts/base/protocols/ssh/__load__.bro index 0f3cb011f8..9e43682d13 100644 --- a/scripts/base/protocols/ssh/__load__.bro +++ b/scripts/base/protocols/ssh/__load__.bro @@ -1,3 +1,3 @@ +# Generated by binpac_quickstart @load ./main - @load-sigs ./dpd.sig \ No newline at end of file diff --git a/scripts/base/protocols/ssh/main.bro b/scripts/base/protocols/ssh/main.bro index 33b0c84147..c3f90ec332 100644 --- a/scripts/base/protocols/ssh/main.bro +++ b/scripts/base/protocols/ssh/main.bro @@ -1,66 +1,31 @@ -##! Base SSH analysis script. The heuristic to blindly determine success or -##! failure for SSH connections is implemented here. At this time, it only -##! uses the size of the data being returned from the server to make the -##! heuristic determination about success of the connection. -##! Requires that :bro:id:`use_conn_size_analyzer` is set to T! The heuristic -##! is not attempted if the connection size analyzer isn't enabled. +##! Implements base functionality for SSH analysis. Generates the ssh.log file. -@load base/protocols/conn -@load base/frameworks/notice -@load base/utils/site -@load base/utils/thresholds -@load base/utils/conn-ids -@load base/utils/directions-and-hosts +# Generated by binpac_quickstart module SSH; export { - ## The SSH protocol logging stream identifier. redef enum Log::ID += { LOG }; type Info: record { - ## Time when the SSH connection began. - ts: time &log; + ## Timestamp for when the event happened. + ts: time &log; ## Unique ID for the connection. - uid: string &log; + uid: string &log; ## The connection's 4-tuple of endpoint addresses/ports. - id: conn_id &log; - ## Indicates if the login was heuristically guessed to be - ## "success", "failure", or "undetermined". - status: string &log &default="undetermined"; - ## Direction of the connection. If the client was a local host - ## logging into an external host, this would be OUTBOUND. INBOUND - ## would be set for the opposite situation. - # TODO: handle local-local and remote-remote better. - direction: Direction &log &optional; - ## Software string from the client. - client: string &log &optional; - ## Software string from the server. - server: string &log &optional; - ## Indicate if the SSH session is done being watched. - done: bool &default=F; + id: conn_id &log; + ## The client's version string + client: string &log &optional; + ## The server's version string + server: string &log &optional; + ## Auth result + result: string &log &optional; + ## Auth method + method: string &log &optional; }; - ## The size in bytes of data sent by the server at which the SSH - ## connection is presumed to be successful. - const authentication_data_size = 4000 &redef; - - ## If true, we tell the event engine to not look at further data - ## packets after the initial SSH handshake. Helps with performance - ## (especially with large file transfers) but precludes some - ## kinds of analyses. - const skip_processing_after_detection = F &redef; - - ## Event that is generated when the heuristic thinks that a login - ## was successful. - global heuristic_successful_login: event(c: connection); - - ## Event that is generated when the heuristic thinks that a login - ## failed. - global heuristic_failed_login: event(c: connection); - - ## Event that can be handled to access the :bro:type:`SSH::Info` - ## record as it is sent on to the logging framework. + ## Event that can be handled to access the SSH record as it is sent on + ## to the loggin framework. global log_ssh: event(rec: Info); } @@ -69,136 +34,55 @@ redef record connection += { }; const ports = { 22/tcp }; -redef likely_server_ports += { ports }; event bro_init() &priority=5 -{ + { Log::create_stream(SSH::LOG, [$columns=Info, $ev=log_ssh]); Analyzer::register_for_ports(Analyzer::ANALYZER_SSH, ports); -} - -function set_session(c: connection) - { - if ( ! c?$ssh ) - { - local info: Info; - info$ts=network_time(); - info$uid=c$uid; - info$id=c$id; - c$ssh = info; - } } -function check_ssh_connection(c: connection, done: bool) + +event ssh_version(c: connection, is_orig: bool, version: string) { - # If already done watching this connection, just return. - if ( c$ssh$done ) - return; - - if ( done ) + if ( !c?$ssh ) { - # If this connection is done, then we can look to see if - # this matches the conditions for a failed login. Failed - # logins are only detected at connection state removal. - - if ( # Require originators and responders to have sent at least 50 bytes. - c$orig$size > 50 && c$resp$size > 50 && - # Responders must be below 4000 bytes. - c$resp$size < authentication_data_size && - # Responder must have sent fewer than 40 packets. - c$resp$num_pkts < 40 && - # If there was a content gap we can't reliably do this heuristic. - c?$conn && c$conn$missed_bytes == 0 )# && - # Only "normal" connections can count. - #c$conn?$conn_state && c$conn$conn_state in valid_states ) - { - c$ssh$status = "failure"; - event SSH::heuristic_failed_login(c); - } - - if ( c$resp$size >= authentication_data_size ) - { - c$ssh$status = "success"; - event SSH::heuristic_successful_login(c); - } + local s: SSH::Info; + s$ts = network_time(); + s$uid = c$uid; + s$id = c$id; + c$ssh = s; } + if ( is_orig ) + c$ssh$client = version; else - { - # If this connection is still being tracked, then it's possible - # to watch for it to be a successful connection. - if ( c$resp$size >= authentication_data_size ) - { - c$ssh$status = "success"; - event SSH::heuristic_successful_login(c); - } - else - # This connection must be tracked longer. Let the scheduled - # check happen again. - return; - } - - # Set the direction for the log. - c$ssh$direction = Site::is_local_addr(c$id$orig_h) ? OUTBOUND : INBOUND; - - # Set the "done" flag to prevent the watching event from rescheduling - # after detection is done. - c$ssh$done=T; - - if ( skip_processing_after_detection ) - { - # Stop watching this connection, we don't care about it anymore. - skip_further_processing(c$id); - set_record_packets(c$id, F); - } + c$ssh$server = version; +# print c$ssh; } - -event heuristic_successful_login(c: connection) &priority=-5 +event ssh_auth_successful(c: connection, method: string) { - Log::write(SSH::LOG, c$ssh); - } - -event heuristic_failed_login(c: connection) &priority=-5 - { - Log::write(SSH::LOG, c$ssh); - } - -event connection_state_remove(c: connection) &priority=-5 - { - if ( c?$ssh ) - { - check_ssh_connection(c, T); - if ( c$ssh$status == "undetermined" ) - Log::write(SSH::LOG, c$ssh); - } - } - -event ssh_watcher(c: connection) - { - local id = c$id; - # don't go any further if this connection is gone already! - if ( ! connection_exists(id) ) + if ( !c?$ssh ) return; - - lookup_connection(c$id); - check_ssh_connection(c, F); - if ( ! c$ssh$done ) - schedule +15secs { ssh_watcher(c) }; + c$ssh$result = "success"; + c$ssh$method = method; + Log::write(SSH::LOG, c$ssh); } -event ssh_server_version(c: connection, version: string) &priority=5 +event ssh_auth_failed(c: connection, method: string) { - set_session(c); - c$ssh$server = version; + if ( !c?$ssh ) + return; + c$ssh$result = "failure"; + c$ssh$method = method; + Log::write(SSH::LOG, c$ssh); } -event ssh_client_version(c: connection, version: string) &priority=5 +event connection_closed(c: connection) { - set_session(c); - c$ssh$client = version; - - # The heuristic detection for SSH relies on the ConnSize analyzer. - # Don't do the heuristics if it's disabled. - if ( use_conn_size_analyzer ) - schedule +15secs { ssh_watcher(c) }; - } + if ( c?$ssh && !c$ssh?$result ) + { + c$ssh$result = "unknown"; + c$ssh$method = "unknown"; + Log::write(SSH::LOG, c$ssh); + } + } \ No newline at end of file diff --git a/src/analyzer/protocol/CMakeLists.txt b/src/analyzer/protocol/CMakeLists.txt index fc63aa4b66..59e33843ac 100644 --- a/src/analyzer/protocol/CMakeLists.txt +++ b/src/analyzer/protocol/CMakeLists.txt @@ -19,11 +19,11 @@ add_subdirectory(ident) add_subdirectory(interconn) add_subdirectory(irc) add_subdirectory(login) -add_subdirectory(modbus) add_subdirectory(mime) +add_subdirectory(modbus) add_subdirectory(ncp) -add_subdirectory(netflow) add_subdirectory(netbios) +add_subdirectory(netflow) add_subdirectory(ntp) add_subdirectory(pia) add_subdirectory(pop3) diff --git a/src/analyzer/protocol/ssh/CMakeLists.txt b/src/analyzer/protocol/ssh/CMakeLists.txt index 505c89332e..1266e4f496 100644 --- a/src/analyzer/protocol/ssh/CMakeLists.txt +++ b/src/analyzer/protocol/ssh/CMakeLists.txt @@ -1,9 +1,11 @@ +# Generated by binpac_quickstart include(BroPlugin) include_directories(BEFORE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}) bro_plugin_begin(Bro SSH) -bro_plugin_cc(SSH.cc Plugin.cc) -bro_plugin_bif(events.bif) -bro_plugin_end() + bro_plugin_cc(SSH.cc Plugin.cc) + bro_plugin_bif(events.bif) + bro_plugin_pac(ssh.pac ssh-analyzer.pac ssh-protocol.pac) +bro_plugin_end() \ No newline at end of file diff --git a/src/analyzer/protocol/ssh/Plugin.cc b/src/analyzer/protocol/ssh/Plugin.cc index 53a0294a88..ddb01964f5 100644 --- a/src/analyzer/protocol/ssh/Plugin.cc +++ b/src/analyzer/protocol/ssh/Plugin.cc @@ -1,10 +1,11 @@ +// Generated by binpac_quickstart #include "plugin/Plugin.h" #include "SSH.h" BRO_PLUGIN_BEGIN(Bro, SSH) - BRO_PLUGIN_DESCRIPTION("SSH analyzer"); - BRO_PLUGIN_ANALYZER("SSH", ssh::SSH_Analyzer); + BRO_PLUGIN_DESCRIPTION("Secure Shell analyzer"); + BRO_PLUGIN_ANALYZER("SSH", SSH::SSH_Analyzer); BRO_PLUGIN_BIF_FILE(events); -BRO_PLUGIN_END +BRO_PLUGIN_END \ No newline at end of file diff --git a/src/analyzer/protocol/ssh/SSH.cc b/src/analyzer/protocol/ssh/SSH.cc index ab3f6a5e5b..caeb5c4ca7 100644 --- a/src/analyzer/protocol/ssh/SSH.cc +++ b/src/analyzer/protocol/ssh/SSH.cc @@ -1,105 +1,135 @@ -// See the file "COPYING" in the main distribution directory for copyright. +// Generated by binpac_quickstart -#include "config.h" - -#include - -#include "NetVar.h" #include "SSH.h" -#include "Event.h" -#include "analyzer/protocol/tcp/ContentLine.h" + +#include "analyzer/protocol/tcp/TCP_Reassembler.h" + +#include "Reporter.h" #include "events.bif.h" -using namespace analyzer::ssh; +using namespace analyzer::SSH; SSH_Analyzer::SSH_Analyzer(Connection* c) + : tcp::TCP_ApplicationAnalyzer("SSH", c) - { - orig = new tcp::ContentLine_Analyzer(c, true); - orig->SetSkipPartial(true); - orig->SetCRLFAsEOL(LF_as_EOL); - AddSupportAnalyzer(orig); - resp = new tcp::ContentLine_Analyzer(c, false); - resp->SetSkipPartial(true); - resp->SetCRLFAsEOL(LF_as_EOL); - AddSupportAnalyzer(resp); + { + interp = new binpac::SSH::SSH_Conn(this); + had_gap = false; + num_encrypted_packets_seen = 0; + } + +SSH_Analyzer::~SSH_Analyzer() + { + delete interp; } -void SSH_Analyzer::DeliverStream(int length, const u_char* data, bool is_orig) +void SSH_Analyzer::Done() { - tcp::TCP_ApplicationAnalyzer::DeliverStream(length, data, is_orig); + + tcp::TCP_ApplicationAnalyzer::Done(); - // We're all done processing this endpoint - flag it as such, - // before we even determine whether we have any event generation - // work to do, to make sure we don't do any further work on it. - if ( is_orig ) - orig->SetSkipDeliveries(true); - else - resp->SetSkipDeliveries(true); + interp->FlowEOF(true); + interp->FlowEOF(false); + + } - if ( TCP() ) +void SSH_Analyzer::EndpointEOF(bool is_orig) + { + tcp::TCP_ApplicationAnalyzer::EndpointEOF(is_orig); + interp->FlowEOF(is_orig); + } + +void SSH_Analyzer::DeliverStream(int len, const u_char* data, bool orig) + { + tcp::TCP_ApplicationAnalyzer::DeliverStream(len, data, orig); + + assert(TCP()); + if ( TCP()->IsPartial() ) + return; + + if ( had_gap ) + // If only one side had a content gap, we could still try to + // deliver data to the other side if the script layer can handle this. + return; + + if ( num_encrypted_packets_seen || interp->get_state(orig) == binpac::SSH::ENCRYPTED ) { - // Don't try to parse version if there has already been a gap. - tcp::TCP_Endpoint* endp = is_orig ? TCP()->Orig() : TCP()->Resp(); - if ( endp->HadGap() ) - return; - } - - const char* line = (const char*) data; - - // The SSH identification looks like this: - // - // SSH-.-\n - // - // We're interested in the "version" part here. - - if ( length < 4 || memcmp(line, "SSH-", 4) != 0 ) - { - Weird("malformed_ssh_identification"); - ProtocolViolation("malformed ssh identification", line, length); + ProcessEncrypted(len, orig); return; } - int i; - for ( i = 4; i < length && line[i] != '-'; ++i ) - ; - - if ( TCP() ) + try { - if ( length >= i ) - { - IPAddr dst; - - if ( is_orig ) - dst = TCP()->Orig()->dst_addr; - else - dst = TCP()->Resp()->dst_addr; - - if ( Conn()->VersionFoundEvent(dst, line + i, - length - i) ) - ProtocolConfirmation(); - else - ProtocolViolation("malformed ssh version", - line, length); - } - else - { - Weird("malformed_ssh_version"); - ProtocolViolation("malformed ssh version", line, length); - } + interp->NewData(orig, data, data + len); + } + catch ( const binpac::Exception& e ) + { + printf(" **** %s\n", e.c_msg()); + ProtocolViolation(fmt("Binpac exception: %s", e.c_msg())); } - - // Generate SSH events. - EventHandlerPtr event = is_orig ? - ssh_client_version : ssh_server_version; - if ( ! event ) - return; - - val_list* vl = new val_list; - vl->append(BuildConnVal()); - vl->append(new StringVal(length, line)); - - ConnectionEvent(event, vl); + } + +void SSH_Analyzer::Undelivered(int seq, int len, bool orig) + { + tcp::TCP_ApplicationAnalyzer::Undelivered(seq, len, orig); + had_gap = true; + interp->NewGap(orig, len); + } + +void SSH_Analyzer::ProcessEncrypted(int len, bool orig) + { + if (!num_encrypted_packets_seen) + { + initial_encrypted_packet_size = len; + } + // printf("Encrypted packet of size %d from %s.\n", len, orig?"client":"server"); + int relative_len = len - initial_encrypted_packet_size; + if ( num_encrypted_packets_seen >= 2 ) + { + int auth_result = AuthResult(relative_len, orig); + if ( auth_result > 0 ) + { + StringVal* method = new StringVal(AuthMethod(relative_len, orig)); + if ( auth_result == 1 ) + BifEvent::generate_ssh_auth_successful(interp->bro_analyzer(), interp->bro_analyzer()->Conn(), method); + if ( auth_result == 2 ) + BifEvent::generate_ssh_auth_failed(interp->bro_analyzer(), interp->bro_analyzer()->Conn(), method); + } + packet_n_2_is_orig = packet_n_1_is_orig; + packet_n_2_size = packet_n_1_size; + } + packet_n_1_is_orig = orig; + packet_n_1_size = relative_len; + num_encrypted_packets_seen++; + } + + +int SSH_Analyzer::AuthResult(int len, bool orig) + { + if ( orig && !packet_n_1_is_orig && packet_n_2_is_orig ) + { + if ( len == -16 ) + return 1; + else if ( len >= 16 && + len <= 32 ) + return 2; + return 0; + } + return -1; + } + +const char* SSH_Analyzer::AuthMethod(int len, bool orig) + { + if ( packet_n_1_size == 96 ) // Password auth + return "keyboard-interactive"; + if ( packet_n_1_size == 32 ) // Challenge-response auth + return "challenge-response"; + if ( packet_n_2_size >= 112 && + packet_n_2_size <= 432 ) // Public key auth + return "pubkey"; + if ( packet_n_2_size == 16 ) // Host-based auth + return "host-based"; + return fmt("unknown auth method: n-1=%d n-2=%d", packet_n_1_size, packet_n_2_size); } diff --git a/src/analyzer/protocol/ssh/SSH.h b/src/analyzer/protocol/ssh/SSH.h index 3878881693..7d391e8d66 100644 --- a/src/analyzer/protocol/ssh/SSH.h +++ b/src/analyzer/protocol/ssh/SSH.h @@ -1,25 +1,62 @@ -// See the file "COPYING" in the main distribution directory for copyright. +// Generated by binpac_quickstart #ifndef ANALYZER_PROTOCOL_SSH_SSH_H #define ANALYZER_PROTOCOL_SSH_SSH_H +#include "events.bif.h" + + #include "analyzer/protocol/tcp/TCP.h" -#include "analyzer/protocol/tcp/ContentLine.h" -namespace analyzer { namespace ssh { +#include "ssh_pac.h" + +namespace analyzer { namespace SSH { + +class SSH_Analyzer + +: public tcp::TCP_ApplicationAnalyzer { -class SSH_Analyzer : public tcp::TCP_ApplicationAnalyzer { public: SSH_Analyzer(Connection* conn); + virtual ~SSH_Analyzer(); + // Overriden from Analyzer. + virtual void Done(); + virtual void DeliverStream(int len, const u_char* data, bool orig); + virtual void Undelivered(int seq, int len, bool orig); + // Overriden from tcp::TCP_ApplicationAnalyzer. + virtual void EndpointEOF(bool is_orig); + static analyzer::Analyzer* InstantiateAnalyzer(Connection* conn) { return new SSH_Analyzer(conn); } -private: - tcp::ContentLine_Analyzer* orig; - tcp::ContentLine_Analyzer* resp; + static bool Available() + { + // TODO: After you define your events, || them together here. + // See events.bif for more information + return ( ssh_event ); + } + +protected: + binpac::SSH::SSH_Conn* interp; + + void ProcessEncrypted(int len, bool orig); + int AuthResult(int len, bool orig); + const char* AuthMethod(int len, bool orig); + + bool had_gap; + + // Packet analysis stuff + int initial_encrypted_packet_size; + int num_encrypted_packets_seen; + + bool packet_n_1_is_orig; + int packet_n_1_size; + bool packet_n_2_is_orig; + int packet_n_2_size; + }; } } // namespace analyzer::* diff --git a/src/analyzer/protocol/ssh/events.bif b/src/analyzer/protocol/ssh/events.bif index 9d73f5e483..f1fa16919d 100644 --- a/src/analyzer/protocol/ssh/events.bif +++ b/src/analyzer/protocol/ssh/events.bif @@ -1,38 +1,17 @@ -## Generated when seeing an SSH client's version identification. The SSH -## protocol starts with a clear-text handshake message that reports client and -## server protocol/software versions. This event provides access to what the -## client sent. -## -## -## See `Wikipedia `__ for more -## information about the SSH protocol. -## -## c: The connection. -## -## version: The version string the client sent (e.g., `SSH-2.0-libssh-0.11`). -## -## .. bro:see:: ssh_server_version -## -## .. note:: As everything after the initial version handshake proceeds -## encrypted, Bro cannot further analyze SSH sessions. -event ssh_client_version%(c: connection, version: string%); +# Generated by binpac_quickstart -## Generated when seeing an SSH server's version identification. The SSH -## protocol starts with a clear-text handshake message that reports client and -## server protocol/software versions. This event provides access to what the -## server sent. -## -## See `Wikipedia `__ for more -## information about the SSH protocol. -## -## c: The connection. -## -## version: The version string the server sent (e.g., -## ``SSH-1.99-OpenSSH_3.9p1``). -## -## .. bro:see:: ssh_client_version -## -## .. note:: As everything coming after the initial version handshake proceeds -## encrypted, Bro cannot further analyze SSH sessions. -event ssh_server_version%(c: connection, version: string%); +# In this file, you'll define the events that your analyzer will generate. A sample event is included. +## Generated for SSH connections +## +## See `Google `__ for more information about SSH +## +## c: The connection +##3 +event ssh_event%(c: connection%); + +event ssh_version%(c: connection, is_orig: bool, version: string%); + +event ssh_auth_successful%(c: connection, method: string%); + +event ssh_auth_failed%(c: connection, method: string%); \ No newline at end of file diff --git a/src/analyzer/protocol/ssh/ssh-analyzer.pac b/src/analyzer/protocol/ssh/ssh-analyzer.pac new file mode 100644 index 0000000000..5cde754521 --- /dev/null +++ b/src/analyzer/protocol/ssh/ssh-analyzer.pac @@ -0,0 +1,25 @@ +# Generated by binpac_quickstart + +refine flow SSH_Flow += { + function proc_ssh_version(msg: SSH_Version): bool + %{ + BifEvent::generate_ssh_version(connection()->bro_analyzer(), connection()->bro_analyzer()->Conn(), ${msg.is_orig}, + bytestring_to_val(${msg.version})); + return true; + %} + + function proc_newkeys(): bool + %{ + connection()->bro_analyzer()->ProtocolConfirmation(); + return true; + %} + +}; + +refine typeattr SSH_Version += &let { + proc: bool = $context.flow.proc_ssh_version(this); +}; + +refine typeattr SSH_Message += &let { + proc_newkeys: bool = $context.flow.proc_newkeys() &if(msg_type == SSH_MSG_NEWKEYS); +}; diff --git a/src/analyzer/protocol/ssh/ssh-protocol.pac b/src/analyzer/protocol/ssh/ssh-protocol.pac new file mode 100644 index 0000000000..84b1bc1f6a --- /dev/null +++ b/src/analyzer/protocol/ssh/ssh-protocol.pac @@ -0,0 +1,175 @@ +enum state { + VERSION_EXCHANGE = 0, + KEY_EXCHANGE_CLEARTEXT = 1, + ENCRYPTED = 2, +}; + +enum message_id { + SSH_MSG_DISCONNECT = 1, + SSH_MSG_IGNORE = 2, + SSH_MSG_UNIMPLEMENTED = 3, + SSH_MSG_DEBUG = 4, + SSH_MSG_SERVICE_REQUEST = 5, + SSH_MSG_SERVICE_ACCEPT = 6, + SSH_MSG_KEXINIT = 20, + SSH_MSG_NEWKEYS = 21, + SSH_MSG_KEX_DH_GEX_REQUEST_OLD = 30, + SSH_MSG_KEX_DH_GEX_GROUP = 31, + SSH_MSG_KEX_DH_GEX_INIT = 32, + SSH_MSG_KEX_DH_GEX_REPLY = 33, + SSH_MSG_KEX_DH_GEX_REQUEST = 34, + SSH_MSG_USERAUTH_REQUEST = 50, + SSH_MSG_USERAUTH_FAILURE = 51, + SSH_MSG_USERAUTH_SUCCESS = 52, + SSH_MSG_USERAUTH_BANNER = 53, + SSH_MSG_GLOBAL_REQUEST = 80, + SSH_MSG_REQUEST_SUCCESS = 81, + SSH_MSG_REQUEST_FAILURE = 82, + SSH_MSG_CHANNEL_OPEN = 90, + SSH_MSG_CHANNEL_OPEN_CONFIRMATION = 91, + SSH_MSG_CHANNEL_OPEN_FAILURE = 92, + SSH_MSG_CHANNEL_WINDOW_ADJUST = 93, + SSH_MSG_CHANNEL_DATA = 94, + SSH_MSG_CHANNEL_EXTENDED_DATA = 95, + SSH_MSG_CHANNEL_EOF = 96, + SSH_MSG_CHANNEL_CLOSE = 97, + SSH_MSG_CHANNEL_REQUEST = 98, + SSH_MSG_CHANNEL_SUCCESS = 99, + SSH_MSG_CHANNEL_FAILURE = 100, +}; + +type SSH_PDU(is_orig: bool) = case $context.connection.get_state(is_orig) of { + VERSION_EXCHANGE -> version: SSH_Version(is_orig); + KEY_EXCHANGE_CLEARTEXT -> kex: SSH_Key_Exchange(is_orig); + ENCRYPTED -> unk: bytestring &length=100; +} &byteorder=bigendian; + +type SSH_Version(is_orig: bool) = record { + version: bytestring &oneline; +} &let { + update_state: bool = $context.connection.update_state(KEY_EXCHANGE_CLEARTEXT, is_orig); +}; + +type SSH_Key_Exchange_Header(is_orig: bool) = record { + packet_length: uint32; + padding_length: uint8; +} &length=5; + +type SSH_Key_Exchange(is_orig: bool) = record { + header : SSH_Key_Exchange_Header(is_orig); + payload: SSH_Payload(is_orig, header.packet_length - header.padding_length - 2); + pad : bytestring &length=header.padding_length; +}; + +type SSH_Payload_Header(length: uint32) = record { + message_type: uint8; +} &length=1; + +type SSH_Payload(is_orig: bool, packet_length: uint32) = record { + header: SSH_Payload_Header(packet_length); + message: SSH_Message(is_orig, header.message_type, packet_length); +}; + +type SSH_Message(is_orig: bool, msg_type: uint8, packet_length: uint32) = case msg_type of { + SSH_MSG_KEXINIT -> kexinit: SSH_KEXINIT(is_orig, packet_length); + SSH_MSG_KEX_DH_GEX_REQUEST -> dh_gex_request: SSH_DH_GEX_REQUEST(is_orig, packet_length); + SSH_MSG_KEX_DH_GEX_GROUP -> dh_gex_group: SSH_DH_GEX_GROUP(is_orig, packet_length); + SSH_MSG_KEX_DH_GEX_INIT -> dh_gex_init: SSH_DH_GEX_INIT(is_orig, packet_length); + SSH_MSG_KEX_DH_GEX_REPLY -> dh_gex_reply: SSH_DH_GEX_REPLY(is_orig, packet_length); + default -> unknown: bytestring &length=packet_length; +} &let { + detach: bool = $context.connection.update_state(ENCRYPTED, is_orig) &if(msg_type == SSH_MSG_NEWKEYS); +}; + +type SSH_KEXINIT(is_orig: bool, length: uint32) = record { + cookie : bytestring &length=16; + kex_algorithms_len : uint32; + kex_algorithms : bytestring &length=kex_algorithms_len; + server_host_key_algorithms_len : uint32; + server_host_key_algorithms : bytestring &length=server_host_key_algorithms_len; + encryption_algorithms_client_to_server_len : uint32; + encryption_algorithms_client_to_server : bytestring &length=encryption_algorithms_client_to_server_len; + encryption_algorithms_server_to_client_len : uint32; + encryption_algorithms_server_to_client : bytestring &length=encryption_algorithms_server_to_client_len; + mac_algorithms_client_to_server_len : uint32; + mac_algorithms_client_to_server : bytestring &length=mac_algorithms_client_to_server_len; + mac_algorithms_server_to_client_len : uint32; + mac_algorithms_server_to_client : bytestring &length=mac_algorithms_server_to_client_len; + compression_algorithms_client_to_server_len : uint32; + compression_algorithms_client_to_server : bytestring &length=compression_algorithms_client_to_server_len; + compression_algorithms_server_to_client_len : uint32; + compression_algorithms_server_to_client : bytestring &length=compression_algorithms_server_to_client_len; + languages_client_to_server_len : uint32; + languages_client_to_server : bytestring &length=languages_client_to_server_len; + languages_server_to_client_len : uint32; + languages_server_to_client : bytestring &length=languages_server_to_client_len; + first_kex_packet_follows : uint8; + reserved : uint32; +} &length=length; + +type SSH_DH_GEX_REQUEST(is_orig: bool, length: uint32) = record { + min: uint32; + n : uint32; + max: uint32; +} &length=12; + +type SSH_DH_GEX_GROUP(is_orig: bool, length: uint32) = record { + p: mpint; + g: mpint; +} &length=length; + +type SSH_DH_GEX_INIT(is_orig: bool, length: uint32) = record { + e: mpint; +} &length=length; + +type SSH_DH_GEX_REPLY(is_orig: bool, length: uint32) = record { + k_s : ssh_string; + f : mpint; + signature: ssh_string; +} &length=length; + +#type SSH_NEWKEYS(is_orig: bool, length: uint32) = record { +# blah: ; +#} &let { +# detach: bool = $context.connection.detach(); +#} &length=0; + +type mpint = record { + len: uint32; + val: bytestring &length=len; +}; + +type ssh_string = record { + len: uint32; + val: bytestring &length=len; +}; + +refine connection SSH_Conn += { + %member{ + int state_up_; + int state_down_; + %} + + %init{ + state_up_ = VERSION_EXCHANGE; + state_down_ = VERSION_EXCHANGE; + %} + + function get_state(is_orig: bool): int + %{ + if ( is_orig ) + return state_up_; + else + return state_down_; + %} + + function update_state(s: state, is_orig: bool): bool + %{ + if ( is_orig ) + state_up_ = s; + else + state_down_ = s; + return true; + %} + +}; \ No newline at end of file diff --git a/src/analyzer/protocol/ssh/ssh.pac b/src/analyzer/protocol/ssh/ssh.pac new file mode 100644 index 0000000000..b3181c4fa1 --- /dev/null +++ b/src/analyzer/protocol/ssh/ssh.pac @@ -0,0 +1,32 @@ +# Generated by binpac_quickstart + +# Analyzer for Secure Shell +# - ssh-protocol.pac: describes the SSH protocol messages +# - ssh-analyzer.pac: describes the SSH analyzer code + +%include binpac.pac +%include bro.pac + +%extern{ + #include "events.bif.h" +%} + +analyzer SSH withcontext { + connection: SSH_Conn; + flow: SSH_Flow; +}; + +# Our connection consists of two flows, one in each direction. +connection SSH_Conn(bro_analyzer: BroAnalyzer) { + upflow = SSH_Flow(true); + downflow = SSH_Flow(false); +}; + +%include ssh-protocol.pac + +# Now we define the flow: +flow SSH_Flow(is_orig: bool) { + flowunit = SSH_PDU(is_orig) withcontext(connection, this); +}; + +%include ssh-analyzer.pac \ No newline at end of file