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.
This commit is contained in:
Seth Hall 2012-04-21 23:50:09 -04:00
parent dff3fabcea
commit e2da969415
15 changed files with 468 additions and 4 deletions

View file

@ -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"
}

View file

@ -1 +1,4 @@
@load ./main
const ports = { 5072/udp } &redef;
redef dpd_config += { [ANALYZER_AYIYA] = [$ports = ports] };

View file

@ -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];
}
}

View file

@ -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

View file

@ -0,0 +1 @@
@load ./main

View file

@ -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 = "<unknown-type>";
# 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) : "<no-port>";
#
# 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) : "<no-dest>");
# 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]);
# }
#

View file

@ -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 },

View file

@ -36,6 +36,7 @@ namespace AnalyzerTag {
// Decapsulation Analyzers
//6to4,
AYIYA,
SOCKS,
//Teredo,
// Other

View file

@ -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

79
src/SOCKS.cc Normal file
View file

@ -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);
}

45
src/SOCKS.h Normal file
View file

@ -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

View file

@ -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

57
src/socks-analyzer.pac Normal file
View file

@ -0,0 +1,57 @@
%header{
StringVal* array_to_string(vector<uint8> *a);
%}
%code{
StringVal* array_to_string(vector<uint8> *a)
{
int len = a->size();
char tmp[len];
char *s = tmp;
for ( vector<uint8>::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<SOCKS_Analyzer*>(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<SOCKS_Analyzer*>(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);
};

34
src/socks-protocol.pac Normal file
View file

@ -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;

24
src/socks.pac Normal file
View file

@ -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