From bcadb67731482b26e2b3c0b7103f1f51c1ca0de3 Mon Sep 17 00:00:00 2001 From: Seth Hall Date: Sat, 21 Apr 2012 14:42:20 -0400 Subject: [PATCH 1/4] First commit of binpac based AYIYA analyzer. - ayiya-analyzer.pac needs work to do something with the actual packet. - Lots more cleanup to do, but it parses the protocol at least. --- src/AYIYA.cc | 90 ++++++++++++++++++++++++++++++++++++++++++ src/AYIYA.h | 55 ++++++++++++++++++++++++++ src/Analyzer.cc | 11 ++++++ src/AnalyzerTags.h | 8 +++- src/CMakeLists.txt | 4 ++ src/ayiya-analyzer.pac | 25 ++++++++++++ src/ayiya-protocol.pac | 14 +++++++ src/ayiya.pac | 10 +++++ 8 files changed, 215 insertions(+), 2 deletions(-) create mode 100644 src/AYIYA.cc create mode 100644 src/AYIYA.h create mode 100644 src/ayiya-analyzer.pac create mode 100644 src/ayiya-protocol.pac create mode 100644 src/ayiya.pac diff --git a/src/AYIYA.cc b/src/AYIYA.cc new file mode 100644 index 0000000000..d69db642b3 --- /dev/null +++ b/src/AYIYA.cc @@ -0,0 +1,90 @@ +#include "AYIYA.h" +#include "TCP_Reassembler.h" + +AYIYA_Analyzer::AYIYA_Analyzer(Connection* conn) +: Analyzer(AnalyzerTag::SYSLOG_BINPAC, conn) + { + interp = new binpac::AYIYA::AYIYA_Conn(this); + did_session_done = 0; + //ADD_ANALYZER_TIMER(&AYIYA_Analyzer::ExpireTimer, + // network_time + Syslog_session_timeout, 1, TIMER_Syslog_EXPIRE); + } + +AYIYA_Analyzer::~AYIYA_Analyzer() + { + delete interp; + } + +void AYIYA_Analyzer::Done() + { + Analyzer::Done(); + + if ( ! did_session_done ) + Event(udp_session_done); + } + +void AYIYA_Analyzer::DeliverPacket(int len, const u_char* data, bool orig, int seq, const IP_Hdr* ip, int caplen) + { + Analyzer::DeliverPacket(len, data, orig, seq, ip, caplen); + interp->NewData(orig, data, data + len); + } + +//void AYIYA_Analyzer::ExpireTimer(double t) +// { +// // The - 1.0 in the following is to allow 1 second for the +// // common case of a single request followed by a single reply, +// // so we don't needlessly set the timer twice in that case. +// if ( t - Conn()->LastTime() >= Syslog_session_timeout - 1.0 || terminating ) +// { +// Event(connection_timeout); +// sessions->Remove(Conn()); +// } +// else +// ADD_ANALYZER_TIMER(&AYIYA_Analyzer::ExpireTimer, +// t + Syslog_session_timeout, 1, TIMER_Syslog_EXPIRE); +// } + +//Syslog_TCP_Analyzer_binpac::Syslog_TCP_Analyzer_binpac(Connection* conn) +//: TCP_ApplicationAnalyzer(AnalyzerTag::Syslog_TCP_BINPAC, conn) +// { +// interp = new binpac::Syslog_on_TCP::Syslog_TCP_Conn(this); +// } + +//Syslog_TCP_Analyzer_binpac::~Syslog_TCP_Analyzer_binpac() +// { +// delete interp; +// } + +//void Syslog_TCP_Analyzer_binpac::Done() +// { +// TCP_ApplicationAnalyzer::Done(); +// +// interp->FlowEOF(true); +// interp->FlowEOF(false); +// } + +//void Syslog_TCP_Analyzer_binpac::EndpointEOF(TCP_Reassembler* endp) +// { +// TCP_ApplicationAnalyzer::EndpointEOF(endp); +// interp->FlowEOF(endp->IsOrig()); +// } + +//void Syslog_TCP_Analyzer_binpac::DeliverStream(int len, const u_char* data, +// bool orig) +// { +// TCP_ApplicationAnalyzer::DeliverStream(len, data, orig); +// +// assert(TCP()); +// +// if ( TCP()->IsPartial() || TCP()->HadGap(orig) ) +// // punt-on-partial or stop-on-gap. +// return; +// +// interp->NewData(orig, data, data + len); +// } + +//void Syslog_TCP_Analyzer_binpac::Undelivered(int seq, int len, bool orig) +// { +// TCP_ApplicationAnalyzer::Undelivered(seq, len, orig); +// interp->NewGap(orig, len); +// } diff --git a/src/AYIYA.h b/src/AYIYA.h new file mode 100644 index 0000000000..294eeca1ea --- /dev/null +++ b/src/AYIYA.h @@ -0,0 +1,55 @@ +#ifndef AYIYA_h +#define AYIYA_h + +#include "UDP.h" +#include "TCP.h" + +#include "ayiya_pac.h" + +class AYIYA_Analyzer : public Analyzer { +public: + AYIYA_Analyzer(Connection* conn); + virtual ~AYIYA_Analyzer(); + + virtual void Done(); + virtual void DeliverPacket(int len, const u_char* data, bool orig, + int seq, const IP_Hdr* ip, int caplen); + + static Analyzer* InstantiateAnalyzer(Connection* conn) + { return new AYIYA_Analyzer(conn); } + + static bool Available() + { return true; } + +protected: + friend class AnalyzerTimer; + void ExpireTimer(double t); + + int did_session_done; + + binpac::AYIYA::AYIYA_Conn* interp; +}; + +// #include "Syslog_tcp_pac.h" +// +//class Syslog_TCP_Analyzer_binpac : public TCP_ApplicationAnalyzer { +//public: +// Syslog_TCP_Analyzer_binpac(Connection* conn); +// virtual ~Syslog_TCP_Analyzer_binpac(); +// +// 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 Syslog_TCP_Analyzer_binpac(conn); } +// +// static bool Available() +// { return (Syslog_request || Syslog_full_request) && FLAGS_use_binpac; } +// +//protected: +// binpac::Syslog_on_TCP::Syslog_TCP_Conn* interp; +//}; +// +#endif diff --git a/src/Analyzer.cc b/src/Analyzer.cc index 92ca3ecc50..70bb5567cc 100644 --- a/src/Analyzer.cc +++ b/src/Analyzer.cc @@ -4,6 +4,7 @@ #include "PIA.h" #include "Event.h" +#include "AYIYA.h" #include "BackDoor.h" #include "BitTorrent.h" #include "BitTorrentTracker.h" @@ -127,6 +128,16 @@ const Analyzer::Config Analyzer::analyzer_configs[] = { Syslog_Analyzer_binpac::InstantiateAnalyzer, Syslog_Analyzer_binpac::Available, 0, false }, + //{ AnalyzerTag::6to4, "6to4", + // 6to4_Analyzer::InstantiateAnalyzer, + // 6to4_Anylzer::Available, 0, false }, + { AnalyzerTag::AYIYA, "AYIYA", + AYIYA_Analyzer::InstantiateAnalyzer, + AYIYA_Analyzer::Available, 0, false }, + //{ AnalyzerTag::Teredo, "Teredo", + // Teredo_Analyzer::InstantiateAnalyzer, + // Teredo_Analyzer::Available, 0, false }, + { AnalyzerTag::File, "FILE", File_Analyzer::InstantiateAnalyzer, File_Analyzer::Available, 0, false }, { AnalyzerTag::Backdoor, "BACKDOOR", diff --git a/src/AnalyzerTags.h b/src/AnalyzerTags.h index dc10a55f22..0f9794527e 100644 --- a/src/AnalyzerTags.h +++ b/src/AnalyzerTags.h @@ -33,11 +33,15 @@ namespace AnalyzerTag { DHCP_BINPAC, DNS_TCP_BINPAC, DNS_UDP_BINPAC, HTTP_BINPAC, SSL, SYSLOG_BINPAC, + // Decapsulation Analyzers + //6to4, + AYIYA, + //Teredo, + // Other File, Backdoor, InterConn, SteppingStone, TCPStats, ConnSize, - - + // Support-analyzers Contents, ContentLine, NVT, Zip, Contents_DNS, Contents_NCP, Contents_NetbiosSSN, Contents_Rlogin, Contents_Rsh, diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a755fde64e..6cca13de16 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -186,6 +186,9 @@ endmacro(BINPAC_TARGET) binpac_target(binpac-lib.pac) binpac_target(binpac_bro-lib.pac) + +binpac_target(ayiya.pac + ayiya-protocol.pac ayiya-analyzer.pac) binpac_target(bittorrent.pac bittorrent-protocol.pac bittorrent-analyzer.pac) binpac_target(dce_rpc.pac @@ -277,6 +280,7 @@ set(bro_SRCS Anon.cc ARP.cc Attr.cc + AYIYA.cc BackDoor.cc Base64.cc BitTorrent.cc diff --git a/src/ayiya-analyzer.pac b/src/ayiya-analyzer.pac new file mode 100644 index 0000000000..888cc575a5 --- /dev/null +++ b/src/ayiya-analyzer.pac @@ -0,0 +1,25 @@ + +connection AYIYA_Conn(bro_analyzer: BroAnalyzer) + { + upflow = AYIYA_Flow; + downflow = AYIYA_Flow; + }; + +flow AYIYA_Flow + { + datagram = PDU withcontext(connection, this); + + function process_ayiya(pdu: PDU): bool + %{ + connection()->bro_analyzer()->ProtocolConfirmation(); + + // Not sure what to do here. + printf("packet: %s\n", ${pdu.packet}.data()); + return true; + %} + + }; + +refine typeattr PDU += &let { + proc_ayiya = $context.flow.process_ayiya(this); +}; diff --git a/src/ayiya-protocol.pac b/src/ayiya-protocol.pac new file mode 100644 index 0000000000..25aca23fb9 --- /dev/null +++ b/src/ayiya-protocol.pac @@ -0,0 +1,14 @@ + +type PDU = record { + identity_byte: uint8; + signature_byte: uint8; + auth_and_op_crap: uint8; + next_header: uint8; + epoch: uint32; + identity: bytestring &length=identity_len; + signature: bytestring &length=signature_len; + packet: bytestring &restofdata; +} &let { + identity_len = (1 << (identity_byte >> 4)); + signature_len = (signature_byte >> 4) * 4; +} &byteorder = littleendian; \ No newline at end of file diff --git a/src/ayiya.pac b/src/ayiya.pac new file mode 100644 index 0000000000..58fa196c15 --- /dev/null +++ b/src/ayiya.pac @@ -0,0 +1,10 @@ +%include binpac.pac +%include bro.pac + +analyzer AYIYA withcontext { + connection: AYIYA_Conn; + flow: AYIYA_Flow; +}; + +%include ayiya-protocol.pac +%include ayiya-analyzer.pac From 69ab13c88ff8598e29692d7fc166a56f7a807f05 Mon Sep 17 00:00:00 2001 From: Seth Hall Date: Sat, 21 Apr 2012 15:10:30 -0400 Subject: [PATCH 2/4] Added some scripts for a tunnels framework. - The AYIYA analyzer is now enabled on it's default port. --- scripts/base/frameworks/tunnels/__load__.bro | 1 + scripts/base/frameworks/tunnels/main.bro | 8 ++++++++ scripts/base/init-default.bro | 1 + 3 files changed, 10 insertions(+) create mode 100644 scripts/base/frameworks/tunnels/__load__.bro create mode 100644 scripts/base/frameworks/tunnels/main.bro diff --git a/scripts/base/frameworks/tunnels/__load__.bro b/scripts/base/frameworks/tunnels/__load__.bro new file mode 100644 index 0000000000..d551be57d3 --- /dev/null +++ b/scripts/base/frameworks/tunnels/__load__.bro @@ -0,0 +1 @@ +@load ./main \ No newline at end of file diff --git a/scripts/base/frameworks/tunnels/main.bro b/scripts/base/frameworks/tunnels/main.bro new file mode 100644 index 0000000000..901bee9a75 --- /dev/null +++ b/scripts/base/frameworks/tunnels/main.bro @@ -0,0 +1,8 @@ +module Tunnels; + +export { + +} + +const ports = { 5072/udp } &redef; +redef dpd_config += { [ANALYZER_AYIYA] = [$ports = ports] }; diff --git a/scripts/base/init-default.bro b/scripts/base/init-default.bro index 1cf125c3ab..ecaa19132c 100644 --- a/scripts/base/init-default.bro +++ b/scripts/base/init-default.bro @@ -29,6 +29,7 @@ @load base/frameworks/metrics @load base/frameworks/intel @load base/frameworks/reporter +@load base/frameworks/tunnels @load base/protocols/conn @load base/protocols/dns From dff3fabcea13d83caf68c985cac5ca8c049c657a Mon Sep 17 00:00:00 2001 From: Seth Hall Date: Sat, 21 Apr 2012 15:25:19 -0400 Subject: [PATCH 3/4] Added a DPD signature for AYIYA, but it's crashing Bro. --- scripts/base/frameworks/dpd/dpd.sig | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/scripts/base/frameworks/dpd/dpd.sig b/scripts/base/frameworks/dpd/dpd.sig index adda0ce54e..8408d7617a 100644 --- a/scripts/base/frameworks/dpd/dpd.sig +++ b/scripts/base/frameworks/dpd/dpd.sig @@ -149,3 +149,9 @@ signature dpd_ssl_client { payload /^(\x16\x03[\x00\x01\x02]..\x01...\x03[\x00\x01\x02]|...?\x01[\x00\x01\x02][\x02\x03]).*/ tcp-state originator } + +#signature dpd_ayiya { +# ip-proto = udp +# payload /^..\x11\x29/ +# enable "ayiya" +#} From e2da96941530a475e4573af1ddb96e23de05b658 Mon Sep 17 00:00:00 2001 From: Seth Hall Date: Sat, 21 Apr 2012 23:50:09 -0400 Subject: [PATCH 4/4] Return of Robin's old SOCKS analyzer/decapsulator and tunnel code checkpoint. - More discussion is needed to figure out how to integrate the SOCKS analyzer best. - Tunnels framework now logs for the SOCKS analyzer. --- scripts/base/frameworks/dpd/dpd.sig | 31 +++++ scripts/base/frameworks/tunnels/__load__.bro | 5 +- scripts/base/frameworks/tunnels/main.bro | 51 +++++++- scripts/base/init-default.bro | 1 + scripts/base/protocols/socks/__load__.bro | 1 + scripts/base/protocols/socks/main.bro | 116 +++++++++++++++++++ src/Analyzer.cc | 4 + src/AnalyzerTags.h | 1 + src/CMakeLists.txt | 3 + src/SOCKS.cc | 79 +++++++++++++ src/SOCKS.h | 45 +++++++ src/event.bif | 20 ++++ src/socks-analyzer.pac | 57 +++++++++ src/socks-protocol.pac | 34 ++++++ src/socks.pac | 24 ++++ 15 files changed, 468 insertions(+), 4 deletions(-) create mode 100644 scripts/base/protocols/socks/__load__.bro create mode 100644 scripts/base/protocols/socks/main.bro create mode 100644 src/SOCKS.cc create mode 100644 src/SOCKS.h create mode 100644 src/socks-analyzer.pac create mode 100644 src/socks-protocol.pac create mode 100644 src/socks.pac 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