zeek/src/SMB.cc
2011-06-20 14:09:15 -07:00

1308 lines
30 KiB
C++

// $Id: SMB.cc 6219 2008-10-01 05:39:07Z vern $
//
// See the file "COPYING" in the main distribution directory for copyright.
#include "NetVar.h"
#include "SMB.h"
#include "smb_pac.h"
#include "Val.h"
namespace {
const bool DEBUG_smb_ipc = true;
}
#define SMB_MAX_LEN (1<<17)
#define BYTEORDER_SWAP16(n) ((256 * ((n) & 0xff)) + ((n) >> 8))
enum SMB_Command {
#define SMB_COMMAND(name, value) name = value,
#include "SMB_COM.def"
#undef SMB_COMMAND
};
enum SMB_Transaction_Command {
HOST_ANNOUNCEMENT = 1,
ANNOUCEMENT_REQUEST = 2,
REQUEST_ELECTION = 8,
GET_BACKUP_LIST_REQUEST = 9,
GET_BACKUP_LIST_RESPONSE = 10,
BECOME_BACKUP_REQUEST = 11,
DOMAIN_ANNOUNCEMENT = 12,
MASTER_ANNOUNCEMENT = 13,
RESET_BROWSER_STATE = 14,
LOCAL_MASTER_ANNOUNCEMENT = 15,
};
const char* SMB_command_name[256];
StringVal* SMB_command_str[256];
const char* SMB_trans_command_name[256];
StringVal* SMB_trans_command_str[256];
static void init_SMB_command_name()
{
static int initialized = 0;
if ( initialized )
return;
initialized = 1;
for ( int i = 0; i < 256; ++i )
{
SMB_command_name[i] = "<unknown>";
SMB_command_str[i] = 0;
}
#define SMB_COMMAND(name, value) SMB_command_name[value] = #name;
#include "SMB_COM.def"
#undef SMB_COMMAND
#define SMB_COMMAND(name, value) SMB_trans_command_name[value] = #name;
SMB_COMMAND(HOST_ANNOUNCEMENT, 1)
SMB_COMMAND(ANNOUCEMENT_REQUEST, 2)
SMB_COMMAND(REQUEST_ELECTION, 8)
SMB_COMMAND(GET_BACKUP_LIST_REQUEST, 9)
SMB_COMMAND(GET_BACKUP_LIST_RESPONSE, 10)
SMB_COMMAND(BECOME_BACKUP_REQUEST, 11)
SMB_COMMAND(DOMAIN_ANNOUNCEMENT, 12)
SMB_COMMAND(MASTER_ANNOUNCEMENT, 13)
SMB_COMMAND(RESET_BROWSER_STATE, 14)
SMB_COMMAND(LOCAL_MASTER_ANNOUNCEMENT, 15)
}
StringVal* get_SMB_command_str(int cmd)
{
if ( ! SMB_command_str[cmd] )
SMB_command_str[cmd] = new StringVal(SMB_command_name[cmd]);
return SMB_command_str[cmd];
}
// ### TODO: the list of IPC pipes needs a lot of expansion.
static int lookup_IPC_name(BroString* name)
{
static const char* IPC_pipe_names[] = {
"\\locator", "\\epmapper", "\\samr", "\\lsarpc", 0
};
for ( int i = 0; IPC_pipe_names[i]; ++i )
{
if ( size_t(name->Len()) == strlen(IPC_pipe_names[i]) &&
strncmp((const char*) name->Bytes(),
IPC_pipe_names[i], name->Len()) == 0 )
return i + 1;
}
return IPC_NONE;
}
SMB_Session::SMB_Session(Analyzer* arg_analyzer)
{
analyzer = arg_analyzer;
dce_rpc_session = 0;
init_SMB_command_name();
// Strangely, one does not have to connect to IPC$ before
// making DCE/RPC calls. So we assume that it's always IPC
// unless confirmed otherwise.
is_IPC = true;
IPC_pipe = IPC_NONE;
transaction_name = 0;
transaction_subcmd = 0;
andx_[0] = andx_[1] = 0;
set_andx(0, 0);
set_andx(1, 0);
}
SMB_Session::~SMB_Session()
{
binpac::Unref(andx_[0]);
binpac::Unref(andx_[1]);
Unref(transaction_name);
delete dce_rpc_session;
}
void SMB_Session::set_andx(int is_orig, binpac::SMB::SMB_andx* andx)
{
int ind = is_orig ? 1 : 0;
if ( andx )
andx->Ref();
binpac::Unref(andx_[ind]);
andx_[ind] = andx;
}
void SMB_Session::Deliver(int is_orig, int len, const u_char* data,
double arg_first_time, double arg_last_time)
{
if ( len == 0 )
return;
first_time = arg_first_time;
last_time = arg_last_time;
try
{
const u_char* data_start = data;
const u_char* data_end = data + len;
binpac::SMB::SMB_header hdr;
int hdr_len = hdr.Parse(data, data_end);
data += hdr_len;
int next_command = hdr.command();
fprintf(stderr, "SMB command: 0x%02x %s (%d) len %-7d dur %.6lf\n", next_command,
SMB_command_name[next_command], is_orig, len,
last_time-first_time);
int ncmds = 0;
while ( data < data_end )
{
ncmds++;
SMB_Body body(data, data_end);
set_andx(is_orig, 0);
ParseMessage(is_orig, next_command, hdr, body);
int next = AndxOffset(is_orig, next_command);
if ( next <= 0 )
break;
//Weird(fmt("ANDX! at %d", next));
const u_char* tmp = data_start + next;
if ( data_start + next < data + body.length() )
{
Weird(fmt("ANDX buffer overlapping: next = %d, buffer_end = %" PRIuPTR, next, data + body.length() - data_start));
break;
}
data = data_start + next;
}
fprintf(stderr, "ncmds %d\n", ncmds);
}
catch ( const binpac::Exception& e )
{
analyzer->Weird(e.msg().c_str());
}
}
void SMB_Session::ParseMessage(int is_orig, int cmd,
binpac::SMB::SMB_header const& hdr,
SMB_Body const& body)
{
if ( smb_message )
{
val_list* vl = new val_list;
StringVal* cmd_str = get_SMB_command_str(cmd);
Ref(cmd_str);
vl->append(analyzer->BuildConnVal());
vl->append(BuildHeaderVal(hdr));
vl->append(new Val(is_orig, TYPE_BOOL));
vl->append(cmd_str);
vl->append(new Val(body.length(), TYPE_COUNT));
vl->append(new StringVal(body.length(),
(const char*) body.data()));
analyzer->ConnectionEvent(smb_message, vl);
}
if ( is_orig )
req_cmd = cmd;
// What if there's an error?
// if ( hdr.status->status() || hdr.status->dos_error() )
// The command code in the header might be right, but
// the response is probably mangled :-.
int ci = hdr.status()->val_case_index();
unsigned int error = 0;
switch ( ci ) {
case 0:
error = hdr.status()->dos_error()->error_class() << 24 ||
hdr.status()->dos_error()->error();
break;
case 1:
error = hdr.status()->status();
break;
}
if (error)
{
val_list* vl = new val_list;
StringVal* cmd_str = get_SMB_command_str(cmd);
Ref(cmd_str);
vl->append(analyzer->BuildConnVal());
vl->append(BuildHeaderVal(hdr));
vl->append(new Val(cmd, TYPE_COUNT));
vl->append(cmd_str);
vl->append(new Val(ci, TYPE_COUNT));
vl->append(new Val(error, TYPE_COUNT));
analyzer->ConnectionEvent(smb_error, vl);
// Is this the right behavior?
return;
}
int ret = 0;
switch ( cmd ) {
case SMB_COM_TREE_CONNECT_ANDX:
if ( is_orig )
ret = ParseTreeConnectAndx(hdr, body);
else
ret = ParseAndx(is_orig, hdr, body);
break;
case SMB_COM_NT_CREATE_ANDX:
if ( is_orig )
ret = ParseNtCreateAndx(hdr, body);
else
ret = ParseAndx(is_orig, hdr, body);
break;
case SMB_COM_TRANSACTION:
case SMB_COM_TRANSACTION2:
case SMB_COM_TRANSACTION_SECONDARY:
case SMB_COM_TRANSACTION2_SECONDARY:
ret = ParseTransaction(is_orig, cmd, hdr, body);
break;
case SMB_COM_READ_ANDX:
if ( is_orig )
ret = ParseReadAndx(hdr, body);
else
ret = ParseReadAndxResponse(hdr, body);
break;
case SMB_COM_WRITE_ANDX:
if ( is_orig )
ret = ParseWriteAndx(hdr, body);
else
ret = ParseWriteAndxResponse(hdr, body);
break;
case SMB_COM_NEGOTIATE:
if ( is_orig )
ret = ParseNegotiate(hdr, body);
else
ret = ParseNegotiateResponse(hdr, body);
break;
case SMB_COM_CLOSE:
ret = ParseClose(is_orig, hdr, body);
break;
case SMB_COM_TREE_DISCONNECT:
ret = ParseTreeDisconnect(is_orig, hdr, body);
break;
case SMB_COM_LOGOFF_ANDX:
if ( is_orig )
ret = ParseLogoffAndx(is_orig, hdr, body);
else
ret = ParseAndx(is_orig, hdr, body);
break;
case SMB_COM_SESSION_SETUP_ANDX:
if ( is_orig )
ret = ParseSetupAndx(is_orig, hdr, body);
else
ret = ParseAndx(is_orig, hdr, body);
break;
default:
Weird(fmt("unknown_SMB_command(0x%x)", cmd));
break;
}
if ( ret == -1 )
Weird("SMB_parsing_error");
}
int SMB_Session::ParseNegotiate(binpac::SMB::SMB_header const& hdr,
SMB_Body const& body)
{
binpac::SMB::SMB_negotiate msg;
msg.Parse(body.data(), body.data() + body.length());
if ( smb_com_negotiate )
{
TableVal* t = new TableVal(smb_negotiate);
for ( int i = 0; i < int(msg.dialects()->size()); ++i )
{
binpac::SMB::SMB_dialect* d = (*msg.dialects())[i];
BroString* tmp = ExtractString(d->dialectname());
t->Assign(new Val(i, TYPE_COUNT), new StringVal(tmp));
}
val_list* vl = new val_list;
vl->append(analyzer->BuildConnVal());
vl->append(BuildHeaderVal(hdr));
vl->append(t);
analyzer->ConnectionEvent(smb_com_negotiate, vl);
}
return 0;
}
int SMB_Session::ParseNegotiateResponse(binpac::SMB::SMB_header const& hdr,
SMB_Body const& body)
{
binpac::SMB::SMB_negotiate_response msg;
msg.Parse(body.data(), body.data() + body.length());
if ( smb_com_negotiate_response )
{
val_list* vl = new val_list;
vl->append(analyzer->BuildConnVal());
vl->append(BuildHeaderVal(hdr));
vl->append(new Val(msg.dialect_index(), TYPE_COUNT));
analyzer->ConnectionEvent(smb_com_negotiate_response, vl);
}
return 0;
}
int SMB_Session::ParseSetupAndx(int is_orig, binpac::SMB::SMB_header const& hdr,
SMB_Body const& body)
{
// The binpac type depends on the negotiated server settings -
// possibly we can just pick the "right" format here, and use that?
if ( hdr.flags2() && 0x0800 )
{
binpac::SMB::SMB_setup_andx_ext msg(hdr.unicode());
msg.Parse(body.data(), body.data() + body.length());
set_andx(1, msg.andx());
}
else
{
binpac::SMB::SMB_setup_andx_basic msg(hdr.unicode());
msg.Parse(body.data(), body.data() + body.length());
set_andx(1, msg.andx());
}
if ( smb_com_setup_andx )
{
val_list* vl = new val_list;
vl->append(analyzer->BuildConnVal());
vl->append(BuildHeaderVal(hdr));
analyzer->ConnectionEvent(smb_com_setup_andx, vl);
}
return 0;
}
int SMB_Session::ParseClose(int is_orig, binpac::SMB::SMB_header const& hdr,
SMB_Body const& body)
{
if ( smb_com_close )
{
val_list* vl = new val_list;
vl->append(analyzer->BuildConnVal());
vl->append(BuildHeaderVal(hdr));
analyzer->ConnectionEvent(smb_com_close, vl);
}
return 0;
}
int SMB_Session::ParseLogoffAndx(int is_orig,
binpac::SMB::SMB_header const& hdr,
SMB_Body const& body)
{
binpac::SMB::SMB_generic_andx msg;
msg.Parse(body.data(), body.data() + body.length());
if ( msg.word_count() > 0 )
set_andx(is_orig, msg.andx());
if ( smb_com_logoff_andx )
{
val_list* vl = new val_list;
vl->append(analyzer->BuildConnVal());
vl->append(BuildHeaderVal(hdr));
analyzer->ConnectionEvent(smb_com_logoff_andx, vl);
}
return 0;
}
int SMB_Session::ParseAndx(int is_orig, binpac::SMB::SMB_header const& hdr,
SMB_Body const& body)
{
// This is a generic ANDX event generator. It passes the header
// and the ANDX data out to the policy.
try
{
binpac::SMB::SMB_generic_andx msg;
msg.Parse(body.data(), body.data() + body.length());
if ( msg.word_count() > 0 )
set_andx(is_orig, msg.andx());
if ( smb_com_generic_andx )
{
val_list* vl = new val_list;
vl->append(analyzer->BuildConnVal());
vl->append(BuildHeaderVal(hdr));
vl->append(new StringVal(msg.data().length(),
(char *) msg.data().begin()));
analyzer->ConnectionEvent(smb_com_generic_andx, vl);
}
}
catch ( const binpac::Exception& )
{
Weird("smb_andx_command_failed_to_parse");
}
return 0;
}
int SMB_Session::ParseTreeConnectAndx(binpac::SMB::SMB_header const& hdr,
SMB_Body const& body)
{
binpac::SMB::SMB_tree_connect_andx req(hdr.unicode());
req.Parse(body.data(), body.data() + body.length());
set_andx(1, req.andx());
BroString* path = ExtractString(req.path());
BroString* service = ExtractString(req.service());
// Replicate path.
BroString* norm_path = new BroString(path->Bytes(), path->Len(), 1);
norm_path->ToUpper();
RecordVal* r = new RecordVal(smb_tree_connect);
r->Assign(0, new Val(req.flags(), TYPE_COUNT));
r->Assign(1, new StringVal(req.password_length(),
(const char*) req.password()));
r->Assign(2, new StringVal(path));
r->Assign(3, new StringVal(service));
if ( strstr_n(norm_path->Len(), norm_path->Bytes(), 5,
(const u_char*) "\\IPC$") != -1 )
is_IPC = true; // TODO: change is_IPC to 0 on tree_disconnect
else
is_IPC = false;
delete norm_path;
if ( smb_com_tree_connect_andx )
{
val_list* vl = new val_list;
vl->append(analyzer->BuildConnVal());
vl->append(BuildHeaderVal(hdr));
vl->append(r);
analyzer->ConnectionEvent(smb_com_tree_connect_andx, vl);
}
else
{
delete path;
delete service;
}
return 0;
}
int SMB_Session::ParseTreeDisconnect(int is_orig,
binpac::SMB::SMB_header const& hdr,
SMB_Body const& body)
{
binpac::SMB::SMB_tree_disconnect msg(hdr.unicode());
msg.Parse(body.data(), body.data() + body.length());
if ( smb_com_nt_create_andx )
{
val_list* vl = new val_list;
vl->append(analyzer->BuildConnVal());
vl->append(BuildHeaderVal(hdr));
analyzer->ConnectionEvent(smb_com_tree_disconnect, vl);
}
return 0;
}
int SMB_Session::ParseNtCreateAndx(binpac::SMB::SMB_header const& hdr,
SMB_Body const& body)
{
binpac::SMB::SMB_nt_create_andx req(hdr.unicode());
req.Parse(body.data(), body.data() + body.length());
set_andx(1, req.andx());
BroString* name = ExtractString(req.name());
IPC_pipe = (enum IPC_named_pipe) lookup_IPC_name(name);
if ( smb_com_nt_create_andx )
{
val_list* vl = new val_list;
vl->append(analyzer->BuildConnVal());
vl->append(BuildHeaderVal(hdr));
vl->append(new StringVal(name));
analyzer->ConnectionEvent(smb_com_nt_create_andx, vl);
}
else
delete name;
return 0;
}
int SMB_Session::ParseReadAndx(binpac::SMB::SMB_header const& hdr,
SMB_Body const& body)
{
binpac::SMB::SMB_read_andx req;
req.Parse(body.data(), body.data() + body.length());
set_andx(1, req.andx());
if ( smb_com_read_andx )
{
val_list* vl = new val_list;
vl->append(analyzer->BuildConnVal());
vl->append(BuildHeaderVal(hdr));
vl->append(new Val(req.fid(), TYPE_COUNT));
//vl->append(new StringVal(""));
analyzer->ConnectionEvent(smb_com_read_andx, vl);
}
return 0;
}
int SMB_Session::ParseReadAndxResponse(binpac::SMB::SMB_header const& hdr,
SMB_Body const& body)
{
binpac::SMB::SMB_read_andx_response resp;
resp.Parse(body.data(), body.data() + body.length());
set_andx(0, resp.andx());
uint32_t data_len = resp.data_len_high();
data_len = (data_len<<16) + resp.data_len();
const u_char* data = resp.data().begin();
if ( smb_com_read_andx_response )
{
val_list* vl = new val_list;
vl->append(analyzer->BuildConnVal());
vl->append(BuildHeaderVal(hdr));
vl->append(new Val(data_len, TYPE_COUNT));
//vl->append(new StringVal(data_count, (const char*) data));
analyzer->ConnectionEvent(smb_com_read_andx_response, vl);
}
CheckRPC(0, data_len, data);
return 0;
}
int SMB_Session::ParseWriteAndx(binpac::SMB::SMB_header const& hdr,
SMB_Body const& body)
{
binpac::SMB::SMB_write_andx req;
req.Parse(body.data(), body.data() + body.length());
set_andx(1, req.andx());
uint32_t data_len = req.data_len_high();
data_len = (data_len<<16) + req.data_len();
const u_char* data = req.data().begin();
if ( smb_com_write_andx )
{
val_list* vl = new val_list;
vl->append(analyzer->BuildConnVal());
vl->append(BuildHeaderVal(hdr));
vl->append(new Val(req.fid(), TYPE_COUNT));
vl->append(new Val(data_len, TYPE_COUNT));
//vl->append(new StringVal(data_count, (const char*) data));
analyzer->ConnectionEvent(smb_com_write_andx, vl);
}
CheckRPC(1, data_len, data);
return 0;
}
int SMB_Session::ParseWriteAndxResponse(binpac::SMB::SMB_header const& hdr,
SMB_Body const& body)
{
binpac::SMB::SMB_write_andx_response resp;
resp.Parse(body.data(), body.data() + body.length());
set_andx(0, resp.andx());
if ( smb_com_write_andx_response )
{
val_list* vl = new val_list;
vl->append(analyzer->BuildConnVal());
vl->append(BuildHeaderVal(hdr));
//vl->append(new StringVal(""));
analyzer->ConnectionEvent(smb_com_write_andx_response, vl);
}
return 0;
}
int SMB_Session::TransactionEvent(EventHandlerPtr f, int is_orig,
binpac::SMB::SMB_header const &hdr,
binpac::SMB::SMB_transaction const &trans,
int data_count,
binpac::SMB::SMB_transaction_data* data)
{
if ( f )
{
val_list* vl = new val_list;
vl->append(analyzer->BuildConnVal());
vl->append(BuildHeaderVal(hdr));
vl->append(BuildTransactionVal(trans));
vl->append(BuildTransactionDataVal(data));
vl->append(new Val(is_orig, TYPE_BOOL));
analyzer->ConnectionEvent(f, vl);
}
else if ( smb_com_transaction )
{ // generic transaction
}
return 0;
}
int SMB_Session::TransactionEvent(EventHandlerPtr f, int is_orig,
binpac::SMB::SMB_header const &hdr,
binpac::SMB::SMB_transaction_secondary const &trans,
int data_count, binpac::SMB::SMB_transaction_data* data)
{
if ( f )
{
val_list* vl = new val_list;
vl->append(analyzer->BuildConnVal());
vl->append(BuildHeaderVal(hdr));
vl->append(BuildTransactionVal(trans));
vl->append(BuildTransactionDataVal(data));
vl->append(new Val(is_orig, TYPE_BOOL));
analyzer->ConnectionEvent(f, vl);
}
else if ( smb_com_transaction )
{ // generic transaction
}
return 0;
}
int SMB_Session::TransactionEvent(EventHandlerPtr f, int is_orig,
binpac::SMB::SMB_header const &hdr,
binpac::SMB::SMB_transaction_response const &trans,
int data_count, binpac::SMB::SMB_transaction_data* data)
{
if ( f )
{
val_list* vl = new val_list;
vl->append(analyzer->BuildConnVal());
vl->append(BuildHeaderVal(hdr));
vl->append(BuildTransactionVal(trans));
vl->append(BuildTransactionDataVal(data));
vl->append(new Val(is_orig, TYPE_BOOL));
analyzer->ConnectionEvent(f, vl);
}
else if ( smb_com_transaction )
{ // generic transaction
}
return 0;
}
int SMB_Session::ParseTransaction(int is_orig, int cmd,
binpac::SMB::SMB_header const& hdr,
SMB_Body const& body)
{
switch ( cmd ) {
case SMB_COM_TRANSACTION:
case SMB_COM_TRANSACTION2:
case SMB_COM_TRANSACTION_SECONDARY:
case SMB_COM_TRANSACTION2_SECONDARY:
break;
default:
internal_error("command mismatch for ParseTransaction");
}
int ret;
if ( is_orig )
{
if ( cmd == SMB_COM_TRANSACTION || cmd == SMB_COM_TRANSACTION2 )
ret = ParseTransactionRequest(cmd, hdr, body);
else if ( cmd == SMB_COM_TRANSACTION_SECONDARY ||
cmd == SMB_COM_TRANSACTION2_SECONDARY )
ret = ParseTransactionSecondaryRequest(cmd, hdr, body);
else
ret = 0;
}
else
ret = ParseTransactionResponse(cmd, hdr, body);
return ret;
}
int SMB_Session::ParseTransactionRequest(int cmd,
binpac::SMB::SMB_header const& hdr,
SMB_Body const& body)
{
binpac::SMB::SMB_transaction trans(cmd == SMB_COM_TRANSACTION ? 1 : 2,
hdr.unicode());
trans.Parse(body.data(), body.data() + body.length());
if ( transaction_name )
{
Unref(transaction_name);
transaction_name = 0;
}
if ( cmd == SMB_COM_TRANSACTION )
{
binpac::SMB::SMB_transaction_data* trans_data = trans.data();
//transaction_name = new StringVal(ExtractString(trans.name()));
//if ( is_orig )
// Weird(fmt("smb_transaction subcmd: 0x%x", transaction_subcmd));
if ( trans_data->val_case_index() ==
binpac::SMB::SMB_MAILSLOT_BROWSE &&
trans_data->mailslot() )
{ // Mailslot transaction event
return TransactionEvent(smb_com_trans_mailslot, true,
hdr, trans, trans.data_count(), trans.data());
}
else if ( trans_data->val_case_index() ==
binpac::SMB::SMB_PIPE && trans_data->pipe() )
{ // Pipe
return TransactionEvent(smb_com_trans_pipe, true, hdr,
trans, trans.data_count(), trans.data());
}
else if ( trans_data->val_case_index() ==
binpac::SMB::SMB_RAP && trans_data->rap() )
{ // Remote Administration Protocol
return TransactionEvent(smb_com_trans_rap, true, hdr,
trans, trans.data_count(), trans.data());
}
else
{
// SOME UNKNOWN TRANSACTION TYPE - COULD BE RPC STILL!
if ( trans.data_count() > 0 && trans.setup_count() == 2 )
{
if ( CheckRPC(true, trans.data_count(),
trans_data->pipe()->data().begin()) )
{
if ( cmd != SMB_COM_TRANSACTION ||
transaction_subcmd != 0x26 )
Weird(fmt("RPC through unknown command: 0x%x/0x%x", cmd, transaction_subcmd));
}
}
}
}
if ( cmd == SMB_COM_TRANSACTION2 )
{
switch ( transaction_subcmd ) {
case 0x3: // QueryFSInfo
case 0x5: // QueryPathInfo
case 0x7: // QueryFileInfo
case 0x8: // SetFileInfo
break;
case 0x10:
// if ( is_orig )
return ParseGetDFSReferral(hdr, trans.param_count(),
trans.parameters().begin());
default:
// if ( is_orig )
Weird(fmt("Unknown smb_transaction2 subcmd: 0x%x",
transaction_subcmd));
break;
}
}
if ( smb_com_transaction )
return TransactionEvent(smb_com_transaction, true, hdr,
trans, trans.data_count(),
trans.data());
else
return 0;
#if 0
// TODO: LANMAN transaction uses the first u_short of
// parameters as subcmd
if ( trans.setup_count() > 0 )
transaction_subcmd = (*trans.setup())[0];
else if ( strncmp( transaction_name->CheckString(), "\\PIPE\\", 6 ) == 0 )
transaction_subcmd = 0;
else if ( strncmp( transaction_name->CheckString(), "\\MAILSLOT\\", 10 ) == 0 )
transaction_subcmd = 0;
else
Weird("transaction_subcmd_missing");
#endif
}
int SMB_Session::ParseTransactionSecondaryRequest(int cmd,
binpac::SMB::SMB_header const& hdr,
SMB_Body const& body)
{
binpac::SMB::SMB_transaction_secondary trans(hdr.unicode());
trans.Parse(body.data(), body.data() + body.length());
return TransactionEvent(smb_com_transaction2, true, hdr,
trans, trans.data_count(), trans.data());
}
int SMB_Session::ParseTransactionResponse(int cmd,
binpac::SMB::SMB_header const& hdr,
SMB_Body const& body)
{
binpac::SMB::SMB_transaction_response trans(hdr.unicode());
trans.Parse(body.data(), body.data() + body.length());
if ( body.word_count() == 0 )
{ // interim response
// Does the transaction get parsed correctly?!
return TransactionEvent(smb_com_transaction, false, hdr,
trans, 0, NULL);
}
return TransactionEvent(smb_com_transaction, false, hdr,
trans, trans.data_count(), trans.data());
}
int SMB_Session::ParseGetDFSReferral(binpac::SMB::SMB_header const& hdr,
int param_count, const u_char* param)
{
binpac::SMB::SMB_get_dfs_referral req(hdr.unicode());
req.Parse(param, param + param_count);
if ( smb_get_dfs_referral )
{
val_list* vl = new val_list;
vl->append(analyzer->BuildConnVal());
vl->append(BuildHeaderVal(hdr));
vl->append(new Val(req.max_referral_level(), TYPE_COUNT));
vl->append(new StringVal(ExtractString(req.file_name())));
analyzer->ConnectionEvent(smb_get_dfs_referral, vl);
}
return 0;
}
int SMB_Session::AndxOffset(int is_orig, int& next_command) const
{
if ( ! andx(is_orig) )
return -1;
next_command = andx(is_orig)->command();
if ( next_command != 0xff )
return andx(is_orig)->offset();
else
return -1;
}
void SMB_Session::Weird(const char* msg)
{
analyzer->Weird(msg);
}
// Extract a NUL-terminated string from [data, data+len-1]. The
// input can be in Unicode (little endian), and the returned string
// will be in ASCII. Note, Unicode strings have NUL characters
// at the end of them already. Adding an additional NUL byte at
// the end leads to embedded-NUL warnings (CheckString() run_time error).
BroString* SMB_Session::ExtractString(binpac::SMB::SMB_string const* s)
{
return s->unicode() ? ExtractString(s->u()) : ExtractString(s->a());
}
BroString* SMB_Session::ExtractString(binpac::SMB::SMB_ascii_string const* s)
{
bool add_NUL = true;
int n = s->size();
if ( n > 0 && (*s)[n - 1] == '\0' )
add_NUL = false; // already has a NUL
if ( add_NUL )
++n;
u_char* b = new u_char[n];
int i;
for ( i = 0; i < int(s->size()); ++i )
b[i] = (*s)[i];
if ( add_NUL )
b[i] = '\0';
return new BroString(1, b, n - 1);
}
BroString* SMB_Session::ExtractString(binpac::SMB::SMB_unicode_string const* s)
{
bool add_NUL = true;
int n = s->s()->size();
if ( n > 0 && ((*s->s())[n - 1] & 0xff) == '\0' )
add_NUL = false; // already has a NUL
if ( add_NUL )
++n;
u_char* b = new u_char[n];
int i;
for ( i = 0; i < int(s->s()->size()); ++i )
{
uint16 x = (*s->s())[i];
if ( x & 0xff00 )
Weird(fmt("unicode string confusion: 0x%04x", x));
b[i] = u_char(x & 0xff);
}
if ( add_NUL )
b[i] = '\0';
return new BroString(1, b, n - 1);
}
Val* SMB_Session::BuildHeaderVal(binpac::SMB::SMB_header const& hdr)
{
RecordVal* r = new RecordVal(smb_hdr);
unsigned int status = 0;
#if 0
try
{
// FIXME: does this work? We need to catch exceptions :-(
// or use guard functions.
status = hdr.status()->status() ||
hdr.status()->dos_error()->error_class() << 24 ||
hdr.status()->dos_error()->error();
}
catch ( const binpac::Exception& )
{ // do nothing
}
#endif
uint32_t pid = hdr.pid_high();
pid = (pid<<16) + hdr.pid();
r->Assign(0, new Val(hdr.command(), TYPE_COUNT));
r->Assign(1, new Val(status, TYPE_COUNT));
r->Assign(2, new Val(hdr.flags(), TYPE_COUNT));
r->Assign(3, new Val(hdr.flags2(), TYPE_COUNT));
r->Assign(4, new Val(hdr.tid(), TYPE_COUNT));
r->Assign(5, new Val(pid, TYPE_COUNT));
r->Assign(6, new Val(hdr.uid(), TYPE_COUNT));
r->Assign(7, new Val(hdr.mid(), TYPE_COUNT));
r->Assign(8, new Val(first_time, TYPE_TIME));
r->Assign(9, new Val(last_time, TYPE_TIME));
return r;
}
Val* SMB_Session::BuildTransactionVal(binpac::SMB::SMB_transaction const& trans)
{
RecordVal* r = new RecordVal(smb_trans);
// r->Assign(0, new Val(variable, type));
return r;
}
Val* SMB_Session::BuildTransactionVal(binpac::SMB::SMB_transaction_secondary const& trans)
{
RecordVal* r = new RecordVal(smb_trans);
// r->Assign(0, new Val(variable, type));
return r;
}
Val* SMB_Session::BuildTransactionVal(binpac::SMB::SMB_transaction_response const& trans)
{
RecordVal* r = new RecordVal(smb_trans);
// r->Assign(0, new Val(variable, type));
return r;
}
Val* SMB_Session::BuildTransactionDataVal(binpac::SMB::SMB_transaction_data *data)
{
RecordVal* r = new RecordVal(smb_trans_data);
// r->Assign(0, new Val(variable, type));
return r;
}
bool SMB_Session::LooksLikeRPC(int len, const u_char* msg)
{
try
{
binpac::DCE_RPC_Simple::DCE_RPC_Header h;
h.Parse(msg, msg + len);
if ( h.rpc_vers() == 5 && h.rpc_vers_minor() == 0 )
{
unsigned short frag_len = h.frag_length();
if ( frag_len == len ||
BYTEORDER_SWAP16(frag_len) == len )
{
if ( ! is_IPC && DEBUG_smb_ipc )
analyzer->Weird("TreeConnect to IPC missing");
return true;
}
else
{
analyzer->Weird(fmt("endianness %d", h.byteorder()));
analyzer->Weird(fmt("length mismatch: %d != %d",
h.frag_length(), len));
return false;
}
}
}
catch ( const binpac::Exception& )
{ // do nothing
}
return false;
}
bool SMB_Session::CheckRPC(int is_orig, int data_count, const u_char *data)
{
if ( LooksLikeRPC(data_count, data) )
{
if ( ! dce_rpc_session )
dce_rpc_session = new DCE_RPC_Session(analyzer);
dce_rpc_session->DeliverPDU(is_orig, data_count, data);
return true;
}
return false;
}
Contents_SMB::Contents_SMB(Connection* conn, bool orig, SMB_Session* s)
: TCP_SupportAnalyzer(AnalyzerTag::Contents_SMB, conn, orig)
{
smb_session = s;
state = WAIT_FOR_HDR;
resync_state = INSYNC;
first_time = last_time = 0.0;
hdr_buf.Init(4,4);
msg_len = 0;
msg_type = 0;
}
void Contents_SMB::Init()
{
TCP_SupportAnalyzer::Init();
NeedResync();
}
Contents_SMB::~Contents_SMB()
{
}
void Contents_SMB::Undelivered(int seq, int len, bool orig)
{
TCP_SupportAnalyzer::Undelivered(seq, len, orig);
NeedResync();
}
void Contents_SMB::DeliverSMB(int len, const u_char* data)
{
// Check the 4-byte header.
if ( strncmp((const char*) data, "\xffSMB", 4) )
{
Conn()->Weird(fmt("SMB-over-TCP header error: %02x %05x, >>\\x%02x%c%c%c<<",
//dshdr[0], dshdr[1], dshdr[2], dshdr[3],
msg_type, msg_len,
data[0], data[1], data[2], data[3]));
NeedResync();
}
else
smb_session->Deliver(IsOrig(), len, data, first_time, last_time);
}
bool Contents_SMB::CheckResync(int& len, const u_char*& data, bool orig)
{
if (resync_state == INSYNC)
return true;
// This is an attempt to re-synchronize the stream after a content gap.
// Returns true if we are in sync.
// Returns false otherwise (we are in resync mode)
//
// We try to look for the beginning of a SMB message, assuming
// SMB messages start at packet boundaries (though they may span
// over multiple packets) (note that the data* of DeliverStream()
// usually starts at a packet boundrary).
//
// Now lets see whether data points to the beginning of a
// SMB message. If the resync processs is successful, we should
// be at the beginning of a frame.
if ( len < 36 )
{
// Ignore small chunks.
// 4 byte NetBIOS header (or length field) + 32 Byte SMB header
Conn()->Weird(fmt("SMB resync: discard %d bytes\n",
len));
NeedResync();
return false;
}
const u_char *xdata = data;
int xlen = len;
bool discard_this_chunk = false;
// Check if it's a data message
if (xdata[0]!=0x00)
discard_this_chunk = true;
// Check if the flags / high-byte of the message length is < 1
if (xdata[1] > 1)
discard_this_chunk = true;
// check if the SMB header starts with \xFFSMB
if (strncmp((const char*) (xdata+4), "\xffSMB", 4)!=0)
discard_this_chunk = true;
if (discard_this_chunk)
{
NeedResync();
return false;
}
resync_state = INSYNC;
first_time = last_time = 0.0;
hdr_buf.Init(4,4);
msg_len = 0;
msg_type = 0;
fprintf(stderr, "Resync successful\n");
return true;
}
void Contents_SMB::DeliverStream(int len, const u_char* data, bool orig)
{
TCP_SupportAnalyzer::DeliverStream(len, data, orig);
if (!CheckResync(len, data, orig))
return; // Not in sync yet. Still resyncing
last_time = network_time;
while ( len > 0 )
{
switch (state) {
case WAIT_FOR_HDR:
{
if (first_time < 1e-3)
first_time = network_time;
bool got_hdr = hdr_buf.ConsumeChunk(data, len);
if (got_hdr)
{
// We have the 4 bytes header now
const u_char *dummy = hdr_buf.GetBuf();
if (dummy[1] > 1)
Conn()->Weird(fmt("NetBIOS session flags > 1: %d", dummy[1]));
msg_len = 0;
msg_type = dummy[0];
for ( int i =1; i < 4; i++)
msg_len = ( msg_len << 8) + dummy[i];
msg_buf.Init(SMB_MAX_LEN, msg_len);
state = WAIT_FOR_DATA;
}
}
break;
case WAIT_FOR_DATA:
{
bool got_all_data = msg_buf.ConsumeChunk(data, len);
if (got_all_data)
{
const u_char *dummy_p = msg_buf.GetBuf();
int dummy_len = (int) msg_buf.GetFill();
if (msg_type == 0x00 && dummy_len >= 32)
DeliverSMB(dummy_len, dummy_p);
else if (msg_type == 0x00)
{
Conn()->Weird(fmt("SMB too short: len=%d", msg_len));
NeedResync();
}
else
Conn()->Weird(fmt("SMB other msg type: %x", msg_type));
state = WAIT_FOR_HDR;
first_time = 0.0;
hdr_buf.Init(4,4);
}
}
break;
} // end switch
} // end while
}
SMB_Analyzer::SMB_Analyzer(Connection* conn)
: TCP_ApplicationAnalyzer(AnalyzerTag::SMB, conn)
{
smb_session = new SMB_Session(this);
o_smb = new Contents_SMB(conn, true, smb_session);
r_smb = new Contents_SMB(conn, false, smb_session);
AddSupportAnalyzer(o_smb);
AddSupportAnalyzer(r_smb);
}
SMB_Analyzer::~SMB_Analyzer()
{
delete smb_session;
}