zeek/src/analyzer/protocol/netbios/NetbiosSSN.cc
2020-07-02 16:15:01 -07:00

535 lines
12 KiB
C++

// See the file "COPYING" in the main distribution directory for copyright.
#include "zeek-config.h"
#include "NetbiosSSN.h"
#include <ctype.h>
#include "BroString.h"
#include "NetVar.h"
#include "Sessions.h"
#include "Event.h"
#include "Net.h"
#include "events.bif.h"
using namespace analyzer::netbios_ssn;
double netbios_ssn_session_timeout = 15.0;
#define MAKE_INT16(dest, src) dest = *src; dest <<=8; src++; dest |= *src; src++;
NetbiosSSN_RawMsgHdr::NetbiosSSN_RawMsgHdr(const u_char*& data, int& len)
{
type = *data; ++data, --len;
flags = *data; ++data, --len;
length = *data; ++data, --len;
length <<= 8;
length |= *data;
++data, --len;
}
NetbiosDGM_RawMsgHdr::NetbiosDGM_RawMsgHdr(const u_char*& data, int& len)
{
type = *data; ++data, --len;
flags = *data; ++data, --len;
MAKE_INT16(id, data); len -= 2;
srcip = *data++ << 24;
srcip |= *data++ << 16;
srcip |= *data++ << 8;
srcip |= *data++;
len -=4;
MAKE_INT16(srcport, data); len -= 2;
MAKE_INT16(length, data); len -= 2;
MAKE_INT16(offset, data);; len -= 2;
}
NetbiosSSN_Interpreter::NetbiosSSN_Interpreter(Analyzer* arg_analyzer)
{
analyzer = arg_analyzer;
//smb_session = arg_smb_session;
}
void NetbiosSSN_Interpreter::ParseMessage(unsigned int type, unsigned int flags,
const u_char* data, int len, bool is_query)
{
if ( netbios_session_message )
analyzer->EnqueueConnEvent(netbios_session_message,
analyzer->ConnVal(),
zeek::val_mgr->Bool(is_query),
zeek::val_mgr->Count(type),
zeek::val_mgr->Count(len)
);
switch ( type ) {
case NETBIOS_SSN_MSG:
ParseSessionMsg(data, len, is_query);
break;
case NETBIOS_SSN_REQ:
ParseSessionReq(data, len, is_query);
break;
case NETBIOS_SSN_POS_RESP:
ParseSessionPosResp(data, len, is_query);
break;
case NETBIOS_SSN_NEG_RESP:
ParseSessionNegResp(data, len, is_query);
break;
case NETBIOS_SSN_RETARG_RESP:
ParseRetArgResp(data, len, is_query);
break;
case NETBIOS_SSN_KEEP_ALIVE:
ParseKeepAlive(data, len, is_query);
break;
case NETBIOS_DGM_DIRECT_UNIQUE:
case NETBIOS_DGM_DIRECT_GROUP:
case NETBIOS_DGM_BROADCAST:
ParseBroadcast(data, len, is_query);
break;
case NETBIOS_DGM_ERROR:
case NETBIOS_DGG_QUERY_REQ:
case NETBIOS_DGM_POS_RESP:
case NETBIOS_DGM_NEG_RESP:
ParseDatagram(data, len, is_query);
break;
default:
analyzer->Weird("unknown_netbios_type", fmt("0x%x", type));
break;
}
}
void NetbiosSSN_Interpreter::ParseDatagram(const u_char* data, int len,
bool is_query)
{
//if ( smb_session )
// {
// smb_session->Deliver(is_query, len, data);
// }
}
void NetbiosSSN_Interpreter::ParseBroadcast(const u_char* data, int len,
bool is_query)
{
// FIND THE NUL-TERMINATED NAME STRINGS HERE!
// Not sure what's in them, so we don't keep them currently.
zeek::String* srcname = new zeek::String((char*) data);
data += srcname->Len()+1;
len -= srcname->Len();
zeek::String* dstname = new zeek::String((char*) data);
data += dstname->Len()+1;
len -= dstname->Len();
delete srcname;
delete dstname;
//if ( smb_session )
// smb_session->Deliver(is_query, len, data);
}
void NetbiosSSN_Interpreter::ParseMessageTCP(const u_char* data, int len,
bool is_query)
{
NetbiosSSN_RawMsgHdr hdr(data, len);
if ( hdr.length > unsigned(len) )
analyzer->Weird("excess_netbios_hdr_len", fmt("(%d > %d)",
hdr.length, len));
else if ( hdr.length < unsigned(len) )
{
analyzer->Weird("deficit_netbios_hdr_len");
len = hdr.length;
}
ParseMessage(hdr.type, hdr.flags, data, len, is_query);
}
void NetbiosSSN_Interpreter::ParseMessageUDP(const u_char* data, int len,
bool is_query)
{
NetbiosDGM_RawMsgHdr hdr(data, len);
if ( unsigned(hdr.length-14) > unsigned(len) )
analyzer->Weird("excess_netbios_hdr_len", fmt("(%d > %d)",
hdr.length, len));
else if ( hdr.length < unsigned(len) )
{
analyzer->Weird("deficit_netbios_hdr_len", fmt("(%d < %d)",
hdr.length, len));
len = hdr.length;
}
ParseMessage(hdr.type, hdr.flags, data, len, is_query);
}
void NetbiosSSN_Interpreter::ParseSessionMsg(const u_char* data, int len,
bool is_query)
{
if ( len < 4 || strncmp((const char*) data, "\xffSMB", 4) )
{
// This should be an event, too.
analyzer->Weird("netbios_raw_session_msg");
Event(netbios_session_raw_message, data, len, is_query);
return;
}
//if ( smb_session )
// {
// smb_session->Deliver(is_query, len, data);
// return 0;
// }
//else
{
analyzer->Weird("no_smb_session_using_parsesambamsg");
data += 4;
len -= 4;
ParseSambaMsg(data, len, is_query);
}
}
void NetbiosSSN_Interpreter::ParseSambaMsg(const u_char* data, int len,
bool is_query)
{
}
int NetbiosSSN_Interpreter::ConvertName(const u_char* name, int name_len,
u_char*& xname, int& xlen)
{
// Taken from tcpdump's smbutil.c.
xname = nullptr;
if ( name_len < 1 )
return 0;
int len = (*name++) / 2;
xlen = len;
if ( len > 30 || len < 1 || name_len < len )
return 0;
u_char* convert_name = new u_char[len + 1];
*convert_name = 0;
xname = convert_name;
while ( len-- )
{
if ( name[0] < 'A' || name[0] > 'P' ||
name[1] < 'A' || name[1] > 'P' )
{
*convert_name = 0;
return 0;
}
*convert_name = ((name[0] - 'A') << 4) + (name[1] - 'A');
name += 2;
++convert_name;
}
*convert_name = 0;
return 1;
}
void NetbiosSSN_Interpreter::ParseSessionReq(const u_char* data, int len,
bool is_query)
{
if ( ! is_query )
analyzer->Weird("netbios_server_session_request");
u_char* xname;
int xlen;
if ( ConvertName(data, len, xname, xlen) )
Event(netbios_session_request, xname, xlen);
delete [] xname;
}
void NetbiosSSN_Interpreter::ParseSessionPosResp(const u_char* data, int len,
bool is_query)
{
if ( is_query )
analyzer->Weird("netbios_client_session_reply");
Event(netbios_session_accepted, data, len);
}
void NetbiosSSN_Interpreter::ParseSessionNegResp(const u_char* data, int len,
bool is_query)
{
if ( is_query )
analyzer->Weird("netbios_client_session_reply");
Event(netbios_session_rejected, data, len);
#if 0
case 0x80:
printf("Not listening on called name\n");
break;
case 0x81:
printf("Not listening for calling name\n");
break;
case 0x82:
printf("Called name not present\n");
break;
case 0x83:
printf("Called name present, but insufficient resources\n");
break;
default:
printf("Unspecified error 0x%X\n",ecode);
break;
#endif
}
void NetbiosSSN_Interpreter::ParseRetArgResp(const u_char* data, int len,
bool is_query)
{
if ( is_query )
analyzer->Weird("netbios_client_session_reply");
Event(netbios_session_ret_arg_resp, data, len);
}
void NetbiosSSN_Interpreter::ParseKeepAlive(const u_char* data, int len,
bool is_query)
{
Event(netbios_session_keepalive, data, len);
}
void NetbiosSSN_Interpreter::Event(EventHandlerPtr event, const u_char* data,
int len, int is_orig)
{
if ( ! event )
return;
if ( is_orig >= 0 )
analyzer->EnqueueConnEvent(event,
analyzer->ConnVal(),
zeek::val_mgr->Bool(is_orig),
zeek::make_intrusive<zeek::StringVal>(new zeek::String(data, len, false)));
else
analyzer->EnqueueConnEvent(event,
analyzer->ConnVal(),
zeek::make_intrusive<zeek::StringVal>(new zeek::String(data, len, false)));
}
Contents_NetbiosSSN::Contents_NetbiosSSN(Connection* conn, bool orig,
NetbiosSSN_Interpreter* arg_interp)
: tcp::TCP_SupportAnalyzer("CONTENTS_NETBIOSSSN", conn, orig)
{
interp = arg_interp;
type = flags = msg_size = 0;
msg_buf = nullptr;
buf_n = buf_len = msg_size = 0;
state = NETBIOS_SSN_TYPE;
}
Contents_NetbiosSSN::~Contents_NetbiosSSN()
{
delete [] msg_buf;
}
void Contents_NetbiosSSN::Flush()
{
if ( buf_n > 0 )
{ // Deliver partial message.
interp->ParseMessage(type, flags, msg_buf, buf_n, IsOrig());
msg_size = 0;
}
}
void Contents_NetbiosSSN::DeliverStream(int len, const u_char* data, bool orig)
{
tcp::TCP_SupportAnalyzer::DeliverStream(len, data, orig);
if ( state == NETBIOS_SSN_TYPE )
{
type = *data;
state = NETBIOS_SSN_FLAGS;
++data;
--len;
if ( len == 0 )
return;
}
if ( state == NETBIOS_SSN_FLAGS )
{
flags = *data;
state = NETBIOS_SSN_LEN_HI;
++data;
--len;
if ( len == 0 )
return;
}
if ( state == NETBIOS_SSN_LEN_HI )
{
msg_size = (*data) << 8;
state = NETBIOS_SSN_LEN_LO;
++data;
--len;
if ( len == 0 )
return;
}
if ( state == NETBIOS_SSN_LEN_LO )
{
msg_size += *data;
state = NETBIOS_SSN_BUF;
buf_n = 0;
if ( msg_buf )
{
if ( buf_len < msg_size )
{
delete [] msg_buf;
buf_len = msg_size;
msg_buf = new u_char[buf_len];
}
}
else
{
buf_len = msg_size;
if ( buf_len > 0 )
msg_buf = new u_char[buf_len];
}
++data;
--len;
if ( len == 0 && msg_size != 0 )
return;
}
if ( state != NETBIOS_SSN_BUF )
Conn()->Internal("state inconsistency in Contents_NetbiosSSN::Deliver");
int n;
for ( n = 0; buf_n < msg_size && n < len; ++n )
msg_buf[buf_n++] = data[n];
if ( buf_n < msg_size )
// Haven't filled up the message buffer yet, no more to do.
return;
interp->ParseMessage(type, flags, msg_buf, msg_size, IsOrig());
buf_n = 0;
state = NETBIOS_SSN_TYPE;
if ( n < len )
// More data to munch on.
DeliverStream(len - n, data + n, orig);
}
NetbiosSSN_Analyzer::NetbiosSSN_Analyzer(Connection* conn)
: tcp::TCP_ApplicationAnalyzer("NETBIOSSSN", conn)
{
//smb_session = new SMB_Session(this);
interp = new NetbiosSSN_Interpreter(this);
orig_netbios = resp_netbios = nullptr;
did_session_done = 0;
if ( Conn()->ConnTransport() == TRANSPORT_TCP )
{
orig_netbios = new Contents_NetbiosSSN(conn, true, interp);
resp_netbios = new Contents_NetbiosSSN(conn, false, interp);
AddSupportAnalyzer(orig_netbios);
AddSupportAnalyzer(resp_netbios);
}
else
{
ADD_ANALYZER_TIMER(&NetbiosSSN_Analyzer::ExpireTimer,
network_time + netbios_ssn_session_timeout, true,
TIMER_NB_EXPIRE);
}
}
NetbiosSSN_Analyzer::~NetbiosSSN_Analyzer()
{
delete interp;
//delete smb_session;
}
void NetbiosSSN_Analyzer::Done()
{
tcp::TCP_ApplicationAnalyzer::Done();
interp->Timeout();
if ( Conn()->ConnTransport() == TRANSPORT_UDP && ! did_session_done )
Event(udp_session_done);
else
interp->Timeout();
}
void NetbiosSSN_Analyzer::EndpointEOF(bool orig)
{
tcp::TCP_ApplicationAnalyzer::EndpointEOF(orig);
(orig ? orig_netbios : resp_netbios)->Flush();
}
void NetbiosSSN_Analyzer::ConnectionClosed(tcp::TCP_Endpoint* endpoint,
tcp::TCP_Endpoint* peer, bool gen_event)
{
tcp::TCP_ApplicationAnalyzer::ConnectionClosed(endpoint, peer, gen_event);
// Question: Why do we flush *both* endpoints upon connection close?
// orig_netbios->Flush();
// resp_netbios->Flush();
}
void NetbiosSSN_Analyzer::DeliverPacket(int len, const u_char* data, bool orig,
uint64_t seq, const IP_Hdr* ip, int caplen)
{
tcp::TCP_ApplicationAnalyzer::DeliverPacket(len, data, orig, seq, ip, caplen);
if ( orig )
interp->ParseMessageUDP(data, len, true);
else
interp->ParseMessageUDP(data, len, false);
}
void NetbiosSSN_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 ( terminating ||
network_time - Conn()->LastTime() >=
netbios_ssn_session_timeout - 1.0 )
{
Event(connection_timeout);
sessions->Remove(Conn());
}
else
ADD_ANALYZER_TIMER(&NetbiosSSN_Analyzer::ExpireTimer,
t + netbios_ssn_session_timeout,
true, TIMER_NB_EXPIRE);
}