mirror of
https://github.com/zeek/zeek.git
synced 2025-10-02 22:58:20 +00:00
Modbus analyzer, current support: FC=3,4,5,6,7,16,22,23
This commit is contained in:
parent
4da209d3b1
commit
5c756dcebf
12 changed files with 1773 additions and 0 deletions
|
@ -53,6 +53,16 @@ type string_vec: vector of string;
|
||||||
## then remove this alias.
|
## then remove this alias.
|
||||||
type addr_vec: vector of addr;
|
type addr_vec: vector of addr;
|
||||||
|
|
||||||
|
|
||||||
|
## A vector of int, used in Modbus function to pass data arrays
|
||||||
|
# (byDina)
|
||||||
|
type int_vec:vector of int;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## A table of strings indexed by strings.
|
## A table of strings indexed by strings.
|
||||||
##
|
##
|
||||||
## .. todo:: We need this type definition only for declaring builtin functions via
|
## .. todo:: We need this type definition only for declaring builtin functions via
|
||||||
|
|
707
scripts/base/protocols/modbus/modbus.bro
Normal file
707
scripts/base/protocols/modbus/modbus.bro
Normal file
|
@ -0,0 +1,707 @@
|
||||||
|
@load base/utils/files
|
||||||
|
@load base/protocols/modbus/utils
|
||||||
|
|
||||||
|
global modbus_ports={502/tcp};
|
||||||
|
|
||||||
|
redef dpd_config+={[ANALYZER_MODBUS]=[$ports=modbus_ports]};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
global path:string="/home/dina/pcaps_all/logs/simulations/";
|
||||||
|
|
||||||
|
# raise this (simple) event if you do not have the specific one bellow
|
||||||
|
event modbus_request(c:connection,is_orig:bool,tid:count, pid:count,uid:count, fc:count)
|
||||||
|
{
|
||||||
|
local e : file;
|
||||||
|
local g:file;
|
||||||
|
local ftime:string;
|
||||||
|
local src:string;
|
||||||
|
local dst:string;
|
||||||
|
local src_p:string;
|
||||||
|
local dst_p:string;
|
||||||
|
|
||||||
|
e=open_for_append (string_cat(path,"fall.log"));
|
||||||
|
g=open_for_append (string_cat(path,"missing_fc.log"));
|
||||||
|
|
||||||
|
ftime=strftime("%F %T",network_time());
|
||||||
|
src_p=cat(c$id$orig_p);
|
||||||
|
dst_p=cat(c$id$resp_p);
|
||||||
|
src= cat(c$id$orig_h);
|
||||||
|
dst=cat(c$id$resp_h);
|
||||||
|
|
||||||
|
|
||||||
|
local text=string_cat(ftime,"\t",src,"\t",dst,"\t",src_p, "\t REQUEST \t",cat(tid), "\t",cat(pid),"\t",cat(uid),"\t",cat(check_e(fc)),"\n");
|
||||||
|
|
||||||
|
|
||||||
|
local nfc:count;
|
||||||
|
nfc=check_e(fc);
|
||||||
|
if ((nfc!=3)&&(nfc!=7)&&(nfc!=16)&&(nfc!=23))
|
||||||
|
{
|
||||||
|
write_file(e,text);
|
||||||
|
local missing=string_cat(cat(nfc),"\n");
|
||||||
|
write_file(g,missing);
|
||||||
|
}
|
||||||
|
close(e);
|
||||||
|
close(g);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
event modbus_response(c:connection,is_orig:bool,tid:count,pid: count,uid:count, fc:count)
|
||||||
|
{
|
||||||
|
local e : file;
|
||||||
|
local g : file;
|
||||||
|
local ftime:string;
|
||||||
|
local src:string;
|
||||||
|
local dst:string;
|
||||||
|
local src_p:string;
|
||||||
|
local dst_p:string;
|
||||||
|
|
||||||
|
|
||||||
|
e=open_for_append (string_cat(path,"fall.log"));
|
||||||
|
g=open_for_append (string_cat(path,"missing_fc_new.log"));
|
||||||
|
ftime=strftime("%F %T",network_time());
|
||||||
|
|
||||||
|
src= cat(c$id$orig_h);
|
||||||
|
dst=cat(c$id$resp_h);
|
||||||
|
src_p=cat(c$id$orig_p);
|
||||||
|
dst_p=cat(c$id$resp_p);
|
||||||
|
|
||||||
|
local text=string_cat(ftime,"\t",src,"\t",dst,"\t",src_p, "\t RESPONSE \t",cat(tid), "\t",cat(pid),"\t",cat(uid),"\t",cat(check_e(fc)),"\n");
|
||||||
|
|
||||||
|
local nfc:count;
|
||||||
|
nfc=check_e(fc);
|
||||||
|
if ((nfc!=3)&&(nfc!=4)&&(nfc!=5)&&(nfc!=6)&&(nfc!=7)&&(nfc!=16)&&(nfc!=23))
|
||||||
|
{
|
||||||
|
|
||||||
|
write_file(e,text);
|
||||||
|
local missing=string_cat(cat(nfc),"\n");
|
||||||
|
# print fmt("******************************************************************* I got this: %d ",fc);
|
||||||
|
write_file(g,missing);
|
||||||
|
}
|
||||||
|
|
||||||
|
#print fmt("Ola amigo, transaction id is %d, process id is %d, slave address is %d, function code request is %d",tid,pid,uid,fc);
|
||||||
|
|
||||||
|
close(e);
|
||||||
|
close(g);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#REQUEST FC=3
|
||||||
|
event modbus_read_multi_request(c:connection,is_orig:bool,tid:count,pid:count,uid:count,fc:count, ref:count, wcount:count,len:count)
|
||||||
|
{
|
||||||
|
|
||||||
|
local f:file;
|
||||||
|
local m:file;
|
||||||
|
local ftime:string;
|
||||||
|
local src:string;
|
||||||
|
local dst:string;
|
||||||
|
local src_p:string;
|
||||||
|
local dst_p:string;
|
||||||
|
|
||||||
|
f=open_for_append (string_cat(path,"f3_new.log"));
|
||||||
|
m=open_for_append (string_cat(path,"fall_new.log"));
|
||||||
|
ftime=strftime("%F %T",network_time());
|
||||||
|
|
||||||
|
src= cat(c$id$orig_h);
|
||||||
|
dst=cat(c$id$resp_h);
|
||||||
|
src_p=cat(c$id$orig_p);
|
||||||
|
dst_p=cat(c$id$resp_p);
|
||||||
|
|
||||||
|
#according to the specification, this FC typically has 4xxxx offset in the memory map
|
||||||
|
local prefix_ref:count;
|
||||||
|
prefix_ref=ref+40000;
|
||||||
|
|
||||||
|
|
||||||
|
local text=string_cat(ftime,"\t",src,"\t",dst,"\t", src_p, "\t REQUEST \t",cat(len),"\t",cat(tid), "\t",cat(pid),"\t", cat(uid),"\t", cat(fc),"\t", cat(prefix_ref), "\t", cat(wcount),"\n");
|
||||||
|
|
||||||
|
write_file(f,text);
|
||||||
|
write_file(m,text);
|
||||||
|
|
||||||
|
print fmt("flying");
|
||||||
|
close(f);
|
||||||
|
close(m);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#RESPONSE FC=3
|
||||||
|
event modbus_read_multi_response(c:connection,is_orig:bool,t:int_vec,tid:count,pid:count,uid:count,fc:count,bCount:count,len:count)
|
||||||
|
{
|
||||||
|
|
||||||
|
local h:file;
|
||||||
|
local m:file;
|
||||||
|
local ftime:string;
|
||||||
|
local src:string;
|
||||||
|
local dst:string;
|
||||||
|
local src_p:string;
|
||||||
|
local dst_p:string;
|
||||||
|
|
||||||
|
h=open_for_append (string_cat(path,"f3_new.log"));
|
||||||
|
m=open_for_append (string_cat(path,"fall_new.log"));
|
||||||
|
ftime=strftime("%F %T",network_time());
|
||||||
|
|
||||||
|
src= cat(c$id$orig_h);
|
||||||
|
dst=cat(c$id$resp_h);
|
||||||
|
src_p=cat(c$id$orig_p);
|
||||||
|
dst_p=cat(c$id$resp_p);
|
||||||
|
|
||||||
|
local text=string_cat(ftime,"\t",src,"\t",dst,"\t", src_p, "\t RESPONSE \t",cat(len),"\t",cat(tid), "\t",cat(pid),"\t", cat(uid),"\t", cat(fc),"\t",cat(bCount), "\t",cat(t),"\n");
|
||||||
|
|
||||||
|
write_file(h,text);
|
||||||
|
write_file(m,text);
|
||||||
|
|
||||||
|
|
||||||
|
close(h);
|
||||||
|
close(m);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#REQUEST FC=4
|
||||||
|
event modbus_read_input_request(c:connection,is_orig:bool,tid:count,pid:count,uid:count,fc:count, ref:count, wcount:count,len:count)
|
||||||
|
{
|
||||||
|
|
||||||
|
local f:file;
|
||||||
|
local m:file;
|
||||||
|
local ftime:string;
|
||||||
|
local src:string;
|
||||||
|
local dst:string;
|
||||||
|
local src_p:string;
|
||||||
|
local dst_p:string;
|
||||||
|
|
||||||
|
f=open_for_append (string_cat(path,"f4_new.log"));
|
||||||
|
m=open_for_append (string_cat(path,"fall_new.log"));
|
||||||
|
ftime=strftime("%F %T",network_time());
|
||||||
|
|
||||||
|
|
||||||
|
src= cat(c$id$orig_h);
|
||||||
|
dst=cat(c$id$resp_h);
|
||||||
|
src_p=cat(c$id$orig_p);
|
||||||
|
dst_p=cat(c$id$resp_p);
|
||||||
|
|
||||||
|
#according to the specification, this FC typically has 3xxxx offset in the memory map
|
||||||
|
local prefix_ref:count;
|
||||||
|
prefix_ref=ref+30000;
|
||||||
|
|
||||||
|
local text=string_cat(ftime,"\t",src,"\t",dst,"\t", src_p, "\t REQUEST \t",cat(len),"\t",cat(tid), "\t",cat(pid),"\t", cat(uid),"\t", cat(fc),"\t", cat(prefix_ref), "\t", cat(wcount),"\n");
|
||||||
|
write_file(f,text);
|
||||||
|
write_file(m,text);
|
||||||
|
|
||||||
|
print fmt("flying");
|
||||||
|
|
||||||
|
close(f);
|
||||||
|
close(m);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#RESPONSE FC=4
|
||||||
|
event modbus_read_input_response(c:connection,is_orig:bool,t:int_vec,tid:count,pid:count,uid:count,fc:count,bCount:count,len:count)
|
||||||
|
{
|
||||||
|
|
||||||
|
local h:file;
|
||||||
|
local m:file;
|
||||||
|
local ftime:string;
|
||||||
|
local src:string;
|
||||||
|
local dst:string;
|
||||||
|
local src_p:string;
|
||||||
|
local dst_p:string;
|
||||||
|
|
||||||
|
h=open_for_append (string_cat(path,"f4_new.log"));
|
||||||
|
m=open_for_append (string_cat(path,"fall_new.log"));
|
||||||
|
ftime=strftime("%F %T",network_time());
|
||||||
|
|
||||||
|
src= cat(c$id$orig_h);
|
||||||
|
dst=cat(c$id$resp_h);
|
||||||
|
src_p=cat(c$id$orig_p);
|
||||||
|
dst_p=cat(c$id$resp_p);
|
||||||
|
|
||||||
|
|
||||||
|
local text=string_cat(ftime,"\t",src,"\t",dst,"\t", src_p, "\t RESPONSE \t",cat(len),"\t",cat(tid), "\t",cat(pid),"\t", cat(uid),"\t", cat(fc),"\t",cat(bCount), "\t",cat(t),"\n");
|
||||||
|
|
||||||
|
write_file(h,text);
|
||||||
|
write_file(m,text);
|
||||||
|
|
||||||
|
|
||||||
|
close(h);
|
||||||
|
close(m);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#REQUEST FC=5
|
||||||
|
event modbus_write_coil_request(c:connection,is_orig:bool,tid:count,pid:count,uid:count,fc:count,ref:count,onOff:count,other:count)
|
||||||
|
{
|
||||||
|
|
||||||
|
local h:file;
|
||||||
|
local m:file;
|
||||||
|
local ftime:string;
|
||||||
|
local src:string;
|
||||||
|
local dst:string;
|
||||||
|
local src_p:string;
|
||||||
|
local dst_p:string;
|
||||||
|
|
||||||
|
h=open_for_append (string_cat(path,"f5_new.log"));
|
||||||
|
m=open_for_append (string_cat(path,"fall_new.log"));
|
||||||
|
|
||||||
|
ftime=strftime("%F %T",network_time());
|
||||||
|
|
||||||
|
src= cat(c$id$orig_h);
|
||||||
|
dst=cat(c$id$resp_h);
|
||||||
|
|
||||||
|
src_p=cat(c$id$orig_p);
|
||||||
|
dst_p=cat(c$id$resp_p);
|
||||||
|
|
||||||
|
#according to the specification, this FC typically has 0xxxx offset in the memory map
|
||||||
|
#local prefix_ref:count;
|
||||||
|
#prefix_ref=ref+40000;
|
||||||
|
|
||||||
|
local text=string_cat(ftime,"\t",src,"\t",dst,"\t", src_p, "\t REQUEST \t",cat(tid), "\t",cat(pid),"\t", cat(uid),"\t", cat(fc),"\t",cat(ref), "\t",cat(onOff),"\t",cat(other),"\n");
|
||||||
|
|
||||||
|
write_file(h,text);
|
||||||
|
write_file(m,text);
|
||||||
|
|
||||||
|
close(h);
|
||||||
|
close(m);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#RESPONSE FC=5
|
||||||
|
event modbus_write_coil_response(c:connection,is_orig:bool,tid:count,pid:count,uid:count,fc:count,ref:count,onOff:count,other:count)
|
||||||
|
{
|
||||||
|
|
||||||
|
local h:file;
|
||||||
|
local m:file;
|
||||||
|
local ftime:string;
|
||||||
|
local src:string;
|
||||||
|
local dst:string;
|
||||||
|
local src_p:string;
|
||||||
|
local dst_p:string;
|
||||||
|
|
||||||
|
h=open_for_append (string_cat(path,"f5_new.log"));
|
||||||
|
m=open_for_append (string_cat(path,"fall_new.log"));
|
||||||
|
|
||||||
|
ftime=strftime("%F %T",network_time());
|
||||||
|
|
||||||
|
src= cat(c$id$orig_h);
|
||||||
|
dst=cat(c$id$resp_h);
|
||||||
|
|
||||||
|
src_p=cat(c$id$orig_p);
|
||||||
|
dst_p=cat(c$id$resp_p);
|
||||||
|
|
||||||
|
#according to the specification, this FC typically has 0xxxx offset in the memory map
|
||||||
|
#local prefix_ref:count;
|
||||||
|
#prefix_ref=ref+00000;
|
||||||
|
|
||||||
|
local text=string_cat(ftime,"\t",src,"\t",dst,"\t", src_p, "\t RESPONSE \t","\t",cat(tid), "\t",cat(pid),"\t", cat(uid),"\t", cat(fc),"\t",cat(ref), "\t",cat(onOff),"\t",cat(other),"\n");
|
||||||
|
|
||||||
|
write_file(h,text);
|
||||||
|
write_file(m,text);
|
||||||
|
|
||||||
|
close(h);
|
||||||
|
close(m);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#REQUEST FC=6
|
||||||
|
event modbus_write_single_request(c:connection,is_orig:bool,tid:count,pid:count,uid:count,fc:count,len:count,ref:count,value:count)
|
||||||
|
{
|
||||||
|
|
||||||
|
local h:file;
|
||||||
|
local m:file;
|
||||||
|
local ftime:string;
|
||||||
|
local src:string;
|
||||||
|
local dst:string;
|
||||||
|
local src_p:string;
|
||||||
|
local dst_p:string;
|
||||||
|
|
||||||
|
h=open_for_append (string_cat(path,"f6_new.log"));
|
||||||
|
m=open_for_append (string_cat(path,"fall_new.log"));
|
||||||
|
|
||||||
|
ftime=strftime("%F %T",network_time());
|
||||||
|
|
||||||
|
src= cat(c$id$orig_h);
|
||||||
|
dst=cat(c$id$resp_h);
|
||||||
|
|
||||||
|
src_p=cat(c$id$orig_p);
|
||||||
|
dst_p=cat(c$id$resp_p);
|
||||||
|
|
||||||
|
#according to the specification, this FC typically has 4xxxx offset in the memory map
|
||||||
|
local prefix_ref:count;
|
||||||
|
prefix_ref=ref+40000;
|
||||||
|
|
||||||
|
|
||||||
|
local text=string_cat(ftime,"\t",src,"\t",dst,"\t", src_p, "\t REQUEST \t",cat(len),"\t",cat(tid), "\t",cat(pid),"\t", cat(uid),"\t", cat(fc),"\t",cat(prefix_ref), "\t",cat(value),"\n");
|
||||||
|
|
||||||
|
write_file(h,text);
|
||||||
|
write_file(m,text);
|
||||||
|
|
||||||
|
close(h);
|
||||||
|
close(m);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#RESPONSE FC=6
|
||||||
|
event modbus_write_single_response(c:connection,is_orig:bool,tid:count,pid:count,uid:count,fc:count,len:count,ref:count,value:count)
|
||||||
|
{
|
||||||
|
|
||||||
|
local h:file;
|
||||||
|
local m:file;
|
||||||
|
local ftime:string;
|
||||||
|
local src:string;
|
||||||
|
local dst:string;
|
||||||
|
local src_p:string;
|
||||||
|
local dst_p:string;
|
||||||
|
|
||||||
|
h=open_for_append (string_cat(path,"f6_new.log"));
|
||||||
|
m=open_for_append (string_cat(path,"fall_new.log"));
|
||||||
|
ftime=strftime("%F %T",network_time());
|
||||||
|
src= cat(c$id$orig_h);
|
||||||
|
dst=cat(c$id$resp_h);
|
||||||
|
|
||||||
|
src_p=cat(c$id$orig_p);
|
||||||
|
dst_p=cat(c$id$resp_p);
|
||||||
|
|
||||||
|
#according to the specification, this FC usually has 4xxxx offset in the memory map
|
||||||
|
local prefix_ref:count;
|
||||||
|
prefix_ref=ref+40000;
|
||||||
|
|
||||||
|
local text=string_cat(ftime,"\t",src,"\t",dst,"\t", src_p, "\t RESPONSE \t",cat(len),"\t",cat(tid), "\t",cat(pid),"\t", cat(uid),"\t", cat(fc),"\t",cat(prefix_ref), "\t",cat(value),"\n");
|
||||||
|
|
||||||
|
write_file(h,text);
|
||||||
|
write_file(m,text);
|
||||||
|
|
||||||
|
close(h);
|
||||||
|
close(m);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#REQUEST FC=16
|
||||||
|
event modbus_write_multi_request(c:connection,is_orig:bool,t:int_vec,tid:count,pid:count,uid:count,fc:count,ref:count,wCount:count,bCount:count,len:count)
|
||||||
|
{
|
||||||
|
|
||||||
|
local k:file;
|
||||||
|
local m:file;
|
||||||
|
local ftime:string;
|
||||||
|
local src:string;
|
||||||
|
local dst:string;
|
||||||
|
local src_p:string;
|
||||||
|
local dst_p:string;
|
||||||
|
|
||||||
|
|
||||||
|
k=open_for_append (string_cat(path,"f16_new.log"));
|
||||||
|
m=open_for_append (string_cat(path,"fall_new.log"));
|
||||||
|
ftime=strftime("%F %T",network_time());
|
||||||
|
|
||||||
|
src= cat(c$id$orig_h);
|
||||||
|
dst=cat(c$id$resp_h);
|
||||||
|
src_p=cat(c$id$orig_p);
|
||||||
|
dst_p=cat(c$id$resp_p);
|
||||||
|
|
||||||
|
#according to the specification, this FC usually has 4xxxx offset in the memory map
|
||||||
|
local prefix_ref:count;
|
||||||
|
prefix_ref=ref+40000;
|
||||||
|
|
||||||
|
|
||||||
|
local text=string_cat(ftime,"\t",src,"\t",dst,"\t",src_p, "\t REQUEST \t",cat(len),"\t",cat(tid), "\t",cat(pid),"\t", cat(uid),"\t",cat(fc),"\t",cat(prefix_ref), "\t",cat(wCount), "\t", cat(bCount),"\t",cat(t),"\n");
|
||||||
|
|
||||||
|
write_file(k,text);
|
||||||
|
write_file(m,text);
|
||||||
|
|
||||||
|
close(k);
|
||||||
|
close(m);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#RESPONSE FC=16
|
||||||
|
event modbus_write_multi_response(c:connection,is_orig:bool,tid:count,pid:count,uid:count,fc:count, ref:count, wcount:count,len:count)
|
||||||
|
{
|
||||||
|
local o:file;
|
||||||
|
local m:file;
|
||||||
|
local ftime:string;
|
||||||
|
local src:string;
|
||||||
|
local dst:string;
|
||||||
|
local src_p:string;
|
||||||
|
local dst_p:string;
|
||||||
|
|
||||||
|
o=open_for_append (string_cat(path,"f16_new.log"));
|
||||||
|
m=open_for_append (string_cat(path,"fall_new.log"));
|
||||||
|
ftime=strftime("%F %T",network_time());
|
||||||
|
|
||||||
|
|
||||||
|
src= cat(c$id$orig_h);
|
||||||
|
dst=cat(c$id$resp_h);
|
||||||
|
src_p=cat(c$id$orig_p);
|
||||||
|
dst_p=cat(c$id$resp_p);
|
||||||
|
|
||||||
|
#according to the specification, this FC usually has 4xxxx offset in the memory map
|
||||||
|
local prefix_ref:count;
|
||||||
|
prefix_ref=ref+40000;
|
||||||
|
|
||||||
|
local text=string_cat(ftime,"\t",src,"\t",dst,"\t",src_p, "\t RESPONSE \t",cat(len),"\t",cat(tid), "\t",cat(pid),"\t", cat(uid),"\t",cat(fc),"\t",cat(prefix_ref), "\t", cat(wcount),"\n");
|
||||||
|
|
||||||
|
write_file(o,text);
|
||||||
|
write_file(m,text);
|
||||||
|
|
||||||
|
close(m);
|
||||||
|
close(o);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#REQUEST FC=22
|
||||||
|
event modbus_mask_write_request(c:connection,is_orig:bool,tid:count,pid:count,uid:count,fc:count,ref:count,andMask:count,orMask:count)
|
||||||
|
{
|
||||||
|
|
||||||
|
local h:file;
|
||||||
|
local m:file;
|
||||||
|
local ftime:string;
|
||||||
|
local src:string;
|
||||||
|
local dst:string;
|
||||||
|
local src_p:string;
|
||||||
|
local dst_p:string;
|
||||||
|
|
||||||
|
h=open_for_append (string_cat(path,"f22_new.log"));
|
||||||
|
m=open_for_append (string_cat(path,"fall_new.log"));
|
||||||
|
|
||||||
|
ftime=strftime("%F %T",network_time());
|
||||||
|
|
||||||
|
src= cat(c$id$orig_h);
|
||||||
|
dst=cat(c$id$resp_h);
|
||||||
|
|
||||||
|
src_p=cat(c$id$orig_p);
|
||||||
|
dst_p=cat(c$id$resp_p);
|
||||||
|
|
||||||
|
#according to the specification, this FC typically has 0xxxx offset in the memory map
|
||||||
|
#local prefix_ref:count;
|
||||||
|
#prefix_ref=ref+00000;
|
||||||
|
|
||||||
|
local text=string_cat(ftime,"\t",src,"\t",dst,"\t", src_p, "\t REQUEST \t",cat(tid), "\t",cat(pid),"\t", cat(uid),"\t", cat(fc),"\t",cat(ref), "\t",cat(andMask),"\t",cat(orMask),"\n");
|
||||||
|
|
||||||
|
write_file(h,text);
|
||||||
|
write_file(m,text);
|
||||||
|
|
||||||
|
close(h);
|
||||||
|
close(m);
|
||||||
|
}
|
||||||
|
|
||||||
|
#RESPONSE FC=22
|
||||||
|
event modbus_mask_write_response(c:connection,is_orig:bool,tid:count,pid:count,uid:count,fc:count,ref:count,andMask:count,orMask:count)
|
||||||
|
{
|
||||||
|
|
||||||
|
local h:file;
|
||||||
|
local m:file;
|
||||||
|
local ftime:string;
|
||||||
|
local src:string;
|
||||||
|
local dst:string;
|
||||||
|
local src_p:string;
|
||||||
|
local dst_p:string;
|
||||||
|
|
||||||
|
h=open_for_append (string_cat(path,"f22_new.log"));
|
||||||
|
m=open_for_append (string_cat(path,"fall_new.log"));
|
||||||
|
|
||||||
|
ftime=strftime("%F %T",network_time());
|
||||||
|
|
||||||
|
src= cat(c$id$orig_h);
|
||||||
|
dst=cat(c$id$resp_h);
|
||||||
|
|
||||||
|
src_p=cat(c$id$orig_p);
|
||||||
|
dst_p=cat(c$id$resp_p);
|
||||||
|
|
||||||
|
#according to the specification, this FC typically has 0xxxx offset in the memory map
|
||||||
|
#local prefix_ref:count;
|
||||||
|
#prefix_ref=ref+00000;
|
||||||
|
|
||||||
|
local text=string_cat(ftime,"\t",src,"\t",dst,"\t", src_p, "\t RESPONSE \t",cat(tid), "\t",cat(pid),"\t", cat(uid),"\t", cat(fc),"\t",cat(ref), "\t",cat(andMask),"\t",cat(orMask),"\n");
|
||||||
|
|
||||||
|
write_file(h,text);
|
||||||
|
write_file(m,text);
|
||||||
|
|
||||||
|
close(h);
|
||||||
|
close(m);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# RESPONSE FC=23
|
||||||
|
event modbus_read_write_response(c:connection,is_orig:bool,t:int_vec,tid:count,pid:count,uid:count,fc:count,bCount:count,len:count)
|
||||||
|
{
|
||||||
|
|
||||||
|
local g:file;
|
||||||
|
local m:file;
|
||||||
|
local ftime:string;
|
||||||
|
local src:string;
|
||||||
|
local dst:string;
|
||||||
|
local src_p:string;
|
||||||
|
local dst_p:string;
|
||||||
|
|
||||||
|
g=open_for_append (string_cat(path,"f23_new.log"));
|
||||||
|
m=open_for_append (string_cat(path,"fall_new.log"));
|
||||||
|
ftime=strftime("%F %T",network_time());
|
||||||
|
src= cat(c$id$orig_h);
|
||||||
|
dst=cat(c$id$resp_h);
|
||||||
|
src_p=cat(c$id$orig_p);
|
||||||
|
dst_p=cat(c$id$resp_p);
|
||||||
|
|
||||||
|
local text=string_cat(ftime,"\t",src,"\t",dst,"\t",src_p, "\t RESPONSE \t",cat(len),"\t",cat(tid), "\t",cat(pid),"\t", cat(uid),"\t",cat(fc),"\t",cat(bCount), "\t",cat(t),"\n");
|
||||||
|
|
||||||
|
write_file(g,text);
|
||||||
|
write_file(m,text);
|
||||||
|
|
||||||
|
close(g);
|
||||||
|
close(m);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# REQUST FC=23
|
||||||
|
event modbus_read_write_request(c:connection,is_orig:bool,t:int_vec,tid:count,pid:count,uid:count,fc:count,refRead:count,wcRead:count,refWrite:count,wcWrite:count,bCount:count,len:count)
|
||||||
|
{
|
||||||
|
|
||||||
|
local n:file;
|
||||||
|
local m:file;
|
||||||
|
local ftime:string;
|
||||||
|
local src:string;
|
||||||
|
local dst:string;
|
||||||
|
local src_p:string;
|
||||||
|
local dst_p:string;
|
||||||
|
|
||||||
|
n=open_for_append (string_cat(path,"f23_new.log"));
|
||||||
|
m=open_for_append (string_cat(path,"fall_new.log"));
|
||||||
|
ftime=strftime("%F %T",network_time());
|
||||||
|
src= cat(c$id$orig_h);
|
||||||
|
dst=cat(c$id$resp_h);
|
||||||
|
src_p=cat(c$id$orig_p);
|
||||||
|
dst_p=cat(c$id$resp_p);
|
||||||
|
|
||||||
|
#according to the specification, this FC usually has 4xxxx offset in the memory map
|
||||||
|
local prefix_refR:count;
|
||||||
|
local prefix_refW:count;
|
||||||
|
|
||||||
|
prefix_refR=refRead+40000;
|
||||||
|
prefix_refW=refWrite+40000;
|
||||||
|
|
||||||
|
local text=string_cat(ftime,"\t",src,"\t",dst,"\t",src_p, "\t REQUEST \t",cat(len),"\t",cat(tid), "\t",cat(pid),"\t", cat(uid),"\t",cat(fc),"\t",cat(prefix_refR),"\t",cat(wcRead),"\t ",cat(prefix_refW),"\t ",cat(wcWrite),"\t",cat(bCount), "\t",cat(t),"\n");
|
||||||
|
|
||||||
|
write_file(n,text);
|
||||||
|
write_file(m,text);
|
||||||
|
|
||||||
|
close(n);
|
||||||
|
close(m);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# REQUEST FC=7 (exception)
|
||||||
|
event modbus_read_except_request(c:connection,is_orig:bool,tid:count,pid:count,uid:count,fc:count,len:count)
|
||||||
|
{
|
||||||
|
|
||||||
|
local h:file;
|
||||||
|
local m:file;
|
||||||
|
local ftime:string;
|
||||||
|
local src:string;
|
||||||
|
local dst:string;
|
||||||
|
local src_p:string;
|
||||||
|
local dst_p:string;
|
||||||
|
|
||||||
|
h=open_for_append (string_cat(path,"f7_new.log"));
|
||||||
|
m=open_for_append (string_cat(path,"fall_new.log"));
|
||||||
|
|
||||||
|
ftime=strftime("%F %T",network_time());
|
||||||
|
src= cat(c$id$orig_h);
|
||||||
|
dst=cat(c$id$resp_h);
|
||||||
|
|
||||||
|
src_p=cat(c$id$orig_p);
|
||||||
|
dst_p=cat(c$id$resp_p);
|
||||||
|
|
||||||
|
local text=string_cat(ftime,"\t",src,"\t",dst,"\t",src_p, "\t REQUEST \t",cat(len),"\t",cat(tid), "\t",cat(pid),"\t", cat(uid),"\t", cat(check_e(fc)),"\n");
|
||||||
|
|
||||||
|
|
||||||
|
write_file(h,text);
|
||||||
|
write_file(m,text);
|
||||||
|
|
||||||
|
close(h);
|
||||||
|
close(m);
|
||||||
|
}
|
||||||
|
|
||||||
|
# RESPONSE FC=7 (exception)
|
||||||
|
event modbus_read_except_response(c:connection,is_orig:bool,tid:count,pid:count,uid:count,fc:count,status:count,len:count)
|
||||||
|
{
|
||||||
|
|
||||||
|
local h:file;
|
||||||
|
local m:file;
|
||||||
|
local ftime:string;
|
||||||
|
local src:string;
|
||||||
|
local dst:string;
|
||||||
|
local src_p:string;
|
||||||
|
local dst_p:string;
|
||||||
|
|
||||||
|
h=open_for_append (string_cat(path,"f7_new.log"));
|
||||||
|
m=open_for_append (string_cat(path,"fall_new.log"));
|
||||||
|
|
||||||
|
ftime=strftime("%F %T",network_time());
|
||||||
|
src= cat(c$id$orig_h);
|
||||||
|
dst=cat(c$id$resp_h);
|
||||||
|
|
||||||
|
src_p=cat(c$id$orig_p);
|
||||||
|
dst_p=cat(c$id$resp_p);
|
||||||
|
|
||||||
|
local text=string_cat(ftime,"\t",src,"\t",dst,"\t",src_p, "\t RESPONSE \t",cat(tid), "\t",cat(len),"\t",cat(pid),"\t", cat(uid),"\t", cat(check_e(fc)),"\t",cat(status),"\n");
|
||||||
|
|
||||||
|
write_file(h,text);
|
||||||
|
write_file(m,text);
|
||||||
|
|
||||||
|
close(h);
|
||||||
|
close(m);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# GENERAL EXCEPTION
|
||||||
|
event modbus_exception(c:connection,is_orig:bool,tid:count,pid:count,uid:count,fc:count, code:count)
|
||||||
|
{
|
||||||
|
|
||||||
|
local h:file;
|
||||||
|
local m:file;
|
||||||
|
local ftime:string;
|
||||||
|
local src:string;
|
||||||
|
local dst:string;
|
||||||
|
local src_p:string;
|
||||||
|
local dst_p:string;
|
||||||
|
|
||||||
|
h=open_for_append (string_cat(path,"fE_new.log"));
|
||||||
|
m=open_for_append (string_cat(path,"fall_new.log"));
|
||||||
|
|
||||||
|
ftime=strftime("%F %T",network_time());
|
||||||
|
src= cat(c$id$orig_h);
|
||||||
|
dst=cat(c$id$resp_h);
|
||||||
|
|
||||||
|
src_p=cat(c$id$orig_p);
|
||||||
|
dst_p=cat(c$id$resp_p);
|
||||||
|
|
||||||
|
local text=string_cat(ftime,"\t",src,"\t",dst,"\t",src_p, "\t EXCEPTION \t",cat(tid), "\t",cat(pid),"\t", cat(uid),"\t", cat(check_e(fc)),"\t",cat(code),"\n");
|
||||||
|
|
||||||
|
write_file(h,text);
|
||||||
|
write_file(m,text);
|
||||||
|
close(h);
|
||||||
|
close(m);
|
||||||
|
}
|
9
scripts/base/protocols/modbus/utils.bro
Normal file
9
scripts/base/protocols/modbus/utils.bro
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
|
||||||
|
#this function checks if the function code is exception (ie. normal fc are 1-127, exception codes are >127)
|
||||||
|
# e.g, fc=128 implies exception repsonse for fc=1
|
||||||
|
function check_e(a:count):count
|
||||||
|
{
|
||||||
|
if (a>127) a=a-128;
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
|
@ -28,6 +28,7 @@
|
||||||
#include "DCE_RPC.h"
|
#include "DCE_RPC.h"
|
||||||
#include "Gnutella.h"
|
#include "Gnutella.h"
|
||||||
#include "Ident.h"
|
#include "Ident.h"
|
||||||
|
#include "Modbus.h"
|
||||||
#include "NCP.h"
|
#include "NCP.h"
|
||||||
#include "NetbiosSSN.h"
|
#include "NetbiosSSN.h"
|
||||||
#include "SMB.h"
|
#include "SMB.h"
|
||||||
|
@ -158,6 +159,11 @@ const Analyzer::Config Analyzer::analyzer_configs[] = {
|
||||||
ConnSize_Analyzer::InstantiateAnalyzer,
|
ConnSize_Analyzer::InstantiateAnalyzer,
|
||||||
ConnSize_Analyzer::Available, 0, false },
|
ConnSize_Analyzer::Available, 0, false },
|
||||||
|
|
||||||
|
{ AnalyzerTag::Modbus, "MODBUS",
|
||||||
|
ModbusTCP_Analyzer::InstantiateAnalyzer,
|
||||||
|
ModbusTCP_Analyzer::Available, 0, false },
|
||||||
|
|
||||||
|
|
||||||
{ AnalyzerTag::Contents, "CONTENTS", 0, 0, 0, false },
|
{ AnalyzerTag::Contents, "CONTENTS", 0, 0, 0, false },
|
||||||
{ AnalyzerTag::ContentLine, "CONTENTLINE", 0, 0, 0, false },
|
{ AnalyzerTag::ContentLine, "CONTENTLINE", 0, 0, 0, false },
|
||||||
{ AnalyzerTag::NVT, "NVT", 0, 0, 0, false },
|
{ AnalyzerTag::NVT, "NVT", 0, 0, 0, false },
|
||||||
|
|
|
@ -42,6 +42,10 @@ namespace AnalyzerTag {
|
||||||
File, Backdoor, InterConn, SteppingStone, TCPStats,
|
File, Backdoor, InterConn, SteppingStone, TCPStats,
|
||||||
ConnSize,
|
ConnSize,
|
||||||
|
|
||||||
|
//ICS related
|
||||||
|
Modbus,
|
||||||
|
|
||||||
|
|
||||||
// Support-analyzers
|
// Support-analyzers
|
||||||
Contents, ContentLine, NVT, Zip, Contents_DNS, Contents_NCP,
|
Contents, ContentLine, NVT, Zip, Contents_DNS, Contents_NCP,
|
||||||
Contents_NetbiosSSN, Contents_Rlogin, Contents_Rsh,
|
Contents_NetbiosSSN, Contents_Rlogin, Contents_Rsh,
|
||||||
|
|
|
@ -216,6 +216,9 @@ binpac_target(ssl.pac
|
||||||
binpac_target(syslog.pac
|
binpac_target(syslog.pac
|
||||||
syslog-protocol.pac syslog-analyzer.pac)
|
syslog-protocol.pac syslog-analyzer.pac)
|
||||||
|
|
||||||
|
binpac_target(modbus.pac
|
||||||
|
modbus-protocol.pac modbus-analyzer.pac)
|
||||||
|
|
||||||
########################################################################
|
########################################################################
|
||||||
## bro target
|
## bro target
|
||||||
|
|
||||||
|
@ -345,6 +348,7 @@ set(bro_SRCS
|
||||||
Reporter.cc
|
Reporter.cc
|
||||||
Login.cc
|
Login.cc
|
||||||
MIME.cc
|
MIME.cc
|
||||||
|
Modbus.cc
|
||||||
NCP.cc
|
NCP.cc
|
||||||
NFA.cc
|
NFA.cc
|
||||||
NFS.cc
|
NFS.cc
|
||||||
|
|
38
src/Modbus.cc
Normal file
38
src/Modbus.cc
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
#include "Modbus.h"
|
||||||
|
#include "TCP_Reassembler.h"
|
||||||
|
|
||||||
|
ModbusTCP_Analyzer::ModbusTCP_Analyzer(Connection* c)
|
||||||
|
: TCP_ApplicationAnalyzer(AnalyzerTag::Modbus, c)
|
||||||
|
{
|
||||||
|
interp = new binpac::ModbusTCP::ModbusTCP_Conn(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
ModbusTCP_Analyzer::~ModbusTCP_Analyzer()
|
||||||
|
{
|
||||||
|
delete interp;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModbusTCP_Analyzer::Done()
|
||||||
|
{
|
||||||
|
TCP_ApplicationAnalyzer::Done();
|
||||||
|
|
||||||
|
interp->FlowEOF(true);
|
||||||
|
interp->FlowEOF(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModbusTCP_Analyzer::DeliverStream(int len, const u_char* data, bool orig)
|
||||||
|
{
|
||||||
|
TCP_ApplicationAnalyzer::DeliverStream(len, data, orig);
|
||||||
|
interp->NewData(orig, data, data + len);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModbusTCP_Analyzer::Undelivered(int seq, int len, bool orig)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModbusTCP_Analyzer::EndpointEOF(TCP_Reassembler* endp)
|
||||||
|
{
|
||||||
|
TCP_ApplicationAnalyzer::EndpointEOF(endp);
|
||||||
|
interp->FlowEOF(endp->IsOrig());
|
||||||
|
}
|
||||||
|
|
62
src/Modbus.h
Normal file
62
src/Modbus.h
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
|
||||||
|
#ifndef modbus_h
|
||||||
|
#define modbus_h
|
||||||
|
|
||||||
|
|
||||||
|
#include "TCP.h"
|
||||||
|
|
||||||
|
#include "modbus_pac.h"
|
||||||
|
|
||||||
|
class ModbusTCP_Analyzer : public TCP_ApplicationAnalyzer {
|
||||||
|
public:
|
||||||
|
ModbusTCP_Analyzer(Connection* conn);
|
||||||
|
virtual ~ModbusTCP_Analyzer();
|
||||||
|
|
||||||
|
virtual void Done();
|
||||||
|
virtual void DeliverStream(int len, const u_char* data, bool orig);
|
||||||
|
|
||||||
|
virtual void Undelivered(int seq, int len, bool orig);
|
||||||
|
virtual void EndpointEOF(TCP_Reassembler* endp);
|
||||||
|
|
||||||
|
static Analyzer* InstantiateAnalyzer(Connection* conn)
|
||||||
|
{ return new ModbusTCP_Analyzer(conn); }
|
||||||
|
|
||||||
|
// Put event names in this function
|
||||||
|
static bool Available()
|
||||||
|
{ return
|
||||||
|
modbus_read_multi_request
|
||||||
|
|| modbus_read_multi_response
|
||||||
|
|
||||||
|
|| modbus_read_input_request
|
||||||
|
|| modbus_read_input_response
|
||||||
|
|
||||||
|
|| modbus_write_single_request
|
||||||
|
|| modbus_write_single_response
|
||||||
|
|
||||||
|
|| modbus_write_coil_request
|
||||||
|
|| modbus_write_coil_response
|
||||||
|
|
||||||
|
|| modbus_write_multi_request
|
||||||
|
|| modbus_write_multi_response
|
||||||
|
|
||||||
|
|| modbus_mask_write_request
|
||||||
|
|| modbus_mask_write_response
|
||||||
|
|
||||||
|
|
||||||
|
|| modbus_read_write_request
|
||||||
|
|| modbus_read_write_response
|
||||||
|
|
||||||
|
|| modbus_read_except_request
|
||||||
|
|| modbus_read_except_response
|
||||||
|
|
||||||
|
|| modbus_exception
|
||||||
|
|| modbus_request
|
||||||
|
|| modbus_response;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
binpac::ModbusTCP::ModbusTCP_Conn* interp;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
|
@ -6612,6 +6612,87 @@ event reporter_error%(t: time, msg: string, location: string%) &error_handler;
|
||||||
## recursively for each ``@load``.
|
## recursively for each ``@load``.
|
||||||
event bro_script_loaded%(path: string, level: count%);
|
event bro_script_loaded%(path: string, level: count%);
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
#### DINA TESTING ##########################################################
|
||||||
|
#### MODBUS EVENTS ##########################################################
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
# Event that passes request header only
|
||||||
|
event modbus_request%(c:connection,is_orig:bool,tid:count,pid:count,uid:count,fc:count%);
|
||||||
|
|
||||||
|
# Event that passes response header only
|
||||||
|
event modbus_response%(c:connection,is_orig:bool,tid:count,pid:count,uid:count,fc:count%);
|
||||||
|
|
||||||
|
# Event that passes modbus request function code =3
|
||||||
|
event modbus_read_multi_request%(c:connection,is_orig:bool,tid:count,pid:count,uid:count,fc:count,ref:count,wcount:count,len:count%);
|
||||||
|
|
||||||
|
# Event that passes modbus request function code =4
|
||||||
|
event modbus_read_input_request%(c:connection,is_orig:bool,tid:count,pid:count,uid:count,fc:count,ref:count,wcount:count,len:count%);
|
||||||
|
|
||||||
|
# Event that passes modbus request function code =5
|
||||||
|
event modbus_write_coil_request%(c:connection,is_orig:bool,tid:count,pid:count,uid:count,fc:count,ref:count,onOff:count,other:count%);
|
||||||
|
|
||||||
|
# Event that passes modbus request function code =6
|
||||||
|
event modbus_write_single_request%(c:connection,is_orig:bool,tid:count,pid:count,uid:count,fc:count,len:count,ref:count,value:count%);
|
||||||
|
|
||||||
|
#Event that passes modbus request function code=16
|
||||||
|
event modbus_write_multi_request%(c:connection,is_orig:bool,t:int_vec,tid:count,pid:count,uid:count,fc:count,ref:count,wCount:count,bCount:count,len:count%);
|
||||||
|
|
||||||
|
|
||||||
|
#Event that passes modbus request function code=22
|
||||||
|
event modbus_mask_write_request%(c:connection,is_orig:bool,tid:count,pid:count,uid:count,fc:count,ref:count,andMask:count,orMask:count%);
|
||||||
|
|
||||||
|
|
||||||
|
#Event that passes modbus request function code=23
|
||||||
|
event modbus_read_write_request%(c:connection,is_orig:bool,t:int_vec,tid:count,pid:count,uid:count,fc:count,refRead:count,wcRead:count,refWrite:count,wcWrite:count,bCount:count,len:count%);
|
||||||
|
|
||||||
|
#Event that passes modbus request function code=7
|
||||||
|
event modbus_read_except_request%(c:connection,is_orig:bool,tid:count,pid:count,uid:count,fc:count,len:count%);
|
||||||
|
|
||||||
|
#Event that passes modbus response function code=3
|
||||||
|
event modbus_read_multi_response%(c:connection,is_orig:bool,t:int_vec,tid:count,pid:count,uid:count,fc:count,bCount:count,len:count%);
|
||||||
|
|
||||||
|
#Event that passes modbus response function code=4
|
||||||
|
event modbus_read_input_response%(c:connection,is_orig:bool,t:int_vec,tid:count,pid:count,uid:count,fc:count,bCount:count,len:count%);
|
||||||
|
|
||||||
|
# Event that passes modbus request function code =5
|
||||||
|
event modbus_write_coil_response%(c:connection,is_orig:bool,tid:count,pid:count,uid:count,fc:count,ref:count,onOff:count,other:count%);
|
||||||
|
|
||||||
|
#Event that passes modbus response function code=6
|
||||||
|
event modbus_write_single_response%(c:connection,is_orig:bool,tid:count,pid:count,uid:count,fc:count,len:count,ref:count,value:count%);
|
||||||
|
|
||||||
|
#Event that passes modbus response function code=16
|
||||||
|
event modbus_write_multi_response%(c:connection,is_orig:bool,tid:count,pid:count,uid:count,fc:count,ref:count,wcount:count,len:count%);
|
||||||
|
|
||||||
|
|
||||||
|
#Event that passes modbus response function code=22
|
||||||
|
event modbus_mask_write_response%(c:connection,is_orig:bool,tid:count,pid:count,uid:count,fc:count,ref:count,andMask:count,orMask:count%);
|
||||||
|
|
||||||
|
|
||||||
|
#Event that passes modbus response function code=23
|
||||||
|
event modbus_read_write_response%(c:connection,is_orig:bool,t:int_vec,tid:count,pid:count,uid:count,fc:count,bCount:count,len:count%);
|
||||||
|
|
||||||
|
#Event that passes modbus response function code=7
|
||||||
|
event modbus_read_except_response%(c:connection,is_orig:bool,tid:count,pid:count,uid:count,fc:count,status:count,len:count%);
|
||||||
|
|
||||||
|
#Event that parses modbus exception
|
||||||
|
event modbus_exception%(c:connection,is_orig:bool,tid:count,pid:count,uid:count,fc:count,code:count%);
|
||||||
|
|
||||||
|
|
||||||
|
##################################################################
|
||||||
|
##################################################################
|
||||||
|
## END OF MODBUS EVENTS #########################################
|
||||||
|
##################################################################
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Deprecated. Will be removed.
|
## Deprecated. Will be removed.
|
||||||
event stp_create_endp%(c: connection, e: int, is_orig: bool%);
|
event stp_create_endp%(c: connection, e: int, is_orig: bool%);
|
||||||
|
|
||||||
|
|
387
src/modbus-analyzer.pac
Normal file
387
src/modbus-analyzer.pac
Normal file
|
@ -0,0 +1,387 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
flow ModbusTCP_Flow(is_orig: bool)
|
||||||
|
{
|
||||||
|
flowunit = ModbusTCP_PDU(is_orig) withcontext (connection, this);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#PARSE ONLY HEADERS FOR REQUEST AND RESPONSE
|
||||||
|
|
||||||
|
function deliver_message(tid:uint16, pid:uint16, uid: uint8, fc: uint8, flag:int): bool
|
||||||
|
%{
|
||||||
|
if(flag==1)
|
||||||
|
{
|
||||||
|
if ( ::modbus_request )
|
||||||
|
{
|
||||||
|
BifEvent::generate_modbus_request(
|
||||||
|
connection()->bro_analyzer(),
|
||||||
|
connection()->bro_analyzer()->Conn(),
|
||||||
|
is_orig(),tid, pid,uid,fc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(flag==2)
|
||||||
|
{
|
||||||
|
if ( ::modbus_response )
|
||||||
|
{
|
||||||
|
BifEvent::generate_modbus_response(
|
||||||
|
connection()->bro_analyzer(),
|
||||||
|
connection()->bro_analyzer()->Conn(),
|
||||||
|
is_orig(),tid,pid,uid,fc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
|
||||||
|
%}
|
||||||
|
|
||||||
|
#REQUEST FC=3
|
||||||
|
function deliver_ReadMultiRegReq(tid:uint16,pid:uint16,uid:uint8,fc:uint8, ref: uint16, wcount:uint16,flag:uint16,len:uint16): bool
|
||||||
|
%{
|
||||||
|
|
||||||
|
if ( ::modbus_read_multi_request )
|
||||||
|
{
|
||||||
|
BifEvent::generate_modbus_read_multi_request(
|
||||||
|
connection()->bro_analyzer(),
|
||||||
|
connection()->bro_analyzer()->Conn(),
|
||||||
|
is_orig(),tid,pid,uid,fc,ref,wcount,len);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
|
||||||
|
%}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#REQUEST FC=4
|
||||||
|
function deliver_ReadInputRegReq(tid:uint16,pid:uint16,uid:uint8,fc:uint8, ref: uint16, wcount:uint16,flag:uint16,len:uint16): bool
|
||||||
|
%{
|
||||||
|
|
||||||
|
if ( ::modbus_read_input_request )
|
||||||
|
{
|
||||||
|
BifEvent::generate_modbus_read_input_request(
|
||||||
|
connection()->bro_analyzer(),
|
||||||
|
connection()->bro_analyzer()->Conn(),
|
||||||
|
is_orig(),tid,pid,uid,fc,ref,wcount,len);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
|
||||||
|
%}
|
||||||
|
|
||||||
|
|
||||||
|
#REQUEST FC=5
|
||||||
|
function deliver_WriteCoilReq(tid:uint16,pid:uint16,uid:uint8,fc:uint8, ref: uint16,onOff:uint8,other:uint8): bool
|
||||||
|
%{
|
||||||
|
|
||||||
|
if ( ::modbus_write_coil_request )
|
||||||
|
{
|
||||||
|
BifEvent::generate_modbus_write_coil_request(
|
||||||
|
connection()->bro_analyzer(),
|
||||||
|
connection()->bro_analyzer()->Conn(),
|
||||||
|
is_orig(),tid,pid,uid,fc,ref,onOff,other);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
|
||||||
|
%}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#REQUEST FC=6
|
||||||
|
function deliver_WriteSingleRegReq(tid:uint16,pid:uint16,uid:uint8,fc:uint8,len:uint16,ref:uint16,value:uint16): bool
|
||||||
|
%{
|
||||||
|
|
||||||
|
if ( ::modbus_write_single_request )
|
||||||
|
{
|
||||||
|
BifEvent::generate_modbus_write_single_request(
|
||||||
|
connection()->bro_analyzer(),
|
||||||
|
connection()->bro_analyzer()->Conn(),
|
||||||
|
is_orig(),tid,pid,uid,fc,len,ref,value);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
|
||||||
|
%}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#REQUEST FC=16
|
||||||
|
function deliver_WriteMultiRegReq( writeMulti: WriteMultipleRegistersRequest, tid:uint16, pid:uint16, uid: uint8, fc: uint8,len:uint16): bool
|
||||||
|
%{
|
||||||
|
|
||||||
|
|
||||||
|
VectorVal * t=new VectorVal( new VectorType(base_type(TYPE_INT)));
|
||||||
|
|
||||||
|
for (unsigned int i=0; i < (${writeMulti.registers}->size()); ++i)
|
||||||
|
{
|
||||||
|
|
||||||
|
Val* r=new Val(((*writeMulti->registers())[i]),TYPE_INT);
|
||||||
|
t->Assign(i,r,0,OP_ASSIGN);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if ( ::modbus_write_multi_request )
|
||||||
|
{
|
||||||
|
|
||||||
|
BifEvent::generate_modbus_write_multi_request(
|
||||||
|
connection()->bro_analyzer(),
|
||||||
|
connection()->bro_analyzer()->Conn(),
|
||||||
|
is_orig(),t,tid,pid,uid,fc,${writeMulti.referenceNumber},${writeMulti.wordCount},${writeMulti.byteCount},len);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
|
||||||
|
%}
|
||||||
|
|
||||||
|
#REQUEST FC=22
|
||||||
|
function deliver_MaskWriteRegReq(tid:uint16,pid:uint16,uid:uint8,fc:uint8,ref:uint16,andMask:uint16,orMask:uint16): bool
|
||||||
|
%{
|
||||||
|
|
||||||
|
if ( ::modbus_mask_write_request )
|
||||||
|
{
|
||||||
|
BifEvent::generate_modbus_mask_write_request(
|
||||||
|
connection()->bro_analyzer(),
|
||||||
|
connection()->bro_analyzer()->Conn(),
|
||||||
|
is_orig(),tid,pid,uid,fc,ref,andMask,orMask);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
|
||||||
|
%}
|
||||||
|
|
||||||
|
|
||||||
|
#REQUEST FC=23
|
||||||
|
function deliver_ReadWriteRegReq(doMulti: ReadWriteRegistersRequest, tid:uint16, pid:uint16, uid: uint8, fc: uint16,len:uint16): bool
|
||||||
|
%{
|
||||||
|
|
||||||
|
VectorVal * t=new VectorVal( new VectorType(base_type(TYPE_INT)));
|
||||||
|
|
||||||
|
for (unsigned int i=0; i < (${doMulti.registerValues})->size(); ++i)
|
||||||
|
{
|
||||||
|
|
||||||
|
Val* r=new Val(((*doMulti->registerValues())[i]),TYPE_INT);
|
||||||
|
t->Assign(i,r,0,OP_ASSIGN);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if ( ::modbus_read_write_request )
|
||||||
|
{
|
||||||
|
|
||||||
|
BifEvent::generate_modbus_read_write_request(
|
||||||
|
connection()->bro_analyzer(),
|
||||||
|
connection()->bro_analyzer()->Conn(),
|
||||||
|
is_orig(),t,tid,pid,uid,fc,${doMulti.referenceNumberRead},${doMulti.wordCountRead}, ${doMulti.referenceNumberWrite}, ${doMulti.wordCountWrite}, ${doMulti.byteCount},len);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
|
||||||
|
%}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#RESPONSE FC=3
|
||||||
|
function deliver_ReadMultiRegRes( doMulti: ReadMultipleRegistersResponse, tid:uint16, pid:uint16, uid: uint8, fc: uint16,len:uint16): bool
|
||||||
|
%{
|
||||||
|
|
||||||
|
VectorVal * t=new VectorVal( new VectorType(base_type(TYPE_INT)));
|
||||||
|
|
||||||
|
for (unsigned int i=0; i < (${doMulti.registers})->size(); ++i)
|
||||||
|
{
|
||||||
|
|
||||||
|
Val* r=new Val(((*doMulti->registers())[i]),TYPE_INT);
|
||||||
|
t->Assign(i,r,0,OP_ASSIGN);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if ( ::modbus_read_multi_response )
|
||||||
|
{
|
||||||
|
|
||||||
|
BifEvent::generate_modbus_read_multi_response(
|
||||||
|
connection()->bro_analyzer(),
|
||||||
|
connection()->bro_analyzer()->Conn(),
|
||||||
|
is_orig(),t,tid,pid,uid,fc,${doMulti.byteCount},len);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
%}
|
||||||
|
|
||||||
|
|
||||||
|
#RESPONSE fc=4
|
||||||
|
function deliver_ReadInputRegRes( doMulti: ReadInputRegistersResponse, tid:uint16, pid:uint16, uid: uint8, fc: uint16,len:uint16): bool
|
||||||
|
%{
|
||||||
|
|
||||||
|
VectorVal * t=new VectorVal( new VectorType(base_type(TYPE_INT)));
|
||||||
|
|
||||||
|
for (unsigned int i=0; i < (${doMulti.registers})->size(); ++i)
|
||||||
|
{
|
||||||
|
|
||||||
|
Val* r=new Val(((*doMulti->registers())[i]),TYPE_INT);
|
||||||
|
t->Assign(i,r,0,OP_ASSIGN);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if ( ::modbus_read_input_response )
|
||||||
|
{
|
||||||
|
|
||||||
|
BifEvent::generate_modbus_read_input_response(
|
||||||
|
connection()->bro_analyzer(),
|
||||||
|
connection()->bro_analyzer()->Conn(),
|
||||||
|
is_orig(),t,tid,pid,uid,fc,${doMulti.byteCount},len);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
%}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#REQUEST fc=5
|
||||||
|
function deliver_WriteCoilRes(tid:uint16,pid:uint16,uid:uint8,fc:uint8, ref: uint16,onOff:uint8,other:uint8): bool
|
||||||
|
%{
|
||||||
|
|
||||||
|
if ( ::modbus_write_coil_response )
|
||||||
|
{
|
||||||
|
BifEvent::generate_modbus_write_coil_response(
|
||||||
|
connection()->bro_analyzer(),
|
||||||
|
connection()->bro_analyzer()->Conn(),
|
||||||
|
is_orig(),tid,pid,uid,fc,ref,onOff,other);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
|
||||||
|
%}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#RESPONSE fc=6
|
||||||
|
function deliver_WriteSingleRegRes(tid:uint16,pid:uint16,uid:uint8,fc:uint8,len:uint16,ref:uint16,value:uint16): bool
|
||||||
|
%{
|
||||||
|
|
||||||
|
if ( ::modbus_write_single_response)
|
||||||
|
{
|
||||||
|
BifEvent::generate_modbus_write_single_response(
|
||||||
|
connection()->bro_analyzer(),
|
||||||
|
connection()->bro_analyzer()->Conn(),
|
||||||
|
is_orig(),tid,pid,uid,fc,len,ref,value);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
|
||||||
|
%}
|
||||||
|
|
||||||
|
|
||||||
|
#RESPONSE fc=16
|
||||||
|
function deliver_WriteMultiRegRes(tid:uint16,pid:uint16,uid:uint8,fc:uint8, ref: uint16, wcount:uint16,len:uint16): bool
|
||||||
|
%{
|
||||||
|
|
||||||
|
if ( ::modbus_write_multi_response)
|
||||||
|
{
|
||||||
|
BifEvent::generate_modbus_write_multi_response(
|
||||||
|
connection()->bro_analyzer(),
|
||||||
|
connection()->bro_analyzer()->Conn(),
|
||||||
|
is_orig(),tid,pid,uid,fc,ref,wcount,len);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
|
||||||
|
%}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#REQUEST FC=22
|
||||||
|
function deliver_MaskWriteRegRes(tid:uint16,pid:uint16,uid:uint8,fc:uint8,ref:uint16,andMask:uint16,orMask:uint16): bool
|
||||||
|
%{
|
||||||
|
|
||||||
|
if ( ::modbus_mask_write_response )
|
||||||
|
{
|
||||||
|
BifEvent::generate_modbus_mask_write_response(
|
||||||
|
connection()->bro_analyzer(),
|
||||||
|
connection()->bro_analyzer()->Conn(),
|
||||||
|
is_orig(),tid,pid,uid,fc,ref,andMask,orMask);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
|
||||||
|
%}
|
||||||
|
|
||||||
|
|
||||||
|
#RESPONSE fc=23
|
||||||
|
function deliver_ReadWriteRegRes(doMulti: ReadWriteRegistersResponse, tid:uint16, pid:uint16, uid: uint8, fc: uint16,len:uint16): bool
|
||||||
|
%{
|
||||||
|
VectorVal * t=new VectorVal( new VectorType(base_type(TYPE_INT)));
|
||||||
|
|
||||||
|
for (unsigned int i=0; i < (${doMulti.registerValues})->size(); ++i)
|
||||||
|
{
|
||||||
|
|
||||||
|
Val* r=new Val(((*doMulti->registerValues())[i]),TYPE_INT);
|
||||||
|
t->Assign(i,r,0,OP_ASSIGN);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if ( ::modbus_read_write_response )
|
||||||
|
{
|
||||||
|
|
||||||
|
BifEvent::generate_modbus_read_write_response(
|
||||||
|
connection()->bro_analyzer(),
|
||||||
|
connection()->bro_analyzer()->Conn(),
|
||||||
|
is_orig(),t,tid,pid,uid,fc,${doMulti.byteCount},len);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
|
||||||
|
%}
|
||||||
|
|
||||||
|
|
||||||
|
#EXCEPTION
|
||||||
|
function deliver_Exception(tid:uint16,pid:uint16,uid:uint8,fc:uint8,code:uint8): bool
|
||||||
|
%{
|
||||||
|
|
||||||
|
if ( ::modbus_exception)
|
||||||
|
{
|
||||||
|
BifEvent::generate_modbus_exception(
|
||||||
|
connection()->bro_analyzer(),
|
||||||
|
connection()->bro_analyzer()->Conn(),
|
||||||
|
is_orig(),tid,pid,uid,fc,code);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
|
||||||
|
%}
|
||||||
|
|
||||||
|
#request Fc=7
|
||||||
|
function deliver_ReadExceptStatReq(tid:uint16,pid:uint16,uid:uint8,fc:uint8,len:uint16): bool
|
||||||
|
%{
|
||||||
|
|
||||||
|
if ( ::modbus_read_except_request)
|
||||||
|
{
|
||||||
|
|
||||||
|
BifEvent::generate_modbus_read_except_request(
|
||||||
|
connection()->bro_analyzer(),
|
||||||
|
connection()->bro_analyzer()->Conn(),
|
||||||
|
is_orig(),tid,pid,uid,fc,len);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
|
||||||
|
%}
|
||||||
|
|
||||||
|
|
||||||
|
#response Fc=7
|
||||||
|
function deliver_ReadExceptStatRes(tid:uint16,pid:uint16,uid:uint8,fc:uint8,status:uint8,len:uint16): bool
|
||||||
|
%{
|
||||||
|
|
||||||
|
if ( ::modbus_read_except_response)
|
||||||
|
{
|
||||||
|
|
||||||
|
BifEvent::generate_modbus_read_except_response(
|
||||||
|
connection()->bro_analyzer(),
|
||||||
|
connection()->bro_analyzer()->Conn(),
|
||||||
|
is_orig(),tid,pid,uid,fc,status,len);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
|
||||||
|
%}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
461
src/modbus-protocol.pac
Normal file
461
src/modbus-protocol.pac
Normal file
|
@ -0,0 +1,461 @@
|
||||||
|
#Copyright (c) 2011 SecurityMatters BV. All rights reserved.
|
||||||
|
|
||||||
|
##Redistribution and use in source and binary forms, with or without
|
||||||
|
##modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
##(1) Redistributions of source code must retain the above copyright notice,
|
||||||
|
## this list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
##(2) Redistributions in binary form must reproduce the above copyright
|
||||||
|
## notice, this list of conditions and the following disclaimer in the
|
||||||
|
## documentation and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
##(3) Neither the name of SecurityMatters BV, nor the names of contributors
|
||||||
|
## may be used to endorse or promote products derived from this software
|
||||||
|
## without specific prior written permission.
|
||||||
|
|
||||||
|
##THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
##AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
##IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
##ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||||
|
##LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
##CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
##SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
##INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
##CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
##ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
##POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
|
||||||
|
##
|
||||||
|
## Modbus/TCP protocol
|
||||||
|
## Based on OPEN MODBUS/TCP SPECIFICATION
|
||||||
|
## Release 1.0, 29 March 1999
|
||||||
|
##
|
||||||
|
|
||||||
|
analyzer ModbusTCP withcontext {
|
||||||
|
connection: ModbusTCP_Conn;
|
||||||
|
flow: ModbusTCP_Flow;
|
||||||
|
};
|
||||||
|
|
||||||
|
connection ModbusTCP_Conn( bro_analyzer: BroAnalyzer) {
|
||||||
|
upflow = ModbusTCP_Flow(true);
|
||||||
|
downflow = ModbusTCP_Flow(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
enum function_codes {
|
||||||
|
# Class 0
|
||||||
|
READ_MULTIPLE_REGISTERS = 3,
|
||||||
|
WRITE_MULTIPLE_REGISTERS = 16,
|
||||||
|
# Class 1
|
||||||
|
READ_COILS = 1,
|
||||||
|
READ_INPUT_DISCRETES = 2,
|
||||||
|
READ_INPUT_REGISTERS = 4,
|
||||||
|
WRITE_COIL = 5,
|
||||||
|
WRITE_SINGLE_REGISTER = 6,
|
||||||
|
READ_EXCEPTION_STATUS = 7,
|
||||||
|
# Class 2
|
||||||
|
FORCE_MULTIPLE_COILS = 15,
|
||||||
|
READ_GENERAL_REFERENCE = 20,
|
||||||
|
WRITE_GENERAL_REFERENCE = 21,
|
||||||
|
MASK_WRITE_REGISTER = 22,
|
||||||
|
READ_WRITE_REGISTERS = 23,
|
||||||
|
READ_FIFO_QUEUE = 24,
|
||||||
|
# Machine/vendor/network specific functions
|
||||||
|
DIAGNOSTICS = 8,
|
||||||
|
PROGRAM_484 = 9,
|
||||||
|
POLL_484 = 10,
|
||||||
|
GET_COMM_EVENT_COUNTERS = 11,
|
||||||
|
GET_COMM_EVENT_LOG = 12,
|
||||||
|
PROGRAM_584_984 = 13,
|
||||||
|
POLL_584_984 = 14,
|
||||||
|
REPORT_SLAVE = 17,
|
||||||
|
PROGRAM_884_U84 = 18,
|
||||||
|
RESET_COMM_LINK_884_U84 = 19,
|
||||||
|
PROGRAM_CONCEPT = 40,
|
||||||
|
FIRMWARE_REPLACEMENT = 125,
|
||||||
|
PROGRAM_584_984_2 = 126,
|
||||||
|
REPORT_LOCAL_ADDRESS = 127,
|
||||||
|
# Exceptions
|
||||||
|
READ_MULTIPLE_REGISTERS_EXCEPTION = 0x83,
|
||||||
|
WRITE_MULTIPLE_REGISTERS_EXCEPTION = 0x90,
|
||||||
|
READ_COILS_EXCEPTION = 0x81,
|
||||||
|
READ_INPUT_DISCRETES_EXCEPTION = 0x82,
|
||||||
|
READ_INPUT_REGISTERS_EXCEPTION = 0x84,
|
||||||
|
WRITE_COIL_EXCEPTION = 0x85,
|
||||||
|
WRITE_SINGLE_REGISTER_EXCEPTION = 0x86,
|
||||||
|
READ_EXCEPTION_STATUS_EXCEPTION = 0x87,
|
||||||
|
FORCE_MULTIPLE_COILS_EXCEPTION = 0x8F,
|
||||||
|
READ_GENERAL_REFERENCE_EXCEPTION = 0x94,
|
||||||
|
WRITE_GENERAL_REFERENCE_EXCEPTION = 0x95,
|
||||||
|
MASK_WRITE_REGISTER_EXCEPTION = 0x96,
|
||||||
|
READ_WRITE_REGISTERS_EXCEPTION = 0x97,
|
||||||
|
READ_FIFO_QUEUE_EXCEPTION = 0x98,
|
||||||
|
};
|
||||||
|
|
||||||
|
#
|
||||||
|
# Main Modbus/TCP PDU
|
||||||
|
#
|
||||||
|
type ModbusTCP_PDU(is_orig: bool) = case is_orig of {
|
||||||
|
true -> request: ModbusTCP_RequestPDU;
|
||||||
|
false -> response: ModbusTCP_ResponsePDU;
|
||||||
|
} &byteorder=bigendian;
|
||||||
|
|
||||||
|
type ModbusTCP_TransportHeader = record {
|
||||||
|
tid: uint16; # Transaction identifier
|
||||||
|
pid: uint16; # Protocol identifier
|
||||||
|
len: uint16; # Length of everyting after this field
|
||||||
|
uid: uint8; # Unit identifier (previously 'slave address')
|
||||||
|
fc: uint8 ; # MODBUS function code (see function_codes enum)
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
type Reference = record {
|
||||||
|
refType: uint8;
|
||||||
|
refNumber: uint32;
|
||||||
|
wordCount: uint16;
|
||||||
|
};
|
||||||
|
|
||||||
|
type ReferenceWithData = record {
|
||||||
|
refType: uint8;
|
||||||
|
refNumber: uint32;
|
||||||
|
wordCount: uint16;
|
||||||
|
registerValue: uint16[wordCount] &length = 2*wordCount; # TODO: check that the array length is calculated correctly
|
||||||
|
};
|
||||||
|
|
||||||
|
type Exception(len: uint16,header:ModbusTCP_TransportHeader) = record {
|
||||||
|
code: uint8;
|
||||||
|
}&let {
|
||||||
|
deliver: bool =$context.flow.deliver_Exception(header.tid,header.pid,header.uid,header.fc,code);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
type ModbusTCP_RequestPDU = record {
|
||||||
|
header: ModbusTCP_TransportHeader;
|
||||||
|
data: case header.fc of {
|
||||||
|
# Class 0
|
||||||
|
READ_MULTIPLE_REGISTERS -> readMultipleRegisters: ReadMultipleRegistersRequest(header.len-2,header);
|
||||||
|
WRITE_MULTIPLE_REGISTERS -> writeMultipleRegisters: WriteMultipleRegistersRequest(header.len-2,header);
|
||||||
|
# Class 1
|
||||||
|
READ_COILS -> readCoils: ReadCoilsRequest(header.len-2);
|
||||||
|
READ_INPUT_DISCRETES -> readInputDiscretes: ReadInputDiscretesRequest(header.len-2);
|
||||||
|
READ_INPUT_REGISTERS -> readInputRegisters: ReadInputRegistersRequest(header.len-2,header);
|
||||||
|
WRITE_COIL -> writeCoil: WriteCoilRequest(header.len-2,header);
|
||||||
|
WRITE_SINGLE_REGISTER -> writeSingleRegister: WriteSingleRegisterRequest(header.len-2,header);
|
||||||
|
READ_EXCEPTION_STATUS -> readExceptionStatus: ReadExceptionStatusRequest(header.len-2,header);
|
||||||
|
# Class 2
|
||||||
|
FORCE_MULTIPLE_COILS -> forceMultipleCoils: ForceMultipleCoilsRequest(header.len-2);
|
||||||
|
READ_GENERAL_REFERENCE -> readGeneralReference: ReadGeneralReferenceRequest(header.len-2);
|
||||||
|
WRITE_GENERAL_REFERENCE -> writeGeneralReference: WriteGeneralReferenceRequest(header.len-2);
|
||||||
|
MASK_WRITE_REGISTER -> maskWriteRegister: MaskWriteRegisterRequest(header.len-2,header);
|
||||||
|
READ_WRITE_REGISTERS -> readWriteRegisters: ReadWriteRegistersRequest(header.len-2,header);
|
||||||
|
READ_FIFO_QUEUE -> readFIFOQueue: ReadFIFOQueueRequest(header.len-2);
|
||||||
|
# All the rest
|
||||||
|
default -> unknown: bytestring &restofdata;
|
||||||
|
};
|
||||||
|
} &length = (header.len+6) &let {
|
||||||
|
deliver: bool =$context.flow.deliver_message(header.tid, header.pid,header.uid, header.fc ,1); #1 is flag for request
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
# Class 0 requests
|
||||||
|
|
||||||
|
|
||||||
|
#REQUEST FC=3
|
||||||
|
type ReadMultipleRegistersRequest(len: uint16,header: ModbusTCP_TransportHeader) = record {
|
||||||
|
referenceNumber: uint16;
|
||||||
|
wordCount: uint16 &check(wordCount <= 125);
|
||||||
|
}
|
||||||
|
&let {
|
||||||
|
deliver: bool =$context.flow.deliver_ReadMultiRegReq(header.tid,header.pid,header.uid,header.fc,referenceNumber,wordCount,1,len);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#REQUEST FC=16
|
||||||
|
|
||||||
|
type WriteMultipleRegistersRequest(len: uint16, header: ModbusTCP_TransportHeader) = record {
|
||||||
|
referenceNumber: uint16;
|
||||||
|
wordCount: uint16 &check(wordCount <= 100);
|
||||||
|
byteCount: uint8;
|
||||||
|
registers: uint16[wordCount] &length = byteCount;
|
||||||
|
} &let {
|
||||||
|
|
||||||
|
deliver: bool =$context.flow.deliver_WriteMultiRegReq(this,header.tid,header.pid,header.uid,header.fc,len);
|
||||||
|
};
|
||||||
|
|
||||||
|
# Class 1 requests
|
||||||
|
|
||||||
|
type ReadCoilsRequest(len: uint16) = record {
|
||||||
|
referenceNumber: uint16;
|
||||||
|
bitCount: uint16 &check(bitCount <= 2000);
|
||||||
|
};
|
||||||
|
|
||||||
|
type ReadInputDiscretesRequest(len: uint16) = record {
|
||||||
|
referenceNumber: uint16;
|
||||||
|
bitCount: uint16 &check(bitCount <= 2000);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#REQUEST FC=4
|
||||||
|
|
||||||
|
type ReadInputRegistersRequest(len: uint16,header: ModbusTCP_TransportHeader) = record {
|
||||||
|
referenceNumber: uint16;
|
||||||
|
wordCount: uint16 &check(wordCount <= 125);
|
||||||
|
}
|
||||||
|
&let {
|
||||||
|
deliver: bool =$context.flow.deliver_ReadInputRegReq(header.tid,header.pid,header.uid,header.fc,referenceNumber,wordCount,1,len);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#REQUEST FC=5
|
||||||
|
type WriteCoilRequest(len: uint16,header:ModbusTCP_TransportHeader) = record {
|
||||||
|
referenceNumber: uint16;
|
||||||
|
onOff: uint8 &check(onOff == 0x00 || onOff == 0xFF);
|
||||||
|
other: uint8 &check(other == 0x00);
|
||||||
|
}
|
||||||
|
&let {
|
||||||
|
deliver: bool =$context.flow.deliver_WriteCoilReq(header.tid,header.pid,header.uid,header.fc,referenceNumber,onOff,other);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#REQUEST FC=6
|
||||||
|
type WriteSingleRegisterRequest(len: uint16, header:ModbusTCP_TransportHeader) = record {
|
||||||
|
referenceNumber: uint16;
|
||||||
|
registerValue: uint16;
|
||||||
|
|
||||||
|
}
|
||||||
|
&let {
|
||||||
|
deliver: bool =$context.flow.deliver_WriteSingleRegReq(header.tid,header.pid,header.uid,header.fc,len,referenceNumber,registerValue);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
type ReadExceptionStatusRequest(len:uint16,header:ModbusTCP_TransportHeader) = record {
|
||||||
|
} &let {
|
||||||
|
|
||||||
|
deliver: bool =$context.flow.deliver_ReadExceptStatReq(header.tid,header.pid,header.uid,header.fc,len);
|
||||||
|
};
|
||||||
|
|
||||||
|
# Class 2 requests
|
||||||
|
type ForceMultipleCoilsRequest(len: uint16) = record {
|
||||||
|
referenceNumber: uint16;
|
||||||
|
bitCount: uint16 &check(bitCount <= 800);
|
||||||
|
byteCount: uint8 &check(byteCount == (bitCount + 7)/8);
|
||||||
|
coils: bytestring &length = byteCount;
|
||||||
|
};
|
||||||
|
|
||||||
|
type ReadGeneralReferenceRequest(len: uint16) = record {
|
||||||
|
byteCount: uint8;
|
||||||
|
references: Reference[referenceCount] &length = byteCount;
|
||||||
|
} &let {
|
||||||
|
referenceCount: uint8 = byteCount/7;
|
||||||
|
};
|
||||||
|
|
||||||
|
type WriteGeneralReferenceRequest(len: uint16) = record {
|
||||||
|
byteCount: uint8;
|
||||||
|
references: ReferenceWithData[] &until($input.length() == 0) &length = byteCount;
|
||||||
|
} &length = len;
|
||||||
|
|
||||||
|
|
||||||
|
#REQUEST FC=22
|
||||||
|
type MaskWriteRegisterRequest(len: uint16,header: ModbusTCP_TransportHeader) = record {
|
||||||
|
referenceNumber: uint16;
|
||||||
|
andMask: uint16;
|
||||||
|
orMask: uint16;
|
||||||
|
}
|
||||||
|
&let{
|
||||||
|
deliver: bool =$context.flow.deliver_MaskWriteRegReq(header.tid,header.pid,header.uid,header.fc,referenceNumber, andMask, orMask);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#REQUEST FC=23
|
||||||
|
|
||||||
|
type ReadWriteRegistersRequest(len: uint16,header: ModbusTCP_TransportHeader) = record {
|
||||||
|
referenceNumberRead: uint16;
|
||||||
|
wordCountRead: uint16 &check(wordCountRead <= 125);
|
||||||
|
referenceNumberWrite: uint16;
|
||||||
|
wordCountWrite: uint16 &check(wordCountWrite <= 100);
|
||||||
|
byteCount: uint8 &check(byteCount == 2*wordCountWrite);
|
||||||
|
registerValues: uint16[registerCount] &length = byteCount;
|
||||||
|
} &length = len, &let{
|
||||||
|
registerCount : uint8 = byteCount / 2;
|
||||||
|
deliver: bool =$context.flow.deliver_ReadWriteRegReq(this,header.tid,header.pid,header.uid,header.fc,len);
|
||||||
|
};
|
||||||
|
|
||||||
|
type ReadFIFOQueueRequest(len: uint16) = record {
|
||||||
|
referenceNumber: uint16;
|
||||||
|
};
|
||||||
|
|
||||||
|
#Responses
|
||||||
|
#
|
||||||
|
type ModbusTCP_ResponsePDU = record {
|
||||||
|
header: ModbusTCP_TransportHeader;
|
||||||
|
data: case header.fc of {
|
||||||
|
# Class 0
|
||||||
|
READ_MULTIPLE_REGISTERS -> readMultipleRegisters: ReadMultipleRegistersResponse(header.len-2, header);
|
||||||
|
WRITE_MULTIPLE_REGISTERS -> writeMultipleRegisters: WriteMultipleRegistersResponse(header.len-2,header);
|
||||||
|
# Class 1
|
||||||
|
READ_COILS -> readCoils: ReadCoilsResponse(header.len-2);
|
||||||
|
READ_INPUT_DISCRETES -> readInputDiscretes: ReadInputDiscretesResponse(header.len-2);
|
||||||
|
READ_INPUT_REGISTERS -> readInputRegisters: ReadInputRegistersResponse(header.len-2,header);
|
||||||
|
WRITE_COIL -> writeCoil: WriteCoilResponse(header.len-2,header);
|
||||||
|
WRITE_SINGLE_REGISTER -> writeSingleRegister: WriteSingleRegisterResponse(header.len-2,header);
|
||||||
|
READ_EXCEPTION_STATUS -> readExceptionStatus: ReadExceptionStatusResponse(header.len-2,header);
|
||||||
|
FORCE_MULTIPLE_COILS -> forceMultipleCoils: ForceMultipleCoilsResponse(header.len-2);
|
||||||
|
READ_GENERAL_REFERENCE -> readGeneralReference: ReadGeneralReferenceResponse(header.len-2);
|
||||||
|
WRITE_GENERAL_REFERENCE -> writeGeneralReference: WriteGeneralReferenceResponse(header.len-2);
|
||||||
|
MASK_WRITE_REGISTER -> maskWriteRegister: MaskWriteRegisterResponse(header.len-2,header);
|
||||||
|
READ_WRITE_REGISTERS -> readWriteRegisters: ReadWriteRegistersResponse(header.len-2,header);
|
||||||
|
READ_FIFO_QUEUE -> readFIFOQueue: ReadFIFOQueueResponse(header.len-2);
|
||||||
|
# Exceptions
|
||||||
|
READ_MULTIPLE_REGISTERS_EXCEPTION -> readMultipleRegistersException : Exception(header.len-2,header);
|
||||||
|
WRITE_MULTIPLE_REGISTERS_EXCEPTION -> writeMultipleRegistersException: Exception(header.len-2,header);
|
||||||
|
READ_COILS_EXCEPTION -> readCoilsException: Exception(header.len-2,header);
|
||||||
|
READ_INPUT_DISCRETES_EXCEPTION -> readInputDiscretesException: Exception(header.len-2,header);
|
||||||
|
READ_INPUT_REGISTERS_EXCEPTION -> readInputRegistersException: Exception(header.len-2,header);
|
||||||
|
WRITE_COIL_EXCEPTION -> writeCoilException: Exception(header.len-2,header);
|
||||||
|
WRITE_SINGLE_REGISTER_EXCEPTION -> writeSingleRegisterException: Exception(header.len-2,header);
|
||||||
|
READ_EXCEPTION_STATUS_EXCEPTION -> readExceptionStatusException: Exception(header.len-2,header);
|
||||||
|
FORCE_MULTIPLE_COILS_EXCEPTION -> forceMultipleCoilsException: Exception(header.len-2,header);
|
||||||
|
READ_GENERAL_REFERENCE_EXCEPTION -> readGeneralReferenceException: Exception(header.len-2,header);
|
||||||
|
WRITE_GENERAL_REFERENCE_EXCEPTION -> writeGeneralReferenceException: Exception(header.len-2,header);
|
||||||
|
MASK_WRITE_REGISTER_EXCEPTION -> maskWriteRegisterException: Exception(header.len-2,header);
|
||||||
|
READ_WRITE_REGISTERS_EXCEPTION -> readWriteRegistersException: Exception(header.len-2,header);
|
||||||
|
READ_FIFO_QUEUE_EXCEPTION -> readFIFOQueueException: Exception(header.len-2,header);
|
||||||
|
# All the rest
|
||||||
|
default -> unknown: bytestring &restofdata;
|
||||||
|
};
|
||||||
|
} &length = (header.len+6) &let {
|
||||||
|
deliver: bool =$context.flow.deliver_message(header.tid,header.pid,header.uid,header.fc,2); #2 is flag for response
|
||||||
|
};
|
||||||
|
|
||||||
|
# Class 0 responses
|
||||||
|
|
||||||
|
|
||||||
|
###RESPONSE FC=3
|
||||||
|
type ReadMultipleRegistersResponse(len: uint16,header:ModbusTCP_TransportHeader) = record {
|
||||||
|
byteCount: uint8;
|
||||||
|
registers: uint16[registerCount] &length = byteCount;
|
||||||
|
} &let{
|
||||||
|
registerCount : uint8 = byteCount/2;
|
||||||
|
|
||||||
|
deliver: bool =$context.flow.deliver_ReadMultiRegRes(this,header.tid,header.pid,header.uid,header.fc,len);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
###RESPONSE FC=16
|
||||||
|
type WriteMultipleRegistersResponse(len: uint16,header:ModbusTCP_TransportHeader) = record {
|
||||||
|
referenceNumber: uint16;
|
||||||
|
wordCount: uint16;
|
||||||
|
} &let {
|
||||||
|
deliver: bool =$context.flow.deliver_WriteMultiRegRes(header.tid,header.pid,header.uid,header.fc,referenceNumber,wordCount,len);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
# Class 1 responses
|
||||||
|
|
||||||
|
type ReadCoilsResponse(len: uint16) = record {
|
||||||
|
byteCount: uint8;
|
||||||
|
bits: bytestring &length = byteCount;
|
||||||
|
};
|
||||||
|
|
||||||
|
type ReadInputDiscretesResponse(len: uint16) = record {
|
||||||
|
byteCount: uint8;
|
||||||
|
bits: bytestring &length = byteCount;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
###RESPONSE FC=4
|
||||||
|
type ReadInputRegistersResponse(len: uint16, header:ModbusTCP_TransportHeader) = record {
|
||||||
|
byteCount: uint8;
|
||||||
|
registers: uint16[registerCount] &length = byteCount;
|
||||||
|
} &let {
|
||||||
|
registerCount = byteCount/2;
|
||||||
|
deliver: bool =$context.flow.deliver_ReadInputRegRes(this,header.tid,header.pid,header.uid,header.fc,len);
|
||||||
|
};
|
||||||
|
|
||||||
|
###RESPONSE FC=5
|
||||||
|
type WriteCoilResponse(len: uint16,header:ModbusTCP_TransportHeader) = record {
|
||||||
|
referenceNumber: uint16;
|
||||||
|
onOff: uint8 &check(onOff == 0x00 || onOff == 0xFF);
|
||||||
|
other: uint8 &check(other == 0x00);
|
||||||
|
}
|
||||||
|
&let {
|
||||||
|
deliver: bool =$context.flow.deliver_WriteCoilRes(header.tid,header.pid,header.uid,header.fc,referenceNumber,onOff,other);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
###RESPONSE FC=6
|
||||||
|
type WriteSingleRegisterResponse(len: uint16, header:ModbusTCP_TransportHeader) = record {
|
||||||
|
referenceNumber: uint16;
|
||||||
|
registerValue: uint16;
|
||||||
|
}
|
||||||
|
&let {
|
||||||
|
deliver: bool =$context.flow.deliver_WriteSingleRegRes(header.tid,header.pid,header.uid,header.fc,len,referenceNumber,registerValue);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
type ReadExceptionStatusResponse(len:uint16,header:ModbusTCP_TransportHeader) = record {
|
||||||
|
status: uint8;
|
||||||
|
} &let {
|
||||||
|
|
||||||
|
deliver: bool =$context.flow.deliver_ReadExceptStatRes(header.tid,header.pid,header.uid,header.fc,status,len);
|
||||||
|
};
|
||||||
|
|
||||||
|
# Class 2 responses
|
||||||
|
|
||||||
|
type ForceMultipleCoilsResponse(len: uint16) = record {
|
||||||
|
referenceNumber: uint16;
|
||||||
|
bitCount: uint16;
|
||||||
|
};
|
||||||
|
|
||||||
|
type ReadGeneralReferenceResponse(len: uint16) = record {
|
||||||
|
byteCount: uint8;
|
||||||
|
references: bytestring &length = byteCount;
|
||||||
|
} &length = len;
|
||||||
|
|
||||||
|
type WriteGeneralReferenceResponse(len: uint16) = record {
|
||||||
|
byteCount: uint8;
|
||||||
|
references: ReferenceWithData[] &until($input.length() == 0) &length = byteCount;
|
||||||
|
} &length = len;
|
||||||
|
|
||||||
|
|
||||||
|
###RESPONSE FC=22
|
||||||
|
type MaskWriteRegisterResponse(len: uint16,header:ModbusTCP_TransportHeader) = record {
|
||||||
|
referenceNumber: uint16;
|
||||||
|
andMask: uint16;
|
||||||
|
orMask: uint16;
|
||||||
|
}
|
||||||
|
&let{
|
||||||
|
deliver: bool =$context.flow.deliver_MaskWriteRegRes(header.tid,header.pid,header.uid,header.fc,referenceNumber, andMask, orMask);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
###RESPONSE FC=23
|
||||||
|
type ReadWriteRegistersResponse(len: uint16,header:ModbusTCP_TransportHeader) = record {
|
||||||
|
byteCount: uint8;
|
||||||
|
registerValues: uint16[registerCount] &length = byteCount;
|
||||||
|
} &length = len, &let {
|
||||||
|
registerCount = byteCount / 2;
|
||||||
|
deliver: bool =$context.flow.deliver_ReadWriteRegRes(this,header.tid,header.pid,header.uid,header.fc,len);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
type ReadFIFOQueueResponse(len: uint16) = record {
|
||||||
|
byteCount: uint16 &check(byteCount <= 64);
|
||||||
|
wordCount: uint16 &check(wordCount <= 31);
|
||||||
|
registerData: uint16[wordCount] &length = byteCount;
|
||||||
|
} &length = len;
|
||||||
|
|
||||||
|
#
|
4
src/modbus.pac
Normal file
4
src/modbus.pac
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
%include bro.pac
|
||||||
|
|
||||||
|
%include modbus-protocol.pac
|
||||||
|
%include modbus-analyzer.pac
|
Loading…
Add table
Add a link
Reference in a new issue