Merge remote-tracking branch 'origin/topic/seth/tunnels-merge'

* origin/topic/seth/tunnels-merge:
  SOCKS DPD fixes.
  Fix a bug in the SOCKS analyzer.
  SOCKS and tunnel test updates.
  Updates for the SOCKS analyzer.
  Very small updates to the tunnels framework.
This commit is contained in:
Robin Sommer 2012-06-20 14:30:40 -07:00
commit 6cda00c75e
30 changed files with 524 additions and 127 deletions

View file

@ -6226,7 +6226,9 @@ event signature_match%(state: signature_state, msg: string, data: string%);
##
## c: The parent connection of the proxy.
##
## t: The type of the request.
## version: The version of SOCKS this message used.
##
## request_type: The type of the request.
##
## dstaddr: Address that the tunneled traffic should be sent to.
##
@ -6234,13 +6236,23 @@ event signature_match%(state: signature_state, msg: string, data: string%);
##
## 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%);
## user: Username given for the SOCKS connection. This is not yet implemented for SOCKSv5.
event socks_request%(c: connection, version: count, request_type: count, dstaddr: addr, dstname: string, p: port, user: string%);
## Generated when a SOCKS reply is analyzed.
##
## c: The parent connection of the proxy.
##
event socks_reply%(c: connection, granted: bool, dst: addr, p: port%);
## version: The version of SOCKS this message used.
##
## reply: The status reply from the server.
##
## dstaddr: The address that the server sent the traffic to.
##
## dstname: The name the server sent the traffic to. Only applicable for SOCKSv5.
##
## p: The destination port for the proxied traffic.
event socks_reply%(c: connection, version: count, reply: count, dstaddr: addr, dstname: string, 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

View file

@ -19,39 +19,169 @@ StringVal* array_to_string(vector<uint8> *a)
%}
refine connection SOCKS_Conn += {
function socks_request(cmd: uint8, dstaddr: uint32, dstname: uint8[], p: uint16, user: uint8[]): bool
function socks4_request(request: SOCKS4_Request): bool
%{
StringVal *dstname = 0;
if ( ${request.v4a} )
dstname = array_to_string(${request.name});
else
dstname = new StringVal("");
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));
4,
${request.command},
new AddrVal(htonl(${request.addr})),
dstname,
new PortVal(${request.port} | TCP_PORT_MASK),
array_to_string(${request.user}));
static_cast<SOCKS_Analyzer*>(bro_analyzer())->EndpointDone(true);
return true;
%}
function socks_reply(granted: bool, dst: uint32, p: uint16): bool
function socks4_reply(reply: SOCKS4_Reply): bool
%{
BifEvent::generate_socks_reply(bro_analyzer(),
bro_analyzer()->Conn(),
granted,
new AddrVal(htonl(dst)),
new PortVal(p | TCP_PORT_MASK));
4,
${reply.status},
new AddrVal(htonl(${reply.addr})),
new StringVal(""),
new PortVal(${reply.port} | TCP_PORT_MASK));
bro_analyzer()->ProtocolConfirmation();
static_cast<SOCKS_Analyzer*>(bro_analyzer())->EndpointDone(false);
return true;
%}
function socks5_request(request: SOCKS5_Request): bool
%{
if ( ${request.reserved} != 0 )
{
bro_analyzer()->ProtocolViolation(fmt("invalid value in reserved field: %d", ${request.reserved}));
return false;
}
AddrVal *ip_addr = 0;
StringVal *domain_name = 0;
// This is dumb and there must be a better way (checking for presence of a field)...
switch ( ${request.remote_name.addr_type} )
{
case 1:
ip_addr = new AddrVal(htonl(${request.remote_name.ipv4}));
break;
case 3:
domain_name = new StringVal(${request.remote_name.domain_name.name}.length(),
(const char*) ${request.remote_name.domain_name.name}.data());
break;
case 4:
ip_addr = new AddrVal(IPAddr(IPv6, (const uint32_t*) ${request.remote_name.ipv6}, IPAddr::Network));
break;
default:
bro_analyzer()->ProtocolViolation(fmt("invalid SOCKSv5 addr type: %d", ${request.remote_name.addr_type}));
return false;
break;
}
if ( ! ip_addr )
ip_addr = new AddrVal(uint32(0));
if ( ! domain_name )
domain_name = new StringVal("");
BifEvent::generate_socks_request(bro_analyzer(),
bro_analyzer()->Conn(),
5,
${request.command},
ip_addr,
domain_name,
new PortVal(${request.port} | TCP_PORT_MASK),
new StringVal(""));
static_cast<SOCKS_Analyzer*>(bro_analyzer())->EndpointDone(true);
return true;
%}
function socks5_reply(reply: SOCKS5_Reply): bool
%{
AddrVal *ip_addr = 0;
StringVal *domain_name = 0;
// This is dumb and there must be a better way (checking for presence of a field)...
switch ( ${reply.bound.addr_type} )
{
case 1:
ip_addr = new AddrVal(htonl(${reply.bound.ipv4}));
break;
case 3:
domain_name = new StringVal(${reply.bound.domain_name.name}.length(),
(const char*) ${reply.bound.domain_name.name}.data());
break;
case 4:
ip_addr = new AddrVal(IPAddr(IPv6, (const uint32_t*) ${reply.bound.ipv6}, IPAddr::Network));
break;
default:
bro_analyzer()->ProtocolViolation(fmt("invalid SOCKSv5 addr type: %d", ${reply.bound.addr_type}));
return false;
break;
}
if ( ! ip_addr )
ip_addr = new AddrVal(uint32(0));
if ( ! domain_name )
domain_name = new StringVal("");
BifEvent::generate_socks_reply(bro_analyzer(),
bro_analyzer()->Conn(),
5,
${reply.reply},
ip_addr,
domain_name,
new PortVal(${reply.port} | TCP_PORT_MASK));
bro_analyzer()->ProtocolConfirmation();
static_cast<SOCKS_Analyzer*>(bro_analyzer())->EndpointDone(false);
return true;
%}
function version_error(version: uint8): bool
%{
bro_analyzer()->ProtocolViolation(fmt("unsupported/unknown SOCKS version %d", version));
return true;
%}
};
refine typeattr SOCKS_Request += &let {
proc: bool = $context.connection.socks_request(command, addr, empty, port, user);
refine typeattr SOCKS_Version_Error += &let {
proc: bool = $context.connection.version_error(version);
};
refine typeattr SOCKS_Reply += &let {
proc: bool = $context.connection.socks_reply((status == 0x5a), addr, port);
refine typeattr SOCKS4_Request += &let {
proc: bool = $context.connection.socks4_request(this);
};
refine typeattr SOCKS4_Reply += &let {
proc: bool = $context.connection.socks4_reply(this);
};
refine typeattr SOCKS5_Request += &let {
proc: bool = $context.connection.socks5_request(this);
};
refine typeattr SOCKS5_Reply += &let {
proc: bool = $context.connection.socks5_reply(this);
};

View file

@ -1,34 +1,119 @@
type SOCKS_Message(is_orig: bool) = case is_orig of {
true -> request: SOCKS_Request;
false -> reply: SOCKS_Reply;
type SOCKS_Version(is_orig: bool) = record {
version: uint8;
msg: case version of {
4 -> socks4_msg: SOCKS4_Message(is_orig);
5 -> socks5_msg: SOCKS5_Message(is_orig);
default -> socks_msg_fail: SOCKS_Version_Error(version);
};
};
type SOCKS_Request = record {
version: uint8;
type SOCKS_Version_Error(version: uint8) = record {
nothing: empty;
};
# SOCKS5 Implementation
type SOCKS5_Message(is_orig: bool) = case $context.connection.v5_past_authentication() of {
true -> msg: SOCKS5_Real_Message(is_orig);
false -> auth: SOCKS5_Auth_Negotiation(is_orig);
};
type SOCKS5_Auth_Negotiation(is_orig: bool) = case is_orig of {
true -> req: SOCKS5_Auth_Negotiation_Request;
false -> rep: SOCKS5_Auth_Negotiation_Reply;
};
type SOCKS5_Auth_Negotiation_Request = record {
method_count: uint8;
methods: uint8[method_count];
};
type SOCKS5_Auth_Negotiation_Reply = record {
selected_auth_method: uint8;
} &let {
past_auth = $context.connection.set_v5_past_authentication();
};
type SOCKS5_Real_Message(is_orig: bool) = case is_orig of {
true -> request: SOCKS5_Request;
false -> reply: SOCKS5_Reply;
};
type Domain_Name = record {
len: uint8;
name: bytestring &length=len;
} &byteorder = bigendian;
type SOCKS5_Address = record {
addr_type: uint8;
addr: case addr_type of {
1 -> ipv4: uint32;
3 -> domain_name: Domain_Name;
4 -> ipv6: uint32[4];
default -> err: bytestring &restofdata &transient;
};
} &byteorder = bigendian;
type SOCKS5_Request = record {
command: uint8;
reserved: uint8;
remote_name: SOCKS5_Address;
port: uint16;
} &byteorder = bigendian;
type SOCKS5_Reply = record {
reply: uint8;
reserved: uint8;
bound: SOCKS5_Address;
port: uint16;
} &byteorder = bigendian;
# SOCKS4 Implementation
type SOCKS4_Message(is_orig: bool) = case is_orig of {
true -> request: SOCKS4_Request;
false -> reply: SOCKS4_Reply;
};
type SOCKS4_Request = record {
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;
type SOCKS4_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;
refine connection SOCKS_Conn += {
%member{
bool v5_authenticated_;
%}
%init{
v5_authenticated_ = false;
%}
function v5_past_authentication(): bool
%{
return v5_authenticated_;
%}
function set_v5_past_authentication(): bool
%{
v5_authenticated_ = true;
return true;
%}
};

View file

@ -18,7 +18,7 @@ connection SOCKS_Conn(bro_analyzer: BroAnalyzer) {
%include socks-protocol.pac
flow SOCKS_Flow(is_orig: bool) {
datagram = SOCKS_Message(is_orig) withcontext(connection, this);
datagram = SOCKS_Version(is_orig) withcontext(connection, this);
};
%include socks-analyzer.pac