diff --git a/scripts/base/frameworks/dpd/dpd.sig b/scripts/base/frameworks/dpd/dpd.sig index 8408d7617a..f5d3651104 100644 --- a/scripts/base/frameworks/dpd/dpd.sig +++ b/scripts/base/frameworks/dpd/dpd.sig @@ -155,3 +155,34 @@ signature dpd_ssl_client { # payload /^..\x11\x29/ # enable "ayiya" #} + +signature dpd_socks_client { + ip-proto == tcp + # '32' is a rather arbitrary max length for the user name. + payload /^\x04[\x01\x02].{0,32}\x00/ + tcp-state originator +} + +signature dpd_socks_server { + ip-proto == tcp + requires-reverse-signature dpd_socks_client + payload /^\x00[\x5a\x5b\x5c\x5d]/ + tcp-state responder + enable "socks" +} + +signature dpd_socks_reverse_client { + ip-proto == tcp + # '32' is a rather arbitrary max length for the user name. + payload /^\x04[\x01\x02].{0,32}\x00/ + tcp-state responder +} + +signature dpd_socks_reverse_server { + ip-proto == tcp + requires-reverse-signature dpd_socks_client + payload /^\x00[\x5a\x5b\x5c\x5d]/ + tcp-state originator + enable "socks" +} + diff --git a/scripts/base/frameworks/tunnels/__load__.bro b/scripts/base/frameworks/tunnels/__load__.bro index d551be57d3..3def3511f5 100644 --- a/scripts/base/frameworks/tunnels/__load__.bro +++ b/scripts/base/frameworks/tunnels/__load__.bro @@ -1 +1,4 @@ -@load ./main \ No newline at end of file +@load ./main + +const ports = { 5072/udp } &redef; +redef dpd_config += { [ANALYZER_AYIYA] = [$ports = ports] }; diff --git a/scripts/base/frameworks/tunnels/main.bro b/scripts/base/frameworks/tunnels/main.bro index 901bee9a75..987939eb6e 100644 --- a/scripts/base/frameworks/tunnels/main.bro +++ b/scripts/base/frameworks/tunnels/main.bro @@ -1,8 +1,53 @@ module Tunnels; export { - + redef enum Log::ID += { LOG }; + + type Action: enum { + DISCOVER, + CLOSE, + }; + + type Info: record { + ts: time &log; + uid: string &log; + id: conn_id &log; + action: Action &log; + tunnel_type: string &log; + user: string &log &optional; + }; + + global register: function(c: connection, tunnel_type: string); + + global active: table[conn_id] of Tunnels::Info = table(); } -const ports = { 5072/udp } &redef; -redef dpd_config += { [ANALYZER_AYIYA] = [$ports = ports] }; +event bro_init() &priority=5 + { + Log::create_stream(Tunnels::LOG, [$columns=Info]); + } + +function register(c: connection, tunnel_type: string) + { + local tunnel: Info; + tunnel$ts = network_time(); + tunnel$uid = c$uid; + tunnel$id = c$id; + tunnel$action = DISCOVER; + tunnel$tunnel_type = tunnel_type; + + active[c$id] = tunnel; + Log::write(LOG, tunnel); + } + +event connection_state_remove(c: connection) &priority=-5 + { + if ( c$id in active ) + { + local tunnel = active[c$id]; + tunnel$action=CLOSE; + Log::write(LOG, tunnel); + + delete active[c$id]; + } + } \ No newline at end of file diff --git a/scripts/base/init-default.bro b/scripts/base/init-default.bro index ecaa19132c..91011738d1 100644 --- a/scripts/base/init-default.bro +++ b/scripts/base/init-default.bro @@ -37,6 +37,7 @@ @load base/protocols/http @load base/protocols/irc @load base/protocols/smtp +@load base/protocols/socks @load base/protocols/ssh @load base/protocols/ssl @load base/protocols/syslog diff --git a/scripts/base/protocols/socks/__load__.bro b/scripts/base/protocols/socks/__load__.bro new file mode 100644 index 0000000000..d551be57d3 --- /dev/null +++ b/scripts/base/protocols/socks/__load__.bro @@ -0,0 +1 @@ +@load ./main \ No newline at end of file diff --git a/scripts/base/protocols/socks/main.bro b/scripts/base/protocols/socks/main.bro new file mode 100644 index 0000000000..61f569d56c --- /dev/null +++ b/scripts/base/protocols/socks/main.bro @@ -0,0 +1,116 @@ +@load base/frameworks/tunnels + +module SOCKS; + +export { + type RequestType: enum { + CONNECTION = 1, + PORT = 2, + }; +} + +event socks_request(c: connection, request_type: count, dstaddr: addr, dstname: string, p: port, user: string) + { + Tunnels::register(c, "SOCKS"); + } + +# +#global output = open_log_file("socks"); +# +#type socks_conn: record { +# id: conn_id; +# t: time; +# req: socks_request_type &optional; +# dstaddr: addr &optional; +# dstname: string &optional; +# p: port &optional; +# user: string &optional; +# service: string &optional; +# variant: string &default = "SOCKS v4"; +# granted: string &default = "no-reply"; +#}; +# +# +#global conns: table[conn_id] of socks_conn; +#global proxies: set[addr] &read_expire = 24hrs; +# +#event socks_request(c: connection, t: socks_request_type, dstaddr: addr, dstname: string, p: port, user: string) +# { +# local id = c$id; +# +# local sc: socks_conn; +# sc$id = id; +# sc$t = c$start_time; +# sc$req = t; +# +# if ( dstaddr != 0.0.0.0 ) +# sc$dstaddr = dstaddr; +# +# if ( dstname != "" ) +# sc$dstname = dstname; +# +# if ( p != 0/tcp ) +# sc$p = p; +# +# if ( user != "" ) +# sc$user = user; +# +# conns[id] = sc; +# } +# +#event socks_reply(c: connection, granted: bool, dst: addr, p: port) +# { +# local id = c$id; +# local sc: socks_conn; +# +# if ( id in conns ) +# sc = conns[id]; +# else +# { +# sc$id = id; +# sc$t = c$start_time; +# conns[id] = sc; +# } +# +# sc$granted = granted ? "ok" : "denied"; +# +# local proxy = c$id$resp_h; +# +# if ( proxy !in proxies ) +# { +# NOTICE([$note=SOCKSProxy, $src=proxy, $sub=sc$variant, +# $msg=fmt("SOCKS proxy seen at %s (%s)", proxy, sc$variant)]); +# add proxies[proxy]; +# } +# } +# +#function print_conn(sc: socks_conn) +# { +# local req = ""; +# if ( sc?$req ) +# { +# if ( sc$req == SOCKS_CONNECTION ) +# req = "relay-to"; +# if ( sc$req == SOCKS_PORT ) +# req = "bind-port"; +# } +# +# local p = sc?$p ? fmt("%s", sc$p) : ""; +# +# local dest = sc?$dstaddr +# ? (fmt("%s:%s%s", sc$dstaddr, p, (sc?$dstname ? fmt(" (%s)", sc$dstname) : ""))) +# : (sc?$dstname ? fmt("%s:%s", sc$dstname, p) : ""); +# local user = sc?$user ? fmt(" (user %s)", sc?$user) : ""; +# +# local service = sc?$service ? fmt(" [%s]", sc$service) : ""; +# +# print output, fmt("%.6f %s %s %s %s-> %s%s", sc$t, id_string(sc$id), req, +# dest, user, sc$granted, service); +# } +# +#event connection_state_remove(c: connection) +# { +# if ( c$id in conns ) +# print_conn(conns[c$id]); +# } +# diff --git a/src/Analyzer.cc b/src/Analyzer.cc index 70bb5567cc..f731b36a70 100644 --- a/src/Analyzer.cc +++ b/src/Analyzer.cc @@ -34,6 +34,7 @@ #include "NFS.h" #include "Portmap.h" #include "POP3.h" +#include "SOCKS.h" #include "SSH.h" #include "SSL-binpac.h" #include "Syslog-binpac.h" @@ -134,6 +135,9 @@ const Analyzer::Config Analyzer::analyzer_configs[] = { { AnalyzerTag::AYIYA, "AYIYA", AYIYA_Analyzer::InstantiateAnalyzer, AYIYA_Analyzer::Available, 0, false }, + { AnalyzerTag::SOCKS, "SOCKS", + SOCKS_Analyzer::InstantiateAnalyzer, + SOCKS_Analyzer::Available, 0, false }, //{ AnalyzerTag::Teredo, "Teredo", // Teredo_Analyzer::InstantiateAnalyzer, // Teredo_Analyzer::Available, 0, false }, diff --git a/src/AnalyzerTags.h b/src/AnalyzerTags.h index 0f9794527e..1b65d5219e 100644 --- a/src/AnalyzerTags.h +++ b/src/AnalyzerTags.h @@ -36,6 +36,7 @@ namespace AnalyzerTag { // Decapsulation Analyzers //6to4, AYIYA, + SOCKS, //Teredo, // Other diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 6cca13de16..0481dd1bcd 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -208,6 +208,8 @@ binpac_target(netflow.pac netflow-protocol.pac netflow-analyzer.pac) binpac_target(smb.pac smb-protocol.pac smb-pipe.pac smb-mailslot.pac) +binpac_target(socks.pac + socks-protocol.pac socks-analyzer.pac) binpac_target(ssl.pac ssl-defs.pac ssl-protocol.pac ssl-analyzer.pac) binpac_target(syslog.pac @@ -379,6 +381,7 @@ set(bro_SRCS SmithWaterman.cc SMB.cc SMTP.cc + SOCKS.cc SSH.cc SSL-binpac.cc Scope.cc diff --git a/src/SOCKS.cc b/src/SOCKS.cc new file mode 100644 index 0000000000..880f4032e9 --- /dev/null +++ b/src/SOCKS.cc @@ -0,0 +1,79 @@ +#include "SOCKS.h" +#include "socks_pac.h" +#include "TCP_Reassembler.h" + +SOCKS_Analyzer::SOCKS_Analyzer(Connection* conn) +: TCP_ApplicationAnalyzer(AnalyzerTag::SOCKS, conn) + { + interp = new binpac::SOCKS::SOCKS_Conn(this); + orig_done = resp_done = false; + pia = 0; + } + +SOCKS_Analyzer::~SOCKS_Analyzer() + { + delete interp; + } + +void SOCKS_Analyzer::EndpointDone(bool orig) + { + if ( orig ) + orig_done = true; + else + resp_done = true; + } + +void SOCKS_Analyzer::Done() + { + TCP_ApplicationAnalyzer::Done(); + + interp->FlowEOF(true); + interp->FlowEOF(false); + } + +void SOCKS_Analyzer::EndpointEOF(TCP_Reassembler* endp) + { + TCP_ApplicationAnalyzer::EndpointEOF(endp); + interp->FlowEOF(endp->IsOrig()); + } + +void SOCKS_Analyzer::DeliverStream(int len, const u_char* data, bool orig) + { + TCP_ApplicationAnalyzer::DeliverStream(len, data, orig); + + assert(TCP()); + + if ( TCP()->IsPartial() ) + // punt on partial. + return; + + if ( orig_done && resp_done ) + { + // Finished decapsulating tunnel layer. Now do standard processing + // with the rest of the conneciton. + // + // Note that we assume that no payload data arrives before both endpoints + // are done with there part of the SOCKS protocol. + + if ( ! pia ) + { + pia = new PIA_TCP(Conn()); + AddChildAnalyzer(pia); + pia->FirstPacket(true, 0); + pia->FirstPacket(false, 0); + } + + ForwardStream(len, data, orig); + } + else + { + interp->NewData(orig, data, data + len); + } + } + +void SOCKS_Analyzer::Undelivered(int seq, int len, bool orig) + { + TCP_ApplicationAnalyzer::Undelivered(seq, len, orig); + interp->NewGap(orig, len); + } + diff --git a/src/SOCKS.h b/src/SOCKS.h new file mode 100644 index 0000000000..4e18e59e76 --- /dev/null +++ b/src/SOCKS.h @@ -0,0 +1,45 @@ +#ifndef socks_h +#define socks_h + +// SOCKS v4 analyzer. + +#include "TCP.h" +#include "PIA.h" + +namespace binpac { + namespace SOCKS { + class SOCKS_Conn; + } +} + + +class SOCKS_Analyzer : public TCP_ApplicationAnalyzer { +public: + SOCKS_Analyzer(Connection* conn); + ~SOCKS_Analyzer(); + + void EndpointDone(bool orig); + + virtual void Done(); + virtual void DeliverStream(int len, const u_char* data, bool orig); + virtual void Undelivered(int seq, int len, bool orig); + virtual void EndpointEOF(TCP_Reassembler* endp); + + static Analyzer* InstantiateAnalyzer(Connection* conn) + { return new SOCKS_Analyzer(conn); } + + static bool Available() + { + return socks_request || socks_reply; + } + +protected: + + bool orig_done; + bool resp_done; + + PIA_TCP *pia; + binpac::SOCKS::SOCKS_Conn* interp; +}; + +#endif diff --git a/src/event.bif b/src/event.bif index 1ce8907f0b..296a910478 100644 --- a/src/event.bif +++ b/src/event.bif @@ -5976,6 +5976,26 @@ event syslog_message%(c: connection, facility: count, severity: count, msg: stri ## to the event. event signature_match%(state: signature_state, msg: string, data: string%); +## Generated when a SOCKS request is analyzed. +## +## c: The parent connection of the proxy. +## +## t: The type of the request. +## +## dstaddr: Address that the tunneled traffic should be sent to. +## +## dstname: DNS name of the host that the tunneled traffic should be sent to. +## +## p: The destination port for the proxied traffic. +## +## user: Username given for the SOCKS connection. +event socks_request%(c: connection, request_type: count, dstaddr: addr, dstname: string, p: port, user: string%); + +## Generated when a SOCKS reply is analyzed. +## +## +event socks_reply%(c: connection, granted: bool, dst: addr, p: port%); + ## Generated when a protocol analyzer finds an identification of a software ## used on a system. This is a protocol-independent event that is fed by ## different analyzers. For example, the HTTP analyzer reports user-agent and diff --git a/src/socks-analyzer.pac b/src/socks-analyzer.pac new file mode 100644 index 0000000000..4c7b6e7a1d --- /dev/null +++ b/src/socks-analyzer.pac @@ -0,0 +1,57 @@ + +%header{ +StringVal* array_to_string(vector *a); +%} + +%code{ +StringVal* array_to_string(vector *a) + { + int len = a->size(); + char tmp[len]; + char *s = tmp; + for ( vector::iterator i = a->begin(); i != a->end(); *s++ = *i++ ); + + while ( len > 0 && tmp[len-1] == '\0' ) + --len; + + return new StringVal(len, tmp); + } +%} + +refine connection SOCKS_Conn += { + function socks_request(cmd: uint8, dstaddr: uint32, dstname: uint8[], p: uint16, user: uint8[]): bool + %{ + BifEvent::generate_socks_request(bro_analyzer(), + bro_analyzer()->Conn(), + cmd, + new AddrVal(htonl(dstaddr)), + array_to_string(dstname), + new PortVal(p | TCP_PORT_MASK), + array_to_string(user)); + + static_cast(bro_analyzer())->EndpointDone(true); + + return true; + %} + + function socks_reply(granted: bool, dst: uint32, p: uint16): bool + %{ + BifEvent::generate_socks_reply(bro_analyzer(), + bro_analyzer()->Conn(), + granted, + new AddrVal(htonl(dst)), + new PortVal(p | TCP_PORT_MASK)); + + bro_analyzer()->ProtocolConfirmation(); + static_cast(bro_analyzer())->EndpointDone(false); + return true; + %} +}; + +refine typeattr SOCKS_Request += &let { + proc: bool = $context.connection.socks_request(command, addr, empty, port, user); +}; + +refine typeattr SOCKS_Reply += &let { + proc: bool = $context.connection.socks_reply((status == 0x5a), addr, port); +}; diff --git a/src/socks-protocol.pac b/src/socks-protocol.pac new file mode 100644 index 0000000000..677daeb175 --- /dev/null +++ b/src/socks-protocol.pac @@ -0,0 +1,34 @@ +type SOCKS_Message(is_orig: bool) = case is_orig of { + true -> request: SOCKS_Request; + false -> reply: SOCKS_Reply; +}; + +type SOCKS_Request = record { + version: uint8; + command: uint8; + port: uint16; + addr: uint32; + user: uint8[] &until($element == 0); + + host: case v4a of { + true -> name: uint8[] &until($element == 0); # v4a + false -> empty: uint8[] &length=0; + } &requires(v4a); + + # FIXME: Can this be non-zero? If so we need to keep it for the + # next analyzer. + rest: bytestring &restofdata; +} &byteorder = bigendian &let { + v4a: bool = (addr <= 0x000000ff); +}; + +type SOCKS_Reply = record { + zero: uint8; + status: uint8; + port: uint16; + addr: uint32; + + # FIXME: Can this be non-zero? If so we need to keep it for the + # next analyzer. + rest: bytestring &restofdata; +} &byteorder = bigendian; \ No newline at end of file diff --git a/src/socks.pac b/src/socks.pac new file mode 100644 index 0000000000..4f16582690 --- /dev/null +++ b/src/socks.pac @@ -0,0 +1,24 @@ +%include binpac.pac +%include bro.pac + +%extern{ +#include "SOCKS.h" +%} + +analyzer SOCKS withcontext { + connection: SOCKS_Conn; + flow: SOCKS_Flow; +}; + +connection SOCKS_Conn(bro_analyzer: BroAnalyzer) { + upflow = SOCKS_Flow(true); + downflow = SOCKS_Flow(false); +}; + +%include socks-protocol.pac + +flow SOCKS_Flow(is_orig: bool) { + datagram = SOCKS_Message(is_orig) withcontext(connection, this); +}; + +%include socks-analyzer.pac \ No newline at end of file