Analyzer and bro script for RFB protocol (VNC)

This analyzer parses the Remote Frame Buffer
protocol, usually referred to as the 'VNC protocol'.

It supports several dialects (3.3, 3.7, 3.8) and
also handles the Apple Remote Desktop variant.

It will log such facts as client/server versions,
authentication method used, authentication result,
height, width and name of the shared screen.

It also includes two testcases.

Todo: Apple Remote Desktop seems to have some
bytes prepended to the screen name. This is
not interepreted correctly.
This commit is contained in:
Martin van Hensbergen 2016-04-11 10:35:00 +02:00
parent 8650841bf5
commit 849875e8be
19 changed files with 766 additions and 0 deletions

View file

@ -55,6 +55,7 @@
@load base/protocols/pop3
@load base/protocols/radius
@load base/protocols/rdp
@load base/protocols/rfb
@load base/protocols/sip
@load base/protocols/snmp
@load base/protocols/smtp

View file

@ -0,0 +1,3 @@
# Generated by binpac_quickstart
@load ./main
@load-sigs ./dpd.sig

View file

@ -0,0 +1,12 @@
signature dpd_rfb_server {
ip-proto == tcp
payload /^RFB/
requires-reverse-signature dpd_rfb_client
enable "rfb"
}
signature dpd_rfb_client {
ip-proto == tcp
payload /^RFB/
tcp-state originator
}

View file

@ -0,0 +1,143 @@
module Rfb;
export {
redef enum Log::ID += { LOG };
type Info: record {
## Timestamp for when the event happened.
ts: time &log;
## Unique ID for the connection.
uid: string &log;
## The connection's 4-tuple of endpoint addresses/ports.
id: conn_id &log;
client_major_version: string &log &optional;
client_minor_version: string &log &optional;
server_major_version: string &log &optional;
server_minor_version: string &log &optional;
authentication_method: string &log &optional;
auth: bool &log &optional;
share_flag: bool &log &optional;
desktop_name: string &log &optional;
width: count &log &optional;
height: count &log &optional;
done: bool &default=F;
};
global log_rfb: event(rec: Info);
}
function friendly_auth_name(auth: count): string {
switch (auth) {
case 0:
return "Invalid";
case 1:
return "None";
case 2:
return "VNC";
case 16:
return "Tight";
case 17:
return "Ultra";
case 18:
return "TLS";
case 19:
return "VeNCrypt";
case 20:
return "GTK-VNC SASL";
case 21:
return "MD5 hash authentication";
case 22:
return "Colin Dean xvp";
case 30:
return "Apple Remote Desktop";
}
return "RealVNC";
}
redef record connection += {
rfb_state: Info &optional;
};
event bro_init() &priority=5
{
Log::create_stream(Rfb::LOG, [$columns=Info, $ev=log_rfb, $path="rfb"]);
}
function write_log(c:connection) {
local state = c$rfb_state;
if ( state?$done && state$done == T) {
return;
}
Log::write(Rfb::LOG, c$rfb_state);
c$rfb_state$done = T;
}
function set_session(c: connection) {
if ( ! c?$rfb_state ) {
local info: Info;
info$ts = network_time();
info$uid = c$uid;
info$id = c$id;
c$rfb_state = info;
}
}
event rfb_event(c: connection)
{
set_session(c);
}
event rfb_client_version(c: connection, major_version: string, minor_version: string)
{
set_session(c);
c$rfb_state$client_major_version = major_version;
c$rfb_state$client_minor_version = minor_version;
}
event rfb_server_version(c: connection, major_version: string, minor_version: string)
{
set_session(c);
c$rfb_state$server_major_version = major_version;
c$rfb_state$server_minor_version = minor_version;
add c$service["rfb"];
}
event rfb_authentication_type(c: connection, authtype: count)
{
c$rfb_state$authentication_method = friendly_auth_name(authtype);
}
event rfb_server_parameters(c: connection, name: string, width: count, height: count)
{
c$rfb_state$desktop_name = name;
c$rfb_state$width = width;
c$rfb_state$height = height;
write_log(c);
}
event rfb_auth_result(c: connection, result: count)
{
if ( result ==0 ) {
c$rfb_state$auth = T;
} else {
c$rfb_state$auth = F;
}
}
event rfb_share_flag(c: connection, flag: bool)
{
c$rfb_state$share_flag = flag;
}
event connection_state_remove(c: connection) {
if ( c?$rfb_state ) {
write_log(c);
}
}

View file

@ -30,6 +30,7 @@ add_subdirectory(pia)
add_subdirectory(pop3)
add_subdirectory(radius)
add_subdirectory(rdp)
add_subdirectory(rfb)
add_subdirectory(rpc)
add_subdirectory(sip)
add_subdirectory(snmp)

View file

@ -0,0 +1,11 @@
# Generated by binpac_quickstart
include(BroPlugin)
include_directories(BEFORE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR})
bro_plugin_begin(Bro RFB)
bro_plugin_cc(RFB.cc Plugin.cc)
bro_plugin_bif(events.bif)
bro_plugin_pac(rfb.pac rfb-analyzer.pac rfb-protocol.pac)
bro_plugin_end()

View file

@ -0,0 +1,25 @@
// Generated by binpac_quickstart
#include "plugin/Plugin.h"
#include "RFB.h"
namespace plugin {
namespace Bro_RFB {
class Plugin : public plugin::Plugin {
public:
plugin::Configuration Configure()
{
AddComponent(new ::analyzer::Component("RFB",
::analyzer::rfb::RFB_Analyzer::InstantiateAnalyzer));
plugin::Configuration config;
config.name = "Bro::RFB";
config.description = "Parser for rfb (VNC) analyzer";
return config;
}
} plugin;
}
}

View file

@ -0,0 +1,69 @@
// Generated by binpac_quickstart
#include "RFB.h"
#include "analyzer/protocol/tcp/TCP_Reassembler.h"
#include "Reporter.h"
#include "events.bif.h"
using namespace analyzer::rfb;
RFB_Analyzer::RFB_Analyzer(Connection* c)
: tcp::TCP_ApplicationAnalyzer("RFB", c)
{
interp = new binpac::RFB::RFB_Conn(this);
had_gap = false;
}
RFB_Analyzer::~RFB_Analyzer()
{
delete interp;
}
void RFB_Analyzer::Done()
{
tcp::TCP_ApplicationAnalyzer::Done();
interp->FlowEOF(true);
interp->FlowEOF(false);
}
void RFB_Analyzer::EndpointEOF(bool is_orig)
{
tcp::TCP_ApplicationAnalyzer::EndpointEOF(is_orig);
interp->FlowEOF(is_orig);
}
void RFB_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;
try
{
interp->NewData(orig, data, data + len);
}
catch ( const binpac::Exception& e )
{
ProtocolViolation(fmt("Binpac exception: %s", e.c_msg()));
}
}
void RFB_Analyzer::Undelivered(uint64 seq, int len, bool orig)
{
tcp::TCP_ApplicationAnalyzer::Undelivered(seq, len, orig);
had_gap = true;
interp->NewGap(orig, len);
}

View file

@ -0,0 +1,45 @@
// Generated by binpac_quickstart
#ifndef ANALYZER_PROTOCOL_RFB_RFB_H
#define ANALYZER_PROTOCOL_RFB_RFB_H
#include "events.bif.h"
#include "analyzer/protocol/tcp/TCP.h"
#include "rfb_pac.h"
namespace analyzer { namespace rfb {
class RFB_Analyzer
: public tcp::TCP_ApplicationAnalyzer {
public:
RFB_Analyzer(Connection* conn);
virtual ~RFB_Analyzer();
// Overriden from Analyzer.
virtual void Done();
virtual void DeliverStream(int len, const u_char* data, bool orig);
virtual void Undelivered(uint64 seq, int len, bool orig);
// Overriden from tcp::TCP_ApplicationAnalyzer.
virtual void EndpointEOF(bool is_orig);
static analyzer::Analyzer* InstantiateAnalyzer(Connection* conn)
{ return new RFB_Analyzer(conn); }
protected:
binpac::RFB::RFB_Conn* interp;
bool had_gap;
};
} } // namespace analyzer::*
#endif

View file

@ -0,0 +1,50 @@
## Generated for RFB event
##
## c: The connection record for the underlying transport-layer session/flow.
event rfb_event%(c: connection%);
## Generated for RFB event authentication mechanism selection
##
## c: The connection record for the underlying transport-layer session/flow.
##
## authtype: the value of the chosen authentication mechanism
event rfb_authentication_type%(c: connection, authtype: count%);
## Generated for RFB event authentication result message
##
## c: The connection record for the underlying transport-layer session/flow.
##
## result: whether or not authentication was succesful
event rfb_auth_result%(c: connection, result: count%);
## Generated for RFB event share flag messages
##
## c: The connection record for the underlying transport-layer session/flow.
##
## flag: whether or not the share flag was set
event rfb_share_flag%(c: connection, flag: bool%);
## Generated for RFB event client banner message
##
## c: The connection record for the underlying transport-layer session/flow.
##
## version: of the client's rfb library
event rfb_client_version%(c: connection, major_version: string, minor_version: string%);
## Generated for RFB event server banner message
##
## c: The connection record for the underlying transport-layer session/flow.
##
## version: of the server's rfb library
event rfb_server_version%(c: connection, major_version: string, minor_version: string%);
## Generated for RFB event server parameter message
##
## c: The connection record for the underlying transport-layer session/flow.
##
## name: name of the shared screen
##
## width: width of the shared screen
##
## height: height of the shared screen
event rfb_server_parameters%(c: connection, name: string, width: count, height: count%);

View file

@ -0,0 +1,193 @@
# Generated by binpac_quickstart
refine flow RFB_Flow += {
function proc_rfb_message(msg: RFB_PDU): bool
%{
BifEvent::generate_rfb_event(connection()->bro_analyzer(), connection()->bro_analyzer()->Conn());
return true;
%}
function proc_rfb_client_version(major: bytestring, minor: bytestring) : bool
%{
BifEvent::generate_rfb_client_version(connection()->bro_analyzer(), connection()->bro_analyzer()->Conn(), bytestring_to_val(major), bytestring_to_val(minor));
return true;
%}
function proc_rfb_version(client: bool, major: bytestring, minor: bytestring) : bool
%{
if (client) {
BifEvent::generate_rfb_client_version(connection()->bro_analyzer(), connection()->bro_analyzer()->Conn(), bytestring_to_val(major), bytestring_to_val(minor));
} else {
BifEvent::generate_rfb_server_version(connection()->bro_analyzer(), connection()->bro_analyzer()->Conn(), bytestring_to_val(major), bytestring_to_val(minor));
}
return true;
%}
function proc_rfb_share_flag(shared: bool) : bool
%{
BifEvent::generate_rfb_share_flag(connection()->bro_analyzer(), connection()->bro_analyzer()->Conn(), shared);
return true;
%}
function proc_security_types(msg: RFBSecurityTypes) : bool
%{
BifEvent::generate_rfb_authentication_type(connection()->bro_analyzer(), connection()->bro_analyzer()->Conn(), ${msg.sectype});
return true;
%}
function proc_security_types37(msg: RFBAuthTypeSelected) : bool
%{
BifEvent::generate_rfb_authentication_type(connection()->bro_analyzer(), connection()->bro_analyzer()->Conn(), ${msg.type});
return true;
%}
function proc_handle_server_params(msg:RFBServerInit) : bool
%{
BifEvent::generate_rfb_server_parameters(connection()->bro_analyzer(), connection()->bro_analyzer()->Conn(), bytestring_to_val(${msg.name}), ${msg.width}, ${msg.height});
return true;
%}
function proc_handle_security_result(result : uint32) : bool
%{
BifEvent::generate_rfb_auth_result(connection()->bro_analyzer(), connection()->bro_analyzer()->Conn(), result);
return true;
%}
};
refine connection RFB_Conn += {
%member{
enum states {
AWAITING_SERVER_BANNER = 0,
AWAITING_CLIENT_BANNER = 1,
AWAITING_SERVER_AUTH_TYPES = 2,
AWAITING_SERVER_CHALLENGE = 3,
AWAITING_CLIENT_RESPONSE = 4,
AWAITING_SERVER_AUTH_RESULT = 5,
AWAITING_CLIENT_SHARE_FLAG = 6,
AWAITING_SERVER_PARAMS = 7,
AWAITING_CLIENT_AUTH_METHOD = 8,
AWAITING_SERVER_ARD_CHALLENGE = 9,
AWAITING_CLIENT_ARD_RESPONSE = 10,
AWAITING_SERVER_AUTH_TYPES37 = 11,
AWAITING_CLIENT_AUTH_TYPE_SELECTED37 = 12,
RFB_MESSAGE = 13
};
%}
function get_state(client: bool) : int
%{
return state;
%}
function handle_banners(client: bool, msg: RFBProtocolVersion) : bool
%{
if ( client ) {
// Set protocol version on client's version
int minor_version = bytestring_to_int(${msg.minor},10);
// Apple specifies minor version "889" but talks v37
if ( minor_version >= 7 ) {
state = AWAITING_SERVER_AUTH_TYPES37;
} else {
state = AWAITING_SERVER_AUTH_TYPES;
}
} else {
if ( !client ) {
state = AWAITING_CLIENT_BANNER;
}
}
return true;
%}
function handle_ard_challenge() : bool
%{
state = AWAITING_CLIENT_ARD_RESPONSE;
return true;
%}
function handle_ard_response() : bool
%{
state = AWAITING_SERVER_AUTH_RESULT;
return true;
%}
function handle_auth_request() : bool
%{
state = AWAITING_CLIENT_RESPONSE;
return true;
%}
function handle_auth_response() : bool
%{
state = AWAITING_SERVER_AUTH_RESULT;
return true;
%}
function handle_security_result(msg: RFBSecurityResult) : bool
%{
if ( ${msg.result} == 0 ) //FIXME
{
state = AWAITING_CLIENT_SHARE_FLAG;
}
return true;
%}
function handle_client_init(msg: RFBClientInit) : bool
%{
state = AWAITING_SERVER_PARAMS;
return true;
%}
function handle_server_init(msg: RFBServerInit) : bool
%{
state = RFB_MESSAGE;
return true;
%}
function handle_security_types(msg: RFBSecurityTypes): bool
%{
if ( msg->sectype() == 0 ) { // No auth
state = AWAITING_CLIENT_SHARE_FLAG;
return true;
}
if ( msg->sectype() == 2 ) { //VNC
state = AWAITING_SERVER_CHALLENGE;
}
return false;
%}
function handle_security_types37(msg: RFBSecurityTypes37): bool
%{
if ( ${msg.count} == 0 ) { // No auth
state = AWAITING_CLIENT_SHARE_FLAG;
return true;
}
state = AWAITING_CLIENT_AUTH_TYPE_SELECTED37;
return true;
%}
function handle_auth_type_selected(msg: RFBAuthTypeSelected): bool
%{
if ( ${msg.type} == 30 ) { // Apple Remote Desktop
state = AWAITING_SERVER_ARD_CHALLENGE;
return true;
}
if ( ${msg.type} == 1 ) { // No Auth
state = AWAITING_SERVER_AUTH_RESULT;
} else {
// Assume VNC
state = AWAITING_SERVER_CHALLENGE;
}
return true;
%}
%member{
uint8 state = AWAITING_SERVER_BANNER;
%}
};
refine typeattr RFB_PDU += &let {
proc: bool = $context.flow.proc_rfb_message(this);
};

View file

@ -0,0 +1,139 @@
enum states {
AWAITING_SERVER_BANNER = 0,
AWAITING_CLIENT_BANNER = 1,
AWAITING_SERVER_AUTH_TYPES = 2,
AWAITING_SERVER_CHALLENGE = 3,
AWAITING_CLIENT_RESPONSE = 4,
AWAITING_SERVER_AUTH_RESULT = 5,
AWAITING_CLIENT_SHARE_FLAG = 6,
AWAITING_SERVER_PARAMS = 7,
AWAITING_CLIENT_AUTH_METHOD = 8,
AWAITING_SERVER_ARD_CHALLENGE = 9,
AWAITING_CLIENT_ARD_RESPONSE = 10,
AWAITING_SERVER_AUTH_TYPES37 = 11,
AWAITING_CLIENT_AUTH_TYPE_SELECTED37 = 12,
RFB_MESSAGE = 13
};
type RFBProtocolVersion (client: bool) = record {
header : "RFB ";
major :bytestring &length=3;
dot: ".";
minor: bytestring &length=3;
pad: uint8;
} &let {
proc: bool = $context.connection.handle_banners(client, this);
proc2: bool = $context.flow.proc_rfb_version(client, major, minor);
}
type RFBSecurityTypes = record {
sectype: uint32;
} &let {
proc: bool = $context.connection.handle_security_types(this);
proc2: bool = $context.flow.proc_security_types(this);
};
type RFBSecurityTypes37 = record {
count: uint8;
types: uint8[count];
} &let {
proc: bool = $context.connection.handle_security_types37(this);
};
type RFBAuthTypeSelected = record {
type: uint8;
} &let {
proc: bool = $context.connection.handle_auth_type_selected(this);
proc2: bool = $context.flow.proc_security_types37(this);
};
type RFBSecurityResult = record {
result: uint32;
} &let {
proc: bool = $context.connection.handle_security_result(this);
proc2: bool = $context.flow.proc_handle_security_result(result);
};
type RFBSecurityResultReason = record {
len: uint32;
reason: bytestring &length=len;
};
type RFBVNCAuthenticationRequest = record {
challenge: bytestring &length=16;
} &let {
proc: bool = $context.connection.handle_auth_request();
};
type RFBVNCAuthenticationResponse = record {
response: bytestring &length= 16;
} &let {
proc: bool = $context.connection.handle_auth_response();
};
type RFBSecurityARDChallenge = record {
challenge: bytestring &restofdata;
} &let {
proc: bool = $context.connection.handle_ard_challenge();
}
type RFBSecurityARDResponse = record {
response: bytestring &restofdata;
} &let {
proc: bool = $context.connection.handle_ard_response();
}
type RFBClientInit = record {
shared_flag: uint8;
} &let {
proc: bool = $context.connection.handle_client_init(this);
proc2: bool = $context.flow.proc_rfb_share_flag(shared_flag);
}
type RFBServerInit = record {
width: uint16;
height: uint16;
pixel_format: bytestring &length= 16;
len : uint32;
name: bytestring &length = len;
} &let {
proc: bool = $context.connection.handle_server_init(this);
proc2: bool = $context.flow.proc_handle_server_params(this);
};
type RFB_PDU_request = record {
request: case state of {
AWAITING_CLIENT_BANNER -> version: RFBProtocolVersion(true);
AWAITING_CLIENT_RESPONSE -> response: RFBVNCAuthenticationResponse;
AWAITING_CLIENT_SHARE_FLAG -> shareflag: RFBClientInit;
AWAITING_CLIENT_AUTH_TYPE_SELECTED37 -> authtype: RFBAuthTypeSelected;
AWAITING_CLIENT_ARD_RESPONSE -> ard_response: RFBSecurityARDResponse;
RFB_MESSAGE -> ignore: bytestring &restofdata;
default -> data: bytestring &restofdata;
} &requires(state);
} &let {
state: uint8 = $context.connection.get_state(true);
};
type RFB_PDU_response = record {
request: case rstate of {
AWAITING_SERVER_BANNER -> version: RFBProtocolVersion(false);
AWAITING_SERVER_AUTH_TYPES -> auth_types: RFBSecurityTypes;
AWAITING_SERVER_AUTH_TYPES37 -> auth_types37: RFBSecurityTypes37;
AWAITING_SERVER_CHALLENGE -> challenge: RFBVNCAuthenticationRequest;
AWAITING_SERVER_AUTH_RESULT -> authresult : RFBSecurityResult;
AWAITING_SERVER_ARD_CHALLENGE -> ard_challenge: RFBSecurityARDChallenge;
AWAITING_SERVER_PARAMS -> serverinit: RFBServerInit;
RFB_MESSAGE -> ignore: bytestring &restofdata;
default -> data: bytestring &restofdata;
} &requires(rstate);
} &let {
rstate: uint8 = $context.connection.get_state(false);
};
type RFB_PDU(is_orig: bool) = record {
payload: case is_orig of {
true -> request: RFB_PDU_request;
false -> response: RFB_PDU_response;
};
} &byteorder = bigendian;

View file

@ -0,0 +1,42 @@
# Generated by binpac_quickstart
# Analyzer for Parser for rfb (VNC)
# - rfb-protocol.pac: describes the rfb protocol messages
# - rfb-analyzer.pac: describes the rfb analyzer code
%include binpac.pac
%include bro.pac
%extern{
#include "events.bif.h"
%}
analyzer RFB withcontext {
connection: RFB_Conn;
flow: RFB_Flow;
};
# Our connection consists of two flows, one in each direction.
connection RFB_Conn(bro_analyzer: BroAnalyzer) {
upflow = RFB_Flow(true);
downflow = RFB_Flow(false);
};
%include rfb-protocol.pac
# Now we define the flow:
flow RFB_Flow(is_orig: bool) {
# ## TODO: Determine if you want flowunit or datagram parsing:
# Using flowunit will cause the anlayzer to buffer incremental input.
# This is needed for &oneline and &length. If you don't need this, you'll
# get better performance with datagram.
# flowunit = RFB_PDU(is_orig) withcontext(connection, this);
datagram = RFB_PDU(is_orig) withcontext(connection, this);
};
%include rfb-analyzer.pac

View file

@ -0,0 +1,12 @@
#separator \x09
#set_separator ,
#empty_field (empty)
#unset_field -
#path rfb
#open 2016-04-11-08-25-48
#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p client_major_version client_minor_version server_major_version server_minor_version authentication_method auth share_flag desktop_name width height
#types time string addr port addr port string string string string string bool bool string count count
1459148054.031382 CCvvfg3TEfuqmmG4bh 192.168.2.115 52353 192.168.2.16 5900 003 889 003 889 Apple Remote Desktop T T \x00\x00\x00\x00\x00\x02\xbf\xfe\xe7\x03\xe0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00MacMini SSD 1920 1080
1459148050.685932 CjhGID4nQcgTWjvg4c 192.168.2.115 52352 192.168.2.16 5900 003 889 003 889 Apple Remote Desktop F - - - -
1459148047.738043 CXWv6p3arKYeMETxOg 192.168.2.115 52351 192.168.2.16 5900 003 889 003 889 - - - - - -
#close 2016-04-11-08-25-48

View file

@ -0,0 +1,12 @@
#separator \x09
#set_separator ,
#empty_field (empty)
#unset_field -
#path rfb
#open 2016-04-06-13-48-56
#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p client_major_version client_minor_version server_major_version server_minor_version authentication_method auth share_flag desktop_name width height
#types time string addr port addr port string string string string string bool bool string count count
1459093553.334734 CsRx2w45OKnoww6xl4 192.168.2.115 49259 192.168.2.125 5901 003 003 003 008 VNC T T root's X desktop (martin-VirtualBox:1) 1024 768
1459093548.745805 CjhGID4nQcgTWjvg4c 192.168.2.115 49256 192.168.2.125 5901 003 003 003 008 VNC - - - - -
1459093551.559391 CCvvfg3TEfuqmmG4bh 192.168.2.115 49257 192.168.2.125 5901 003 003 003 008 VNC F - - - -
#close 2016-04-06-13-48-56

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,4 @@
# @TEST-EXEC: bro -C -r $TRACES/rfb/vncmac.pcap
# @TEST-EXEC: btest-diff rfb.log
@load base/protocols/rfb

View file

@ -0,0 +1,4 @@
# @TEST-EXEC: bro -C -r $TRACES/rfb/vnc-mac-to-linux.pcap
# @TEST-EXEC: btest-diff rfb.log
@load base/protocols/rfb