Final touches to modbus analyzer for now.

- There are still some broken events in the modbus analyzer because
  I don't have traffic to test with (coil and record related events primarily).

- There are a few example scripts in policy/protocols/modbus
This commit is contained in:
Seth Hall 2012-10-31 23:34:43 -04:00
parent 009efbcb27
commit a2f336cc72
6 changed files with 549 additions and 179 deletions

View file

@ -3,24 +3,71 @@
module Modbus; module Modbus;
export { export {
redef enum Log::ID += { LOG };
type Info: record {
## Time of the request.
ts: time &log;
## Unique identifier for the connnection.
uid: string &log;
## Identifier for the connection.
id: conn_id &log;
## The name of the function message that was sent.
func: string &log &optional;
## The status of the response.
success: bool &log &default=T;
## The exception if the response was a failure.
exception: string &log &optional;
};
## Event that can be handled to access the Modbus record as it is sent on
## to the logging framework.
global log_modbus: event(rec: Info);
} }
redef record connection += {
modbus: Info &optional;
};
# Configure DPD and the packet filter. # Configure DPD and the packet filter.
redef capture_filters += { ["modbus"] = "tcp port 502" }; redef capture_filters += { ["modbus"] = "tcp port 502" };
redef dpd_config += { [ANALYZER_MODBUS] = [$ports = set(502/tcp)] }; redef dpd_config += { [ANALYZER_MODBUS] = [$ports = set(502/tcp)] };
redef likely_server_ports += { 502/tcp }; redef likely_server_ports += { 502/tcp };
event bro_init() &priority=5
event modbus_exception(c: connection, header: ModbusHeaders, code: count)
{ {
print fmt("%.6f %s There was an exception: %s", network_time(), c$id, exception_codes[code]); Log::create_stream(Modbus::LOG, [$columns=Info, $ev=log_modbus]);
} }
event modbus_message(c: connection, header: ModbusHeaders, is_orig: bool) event modbus_message(c: connection, headers: ModbusHeaders, is_orig: bool) &priority=5
{ {
#if ( function_codes[header$function_code] in set("READ_MULTIPLE_REGISTERS", "READ_WRITE_REGISTERS", "WRITE_MULTIPLE_REGISTERS") ) if ( ! c?$modbus )
# return; {
c$modbus = [$ts=network_time(), $uid=c$uid, $id=c$id];
}
print fmt("%.6f %s %s: %s", network_time(), c$id, is_orig ? "request":"response", function_codes[header$function_code]); c$modbus$ts = network_time();
c$modbus$func = function_codes[headers$function_code];
if ( ! is_orig &&
( headers$function_code >= 0x81 || headers$function_code <= 0x98 ) )
c$modbus$success = F;
else
c$modbus$success = T;
} }
event modbus_message(c: connection, headers: ModbusHeaders, is_orig: bool) &priority=-5
{
# Don't log now if this is an exception (log in the exception event handler)
if ( c$modbus$success )
Log::write(LOG, c$modbus);
}
event modbus_exception(c: connection, headers: ModbusHeaders, code: count) &priority=5
{
c$modbus$exception = exception_codes[code];
Log::write(LOG, c$modbus);
delete c$modbus$exception;
}

View file

@ -0,0 +1,56 @@
##! Script for tracking known Modbus masters and slaves.
##!
##! .. todo: This script needs a lot of work. What might be more interesting is to track
##! master/slave relationships based on commands sent and successful (non-exception)
##! responses.
module Known;
export {
redef enum Log::ID += { MODBUS_LOG };
type ModbusDeviceType: enum {
MODBUS_MASTER,
MODBUS_SLAVE,
};
type ModbusInfo: record {
## The time the device was discovered.
ts: time &log;
## The IP address of the host.
host: addr &log;
## The type of device being tracked.
device_type: ModbusDeviceType &log;
};
## The Modbus nodes being tracked.
global modbus_nodes: set[addr, ModbusDeviceType] &create_expire=1day &redef;
## Event that can be handled to access the loggable record as it is sent
## on to the logging framework.
global log_known_modbus: event(rec: ModbusInfo);
}
event bro_init() &priority=5
{
Log::create_stream(Known::MODBUS_LOG, [$columns=ModbusInfo, $ev=log_known_modbus]);
}
event modbus_message(c: connection, headers: ModbusHeaders, is_orig: bool)
{
local master = c$id$orig_h;
local slave = c$id$resp_h;
if ( [master, MODBUS_MASTER] !in modbus_nodes )
{
add modbus_nodes[master, MODBUS_MASTER];
Log::write(MODBUS_LOG, [$ts=network_time(), $host=master, $device_type=MODBUS_MASTER]);
}
if ( [slave, MODBUS_SLAVE] !in modbus_nodes )
{
add modbus_nodes[slave, MODBUS_SLAVE];
Log::write(MODBUS_LOG, [$ts=network_time(), $host=slave, $device_type=MODBUS_SLAVE]);
}
}

View file

@ -0,0 +1,94 @@
##! This script tracks the memory map of holding (read/write) registers and logs
##! changes as they are discovered.
##!
##! .. todo: Not all register reads and write functions are being supported yet.
module Modbus;
export {
redef enum Log::ID += { Modbus::REGISTER_CHANGE_LOG };
## The hosts that should have memory mapping enabled.
const track_memmap: Host = ALL_HOSTS &redef;
type MemmapInfo: record {
ts: time &log;
uid: string &log;
id: conn_id &log;
register: count &log;
old_val: count &log;
new_val: count &log;
delta: interval &log;
};
type RegisterValue: record {
last_set: time;
value: count;
};
## Indexed on the device register value and yielding the register value.
type Registers: table[count] of RegisterValue;
## The memory map of slaves is tracked with this variable.
global device_registers: table[addr] of Registers;
## This event is generated every time a register is seen to be different than
## it was previously seen to be.
global changed_register: event(c: connection, register: count, old_val: count, new_val: count, delta: interval);
}
redef record Modbus::Info += {
track_address: count &default=0;
};
event bro_init() &priority=5
{
Log::create_stream(Modbus::REGISTER_CHANGE_LOG, [$columns=MemmapInfo]);
}
event modbus_read_holding_registers_request(c: connection, headers: ModbusHeaders, start_address: count, quantity: count)
{
c$modbus$track_address = start_address+1;
}
event modbus_read_holding_registers_response(c: connection, headers: ModbusHeaders, registers: ModbusRegisters)
{
local slave = c$id$resp_h;
if ( ! addr_matches_host(slave, track_memmap ) )
return;
if ( slave !in device_registers )
device_registers[slave] = table();
local slave_regs = device_registers[slave];
for ( i in registers )
{
if ( c$modbus$track_address in slave_regs )
{
if ( slave_regs[c$modbus$track_address]$value != registers[i] )
{
local delta = network_time() - slave_regs[c$modbus$track_address]$last_set;
event Modbus::changed_register(c, c$modbus$track_address,
slave_regs[c$modbus$track_address]$value, registers[i],
delta);
slave_regs[c$modbus$track_address]$last_set = network_time();
slave_regs[c$modbus$track_address]$value = registers[i];
}
}
else
{
local tmp_reg: RegisterValue = [$last_set=network_time(), $value=registers[i]];
slave_regs[c$modbus$track_address] = tmp_reg;
}
++c$modbus$track_address;
}
}
event Modbus::changed_register(c: connection, register: count, old_val: count, new_val: count, delta: interval)
{
local rec: MemmapInfo = [$ts=network_time(), $uid=c$uid, $id=c$id,
$register=register, $old_val=old_val, $new_val=new_val, $delta=delta];
Log::write(REGISTER_CHANGE_LOG, rec);
}

View file

@ -6547,146 +6547,300 @@ event netflow_v5_header%(h: nf_v5_header%);
## .. bro:see:: netflow_v5_record ## .. bro:see:: netflow_v5_record
event netflow_v5_record%(r: nf_v5_record%); event netflow_v5_record%(r: nf_v5_record%);
## Event for any function code whether or not it's supported. ## Generated for any modbus message regardless of if the particular function
## is further supported or not.
## ##
## TODO-Dina: Document event. ## c: The connection.
event modbus_message%(c: connection, header: ModbusHeaders, is_orig: bool%);
## Event that parses modbus exception
## ##
## TODO-Dina: Document event. ## headers: The headers for the modbus function.
event modbus_exception%(c: connection, header: ModbusHeaders, code: count%);
## Event that passes modbus request function code=1
## ##
## TODO-Dina: Document event. ## is_orig: True if the event is raised for the originator side.
event modbus_read_coils_request%(c: connection, header: ModbusHeaders, start_address: count, quantity: count%); event modbus_message%(c: connection, headers: ModbusHeaders, is_orig: bool%);
## Event that passes modbus response function code=1 ## Generated for any modbus exception message.
## ##
## TODO-Dina: Document event. ## c: The connection.
event modbus_read_coils_response%(c: connection, header: ModbusHeaders, coils: ModbusCoils%);
## Event that passes modbus request function code=2
## ##
## TODO-Dina: Document event. ## headers: The headers for the modbus function.
event modbus_read_discrete_inputs_request%(c: connection, header: ModbusHeaders, start_address: count, quantity: count%);
## Event that passes modbus response function code=2
## ##
## TODO-Dina: Document event. ## code: The exception code.
event modbus_read_discrete_inputs_response%(c: connection, header: ModbusHeaders, coils: ModbusCoils%); event modbus_exception%(c: connection, headers: ModbusHeaders, code: count%);
## Event that passes modbus request function code=3 ## Generated for a Modbus read coils request.
## ##
## TODO-Dina: Document event. ## c: The connection.
event modbus_read_holding_registers_request%(c: connection, header: ModbusHeaders, start_address: count, quantity: count%);
## Event that passes modbus response function code=3
## ##
## TODO-Dina: Document event. ## headers: The headers for the modbus function.
event modbus_read_holding_registers_response%(c: connection, header: ModbusHeaders, registers: ModbusRegisters%);
## Event that passes modbus request function code =4
## ##
## TODO-Dina: Document event. ## start_address: The memory address where of the first coil to be read.
event modbus_read_input_registers_request%(c: connection, header: ModbusHeaders, start_address: count, quantity: count%);
## Event that passes modbus response function code=4
## ##
## TODO-Dina: Document event. ## quantity: The number of coils to be read.
event modbus_read_input_registers_response%(c: connection, header: ModbusHeaders, registers: ModbusRegisters%); event modbus_read_coils_request%(c: connection, headers: ModbusHeaders, start_address: count, quantity: count%);
## Event that passes modbus request function code=5 ## Generated for a Modbus read coils response.
## ##
## TODO-Dina: Document event. ## c: The connection.
event modbus_write_single_coil_request%(c: connection, header: ModbusHeaders, start_address: count, on_off: count, other: count%);
## Event that passes modbus request function code=5
## ##
## TODO-Dina: Document event. ## headers: The headers for the modbus function.
event modbus_write_single_coil_response%(c: connection, header: ModbusHeaders, start_address: count, on_off: count, other: count%);
## Event that passes modbus request function code=6
## ##
## TODO-Dina: Document event. ## coils: The coil values returned from the device.
event modbus_write_single_register_request%(c: connection, header: ModbusHeaders, start_address: count, value: count%); event modbus_read_coils_response%(c: connection, headers: ModbusHeaders, coils: ModbusCoils%);
## Event that passes modbus response function code=6 ## Generated for a Modbus read discrete inputs request.
## ##
## TODO-Dina: Document event. ## c: The connection.
event modbus_write_single_register_response%(c: connection, header: ModbusHeaders, start_address: count, value: count%);
## Event that passes modbus request function code=15
## ##
## TODO-Dina: Document event. ## headers: The headers for the modbus function.
event modbus_write_multiple_coils_request%(c: connection, header: ModbusHeaders, start_address: count, coils: ModbusCoils%);
## Event that passes modbus response function code=15
## ##
## TODO-Dina: Document event. ## start_address: The memory address of the first coil to be read.
event modbus_write_multiple_coils_response%(c: connection, header: ModbusHeaders, start_address: count, quantity: count%);
## Event that passes modbus request function code=16
## ##
## TODO-Dina: Document event. ## quantity: The number of coils to be read.
event modbus_write_multiple_registers_request%(c: connection, header: ModbusHeaders, start_address: count, registers: ModbusRegisters%); event modbus_read_discrete_inputs_request%(c: connection, headers: ModbusHeaders, start_address: count, quantity: count%);
## Event that passes modbus response function code=16 ## Generated for a Modbus read discrete inputs response.
## ##
## TODO-Dina: Document event. ## c: The connection.
event modbus_write_multiple_registers_response%(c: connection, header: ModbusHeaders, start_address: count, quantity: count%); ##
## headers: The headers for the modbus function.
##
## coils: The coil values returned from the device.
event modbus_read_discrete_inputs_response%(c: connection, headers: ModbusHeaders, coils: ModbusCoils%);
## Event that passes modbus request function code=20 ## Generated for a Modbus read holding registers request.
## ##
## TODO-Dina: Document event. ## c: The connection.
event modbus_read_file_record_request%(c: connection, header: ModbusHeaders%); ##
## headers: The headers for the modbus function.
##
## start_address: The memory address of the first register to be read.
##
## quantity: The number of registers to be read.
event modbus_read_holding_registers_request%(c: connection, headers: ModbusHeaders, start_address: count, quantity: count%);
## Event that passes modbus response function code=20 ## Generated for a Modbus read holding registers response.
## ##
## TODO-Dina: Document event. ## c: The connection.
event modbus_read_file_record_response%(c: connection, header: ModbusHeaders%); ##
## headers: The headers for the modbus function.
##
## registers: The register values returned from the device.
event modbus_read_holding_registers_response%(c: connection, headers: ModbusHeaders, registers: ModbusRegisters%);
## Event that passes modbus request function code=21 ## Generated for a Modbus read input registers request.
## ##
## TODO-Dina: Document event. ## c: The connection.
event modbus_write_file_record_request%(c: connection, header: ModbusHeaders%); ##
## headers: The headers for the modbus function.
##
## start_address: The memory address of the first register to be read.
##
## quantity: The number of registers to be read.
event modbus_read_input_registers_request%(c: connection, headers: ModbusHeaders, start_address: count, quantity: count%);
## Event that passes modbus response function code=21 ## Generated for a Modbus read input registers response.
## ##
## TODO-Dina: Document event. ## c: The connection.
event modbus_write_file_record_response%(c: connection, header: ModbusHeaders%); ##
## headers: The headers for the modbus function.
##
## registers: The register values returned from the device.
event modbus_read_input_registers_response%(c: connection, headers: ModbusHeaders, registers: ModbusRegisters%);
## Event that passes modbus request function code=22 ## Generated for a Modbus write single coil request.
## ##
## TODO-Dina: Document event. ## c: The connection.
event modbus_mask_write_register_request%(c: connection, header: ModbusHeaders, start_address: count, and_mask: count, or_mask: count%); ##
## headers: The headers for the modbus function.
##
## address: The memory address of the coil to be written.
##
## value: The value to be written to the coil.
event modbus_write_single_coil_request%(c: connection, headers: ModbusHeaders, address: count, value: bool%);
## Event that passes modbus response function code=22 ## Generated for a Modbus write single coil response.
## ##
## TODO-Dina: Document event. ## c: The connection.
event modbus_mask_write_register_response%(c: connection, header: ModbusHeaders, start_address: count, and_mask: count, or_mask: count%); ##
## headers: The headers for the modbus function.
##
## address: The memory address of the coil that was written.
##
## value: The value that was written to the coil.
event modbus_write_single_coil_response%(c: connection, headers: ModbusHeaders, address: count, value: bool%);
## Event that passes modbus request function code=23 ## Generated for a Modbus write single register request.
## ##
## TODO-Dina: Document event. ## c: The connection.
event modbus_read_write_multiple_registers_request%(c: connection, header: ModbusHeaders, read_start_address: count, read_quantity: count, write_start_address: count, write_registers: ModbusRegisters%); ##
## headers: The headers for the modbus function.
##
## address: The memory address of the register to be written.
##
## value: The value to be written to the register.
event modbus_write_single_register_request%(c: connection, headers: ModbusHeaders, address: count, value: count%);
## Event that passes modbus response function code=23 ## Generated for a Modbus write single register response.
## ##
## TODO-Dina: Document event. ## c: The connection.
event modbus_read_write_multiple_registers_response%(c: connection, header: ModbusHeaders, written_registers: ModbusRegisters%); ##
## headers: The headers for the modbus function.
##
## address: The memory address of the register that was written.
##
## value: The value that was written to the register.
event modbus_write_single_register_response%(c: connection, headers: ModbusHeaders, address: count, value: count%);
## Event that passes modbus request function code=24 ## Generated for a Modbus write multiple coils request.
## ##
## TODO-Dina: Document event. ## c: The connection.
event modbus_read_fifo_queue_request%(c: connection, header: ModbusHeaders, start_address: count%); ##
## headers: The headers for the modbus function.
##
## start_address: The memory address of the first coil to be written.
##
## value: The values to be written to the coils.
event modbus_write_multiple_coils_request%(c: connection, headers: ModbusHeaders, start_address: count, coils: ModbusCoils%);
## Event that passes modbus response function code=24 ## Generated for a Modbus write multiple coils response.
## ##
## TODO-Dina: Document event. ## c: The connection.
event modbus_read_fifo_queue_response%(c: connection, header: ModbusHeaders, fifos: ModbusRegisters%); ##
## headers: The headers for the modbus function.
##
## start_address: The memory address of the first coil that was written.
##
## quantity: The quantity of coils that were written.
event modbus_write_multiple_coils_response%(c: connection, headers: ModbusHeaders, start_address: count, quantity: count%);
## Generated for a Modbus write multiple registers request.
##
## c: The connection.
##
## headers: The headers for the modbus function.
##
## start_address: The memory address of the first register to be written.
##
## registers: The values to be written to the registers.
event modbus_write_multiple_registers_request%(c: connection, headers: ModbusHeaders, start_address: count, registers: ModbusRegisters%);
## Generated for a Modbus write multiple registers response.
##
## c: The connection.
##
## headers: The headers for the modbus function.
##
## start_address: The memory address of the first register that was written.
##
## quantity: The quantity of registers that were written.
event modbus_write_multiple_registers_response%(c: connection, headers: ModbusHeaders, start_address: count, quantity: count%);
## Generated for a Modbus read file record request.
##
## c: The connection.
##
## headers: The headers for the modbus function.
##
## .. note: This event is incomplete. The information from the data structure is not
## yet passed through to the event.
event modbus_read_file_record_request%(c: connection, headers: ModbusHeaders%);
## Generated for a Modbus read file record response.
##
## c: The connection.
##
## headers: The headers for the modbus function.
##
## .. note: This event is incomplete. The information from the data structure is not
## yet passed through to the event.
event modbus_read_file_record_response%(c: connection, headers: ModbusHeaders%);
## Generated for a Modbus write file record request.
##
## c: The connection.
##
## headers: The headers for the modbus function.
##
## .. note: This event is incomplete. The information from the data structure is not
## yet passed through to the event.
event modbus_write_file_record_request%(c: connection, headers: ModbusHeaders%);
## Generated for a Modbus write file record response.
##
## c: The connection.
##
## headers: The headers for the modbus function.
##
## .. note: This event is incomplete. The information from the data structure is not
## yet passed through to the event.
event modbus_write_file_record_response%(c: connection, headers: ModbusHeaders%);
## Generated for a Modbus mask write register request.
##
## c: The connection.
##
## headers: The headers for the modbus function.
##
## address: The memory address of the register where the masks should be applied.
##
## and_mask: The value of the logical AND mask to apply to the register.
##
## or_mask: The value of the logical OR mask to apply to the register.
event modbus_mask_write_register_request%(c: connection, headers: ModbusHeaders, address: count, and_mask: count, or_mask: count%);
## Generated for a Modbus mask write register request.
##
## c: The connection.
##
## headers: The headers for the modbus function.
##
## address: The memory address of the register where the masks were applied.
##
## and_mask: The value of the logical AND mask applied register.
##
## or_mask: The value of the logical OR mask applied to the register.
event modbus_mask_write_register_response%(c: connection, headers: ModbusHeaders, address: count, and_mask: count, or_mask: count%);
## Generated for a Modbus read/write multiple registers request.
##
## c: The connection.
##
## headers: The headers for the modbus function.
##
## read_start_address: The memory address of the first register to be read.
##
## read_quantity: The number of registers to read.
##
## write_start_address: The memory address of the first register to be written.
##
## write_registers: The values to be written to the registers.
event modbus_read_write_multiple_registers_request%(c: connection, headers: ModbusHeaders, read_start_address: count, read_quantity: count, write_start_address: count, write_registers: ModbusRegisters%);
## Generated for a Modbus read/write multiple registers response.
##
## c: The connection.
##
## headers: The headers for the modbus function.
##
## written_registers: The register values read from the registers specified in the request.
event modbus_read_write_multiple_registers_response%(c: connection, headers: ModbusHeaders, written_registers: ModbusRegisters%);
## Generated for a Modbus read FIFO queue request.
##
## c: The connection.
##
## headers: The headers for the modbus function.
##
## start_address: The address of the FIFO queue to read.
event modbus_read_fifo_queue_request%(c: connection, headers: ModbusHeaders, start_address: count%);
## Generated for a Modbus read FIFO queue response.
##
## c: The connection.
##
## headers: The headers for the modbus function.
##
## fifos: The register values read from the FIFO queue on the device.
event modbus_read_fifo_queue_response%(c: connection, headers: ModbusHeaders, fifos: ModbusRegisters%);
## Raised for informational messages reported via Bro's reporter framework. Such ## Raised for informational messages reported via Bro's reporter framework. Such
## messages may be generated internally by the event engine and also by other ## messages may be generated internally by the event engine and also by other

View file

@ -194,11 +194,23 @@ refine flow ModbusTCP_Flow += {
%{ %{
if ( ::modbus_write_single_coil_request ) if ( ::modbus_write_single_coil_request )
{ {
int val;
if ( ${message.value} == 0x0000 )
val = 0;
else if ( ${message.value} == 0xFF00 )
val = 1;
else
{
connection()->bro_analyzer()->ProtocolViolation(fmt("invalid value for modbus write single coil request %d",
${message.value}));
return false;
}
BifEvent::generate_modbus_write_single_coil_request(connection()->bro_analyzer(), BifEvent::generate_modbus_write_single_coil_request(connection()->bro_analyzer(),
connection()->bro_analyzer()->Conn(), connection()->bro_analyzer()->Conn(),
HeaderToBro(header), HeaderToBro(header),
${message.start_address}, ${message.address},
${message.on_off}, ${message.other}); val);
} }
return true; return true;
@ -209,11 +221,23 @@ refine flow ModbusTCP_Flow += {
%{ %{
if ( ::modbus_write_single_coil_response ) if ( ::modbus_write_single_coil_response )
{ {
int val;
if ( ${message.value} == 0x0000 )
val = 0;
else if ( ${message.value} == 0xFF00 )
val = 1;
else
{
connection()->bro_analyzer()->ProtocolViolation(fmt("invalid value for modbus write single coil response %d",
${message.value}));
return false;
}
BifEvent::generate_modbus_write_single_coil_response(connection()->bro_analyzer(), BifEvent::generate_modbus_write_single_coil_response(connection()->bro_analyzer(),
connection()->bro_analyzer()->Conn(), connection()->bro_analyzer()->Conn(),
HeaderToBro(header), HeaderToBro(header),
${message.start_address}, ${message.address},
${message.on_off}, ${message.other}); val);
} }
return true; return true;
@ -228,7 +252,7 @@ refine flow ModbusTCP_Flow += {
BifEvent::generate_modbus_write_single_register_request(connection()->bro_analyzer(), BifEvent::generate_modbus_write_single_register_request(connection()->bro_analyzer(),
connection()->bro_analyzer()->Conn(), connection()->bro_analyzer()->Conn(),
HeaderToBro(header), HeaderToBro(header),
${message.start_address}, ${message.value}); ${message.address}, ${message.value});
} }
return true; return true;
@ -242,7 +266,7 @@ refine flow ModbusTCP_Flow += {
BifEvent::generate_modbus_write_single_register_response(connection()->bro_analyzer(), BifEvent::generate_modbus_write_single_register_response(connection()->bro_analyzer(),
connection()->bro_analyzer()->Conn(), connection()->bro_analyzer()->Conn(),
HeaderToBro(header), HeaderToBro(header),
${message.start_address}, ${message.value}); ${message.address}, ${message.value});
} }
return true; return true;
@ -434,7 +458,7 @@ refine flow ModbusTCP_Flow += {
BifEvent::generate_modbus_mask_write_register_request(connection()->bro_analyzer(), BifEvent::generate_modbus_mask_write_register_request(connection()->bro_analyzer(),
connection()->bro_analyzer()->Conn(), connection()->bro_analyzer()->Conn(),
HeaderToBro(header), HeaderToBro(header),
${message.start_address}, ${message.address},
${message.and_mask}, ${message.or_mask}); ${message.and_mask}, ${message.or_mask});
} }
@ -449,7 +473,7 @@ refine flow ModbusTCP_Flow += {
BifEvent::generate_modbus_mask_write_register_response(connection()->bro_analyzer(), BifEvent::generate_modbus_mask_write_register_response(connection()->bro_analyzer(),
connection()->bro_analyzer()->Conn(), connection()->bro_analyzer()->Conn(),
HeaderToBro(header), HeaderToBro(header),
${message.start_address}, ${message.address},
${message.and_mask}, ${message.or_mask}); ${message.and_mask}, ${message.or_mask});
} }

View file

@ -14,13 +14,13 @@ enum function_codes {
READ_INPUT_REGISTERS = 0x04, READ_INPUT_REGISTERS = 0x04,
WRITE_SINGLE_COIL = 0x05, WRITE_SINGLE_COIL = 0x05,
WRITE_SINGLE_REGISTER = 0x06, WRITE_SINGLE_REGISTER = 0x06,
READ_EXCEPTION_STATUS = 0x07, # READ_EXCEPTION_STATUS = 0x07,
DIAGNOSTICS = 0x08, # DIAGNOSTICS = 0x08,
GET_COMM_EVENT_COUNTER = 0x0B, # GET_COMM_EVENT_COUNTER = 0x0B,
GET_COMM_EVENT_LOG = 0x0C, # GET_COMM_EVENT_LOG = 0x0C,
WRITE_MULTIPLE_COILS = 0x0F, WRITE_MULTIPLE_COILS = 0x0F,
WRITE_MULTIPLE_REGISTERS = 0x10, WRITE_MULTIPLE_REGISTERS = 0x10,
REPORT_SLAVE_ID = 0x11, # REPORT_SLAVE_ID = 0x11,
READ_FILE_RECORD = 0x14, READ_FILE_RECORD = 0x14,
WRITE_FILE_RECORD = 0x15, WRITE_FILE_RECORD = 0x15,
MASK_WRITE_REGISTER = 0x16, MASK_WRITE_REGISTER = 0x16,
@ -64,9 +64,7 @@ type ModbusTCP_PDU(is_orig: bool) = record {
true -> request: ModbusTCP_Request(header); true -> request: ModbusTCP_Request(header);
false -> response: ModbusTCP_Response(header); false -> response: ModbusTCP_Response(header);
}; };
} &length=header.len+6, &byteorder=bigendian, &let { } &length=header.len+6, &byteorder=bigendian;
deliver: bool = $context.flow.deliver_message(header);
};
type ModbusTCP_TransportHeader = record { type ModbusTCP_TransportHeader = record {
tid: uint16; # Transaction identifier tid: uint16; # Transaction identifier
@ -74,6 +72,8 @@ type ModbusTCP_TransportHeader = record {
len: uint16; # Length of everyting after this field len: uint16; # Length of everyting after this field
uid: uint8; # Unit identifier (previously 'slave address') uid: uint8; # Unit identifier (previously 'slave address')
fc: uint8; # MODBUS function code (see function_codes enum) fc: uint8; # MODBUS function code (see function_codes enum)
} &byteorder=bigendian, &let {
deliver: bool = $context.flow.deliver_message(this);
}; };
type ModbusTCP_Request(header: ModbusTCP_TransportHeader) = case header.fc of { type ModbusTCP_Request(header: ModbusTCP_TransportHeader) = case header.fc of {
@ -98,7 +98,7 @@ type ModbusTCP_Request(header: ModbusTCP_TransportHeader) = case header.fc of {
#ENCAP_INTERFACE_TRANSPORT #ENCAP_INTERFACE_TRANSPORT
# All the rest # All the rest
default -> unknown: empty &restofdata; default -> unknown: bytestring &restofdata;
}; };
# Responses # Responses
@ -140,7 +140,7 @@ type ModbusTCP_Response(header: ModbusTCP_TransportHeader) = case header.fc of {
READ_FIFO_QUEUE_EXCEPTION -> readFIFOQueueException: Exception(header); READ_FIFO_QUEUE_EXCEPTION -> readFIFOQueueException: Exception(header);
# All the rest # All the rest
default -> unknown: empty &restofdata; default -> unknown: bytestring &restofdata;
}; };
type Exception(header: ModbusTCP_TransportHeader) = record { type Exception(header: ModbusTCP_TransportHeader) = record {
@ -155,7 +155,7 @@ type ReadCoilsRequest(header: ModbusTCP_TransportHeader) = record {
quantity: uint16 &check(quantity <= 2000); quantity: uint16 &check(quantity <= 2000);
} &let { } &let {
deliver: bool = $context.flow.deliver_ReadCoilsRequest(header, this); deliver: bool = $context.flow.deliver_ReadCoilsRequest(header, this);
}; } &byteorder=bigendian;
# RESPONSE FC=1 # RESPONSE FC=1
type ReadCoilsResponse(header: ModbusTCP_TransportHeader) = record { type ReadCoilsResponse(header: ModbusTCP_TransportHeader) = record {
@ -163,7 +163,7 @@ type ReadCoilsResponse(header: ModbusTCP_TransportHeader) = record {
bits: bytestring &length=byte_count; bits: bytestring &length=byte_count;
} &let { } &let {
deliver: bool = $context.flow.deliver_ReadCoilsResponse(header, this); deliver: bool = $context.flow.deliver_ReadCoilsResponse(header, this);
}; } &byteorder=bigendian;
# REQUEST FC=2 # REQUEST FC=2
type ReadDiscreteInputsRequest(header: ModbusTCP_TransportHeader) = record { type ReadDiscreteInputsRequest(header: ModbusTCP_TransportHeader) = record {
@ -171,7 +171,7 @@ type ReadDiscreteInputsRequest(header: ModbusTCP_TransportHeader) = record {
quantity: uint16 &check(quantity <= 2000); quantity: uint16 &check(quantity <= 2000);
} &let { } &let {
deliver: bool = $context.flow.deliver_ReadDiscreteInputsRequest(header, this); deliver: bool = $context.flow.deliver_ReadDiscreteInputsRequest(header, this);
}; } &byteorder=bigendian;
# RESPONSE FC=2 # RESPONSE FC=2
type ReadDiscreteInputsResponse(header: ModbusTCP_TransportHeader) = record { type ReadDiscreteInputsResponse(header: ModbusTCP_TransportHeader) = record {
@ -179,7 +179,7 @@ type ReadDiscreteInputsResponse(header: ModbusTCP_TransportHeader) = record {
bits: bytestring &length=byte_count; bits: bytestring &length=byte_count;
} &let { } &let {
deliver: bool = $context.flow.deliver_ReadDiscreteInputsResponse(header, this); deliver: bool = $context.flow.deliver_ReadDiscreteInputsResponse(header, this);
}; } &byteorder=bigendian;
# REQUEST FC=3 # REQUEST FC=3
type ReadHoldingRegistersRequest(header: ModbusTCP_TransportHeader) = record { type ReadHoldingRegistersRequest(header: ModbusTCP_TransportHeader) = record {
@ -187,7 +187,7 @@ type ReadHoldingRegistersRequest(header: ModbusTCP_TransportHeader) = record {
quantity: uint16 &check(1 <= quantity && quantity <= 125); quantity: uint16 &check(1 <= quantity && quantity <= 125);
} &let { } &let {
deliver: bool = $context.flow.deliver_ReadHoldingRegistersRequest(header, this); deliver: bool = $context.flow.deliver_ReadHoldingRegistersRequest(header, this);
}; } &byteorder=bigendian;
# RESPONSE FC=3 # RESPONSE FC=3
type ReadHoldingRegistersResponse(header: ModbusTCP_TransportHeader) = record { type ReadHoldingRegistersResponse(header: ModbusTCP_TransportHeader) = record {
@ -195,7 +195,7 @@ type ReadHoldingRegistersResponse(header: ModbusTCP_TransportHeader) = record {
registers: uint16[] &length=byte_count; registers: uint16[] &length=byte_count;
} &let { } &let {
deliver: bool = $context.flow.deliver_ReadHoldingRegistersResponse(header, this); deliver: bool = $context.flow.deliver_ReadHoldingRegistersResponse(header, this);
}; } &byteorder=bigendian;
# REQUEST FC=4 # REQUEST FC=4
type ReadInputRegistersRequest(header: ModbusTCP_TransportHeader) = record { type ReadInputRegistersRequest(header: ModbusTCP_TransportHeader) = record {
@ -203,7 +203,7 @@ type ReadInputRegistersRequest(header: ModbusTCP_TransportHeader) = record {
quantity: uint16 &check(1 <= quantity && quantity <= 125); quantity: uint16 &check(1 <= quantity && quantity <= 125);
} &let { } &let {
deliver: bool = $context.flow.deliver_ReadInputRegistersRequest(header, this); deliver: bool = $context.flow.deliver_ReadInputRegistersRequest(header, this);
}; } &byteorder=bigendian;
# RESPONSE FC=4 # RESPONSE FC=4
type ReadInputRegistersResponse(header: ModbusTCP_TransportHeader) = record { type ReadInputRegistersResponse(header: ModbusTCP_TransportHeader) = record {
@ -211,41 +211,39 @@ type ReadInputRegistersResponse(header: ModbusTCP_TransportHeader) = record {
registers: uint16[] &length=byte_count; registers: uint16[] &length=byte_count;
} &let { } &let {
deliver: bool = $context.flow.deliver_ReadInputRegistersResponse(header, this); deliver: bool = $context.flow.deliver_ReadInputRegistersResponse(header, this);
}; } &byteorder=bigendian;
# REQUEST FC=5 # REQUEST FC=5
type WriteSingleCoilRequest(header: ModbusTCP_TransportHeader) = record { type WriteSingleCoilRequest(header: ModbusTCP_TransportHeader) = record {
start_address: uint16; address: uint16;
on_off: uint8 &check(on_off == 0x00 || on_off == 0xFF); value: uint16 &check(value == 0x0000 || value == 0xFF00);
other: uint8 &check(other == 0x00);
} &let { } &let {
deliver: bool = $context.flow.deliver_WriteSingleCoilRequest(header, this); deliver: bool = $context.flow.deliver_WriteSingleCoilRequest(header, this);
}; } &byteorder=bigendian;
# RESPONSE FC=5 # RESPONSE FC=5
type WriteSingleCoilResponse(header: ModbusTCP_TransportHeader) = record { type WriteSingleCoilResponse(header: ModbusTCP_TransportHeader) = record {
start_address: uint16; address: uint16;
on_off: uint8 &check(on_off == 0x00 || on_off == 0xFF); value: uint16 &check(value == 0x0000 || value == 0xFF00);
other: uint8 &check(other == 0x00);
} &let { } &let {
deliver: bool = $context.flow.deliver_WriteSingleCoilResponse(header, this); deliver: bool = $context.flow.deliver_WriteSingleCoilResponse(header, this);
}; } &byteorder=bigendian;
# REQUEST FC=6 # REQUEST FC=6
type WriteSingleRegisterRequest(header: ModbusTCP_TransportHeader) = record { type WriteSingleRegisterRequest(header: ModbusTCP_TransportHeader) = record {
start_address: uint16; address: uint16;
value: uint16; value: uint16;
} &let { } &let {
deliver: bool = $context.flow.deliver_WriteSingleRegisterRequest(header, this); deliver: bool = $context.flow.deliver_WriteSingleRegisterRequest(header, this);
}; } &byteorder=bigendian;
# RESPONSE FC=6 # RESPONSE FC=6
type WriteSingleRegisterResponse(header: ModbusTCP_TransportHeader) = record { type WriteSingleRegisterResponse(header: ModbusTCP_TransportHeader) = record {
start_address: uint16; address: uint16;
value: uint16; value: uint16;
} &let { } &let {
deliver: bool = $context.flow.deliver_WriteSingleRegisterResponse(header, this); deliver: bool = $context.flow.deliver_WriteSingleRegisterResponse(header, this);
}; } &byteorder=bigendian;
# REQUEST FC=15 # REQUEST FC=15
type WriteMultipleCoilsRequest(header: ModbusTCP_TransportHeader) = record { type WriteMultipleCoilsRequest(header: ModbusTCP_TransportHeader) = record {
@ -255,7 +253,7 @@ type WriteMultipleCoilsRequest(header: ModbusTCP_TransportHeader) = record {
coils: bytestring &length=byte_count; coils: bytestring &length=byte_count;
} &let { } &let {
deliver: bool = $context.flow.deliver_WriteMultipleCoilsRequest(header, this); deliver: bool = $context.flow.deliver_WriteMultipleCoilsRequest(header, this);
}; } &byteorder=bigendian;
# RESPONSE FC=15 # RESPONSE FC=15
type WriteMultipleCoilsResponse(header: ModbusTCP_TransportHeader) = record { type WriteMultipleCoilsResponse(header: ModbusTCP_TransportHeader) = record {
@ -263,7 +261,7 @@ type WriteMultipleCoilsResponse(header: ModbusTCP_TransportHeader) = record {
quantity: uint16 &check(quantity <= 0x07B0); quantity: uint16 &check(quantity <= 0x07B0);
} &let { } &let {
deliver: bool = $context.flow.deliver_WriteMultipleCoilsResponse(header, this); deliver: bool = $context.flow.deliver_WriteMultipleCoilsResponse(header, this);
}; } &byteorder=bigendian;
# REQUEST FC=16 # REQUEST FC=16
type WriteMultipleRegistersRequest(header: ModbusTCP_TransportHeader) = record { type WriteMultipleRegistersRequest(header: ModbusTCP_TransportHeader) = record {
@ -275,7 +273,7 @@ type WriteMultipleRegistersRequest(header: ModbusTCP_TransportHeader) = record {
registers: uint16[quantity] &length=byte_count; registers: uint16[quantity] &length=byte_count;
} &let { } &let {
deliver: bool = $context.flow.deliver_WriteMultipleRegistersRequest(header, this); deliver: bool = $context.flow.deliver_WriteMultipleRegistersRequest(header, this);
}; } &byteorder=bigendian;
# RESPONSE FC=16 # RESPONSE FC=16
type WriteMultipleRegistersResponse(header: ModbusTCP_TransportHeader) = record { type WriteMultipleRegistersResponse(header: ModbusTCP_TransportHeader) = record {
@ -283,39 +281,38 @@ type WriteMultipleRegistersResponse(header: ModbusTCP_TransportHeader) = record
quantity: uint16; quantity: uint16;
} &let { } &let {
deliver: bool = $context.flow.deliver_WriteMultipleRegistersResponse(header, this); deliver: bool = $context.flow.deliver_WriteMultipleRegistersResponse(header, this);
}; } &byteorder=bigendian;
# Support data structure for following message type. # Support data structure for following message type.
type FileRecordRequest = record { type FileRecordRequest = record {
ref_type: uint8; ref_type: uint8 &check(ref_type == 6);
file_num: uint16; file_num: uint16 &check(file_num > 0);
record_num: uint16; record_num: uint16 &check(record_num <= 0x270F);
record_len: uint16; record_len: uint16;
}; } &byteorder=bigendian;
# REQUEST FC=20 # REQUEST FC=20
type ReadFileRecordRequest(header: ModbusTCP_TransportHeader) = record { type ReadFileRecordRequest(header: ModbusTCP_TransportHeader) = record {
byte_count: uint8; byte_count: uint8 &check(byte_count >= 0x07 && byte_count <= 0xF5);
references: FileRecordRequest[] &length=byte_count; references: FileRecordRequest[] &length=byte_count;
} &let { } &let {
deliver: bool = $context.flow.deliver_ReadFileRecordRequest(header, this); deliver: bool = $context.flow.deliver_ReadFileRecordRequest(header, this);
}; } &byteorder=bigendian;
# Support data structure for the following message type. # Support data structure for the following message type.
type FileRecordResponse = record { type FileRecordResponse = record {
file_len: uint8; file_len: uint8 &check(file_len >= 0x07 && file_len <= 0xF5);
ref_type: uint8; ref_type: uint8 &check(ref_type == 6);
record_data: uint16[] &length=file_len; record_data: uint16[] &length=file_len;
}; } &byteorder=bigendian;
# RESPONSE FC=20 # RESPONSE FC=20
type ReadFileRecordResponse(header: ModbusTCP_TransportHeader) = record { type ReadFileRecordResponse(header: ModbusTCP_TransportHeader) = record {
byte_count: uint8; byte_count: uint8 &check(byte_count >= 0x07 && byte_count <= 0xF5);
references: FileRecordResponse[] &length=byte_count; references: FileRecordResponse[] &length=byte_count;
} &let { } &let {
deliver: bool = $context.flow.deliver_ReadFileRecordResponse(header, this); deliver: bool = $context.flow.deliver_ReadFileRecordResponse(header, this);
}; } &byteorder=bigendian;
# Support data structure for the two following message types. # Support data structure for the two following message types.
type ReferenceWithData = record { type ReferenceWithData = record {
@ -324,7 +321,7 @@ type ReferenceWithData = record {
record_num: uint16; record_num: uint16;
word_count: uint16; word_count: uint16;
register_value: uint16[word_count]; register_value: uint16[word_count];
}; } &byteorder=bigendian;
# REQUEST FC=21 # REQUEST FC=21
type WriteFileRecordRequest(header: ModbusTCP_TransportHeader) = record { type WriteFileRecordRequest(header: ModbusTCP_TransportHeader) = record {
@ -332,7 +329,7 @@ type WriteFileRecordRequest(header: ModbusTCP_TransportHeader) = record {
references: ReferenceWithData[] &length=byte_count; references: ReferenceWithData[] &length=byte_count;
} &let { } &let {
deliver: bool = $context.flow.deliver_WriteFileRecordRequest(header, this); deliver: bool = $context.flow.deliver_WriteFileRecordRequest(header, this);
}; } &byteorder=bigendian;
# RESPONSE FC=21 # RESPONSE FC=21
type WriteFileRecordResponse(header: ModbusTCP_TransportHeader) = record { type WriteFileRecordResponse(header: ModbusTCP_TransportHeader) = record {
@ -340,27 +337,25 @@ type WriteFileRecordResponse(header: ModbusTCP_TransportHeader) = record {
references: ReferenceWithData[] &length=byte_count; references: ReferenceWithData[] &length=byte_count;
} &let { } &let {
deliver: bool = $context.flow.deliver_WriteFileRecordResponse(header, this); deliver: bool = $context.flow.deliver_WriteFileRecordResponse(header, this);
}; } &byteorder=bigendian;
# REQUEST FC=22 # REQUEST FC=22
type MaskWriteRegisterRequest(header: ModbusTCP_TransportHeader) = record { type MaskWriteRegisterRequest(header: ModbusTCP_TransportHeader) = record {
start_address: uint16; address: uint16;
and_mask: uint16; and_mask: uint16;
or_mask: uint16; or_mask: uint16;
} &let { } &let {
deliver: bool = $context.flow.deliver_MaskWriteRegisterRequest(header, this); deliver: bool = $context.flow.deliver_MaskWriteRegisterRequest(header, this);
}; } &byteorder=bigendian;
# RESPONSE FC=22 # RESPONSE FC=22
type MaskWriteRegisterResponse(header: ModbusTCP_TransportHeader) = record { type MaskWriteRegisterResponse(header: ModbusTCP_TransportHeader) = record {
start_address: uint16; address: uint16;
and_mask: uint16; and_mask: uint16;
or_mask: uint16; or_mask: uint16;
} &let { } &let {
deliver: bool = $context.flow.deliver_MaskWriteRegisterResponse(header, this); deliver: bool = $context.flow.deliver_MaskWriteRegisterResponse(header, this);
}; } &byteorder=bigendian;
# REQUEST FC=23 # REQUEST FC=23
type ReadWriteMultipleRegistersRequest(header: ModbusTCP_TransportHeader) = record { type ReadWriteMultipleRegistersRequest(header: ModbusTCP_TransportHeader) = record {
@ -372,7 +367,7 @@ type ReadWriteMultipleRegistersRequest(header: ModbusTCP_TransportHeader) = reco
write_register_values: uint16[write_quantity] &length=write_byte_count; write_register_values: uint16[write_quantity] &length=write_byte_count;
} &let { } &let {
deliver: bool = $context.flow.deliver_ReadWriteMultipleRegistersRequest(header, this); deliver: bool = $context.flow.deliver_ReadWriteMultipleRegistersRequest(header, this);
}; } &byteorder=bigendian;
# RESPONSE FC=23 # RESPONSE FC=23
type ReadWriteMultipleRegistersResponse(header: ModbusTCP_TransportHeader) = record { type ReadWriteMultipleRegistersResponse(header: ModbusTCP_TransportHeader) = record {
@ -380,20 +375,20 @@ type ReadWriteMultipleRegistersResponse(header: ModbusTCP_TransportHeader) = rec
registers: uint16[] &length=byte_count; registers: uint16[] &length=byte_count;
} &let { } &let {
deliver: bool = $context.flow.deliver_ReadWriteMultipleRegistersResponse(header, this); deliver: bool = $context.flow.deliver_ReadWriteMultipleRegistersResponse(header, this);
}; } &byteorder=bigendian;
# REQUEST FC=24 # REQUEST FC=24
type ReadFIFOQueueRequest(header: ModbusTCP_TransportHeader) = record { type ReadFIFOQueueRequest(header: ModbusTCP_TransportHeader) = record {
start_address: uint16; start_address: uint16;
} &let{ } &let{
deliver: bool = $context.flow.deliver_ReadFIFOQueueRequest(header, this); deliver: bool = $context.flow.deliver_ReadFIFOQueueRequest(header, this);
}; } &byteorder=bigendian;
# RESPONSE FC=24 # RESPONSE FC=24
type ReadFIFOQueueResponse(header: ModbusTCP_TransportHeader) = record { type ReadFIFOQueueResponse(header: ModbusTCP_TransportHeader) = record {
byte_count: uint16 &check(byte_count <= 62); byte_count: uint16 &check(byte_count <= 62);
fifo_count: uint16 &check(word_count <= 31); fifo_count: uint16 &check(fifo_count <= 31);
register_data: uint16[fifo_count]; register_data: uint16[fifo_count] &length=byte_count/2;
} &let { } &let {
deliver: bool = $context.flow.deliver_ReadFIFOQueueResponse(header, this); deliver: bool = $context.flow.deliver_ReadFIFOQueueResponse(header, this);
}; } &byteorder=bigendian;