diff --git a/scripts/base/init-bare.bro b/scripts/base/init-bare.bro index ec75c76beb..2aa85a6e4e 100644 --- a/scripts/base/init-bare.bro +++ b/scripts/base/init-bare.bro @@ -53,6 +53,16 @@ type string_vec: vector of string; ## then remove this alias. 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. ## ## .. todo:: We need this type definition only for declaring builtin functions via diff --git a/scripts/base/protocols/modbus/modbus.bro b/scripts/base/protocols/modbus/modbus.bro new file mode 100644 index 0000000000..ec8dfbd58f --- /dev/null +++ b/scripts/base/protocols/modbus/modbus.bro @@ -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); + } diff --git a/scripts/base/protocols/modbus/utils.bro b/scripts/base/protocols/modbus/utils.bro new file mode 100644 index 0000000000..917b1bfff8 --- /dev/null +++ b/scripts/base/protocols/modbus/utils.bro @@ -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; +} + diff --git a/src/Analyzer.cc b/src/Analyzer.cc index 9e30da0066..887992cb0f 100644 --- a/src/Analyzer.cc +++ b/src/Analyzer.cc @@ -28,6 +28,7 @@ #include "DCE_RPC.h" #include "Gnutella.h" #include "Ident.h" +#include "Modbus.h" #include "NCP.h" #include "NetbiosSSN.h" #include "SMB.h" @@ -158,6 +159,11 @@ const Analyzer::Config Analyzer::analyzer_configs[] = { ConnSize_Analyzer::InstantiateAnalyzer, ConnSize_Analyzer::Available, 0, false }, + { AnalyzerTag::Modbus, "MODBUS", + ModbusTCP_Analyzer::InstantiateAnalyzer, + ModbusTCP_Analyzer::Available, 0, false }, + + { AnalyzerTag::Contents, "CONTENTS", 0, 0, 0, false }, { AnalyzerTag::ContentLine, "CONTENTLINE", 0, 0, 0, false }, { AnalyzerTag::NVT, "NVT", 0, 0, 0, false }, diff --git a/src/AnalyzerTags.h b/src/AnalyzerTags.h index 7fad4d35bb..02d22aeb47 100644 --- a/src/AnalyzerTags.h +++ b/src/AnalyzerTags.h @@ -41,6 +41,10 @@ namespace AnalyzerTag { // Other File, Backdoor, InterConn, SteppingStone, TCPStats, ConnSize, + + //ICS related + Modbus, + // Support-analyzers Contents, ContentLine, NVT, Zip, Contents_DNS, Contents_NCP, diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ce440852d7..46bbf5f3a3 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -216,6 +216,9 @@ binpac_target(ssl.pac binpac_target(syslog.pac syslog-protocol.pac syslog-analyzer.pac) +binpac_target(modbus.pac +modbus-protocol.pac modbus-analyzer.pac) + ######################################################################## ## bro target @@ -345,6 +348,7 @@ set(bro_SRCS Reporter.cc Login.cc MIME.cc + Modbus.cc NCP.cc NFA.cc NFS.cc diff --git a/src/Modbus.cc b/src/Modbus.cc new file mode 100644 index 0000000000..61256fbfd6 --- /dev/null +++ b/src/Modbus.cc @@ -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()); + } + diff --git a/src/Modbus.h b/src/Modbus.h new file mode 100644 index 0000000000..8acd81dc28 --- /dev/null +++ b/src/Modbus.h @@ -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 diff --git a/src/event.bif b/src/event.bif index 0c79c6159d..efdbe8f23a 100644 --- a/src/event.bif +++ b/src/event.bif @@ -6612,6 +6612,87 @@ event reporter_error%(t: time, msg: string, location: string%) &error_handler; ## recursively for each ``@load``. 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. event stp_create_endp%(c: connection, e: int, is_orig: bool%); diff --git a/src/modbus-analyzer.pac b/src/modbus-analyzer.pac new file mode 100644 index 0000000000..f2bde53ac0 --- /dev/null +++ b/src/modbus-analyzer.pac @@ -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; + + %} + + + + + +}; + + + diff --git a/src/modbus-protocol.pac b/src/modbus-protocol.pac new file mode 100644 index 0000000000..843a50fa99 --- /dev/null +++ b/src/modbus-protocol.pac @@ -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; + +# diff --git a/src/modbus.pac b/src/modbus.pac new file mode 100644 index 0000000000..bece9cc9a6 --- /dev/null +++ b/src/modbus.pac @@ -0,0 +1,4 @@ +%include bro.pac + +%include modbus-protocol.pac +%include modbus-analyzer.pac