├── CMakeLists.txt ├── LICENSE ├── Makefile ├── README ├── README.md ├── VERSION ├── configure ├── configure.plugin ├── scripts ├── Crysys │ └── S7comm │ │ └── __load__.bro ├── __load__.bro ├── __preload__.bro ├── consts.bro ├── init.bro └── types.bro ├── src ├── Plugin.cc ├── Plugin.h ├── S7comm.cc ├── S7comm.h ├── S7constants.h └── events.bif └── tests ├── Makefile ├── Scripts └── get-bro-env ├── btest.cfg └── s7comm └── show-plugin.bro /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | cmake_minimum_required(VERSION 2.8) 3 | 4 | project(Plugin) 5 | 6 | include(BroPlugin) 7 | 8 | bro_plugin_begin(Crysys s7comm) 9 | bro_plugin_cc(src/Plugin.cc src/S7comm.cc) 10 | bro_plugin_bif(src/events.bif) 11 | bro_plugin_dist_files(README CHANGES COPYING VERSION) 12 | bro_plugin_end() 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, Laboratory of Cryptography and System Security 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 5 | 6 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 7 | 8 | 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. 9 | 10 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 11 | 12 | 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 HOLDER 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 13 | 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. 14 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # Convenience Makefile providing a few common top-level targets. 3 | # 4 | 5 | cmake_build_dir=build 6 | arch=`uname -s | tr A-Z a-z`-`uname -m` 7 | 8 | all: build-it 9 | 10 | build-it: 11 | @test -e $(cmake_build_dir)/config.status || ./configure 12 | -@test -e $(cmake_build_dir)/CMakeCache.txt && \ 13 | test $(cmake_build_dir)/CMakeCache.txt -ot `cat $(cmake_build_dir)/CMakeCache.txt | grep BRO_DIST | cut -d '=' -f 2`/build/CMakeCache.txt && \ 14 | echo Updating stale CMake cache && \ 15 | touch $(cmake_build_dir)/CMakeCache.txt 16 | 17 | ( cd $(cmake_build_dir) && make ) 18 | 19 | install: 20 | ( cd $(cmake_build_dir) && make install ) 21 | 22 | clean: 23 | ( cd $(cmake_build_dir) && make clean ) 24 | 25 | distclean: 26 | rm -rf $(cmake_build_dir) 27 | 28 | test: 29 | make -C tests 30 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | 2 | Crysys::S7comm 3 | ================================= 4 | 5 | This is a proof of concept implementation of the Siemens S7 protocol analyser for the Bro IDS. It support the s7-300, s7-400, s7-1200 series PLC communication and partially some other Siemens products as well. 6 | This plugin was part of a larger project and was rapidly developed as a PoC. 7 | 8 | The analyzer is not intended nor suitable to be used in a production environment it comes without any expressed or implied warranty of fitness for any particular purpose. 9 | The authors do not take responsibility for any damage or loss caused by this piece of software. 10 | 11 | It is released in a hope that one might find it useful when implementing a production ready S7 analyzer. 12 | 13 | With any questions feel free to contact me at: miru [at] crysys [dot] hu 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ## Siemens S7 protocol analyzer plugin 3 | 4 | This is a proof of concept implementation of the Siemens S7 protocol analyser for the Bro IDS. It support the s7-300, s7-400, s7-1200 series PLC communication and partially some other Siemens products as well. 5 | This plugin was part of a larger project and was rapidly developed as a PoC which is reflected on the quality of the code. 6 | 7 | #### Disclaimer 8 | 9 | **The analyzer is not intended nor suitable to be used in a production environment it comes without any expressed or implied warranty of fitness for any particular purpose. 10 | The authors do not take responsibility for any damage or loss caused by this piece of software.** 11 | 12 | It is released in a hope that one might find it useful when implementing a production ready S7 analyzer. 13 | 14 | #### Usage 15 | 16 | See the [bro documentation](https://www.bro.org/sphinx-git/devel/plugins.htms) for detailed information on how to install analyzer plugins. 17 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 0.8 2 | -------------------------------------------------------------------------------- /configure: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Wrapper for viewing/setting options that the plugin's CMake 4 | # scripts will recognize. 5 | # 6 | # Don't edit this. Edit configure.plugin to add plugin-specific options. 7 | # 8 | 9 | set -e 10 | command="$0 $*" 11 | 12 | if [ -e `dirname $0`/configure.plugin ]; then 13 | # Include custom additions. 14 | . `dirname $0`/configure.plugin 15 | fi 16 | 17 | # Check for `cmake` command. 18 | type cmake > /dev/null 2>&1 || { 19 | echo "\ 20 | This package requires CMake, please install it first, then you may 21 | use this configure script to access CMake equivalent functionality.\ 22 | " >&2; 23 | exit 1; 24 | } 25 | 26 | usage() { 27 | 28 | cat 1>&2 </dev/null 2>&1; then 37 | plugin_usage 1>&2 38 | fi 39 | 40 | echo 41 | 42 | exit 1 43 | } 44 | 45 | # Function to append a CMake cache entry definition to the 46 | # CMakeCacheEntries variable 47 | # $1 is the cache entry variable name 48 | # $2 is the cache entry variable type 49 | # $3 is the cache entry variable value 50 | append_cache_entry () { 51 | CMakeCacheEntries="$CMakeCacheEntries -D $1:$2=$3" 52 | } 53 | 54 | # set defaults 55 | builddir=build 56 | brodist=`cd ../../.. && pwd` 57 | installroot="default" 58 | CMakeCacheEntries="" 59 | 60 | while [ $# -ne 0 ]; do 61 | case "$1" in 62 | -*=*) optarg=`echo "$1" | sed 's/[-_a-zA-Z0-9]*=//'` ;; 63 | *) optarg= ;; 64 | esac 65 | 66 | case "$1" in 67 | --help|-h) 68 | usage 69 | ;; 70 | 71 | --bro-dist=*) 72 | brodist=`cd $optarg && pwd` 73 | ;; 74 | 75 | --install-root=*) 76 | installroot=$optarg 77 | ;; 78 | 79 | *) 80 | if type plugin_option >/dev/null 2>&1; then 81 | plugin_option $1 && shift && continue; 82 | fi 83 | 84 | echo "Invalid option '$1'. Try $0 --help to see available options." 85 | exit 1 86 | ;; 87 | esac 88 | shift 89 | done 90 | 91 | if [ ! -e "$brodist/bro-path-dev.in" ]; then 92 | echo "Cannot determine Bro source directory, use --bro-dist=DIR." 93 | exit 1 94 | fi 95 | 96 | append_cache_entry BRO_DIST PATH $brodist 97 | append_cache_entry CMAKE_MODULE_PATH PATH $brodist/cmake 98 | 99 | if [ "$installroot" != "default" ]; then 100 | mkdir -p $installroot 101 | append_cache_entry BRO_PLUGIN_INSTALL_ROOT PATH $installroot 102 | fi 103 | 104 | echo "Build Directory : $builddir" 105 | echo "Bro Source Directory : $brodist" 106 | 107 | mkdir -p $builddir 108 | cd $builddir 109 | 110 | cmake $CMakeCacheEntries .. 111 | 112 | echo "# This is the command used to configure this build" > config.status 113 | echo $command >> config.status 114 | chmod u+x config.status 115 | -------------------------------------------------------------------------------- /configure.plugin: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Hooks to add custom options to the configure script. 4 | # 5 | 6 | plugin_usage() 7 | { 8 | : # Do nothing 9 | # cat <//__load__.bro instead. 8 | # 9 | 10 | @load ./init.bro 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /scripts/__preload__.bro: -------------------------------------------------------------------------------- 1 | # 2 | # This is loaded unconditionally at Bro startup before any of the BiFs that the 3 | # plugin defines become available. 4 | # 5 | # This is primarily for defining types that BiFs already depend on. If you need 6 | # to do any other unconditional initialization (usually that's just for other BiF 7 | # elemets), that should go into __load__.bro instead. 8 | # 9 | 10 | @load ./types.bro 11 | 12 | 13 | -------------------------------------------------------------------------------- /scripts/consts.bro: -------------------------------------------------------------------------------- 1 | ## This script contains the exported S7 protocol constants 2 | ## Author: Gyorgy Miru 3 | ## Date: 2015.11.28 4 | ## Version: 0.1 5 | 6 | module S7comm; 7 | 8 | export { 9 | const s7msg_types = { 10 | [0x01] = "S7 ROSCTR JOB", 11 | [0x02] = "S7 ROSCTR ACK", 12 | [0x03] = "S7 ROSCTR ACK_DATA", 13 | [0x07] = "S7 ROSCTR USERDATA", 14 | } &default=function(i: count):string { return fmt("unknown-%d", i); } &redef; 15 | 16 | const s7func_types = { 17 | [0x00] = "CPU services", 18 | [0xF0] = "Setup communication", 19 | [0x04] = "Read Var", 20 | [0x05] = "Write Var", 21 | [0x1A] = "Request download", 22 | [0x1B] = "Download block", 23 | [0x1C] = "Download ended", 24 | [0x1D] = "Start upload", 25 | [0x1E] = "Upload", 26 | [0x1F] = "End upload", 27 | [0x28] = "PLC Control", 28 | [0x29] = "PLC Stop", 29 | } &default=function(i: count):string { return fmt("unknown-%d", i); } &redef; 30 | 31 | const s7area_types = { 32 | [0x03] = "System info of 200 family", 33 | [0x05] = "System flags of 200 family", 34 | [0x06] = "Analog inputs of 200 family", 35 | [0x07] = "Analog outputs of 200 family", 36 | 37 | [28] = "C (S7 counter)", 38 | [29] = "T (S7 timer)", 39 | [30] = "IEC counters (200 family)", 40 | [31] = "IEC timers (200 family)", 41 | 42 | [0x80] = "P", 43 | [0x81] = "I", 44 | [0x82] = "Q", 45 | [0x83] = "M", 46 | [0x84] = "DB", 47 | [0x85] = "DBI", 48 | [0x86] = "L", 49 | [0x87] = "V (Unknown yet)", 50 | } &default=function(i: count):string { return fmt("unknown-%d", i); } &redef; 51 | 52 | const s7type_types = { 53 | [1] = "BIT", 54 | [2] = "BYTE", 55 | [3] = "CHAR", 56 | 57 | [4] = "WORD", 58 | [5] = "INT", 59 | 60 | [6] = "DWORD", 61 | [7] = "DINT", 62 | [8] = "REAL", 63 | 64 | [9] = "DATE", 65 | [10] = "TOD", 66 | [11] = "TIME", 67 | [12] = "S5TIME", 68 | [15] = "DATE_AND_TIME", 69 | 70 | [28] = "COUNTER", 71 | [29] = "TIMER", 72 | [30] = "IEC TIMER", 73 | [31] = "IEC COUNTER", 74 | [32] = "HS COUNTER", 75 | 76 | } &default=function(i: count):string { return fmt("unknown-%d", i); } &redef; 77 | 78 | const s7udfunc_modes = { 79 | [0] = "UD PUSH", 80 | [4] = "UD RESUEST", 81 | [8] = "UD RESPONSE", 82 | } &default=function(i: count):string { return fmt("unknown-%d", i); } &redef; 83 | 84 | const s7udfunc_types = { 85 | [1] = "UD Programming", 86 | [2] = "UD Cyclic", 87 | [3] = "UD Block", 88 | [4] = "UD CPU", 89 | [5] = "UD Security", 90 | [6] = "UD Time", 91 | } &default=function(i: count):string { return fmt("unknown-%d", i); } &redef; 92 | 93 | const s7udsubfunc_type_prog = { 94 | [0x14] = "Vartab Request", 95 | [0x04] = "Vartab Response", 96 | [0x01] = "Request Diag Data", 97 | [0x02] = "VAT1", 98 | [0x0c] = "Erase", 99 | [0x0e] = "Read Diag Data", 100 | [0x0f] = "Remove Diag Data", 101 | [0x10] = "Force", 102 | [0x13] = "Request Diag Data2", 103 | } &default=function(i: count):string { return fmt("unknown-%d", i); } &redef; 104 | 105 | const s7udsubfunc_type_cyclic = { 106 | [0x01] = "Memory", 107 | [0x04] = "Unsubscribe", 108 | } &default=function(i: count):string { return fmt("unknown-%d", i); } &redef; 109 | 110 | const s7udsubfunc_type_block = { 111 | [0x01] = "List", 112 | [0x02] = "List Type", 113 | [0x03] = "Block Info", 114 | } &default=function(i: count):string { return fmt("unknown-%d", i); } &redef; 115 | 116 | const s7udsubfunc_type_sec = { 117 | [0x01] = "PLC Password", 118 | } &default=function(i: count):string { return fmt("unknown-%d", i); } &redef; 119 | 120 | const s7udsubfunc_type_time = { 121 | [0x01] = "Read", 122 | [0x02] = "Set", 123 | [0x03] = "Readf", 124 | [0x04] = "Set2", 125 | } &default=function(i: count):string { return fmt("unknown-%d", i); } &redef; 126 | 127 | const s7udsubfunc_type_cpu = { 128 | [1] = "Read SZL", 129 | [2] = "Message Service", 130 | [3] = "STOP", 131 | [4] = "ALARM indication", 132 | [5] = "ALARM initiate", 133 | [6] = "ALARM Ack1", 134 | [7] = "ALARM Ack2", 135 | } &default=function(i: count):string { return fmt("unknown-%d", i); } &redef; 136 | 137 | } 138 | -------------------------------------------------------------------------------- /scripts/init.bro: -------------------------------------------------------------------------------- 1 | ### 2 | ## This is the S7comm plugin's event handler script 3 | ## Author: Gyorgy Miru 4 | ## Date: 2015.12.17. 5 | ## Version: 0.3 6 | 7 | module S7comm; 8 | @load ./consts 9 | 10 | 11 | export { 12 | redef enum Log::ID += { LOG1, LOG2, LOG3, }; 13 | 14 | type InfoIso: record { 15 | ## Time when the command was sent. 16 | ts: time &log; 17 | ## Unique ID for the connection. 18 | uid: string &log; 19 | ## The connection's 4-tuple of endpoint addresses/ports. 20 | id: conn_id &log; 21 | ## COTP msg type. 22 | msg: string &log; 23 | }; 24 | 25 | type InfoS7comm: record { 26 | ## Time when the command was sent. 27 | ts: time &log; 28 | ## Unique ID for the connection. 29 | uid: string &log; 30 | ## The connection's 4-tuple of endpoint addresses/ports. 31 | id: conn_id &log; 32 | ## the s7 message type 33 | msgtype: string &log; 34 | ## the s7 message type number 35 | msgtypenum: count ; 36 | ## function mode for UD 37 | funcmode: string &optional &log; 38 | ## function mode num fo ud 39 | funcmodenum: count &optional; 40 | ## the function number of the msg 41 | functypenum: count ; 42 | ## the function type of the msg 43 | functype: string &log; 44 | ## subfunction for ud 45 | subfunctypenum: count &optional; 46 | ## subfunction str fo ud 47 | subfunctype: string &optional &log; 48 | ## 49 | error: count &log; 50 | }; 51 | 52 | type InfoS7data: record { 53 | ## Time when the command was sent. 54 | ts: time &log; 55 | ## Unique ID for the connection. 56 | uid: string &log; 57 | ## The connection's 4-tuple of endpoint addresses/ports. 58 | id: conn_id &log; 59 | ## memory area 60 | area: string &log; 61 | ## memory areanum 62 | areanum: count ; 63 | ## the function type of the msg 64 | dbnum: count &log; 65 | ## s7 type 66 | s7type: string &log; 67 | ## s7 typenum 68 | s7typenum: count ; 69 | ## s7 address 70 | address: count &log; 71 | ## s7 signed data 72 | sdata: int &optional &log; 73 | ## s7 unsigned data 74 | udata: count &optional &log; 75 | ## s7 real data 76 | ddata: double &optional &log; 77 | isread: bool &log; 78 | 79 | }; 80 | 81 | global log_iso_cotp: event(rec: InfoIso); 82 | 83 | global log_s7comm: event(rec: InfoS7comm); 84 | 85 | global log_s7data: event(rec: InfoS7data); 86 | 87 | } 88 | 89 | redef record connection += { 90 | iso_cotp: InfoIso &optional; 91 | s7comm: InfoS7comm &optional; 92 | s7data: InfoS7data &optional; 93 | }; 94 | 95 | const ports = { 102/tcp }; 96 | # redef likely_server_ports += { ports }; 97 | 98 | # ../lib/bif/s7comm.bif 99 | 100 | 101 | event bro_init() &priority=5 102 | { 103 | Log::create_stream(S7comm::LOG1, [$columns=InfoIso, $ev=log_iso_cotp, $path="iso_cotp"]); 104 | Log::create_stream(S7comm::LOG2, [$columns=InfoS7comm, $ev=log_s7comm, $path="s7comm"]); 105 | Log::create_stream(S7comm::LOG3, [$columns=InfoS7data, $ev=log_s7data, $path="s7data"]); 106 | Analyzer::register_for_ports(Analyzer::ANALYZER_S7COMM, ports); 107 | } 108 | 109 | event iso_cotp_packet(c: connection, msg: string, cdt: count) &priority=5 110 | { 111 | local s: InfoIso; 112 | s$ts=network_time(); 113 | s$uid=c$uid; 114 | s$id=c$id; 115 | c$iso_cotp=s; 116 | 117 | c$iso_cotp$msg=msg; 118 | Log::write(S7comm::LOG1, c$iso_cotp); 119 | } 120 | 121 | event siemenss7_packet(c: connection, msgtype: count, functype: count, errno: count) &priority=5 122 | { 123 | local s: InfoS7comm; 124 | s$ts=network_time(); 125 | s$uid=c$uid; 126 | s$id=c$id; 127 | s$msgtype=s7msg_types[msgtype]; 128 | s$msgtypenum=msgtype; 129 | s$functype=s7func_types[functype]; 130 | s$functypenum=functype; 131 | s$error=errno; 132 | 133 | c$s7comm=s; 134 | 135 | Log::write(S7comm::LOG2, c$s7comm); 136 | } 137 | 138 | event siemenss7_ud_packet(c: connection, msgtype: count, functionmode: count, functiontype: count, subfunction: count, errno: count) &priority=5 139 | { 140 | local s: InfoS7comm; 141 | s$ts=network_time(); 142 | s$uid=c$uid; 143 | s$id=c$id; 144 | s$msgtype=s7msg_types[msgtype]; 145 | s$msgtypenum=msgtype; 146 | s$funcmodenum=functionmode; 147 | s$funcmode=s7udfunc_modes[functionmode]; 148 | s$functype=s7udfunc_types[functiontype]; 149 | s$functypenum=functiontype; 150 | s$subfunctypenum=subfunction; 151 | 152 | switch subfunction 153 | { 154 | case 1: 155 | s$subfunctype=s7udsubfunc_type_prog[subfunction]; 156 | break; 157 | case 2: 158 | s$subfunctype=s7udsubfunc_type_cyclic[subfunction]; 159 | break; 160 | case 3: 161 | s$subfunctype=s7udsubfunc_type_block[subfunction]; 162 | break; 163 | case 4: 164 | s$subfunctype=s7udsubfunc_type_cpu[subfunction]; 165 | break; 166 | case 5: 167 | s$subfunctype=s7udsubfunc_type_sec[subfunction]; 168 | break; 169 | case 6: 170 | s$subfunctype=s7udsubfunc_type_time[subfunction]; 171 | break; 172 | default: 173 | s$subfunctype = fmt("unknown-%d", subfunction); 174 | break; 175 | } 176 | 177 | s$error=errno; 178 | 179 | c$s7comm=s; 180 | 181 | Log::write(S7comm::LOG2, c$s7comm); 182 | 183 | } 184 | 185 | event siemenss7_read_data_unsigned(c: connection, area: count, db: count, s7type: count, address: count, data: count) &priority=5 186 | { 187 | local s: InfoS7data; 188 | s$ts=network_time(); 189 | s$uid=c$uid; 190 | s$id=c$id; 191 | s$area=s7area_types[area]; 192 | s$areanum=area; 193 | s$dbnum=db; 194 | s$s7type=s7type_types[s7type]; 195 | s$s7typenum=s7type; 196 | s$address=address; 197 | s$udata=data; 198 | s$isread=T; 199 | 200 | c$s7data=s; 201 | 202 | # print "This is siemenss7_read_data_unsigned"; #or siemenss7_read_data_unsigned 203 | # print c$s7data; 204 | 205 | Log::write(S7comm::LOG3, c$s7data); 206 | } 207 | 208 | event siemenss7_read_data_signed(c: connection, area: count, db: count, s7type: count, address: count, data: int) &priority=5 209 | { 210 | local s: InfoS7data; 211 | s$ts=network_time(); 212 | s$uid=c$uid; 213 | s$id=c$id; 214 | s$area=s7area_types[area]; 215 | s$areanum=area; 216 | s$dbnum=db; 217 | s$s7type=s7type_types[s7type]; 218 | s$s7typenum=s7type; 219 | s$address=address; 220 | s$sdata=data; 221 | s$isread=T; 222 | 223 | c$s7data=s; 224 | 225 | # print "This is siemenss7_read_data_signed"; #or siemenss7_read_data_unsigned 226 | # print c$s7data; 227 | 228 | Log::write(S7comm::LOG3, c$s7data); 229 | } 230 | 231 | event siemenss7_read_data_real(c: connection, area: count, db: count, s7type: count, address: count, data: double) &priority=5 232 | { 233 | local s: InfoS7data; 234 | s$ts=network_time(); 235 | s$uid=c$uid; 236 | s$id=c$id; 237 | s$area=s7area_types[area]; 238 | s$areanum=area; 239 | s$dbnum=db; 240 | s$s7type=s7type_types[s7type]; 241 | s$s7typenum=s7type; 242 | s$address=address; 243 | s$ddata=data; 244 | s$isread=T; 245 | 246 | c$s7data=s; 247 | 248 | # print "This is siemenss7_read_data_real"; #or siemenss7_read_data_unsigned 249 | # print c$s7data; 250 | 251 | Log::write(S7comm::LOG3, c$s7data); 252 | } 253 | 254 | event siemenss7_write_data_unsigned(c: connection, area: count, db: count, s7type: count, address: count, data: count) &priority=5 255 | { 256 | local s: InfoS7data; 257 | s$ts=network_time(); 258 | s$uid=c$uid; 259 | s$id=c$id; 260 | s$area=s7area_types[area]; 261 | s$areanum=area; 262 | s$dbnum=db; 263 | s$s7type=s7type_types[s7type]; 264 | s$s7typenum=s7type; 265 | s$address=address; 266 | s$udata=data; 267 | s$isread=F; 268 | 269 | c$s7data=s; 270 | 271 | # print "This is siemenss7_write_data_unsigned"; #or siemenss7_read_data_unsigned 272 | # print c$s7data; 273 | 274 | Log::write(S7comm::LOG3, c$s7data); 275 | } 276 | 277 | event siemenss7_write_data_signed(c: connection, area: count, db: count, s7type: count, address: count, data: int) &priority=5 278 | { 279 | local s: InfoS7data; 280 | s$ts=network_time(); 281 | s$uid=c$uid; 282 | s$id=c$id; 283 | s$area=s7area_types[area]; 284 | s$areanum=area; 285 | s$dbnum=db; 286 | s$s7type=s7type_types[s7type]; 287 | s$s7typenum=s7type; 288 | s$address=address; 289 | s$sdata=data; 290 | s$isread=F; 291 | 292 | c$s7data=s; 293 | 294 | # print "This is siemenss7_write_data_signed"; #or siemenss7_read_data_unsigned 295 | # print c$s7data; 296 | 297 | Log::write(S7comm::LOG3, c$s7data); 298 | } 299 | 300 | event siemenss7_write_data_real(c: connection, area: count, db: count, s7type: count, address: count, data: double) &priority=5 301 | { 302 | local s: InfoS7data; 303 | s$ts=network_time(); 304 | s$uid=c$uid; 305 | s$id=c$id; 306 | s$area=s7area_types[area]; 307 | s$areanum=area; 308 | s$dbnum=db; 309 | s$s7type=s7type_types[s7type]; 310 | s$s7typenum=s7type; 311 | s$address=address; 312 | s$ddata=data; 313 | s$isread=F; 314 | 315 | c$s7data=s; 316 | 317 | # print "This is siemenss7_write_data_real"; #or siemenss7_read_data_unsigned 318 | # print c$s7data; 319 | 320 | Log::write(S7comm::LOG3, c$s7data); 321 | } 322 | -------------------------------------------------------------------------------- /scripts/types.bro: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/Plugin.cc: -------------------------------------------------------------------------------- 1 | /** 2 | * Generated S7 analyzer plugin class 3 | */ 4 | 5 | #include "Plugin.h" 6 | 7 | namespace plugin { namespace Crysys_S7comm { Plugin plugin; } } 8 | 9 | using namespace plugin::Crysys_S7comm; 10 | 11 | plugin::Configuration Plugin::Configure() 12 | { 13 | AddComponent(new ::analyzer::Component("S7comm", ::analyzer::Crysys::S7comm_Analyzer::Instantiate)); 14 | 15 | plugin::Configuration config; 16 | config.name = "Crysys::S7comm"; 17 | config.description = "ISO-COTP on TPKT rfc 905 and S7 communication analyzer"; 18 | config.version.major = 0; 19 | config.version.minor = 1; 20 | return config; 21 | } 22 | -------------------------------------------------------------------------------- /src/Plugin.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Header file for the S7 protocol analyzer 3 | */ 4 | #ifndef BRO_PLUGIN_CRYSYS_S7COMM 5 | #define BRO_PLUGIN_CRYSYS_S7COMM 6 | 7 | #include 8 | #include "S7comm.h" 9 | 10 | namespace plugin { 11 | namespace Crysys_S7comm { 12 | 13 | class Plugin : public ::plugin::Plugin 14 | { 15 | protected: 16 | // Overridden from plugin::Plugin. 17 | virtual plugin::Configuration Configure(); 18 | }; 19 | 20 | extern Plugin plugin; 21 | 22 | } 23 | } 24 | 25 | #endif 26 | -------------------------------------------------------------------------------- /src/S7comm.cc: -------------------------------------------------------------------------------- 1 | /** 2 | * This file implements the s7 analyzer plugin 3 | * Author: Gyorgy Miru 4 | * Date: 2015.10.27. 5 | * Version: 0.13 6 | */ 7 | 8 | #include 9 | 10 | #include "S7comm.h" 11 | #include "S7constants.h" 12 | #include "Event.h" 13 | #include "events.bif.h" 14 | 15 | using namespace analyzer::Crysys; 16 | 17 | S7comm_Analyzer::S7comm_Analyzer(Connection* conn) 18 | : tcp::TCP_ApplicationAnalyzer("S7comm", conn) 19 | { 20 | requests = new std::list(); 21 | //possible place to set up support analyzers 22 | //nvt_orig = new login::NVT_Analyzer(conn, true); 23 | //nvt_orig->SetIsNULSensitive(true); 24 | //AddSupportAnalyzer(nvt_orig); 25 | } 26 | 27 | S7comm_Analyzer::~S7comm_Analyzer() 28 | { 29 | requests->clear(); 30 | delete requests; 31 | // need to clear child analyzers 32 | } 33 | 34 | void S7comm_Analyzer::Init() 35 | { 36 | tcp::TCP_ApplicationAnalyzer::Init(); 37 | } 38 | 39 | void S7comm_Analyzer::Done() 40 | { 41 | tcp::TCP_ApplicationAnalyzer::Done(); 42 | // place to check for partial fin sequence (inproper) 43 | // 44 | //if ( nvt_orig->HasPartialLine() && 45 | // (TCP()->OrigState() == tcp::TCP_ENDPOINT_CLOSED || 46 | // TCP()->OrigPrevState() == tcp::TCP_ENDPOINT_CLOSED) ) 47 | // ### should include the partial text 48 | // Weird("partial_ftp_request"); 49 | } 50 | 51 | // iso-cotp connection request parsing 52 | static int parse_crcc_variable_class0(CONN_INFO* ci, const unsigned char* variable_part, unsigned int length){ 53 | unsigned int i = 0; 54 | unsigned char size; 55 | while (i < length){ 56 | switch(variable_part[i]){ 57 | case SRC_TSAP: 58 | ci->src_tsap = ubs16(*((unsigned short*)(variable_part + i + 2))); 59 | case DST_TSAP: 60 | ci->dst_tsap = ubs16(*((unsigned short*)(variable_part + i + 2))); 61 | case TPDU_LEN: 62 | ci->tpdu_len = variable_part[i + 2]; 63 | default: 64 | i += variable_part[i+1] + 2; 65 | break; 66 | } 67 | } 68 | return 0; 69 | } 70 | 71 | // parses the iso-cotp part of the message 72 | void S7comm_Analyzer::DeliverStream(int length, const u_char* data, bool orig) 73 | { 74 | tcp::TCP_ApplicationAnalyzer::DeliverStream(length, data, orig); 75 | 76 | //if ( (orig && ! ftp_request) || (! orig && ! ftp_reply) ) 77 | // return; 78 | 79 | val_list* vl = new val_list; 80 | vl->append(BuildConnVal()); 81 | 82 | ISO_HDR* iso = (ISO_HDR*) data; 83 | u_int16 tpkt_length = ubs16(iso->tpkt_length); 84 | CR_HDR* cr; 85 | CONN_INFO ci; 86 | int variable_start; 87 | 88 | // get pdu data length 89 | const unsigned char* PDU_data = data + iso->cotp_length + 5; 90 | int data_length = length - (iso->cotp_length + 5); 91 | 92 | if (iso->tpkt_version != 3) 93 | Weird("Unexpected TPKT version"); 94 | 95 | if (tpkt_length != length) 96 | { 97 | Weird("Ambigous TPKT length"); 98 | DBG("TPKT: %u/%x bro: %d/%x\n", tpkt_length, iso->tpkt_length, length, length); 99 | DBG("Weird packet DUMP: %0.8x.%0.8x.%0.8x\n", *((u_int32*)data), *((u_int32*)data+1), *((u_int32*)data+2) ); 100 | } 101 | switch (iso->cotp_type & 0xF0){ 102 | // iso-cotp connection request 103 | case CR: 104 | vl->append(new StringVal(18, "Connection Request")); 105 | vl->append(new Val(iso->cotp_type & 0x0F, TYPE_COUNT)); 106 | cr = (CR_HDR*)(data + sizeof(ISO_HDR)); 107 | if (cr->class_options == 0){ 108 | variable_start = sizeof(ISO_HDR) + sizeof(CR_HDR); 109 | parse_crcc_variable_class0(&ci, data + variable_start, (iso->cotp_length + 1) - variable_start); 110 | } 111 | break; 112 | // iso-cotp connection confirm 113 | case CC: 114 | vl->append(new StringVal(18, "Connection Confirm")); 115 | vl->append(new Val(iso->cotp_type & 0x0F, TYPE_COUNT)); 116 | cr = (CR_HDR*)(data + sizeof(ISO_HDR)); 117 | if (cr->class_options == 0){ 118 | variable_start = sizeof(ISO_HDR) + sizeof(CR_HDR); 119 | parse_crcc_variable_class0(&ci, data + variable_start, (iso->cotp_length + 1) - variable_start); 120 | } 121 | break; 122 | // not used by s7 123 | case DR: 124 | vl->append(new StringVal(18, "Disconnect Request")); 125 | vl->append(new Val(0x0, TYPE_COUNT)); 126 | break; 127 | // not used by s7 128 | case DC: 129 | vl->append(new StringVal(18, "Disconnect Confirm")); 130 | vl->append(new Val(0x0, TYPE_COUNT)); 131 | break; 132 | // normal S7 message 133 | case DT: 134 | vl->append(new StringVal(4, "Data")); 135 | vl->append(new Val(0x0, TYPE_COUNT)); 136 | break; 137 | // not used by s7 138 | case ED: 139 | vl->append(new StringVal(14, "Expedited Data")); 140 | vl->append(new Val(0x0, TYPE_COUNT)); 141 | break; 142 | case AK: 143 | vl->append(new StringVal(20, "Data Acknowledgement")); 144 | vl->append(new Val(iso->cotp_type & 0x0F, TYPE_COUNT)); 145 | break; 146 | // not used by s7 147 | case EA: 148 | vl->append(new StringVal(30, "Expedited Data Acknowledgement")); 149 | vl->append(new Val(0x0, TYPE_COUNT)); 150 | break; 151 | case RJ: 152 | vl->append(new StringVal(6, "Reject")); 153 | vl->append(new Val(iso->cotp_type & 0x0F, TYPE_COUNT)); 154 | break; 155 | case ERR: 156 | vl->append(new StringVal(10, "TPDU Error")); 157 | vl->append(new Val(0x0, TYPE_COUNT)); 158 | break; 159 | default: 160 | Weird("Unknown COTP PDU type"); 161 | } 162 | 163 | // send iso-cotp event 164 | ConnectionEvent(iso_cotp_packet, vl); 165 | // than parse the s7 part 166 | if (data_length>0) 167 | ParseS7PDU(data_length, PDU_data, orig); 168 | 169 | ForwardStream(length, data, orig); 170 | } 171 | 172 | // used to fill out a val_list based on a data item 173 | val_list* 174 | S7comm_Analyzer::CreateDataEventVal(Item* item) 175 | { 176 | DBG("Data Item--> area: %u, dbnum: %u, size: %u, start: %u\n", item->area, item->dbnum, item->size, item->start); 177 | val_list* vl = new val_list(); 178 | vl->append(BuildConnVal()); 179 | vl->append(new Val(item->area, TYPE_COUNT)); 180 | vl->append(new Val(item->dbnum, TYPE_COUNT)); 181 | vl->append(new Val(item->size, TYPE_COUNT)); 182 | vl->append(new Val(item->start, TYPE_COUNT)); 183 | 184 | return vl; 185 | } 186 | 187 | // parses the data of read/write messages and sends the appropriate events 188 | const u_char* 189 | S7comm_Analyzer::ParseDataSendEvent(Item* item, const u_char* next_data, bool is_read , int* err) 190 | { 191 | *err=0; 192 | // different events for signed, unsigned and float data 193 | EventHandlerPtr fs; 194 | EventHandlerPtr fu; 195 | EventHandlerPtr fr; 196 | if (is_read) 197 | { 198 | fs = siemenss7_read_data_signed; 199 | fu = siemenss7_read_data_unsigned; 200 | fr = siemenss7_read_data_real; 201 | } 202 | else 203 | { 204 | fs = siemenss7_write_data_signed; 205 | fu = siemenss7_write_data_unsigned; 206 | fr = siemenss7_write_data_real; 207 | } 208 | 209 | /*Need to get the value for the data Item*/ 210 | // Check if read/write was successful if not return pointer to the next item 211 | if (!((!is_read && next_data[0] == 0x00) || (is_read && next_data[0] == 0xff))) 212 | { 213 | u_int16 size = ubs16(*(u_int16*)(next_data + 2)); 214 | *err = next_data[0]; 215 | return next_data + 4 + size; 216 | } 217 | 218 | // check if data is subitem 219 | if (!item->is_subitem) 220 | { 221 | // parse the different s7 variable types 222 | switch(item->size) 223 | { 224 | case S7COMM_TRANSPORT_SIZE_BIT: 225 | case S7COMM_TRANSPORT_SIZE_CHAR: 226 | for(int i = 0; i < item->count; i++) 227 | { 228 | u_char data = next_data[4 + i]; 229 | val_list* vl = CreateDataEventVal(item); 230 | vl->append(new Val(data, TYPE_COUNT)); 231 | ConnectionEvent(fu, vl); 232 | } 233 | break; 234 | case S7COMM_TRANSPORT_SIZE_BYTE: 235 | for(int i = 0; i < item->count; i++) 236 | { 237 | char data = next_data[4 + i]; 238 | val_list* vl = CreateDataEventVal(item); 239 | vl->append(new Val(data, TYPE_INT)); 240 | ConnectionEvent(fs, vl); 241 | 242 | } 243 | break; 244 | case S7COMM_TRANSPORT_SIZE_S5TIME: 245 | case S7COMM_TRANSPORT_SIZE_DATE: 246 | case S7COMM_TRANSPORT_SIZE_WORD: 247 | for(int i = 0; i < item->count; i++) 248 | { 249 | u_int16 data = ubs16(*((u_int16*)(next_data + 4 + 2 * i))); 250 | val_list* vl = CreateDataEventVal(item); 251 | vl->append(new Val(data, TYPE_COUNT)); 252 | ConnectionEvent(fu, vl); 253 | } 254 | break; 255 | case S7COMM_TRANSPORT_SIZE_INT: 256 | for(int i = 0; i < item->count; i++) 257 | { 258 | int16 data = ubs16(*((int16*)(next_data + 4 + 2 * i))); 259 | val_list* vl = CreateDataEventVal(item); 260 | vl->append(new Val(data, TYPE_INT)); 261 | ConnectionEvent(fs, vl); 262 | } 263 | break; 264 | case S7COMM_TRANSPORT_SIZE_TIME: 265 | case S7COMM_TRANSPORT_SIZE_TOD: 266 | case S7COMM_TRANSPORT_SIZE_DWORD: 267 | for(int i = 0; i < item->count; i++) 268 | { 269 | u_int32 data = ubs32(*((u_int32*)(next_data + 4 + 4 * i))); 270 | val_list* vl = CreateDataEventVal(item); 271 | vl->append(new Val(data, TYPE_COUNT)); 272 | ConnectionEvent(fu, vl); 273 | } 274 | break; 275 | case S7COMM_TRANSPORT_SIZE_DINT: 276 | for(int i = 0; i < item->count; i++) 277 | { 278 | int32 data = ubs32(*((int32*)(next_data + 4 + 4 * i))); 279 | val_list* vl = CreateDataEventVal(item); 280 | vl->append(new Val(data, TYPE_INT)); 281 | ConnectionEvent(fs, vl); 282 | } 283 | break; 284 | case S7COMM_TRANSPORT_SIZE_REAL: 285 | for(int i = 0; i < item->count; i++) 286 | { 287 | float data = bsf(*((float*)(next_data + 4 + 4 * i))); 288 | DBG("Float Value %f dump %08x\n", data, data); 289 | val_list* vl = CreateDataEventVal(item); 290 | vl->append(new Val(data, TYPE_DOUBLE)); 291 | ConnectionEvent(fr, vl); 292 | } 293 | break; 294 | case S7COMM_TRANSPORT_SIZE_COUNTER: 295 | case S7COMM_TRANSPORT_SIZE_TIMER: 296 | case S7COMM_TRANSPORT_SIZE_IEC_TIMER: 297 | case S7COMM_TRANSPORT_SIZE_IEC_COUNTER: 298 | case S7COMM_TRANSPORT_SIZE_HS_COUNTER: 299 | //TODO figure data length and generate event 300 | case S7COMM_TRANSPORT_SIZE_DT: 301 | default: 302 | *err = ANALYZER_ERROR_UNSUPPORTED_DATA_TYPE; 303 | u_int16 size = ubs16(*((u_int16*)(next_data + 2))); 304 | return next_data + 4 + size; 305 | } 306 | } 307 | else 308 | { 309 | const u_char *data_ptr = item->item_id == 0 ? next_data + 4: next_data; 310 | if (data_ptr[0] != 0xff) 311 | { 312 | *err = data_ptr[0]; 313 | } 314 | 315 | // we have no type information at this point so we send the data as unsigned 316 | if (item->count ==1) 317 | { 318 | u_char data = data_ptr[1]; 319 | val_list* vl = CreateDataEventVal(item); 320 | vl->append(new Val(data, TYPE_COUNT)); 321 | ConnectionEvent(fu, vl); 322 | } 323 | else if (item->count==2) 324 | { 325 | u_int16 data = ubs16(*((u_int16*)(data_ptr + 1))); 326 | item->size = S7COMM_TRANSPORT_SIZE_WORD; 327 | val_list* vl = CreateDataEventVal(item); 328 | vl->append(new Val(data, TYPE_COUNT)); 329 | ConnectionEvent(fu, vl); 330 | } 331 | else if (item->count==4) 332 | { 333 | u_int32 data = ubs32(*((u_int32*)(data_ptr + 1))); 334 | item->size = S7COMM_TRANSPORT_SIZE_DWORD; 335 | val_list* vl = CreateDataEventVal(item); 336 | vl->append(new Val(data, TYPE_COUNT)); 337 | ConnectionEvent(fu, vl); 338 | 339 | } 340 | else 341 | { 342 | //u_char data[item->count]; 343 | //memcpy(data, data_ptr + 1, item->count); 344 | //TODO figure how to send arbitrary data length in event 345 | } 346 | return data_ptr + item->count + 1; 347 | } 348 | *err = ANALYZER_ERROR_UNEXPECTED_LENGTH; 349 | return next_data + 4; //TODO check if valid 350 | } 351 | 352 | // used to parse write data requests 353 | int 354 | S7comm_Analyzer::ParseWriteItems(const u_char* data, int offset, int length, const u_char** next_data, int* err) 355 | { 356 | u_char var_spec = data[offset]; 357 | u_char addr_len = data[offset+1]; 358 | Item temp; 359 | 360 | if ((addr_len + 2 + offset) > length || *next_data + 4 > data + length) //TODO check if correct 361 | { 362 | Weird("Malformed S7 packet wrong length"); 363 | *err = ANALYZER_ERROR_UNEXPECTED_LENGTH; 364 | return length; 365 | } 366 | u_char pointer_id = data[offset + 2]; 367 | u_char areas; 368 | // check addressing mode and extract data (request contains it) 369 | switch (pointer_id) 370 | { 371 | case S7COMM_SYNTAXID_S7ANY: 372 | // normal addressing 373 | temp.size = data[offset + 3]; //constant 374 | temp.count = ubs16(*((u_int16*)(data + offset + 4))); 375 | temp.dbnum = ubs16(*((u_int16*)(data + offset + 6))); 376 | temp.area = data[offset + 8]; 377 | temp.start = ubs16(*((u_int16*)(data + offset + 10))); // 3 bytes field with 0x00 pad (padding start needs to be checked!) 378 | temp.item_id = 0; 379 | temp.is_subitem = false; 380 | 381 | *next_data = ParseDataSendEvent(&temp, *next_data, false, err); 382 | 383 | break; 384 | case S7COMM_SYNTAXID_DBREAD: 385 | areas = data[offset + 3]; 386 | if (2 + areas * 5 > addr_len) 387 | { 388 | // Error malformed packet 389 | Weird("Malformed S7 packet wrong length"); 390 | *err = ANALYZER_ERROR_UNEXPECTED_LENGTH; 391 | return length; 392 | } 393 | for (int i = 0; i < areas; i++) 394 | { 395 | temp.size = S7COMM_TRANSPORT_SIZE_BYTE; 396 | temp.count = data[offset + (i * 5) + 4]; 397 | temp.dbnum = ubs16(*((u_int16*)(data + offset + (i * 5) + 5))); 398 | temp.area = S7COMM_AREA_DB; //area db 399 | temp.start = ubs16(*((u_int16*)(data + offset + (i * 5) + 7))); 400 | temp.item_id = i; 401 | temp.is_subitem = true; 402 | 403 | *next_data = ParseDataSendEvent(&temp, *next_data, false, err); 404 | } 405 | // special 406 | break; 407 | case S7COMM_SYNTAXID_1200SYM: 408 | //TODO symbolic addressing used by s1200 series 409 | //note must be handled to set data pointer 410 | *err = ANALYZER_ERROR_UNSOPPORTED_ADDRESSING; 411 | return length; 412 | default: 413 | Weird("Unsupported Variable Addressing"); 414 | *err = ANALYZER_ERROR_UNSOPPORTED_ADDRESSING; 415 | return length; 416 | } 417 | 418 | return offset + addr_len + 2; 419 | } 420 | 421 | // extract variable data from read request (response only contains the data) 422 | int 423 | S7comm_Analyzer::ParseReadItems(std::list* items, const u_char* data, int offset, int length, int* err) 424 | { 425 | //parse read request 426 | u_char var_spec = data[offset]; 427 | u_char addr_len = data[offset+1]; 428 | Item temp; 429 | /* Classic S7: type = 0x12, len=10, syntax-id=0x10 for ANY-Pointer 430 | * TIA S7-1200: type = 0x12, len=14, syntax-id=0xb2 (symbolic addressing??) 431 | * Drive-ES Starter with routing: type = 0x12, len=10, syntax-id=0xa2 for ANY-Pointer 432 | */ 433 | if ((addr_len + 2 + offset) > length) 434 | { 435 | // Error malformed packet 436 | Weird("Malformed S7 packet wrong length"); 437 | *err = ANALYZER_ERROR_UNEXPECTED_LENGTH; 438 | return length; 439 | } 440 | u_char pointer_id = data[offset+2]; 441 | u_char areas; 442 | switch (pointer_id) 443 | { 444 | case S7COMM_SYNTAXID_S7ANY: 445 | // normal addressing 446 | temp.size = data[offset +3]; //constant 447 | temp.count = ubs16(*((u_int16*)(data + offset+4))); 448 | temp.dbnum = ubs16(*((u_int16*)(data + offset+6))); 449 | temp.area = data[offset + 8]; 450 | temp.start = ubs16(*((u_int16*)(data + offset+10))); // 3 bytes field with 0x00 pad (padding start needs to be checked!) 451 | temp.item_id = 0; 452 | temp.is_subitem = false; 453 | 454 | items->push_back(temp); 455 | DBG("Read item--> size: %u, count: %u, dbnum: %u, area: %x, start: %u\n", temp.size, temp.count, temp.dbnum, temp.area, temp.start); 456 | break; 457 | case S7COMM_SYNTAXID_DBREAD: 458 | { 459 | areas = data[offset + 3]; 460 | if (2 + areas * 5 > addr_len) 461 | { 462 | // Error malformed packet 463 | DBG("expected length: %u, real: %u\n", addr_len, 2 + areas*5); 464 | Weird("Malformed S7 packet wrong length"); 465 | *err = ANALYZER_ERROR_UNEXPECTED_LENGTH; 466 | return length; 467 | } 468 | for (int i = 0; i < areas; i++) 469 | { 470 | temp.size = S7COMM_TRANSPORT_SIZE_BYTE; 471 | temp.count = data[offset + (i * 5) + 4]; 472 | temp.dbnum = ubs16(*((u_int16*)(data + offset + (i*5) + 5))); 473 | temp.area = S7COMM_AREA_DB; //area db 474 | temp.start = ubs16(*((u_int16*)(data + offset+(i*5)+7))); 475 | temp.item_id = i; 476 | temp.is_subitem = true; 477 | 478 | items->push_back(temp); 479 | DBG("Read item--> size: %u, count: %u, dbnum: %u, area: %x, start: %u\n", temp.size, temp.count, temp.dbnum, temp.area, temp.start); 480 | } 481 | // special 482 | break; 483 | } 484 | case S7COMM_SYNTAXID_1200SYM: 485 | //TODO reverse s1200 symbolic addressing 486 | *err = ANALYZER_ERROR_UNSOPPORTED_ADDRESSING; 487 | return length; 488 | default: 489 | Weird("Unsupported Variable Addressing"); 490 | *err = ANALYZER_ERROR_UNSOPPORTED_ADDRESSING; 491 | return length; 492 | } 493 | 494 | return offset + addr_len + 2; 495 | 496 | } 497 | 498 | // parse the requests and responses (it contains the data for read requests) 499 | void 500 | S7comm_Analyzer::ParseRequestResponse(val_list* vl, PDU_HDR* main_hdr, const u_char* data, int length, bool orig) 501 | { 502 | int offset = 0; 503 | u_char function = data[offset]; 504 | offset++; 505 | vl->append(new Val(function, TYPE_COUNT)); 506 | int err = 0; 507 | int len; 508 | 509 | if (main_hdr->type == S7COMM_ROSCTR_JOB) 510 | { 511 | switch (function) 512 | { 513 | // read request 514 | case S7COMM_SERV_READVAR: 515 | { 516 | u_char item_count = data[offset]; 517 | offset += 1; 518 | 519 | DBG("ReadVar request cnt: %u\n", item_count); 520 | Request temp; 521 | temp.pdu_ref = main_hdr->pdu_ref; 522 | temp.age = 0; 523 | int offset_old; 524 | /* parse item data */ 525 | for (int i = 0; i < item_count; i++) 526 | { 527 | offset_old = offset; 528 | offset = ParseReadItems(temp.items, data, offset, length, &err); 529 | /* if length is not a multiple of 2 and this is not the last item, then add a fill-byte */ 530 | len = offset - offset_old; 531 | if ((len % 2) && (i < item_count)) 532 | { 533 | offset += 1; 534 | } 535 | if (offset >= length) 536 | { 537 | break; 538 | } 539 | } 540 | requests->push_back(temp); 541 | 542 | break; 543 | } 544 | // write requests 545 | case S7COMM_SERV_WRITEVAR: 546 | { 547 | u_char item_count = data[offset]; 548 | const u_char* real_data = data + main_hdr->param_len; 549 | offset += 1; 550 | 551 | int offset_old; 552 | DBG("Writevar request cnt: %u\n", item_count); 553 | /* parse item data */ 554 | for (int i = 0; i < item_count; i++) 555 | { 556 | offset_old = offset; 557 | offset = ParseWriteItems(data, offset, length, &real_data, &err); 558 | /* if length is not a multiple of 2 and this is not the last item, then add a fill-byte */ 559 | len = offset - offset_old; 560 | if ((len % 2) && (i < item_count)) 561 | { 562 | offset += 1; 563 | } 564 | if (offset >= length) 565 | { 566 | break; 567 | } 568 | } 569 | 570 | break; 571 | } 572 | // these functions are no longer parsed, they are logged tho 573 | case S7COMM_SERV_SETUPCOMM: 574 | /* Special functions */ 575 | //parsing the affected block could useful TODO 576 | case S7COMM_FUNCREQUESTDOWNLOAD: 577 | case S7COMM_FUNCDOWNLOADBLOCK: 578 | case S7COMM_FUNCDOWNLOADENDED: 579 | case S7COMM_FUNCSTARTUPLOAD: 580 | case S7COMM_FUNCUPLOAD: 581 | case S7COMM_FUNCENDUPLOAD: 582 | case S7COMM_FUNC_PLC_CONTROL: 583 | case S7COMM_FUNC_PLC_STOP: 584 | default: 585 | break; 586 | } 587 | } //parse replies 588 | else if (main_hdr->type == S7COMM_ROSCTR_ACK_DATA) 589 | { 590 | switch (function) 591 | { 592 | // extract the read data and match it with the request 593 | case S7COMM_SERV_READVAR: 594 | { 595 | u_char item_count = data[offset]; 596 | offset += 1; 597 | const u_char* next_data = &data[offset]; 598 | 599 | /* handle read ack */ 600 | for (std::list::iterator it = requests->begin(); it != requests->end(); ) 601 | { 602 | DBG("Request list--> pdu ref: %u count: %lu age: %d\n", it->pdu_ref, it->items->size(), it->age); 603 | if (it->pdu_ref == main_hdr->pdu_ref) 604 | { 605 | // read variable data 606 | if (item_count != it->items->size()) 607 | { 608 | err = ANALYZER_ERROR_UNEXPECTED_ITEM_COUNT; 609 | } 610 | for (std::list::iterator il = it->items->begin(); il != it->items->end(); il++) 611 | { 612 | next_data = ParseDataSendEvent(&(*il), next_data, true, &err); 613 | } 614 | it = requests->erase(it); 615 | } 616 | else if (it->age >= S7COMM_MAX_AGE) 617 | { 618 | // drop the request 619 | it = requests->erase(it); 620 | } 621 | else 622 | { 623 | it->age++; 624 | it++; 625 | } 626 | } 627 | 628 | break; 629 | } 630 | case S7COMM_SERV_WRITEVAR: 631 | 632 | break; 633 | case S7COMM_SERV_SETUPCOMM: 634 | break; 635 | default: 636 | 637 | break; 638 | } 639 | 640 | } 641 | // raise the s7 message type event 642 | vl->append(new Val(err, TYPE_COUNT)); 643 | ConnectionEvent(siemenss7_packet, vl); 644 | } 645 | 646 | // s7-400 plcs can periodically push data, this needs to be reversed TODO 647 | void 648 | S7comm_Analyzer::ParseCyclicData( const u_char* data, u_char fnmode, u_char fnsub, u_char seqnum) //error, length, dataref 649 | { 650 | return; 651 | } 652 | 653 | // parse user data messages 654 | void 655 | S7comm_Analyzer::ParseUserData(val_list* vl, PDU_HDR* main_hdr, const u_char* data, int length, bool orig) 656 | { 657 | // SZL READS + CYCLIC updates 658 | u_int32 offset = 0; 659 | u_char fnmode; 660 | u_char fntype; 661 | u_char fnsub; 662 | u_char param_length; 663 | u_char seqnum; 664 | u_char datauref = 0; 665 | u_int16 error = 0; 666 | if (length < 4) 667 | { 668 | Weird("Malformed UserData packet cannot parse it!"); 669 | return; 670 | } 671 | offset += 3; //skipping 3 byte constant parameter head (0x00010C) 672 | param_length = data[offset++]; 673 | if (length < 4 + param_length || param_length < 4) 674 | { 675 | Weird("Malformed UserData parameter length cannot parse it!"); 676 | return; 677 | } 678 | offset++; //skipping reqresp field 679 | fnmode = (data[offset] & 0xf0) >> 4; 680 | fntype = data[offset++] & 0x0f; 681 | fnsub = data[offset++]; 682 | 683 | if (param_length == 8) 684 | { 685 | error = ubs16(*(u_int16*)(data + 10)); 686 | } 687 | 688 | if (fnmode == S7COMM_UD_TYPE_REQ && !orig) 689 | { 690 | Weird("UserData request coming from slave!"); 691 | } 692 | else if ( (fnmode == S7COMM_UD_TYPE_PUSH || fnmode == S7COMM_UD_TYPE_RES) && orig) 693 | { 694 | Weird("UserData response coming from master!"); 695 | } 696 | // send user data request type and subtype 697 | vl->append(new Val(fnmode, TYPE_COUNT)); 698 | vl->append(new Val(fntype, TYPE_COUNT)); 699 | vl->append(new Val(fnsub, TYPE_COUNT)); 700 | vl->append(new Val(error, TYPE_COUNT)); 701 | ConnectionEvent(siemenss7_ud_packet, vl); 702 | 703 | // Parse Cyclic Data 704 | if ( fntype == S7COMM_UD_FUNCGROUP_CYCLIC) 705 | { 706 | seqnum = data[offset++]; 707 | if (param_length > 4) 708 | { 709 | datauref = data[offset]; 710 | } 711 | ParseCyclicData(data, fnmode, fnsub, seqnum); 712 | } 713 | // parsing szl requests and authentication requests could be useful TODO 714 | 715 | return; 716 | } 717 | 718 | // parses the S7 header than calls the apprpriate subfunction 719 | void 720 | S7comm_Analyzer::ParseS7PDU(int length, const u_char* data, bool orig) 721 | { 722 | PDU_HDR main_hdr; 723 | memcpy(&main_hdr, data, sizeof(PDU_HDR)); 724 | main_hdr.param_len = ubs16(main_hdr.param_len); 725 | main_hdr.data_len = ubs16(main_hdr.data_len); 726 | main_hdr.error = ubs16(main_hdr.error); 727 | 728 | val_list* vl = new val_list; 729 | vl->append(BuildConnVal()); 730 | 731 | if (main_hdr.pid != 0x32) 732 | { 733 | Weird("Unexpected Protocol ID, non s7 data"); 734 | return; 735 | } 736 | if(length < S7COMM_MIN_TELEGRAM_LENGTH) 737 | { 738 | Weird("Data too short for s7 packet"); 739 | return; 740 | } 741 | if (main_hdr.type < 0x01 || main_hdr.type > 0x07) 742 | { 743 | Weird("Unrecognized s7 message type"); 744 | return; 745 | } 746 | 747 | vl->append(new Val(main_hdr.type, TYPE_COUNT)); 748 | switch (main_hdr.type) 749 | { 750 | // S7 requests 751 | case S7COMM_ROSCTR_JOB: 752 | if(orig) 753 | { 754 | ParseRequestResponse(vl, &main_hdr, data+10, length-10, orig); 755 | } 756 | else 757 | { 758 | // this might happen legitemately during block upload 759 | Weird("Job request coming from the slave"); 760 | return; 761 | } 762 | break; 763 | // S7 response 764 | case S7COMM_ROSCTR_ACK_DATA: 765 | if(!orig && !main_hdr.error) 766 | { 767 | ParseRequestResponse(vl, &main_hdr, data+12, length-12, orig); 768 | } 769 | else 770 | { 771 | if(orig) 772 | { 773 | Weird("Job Response coming from master!"); 774 | return; 775 | } 776 | //TODO raise error event! 777 | 778 | } 779 | break; 780 | // 781 | case S7COMM_ROSCTR_USERDATA: 782 | ParseUserData(vl, &main_hdr, data+10, length-10, orig); 783 | break; 784 | case S7COMM_ROSCTR_ACK: 785 | //TODO when does this even come? 786 | break; 787 | default: 788 | Weird("Unknown s7 message type"); 789 | return; 790 | } 791 | } 792 | 793 | -------------------------------------------------------------------------------- /src/S7comm.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Defines and structures for the S7 protocol analyzer 3 | * Author: Gyorgy Miru 4 | * Date: 2015.12.11. 5 | * Version: 0.11 6 | */ 7 | 8 | #ifndef ANALYZER_PROTOCOL_S7comm_H 9 | #define ANALYZER_PROTOCOL_S7comm_H 10 | 11 | #include 12 | 13 | // Debug print macro 14 | #define S7DEBUG 0 15 | #define DBG(fmt, ...) \ 16 | do { if(S7DEBUG) fprintf(stderr, "%s:%d:%s() " fmt, __FILE__, \ 17 | __LINE__, __func__, __VA_ARGS__);} while (0) 18 | 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | // typedefs 26 | typedef unsigned char u_char; 27 | typedef unsigned short u_int16; 28 | typedef short int16; 29 | typedef unsigned int u_int32; 30 | typedef int int32; 31 | 32 | // TODO replace with ntohs 33 | inline u_int16 ubs16(u_int16 us) 34 | { 35 | return ((us>>8) & 0xFF) | ((us<<8) & 0xFF00); 36 | } 37 | 38 | // TODO replace with ntohl 39 | inline u_int32 ubs32(u_int32 ui) 40 | { 41 | return ((ui >> 24) & 0x000000FF) | 42 | ((ui<<8) & 0x00FF0000) | 43 | ((ui>>8) & 0x0000FF00) | 44 | ((ui << 24) & 0xFF000000); 45 | } 46 | // TODO replace 47 | inline float bsf(float ui) 48 | { 49 | float ret=0; 50 | u_char* src = (u_char*)&ui; 51 | u_char* dst = (u_char*)&ret; 52 | 53 | //DBG("float dump %f\n", ui); 54 | dst[0] = src[3]; 55 | DBG("%02x\n", dst[0]); 56 | dst[1] = src[2]; 57 | DBG("%02x\n", dst[1]); 58 | dst[2] = src[1]; 59 | DBG("%02x\n", dst[2]); 60 | dst[3] = src[0]; 61 | DBG("%02x\n", dst[3]); 62 | 63 | return ret; 64 | } 65 | 66 | // store PLC variable meta data 67 | typedef struct item{ 68 | u_char area; 69 | u_char item_id; 70 | u_int16 dbnum; 71 | u_int16 start; 72 | u_int16 count; 73 | u_char size; 74 | bool is_subitem; 75 | }Item; 76 | 77 | // used to match requests and responses 78 | typedef struct request{ 79 | u_int16 pdu_ref; 80 | int age; 81 | std::list* items; 82 | request() 83 | { 84 | items = new std::list(); 85 | } 86 | ~request() 87 | { 88 | delete items; 89 | } 90 | //request(request&& orig) 91 | //{ 92 | // pdu_ref = orig.pdu_ref; 93 | // age = orig.age; 94 | // items = orig.items; 95 | // orig.items= NULL; 96 | //} 97 | request(const request& orig) 98 | { 99 | pdu_ref = orig.pdu_ref; 100 | age = orig.age; 101 | items = new std::list(); 102 | *items = *orig.items; 103 | } 104 | 105 | }Request; 106 | 107 | // Structure of the iso-cotp header 108 | typedef struct iso_hdr{ 109 | unsigned char tpkt_version; 110 | unsigned char tpkt_reserved; 111 | unsigned short tpkt_length; 112 | unsigned char cotp_length; 113 | unsigned char cotp_type; 114 | //unsigned short rest; 115 | }ISO_HDR; 116 | 117 | // iso-cotp connection request header 118 | typedef struct cr_hdr{ 119 | unsigned short dest_ref; 120 | unsigned short src_ref; 121 | unsigned char class_options; 122 | }CR_HDR; 123 | 124 | // iso-cotp connection info 125 | typedef struct connection_info{ 126 | unsigned short src_tsap; 127 | unsigned short dst_tsap; 128 | unsigned char tpdu_len; 129 | }CONN_INFO; 130 | 131 | // S7comm pdu header 132 | typedef struct pdu_hdr{ 133 | u_char pid; //0x32 134 | u_char type; // message type 135 | u_int16 reserved; //redundancy id always 0 136 | u_int16 pdu_ref; 137 | u_int16 param_len; 138 | u_int16 data_len; 139 | u_int16 error; //only in ACK msg 140 | } PDU_HDR; 141 | 142 | 143 | // The S7 Analyzer plugin class 144 | namespace analyzer { namespace Crysys { 145 | 146 | class S7comm_Analyzer : public tcp::TCP_ApplicationAnalyzer { 147 | public: 148 | S7comm_Analyzer(Connection* conn); 149 | virtual ~S7comm_Analyzer(); 150 | virtual void Done(); 151 | virtual void Init(); 152 | virtual void DeliverStream(int len, const u_char* data, bool orig); 153 | 154 | static analyzer::Analyzer* Instantiate(Connection* conn) 155 | { 156 | return new S7comm_Analyzer(conn); 157 | } 158 | 159 | protected: 160 | // create variable data event from item 161 | val_list* CreateDataEventVal(Item* item); 162 | // parse data messages 163 | const u_char* ParseDataSendEvent(Item* item, const u_char* next_data, bool is_read, int* err); 164 | int ParseWriteItems(const u_char* data, int offset, int length, const u_char** next_data, int* err); 165 | int ParseReadItems(list* items, const u_char* data, int offset, int length, int* err); 166 | // parse S7 requests and responses (non user data message) 167 | void ParseRequestResponse(val_list* vl, PDU_HDR* main_hdr, const u_char* data, int length, bool orig); 168 | // parse user data messages 169 | void ParseUserData(val_list* vl, PDU_HDR* main_hdr, const u_char* data, int length, bool orig); 170 | // parse the received PDU 171 | void ParseS7PDU(int length, const u_char* data, bool orig); 172 | // parse cyclic data TODO (requires S400) 173 | void ParseCyclicData( const u_char* data, u_char fnmode, u_char fnsub, u_char seqnum); //error, length, dataref 174 | // list to match requests with responses 175 | list* requests; 176 | }; 177 | }} 178 | #endif 179 | -------------------------------------------------------------------------------- /src/S7constants.h: -------------------------------------------------------------------------------- 1 | /** 2 | * S7 Protocol constants 3 | * Most of this file was copied from the 4 | * source of the S7 wireshark dissector 5 | * written by Thomas Wiens 6 | * https://sourceforge.net/projects/s7commwireshark/ 7 | * Author: Gyorgy Miru 8 | * Date: 2015.11.04. 9 | * Version: 0.3 10 | */ 11 | 12 | #ifndef ANALYZER_PROTOCOL_S7CONSTANTS_H 13 | #define ANALYZER_PROTOCOL_S7CONSTANTS_H 14 | 15 | // COTP PDU types 16 | #define CR 0xe0 17 | #define CC 0xd0 18 | #define DR 0x80 19 | #define DC 0xc0 20 | #define DT 0xf0 21 | #define ED 0x10 22 | #define AK 0x60 23 | #define EA 0x20 24 | #define RJ 0x50 25 | #define ERR 0x70 26 | 27 | // CR CC class 0 variable parameter types 28 | #define SRC_TSAP 0xc1 29 | #define DST_TSAP 0xc2 30 | #define TPDU_LEN 0xc0 31 | 32 | #define ANALYZER_ERROR_UNSUPPORTED_DATA_TYPE 0x4141 33 | #define ANALYZER_ERROR_UNEXPECTED_ITEM_COUNT 0x4242 34 | #define ANALYZER_ERROR_UNEXPECTED_LENGTH 0x4343 35 | #define ANALYZER_ERROR_UNSOPPORTED_ADDRESSING 0x4444 36 | #define S7COMM_MIN_TELEGRAM_LENGTH 10 37 | /************************************************************************** 38 | * PDU types 39 | */ 40 | #define S7COMM_ROSCTR_JOB 0x01 41 | #define S7COMM_ROSCTR_ACK 0x02 42 | #define S7COMM_ROSCTR_ACK_DATA 0x03 43 | #define S7COMM_ROSCTR_USERDATA 0x07 44 | 45 | #define S7COMM_MAX_AGE 3 46 | 47 | /************************************************************************** 48 | * Error classes in header 49 | */ 50 | #define S7COMM_ERRCLS_NONE 0x00 51 | #define S7COMM_ERRCLS_APPREL 0x81 52 | #define S7COMM_ERRCLS_OBJDEF 0x82 53 | #define S7COMM_ERRCLS_RESSOURCE 0x83 54 | #define S7COMM_ERRCLS_SERVICE 0x84 55 | #define S7COMM_ERRCLS_SUPPLIES 0x85 56 | #define S7COMM_ERRCLS_ACCESS 0x87 57 | 58 | 59 | /************************************************************************** 60 | * Error code in parameter part 61 | */ 62 | #define S7COMM_PERRCOD_NO_ERROR 0x0000 63 | #define S7COMM_PERRCOD_INVALID_BLOCK_TYPE_NUM 0x0110 64 | #define S7COMM_PERRCOD_INVALID_PARAM 0x0112 65 | #define S7COMM_PERRCOD_PG_RESOURCE_ERROR 0x011A 66 | #define S7COMM_PERRCOD_PLC_RESOURCE_ERROR 0x011B 67 | #define S7COMM_PERRCOD_PROTOCOL_ERROR 0x011C 68 | #define S7COMM_PERRCOD_USER_BUFFER_TOO_SHORT 0x011F 69 | #define S7COMM_PERRCOD_REQ_INI_ERR 0x0141 70 | #define S7COMM_PERRCOD_VERSION_MISMATCH 0x01C0 71 | #define S7COMM_PERRCOD_NOT_IMPLEMENTED 0x01F0 72 | #define S7COMM_PERRCOD_L7_INVALID_CPU_STATE 0x8001 73 | #define S7COMM_PERRCOD_L7_PDU_SIZE_ERR 0x8500 74 | #define S7COMM_PERRCOD_L7_INVALID_SZL_ID 0xD401 75 | #define S7COMM_PERRCOD_L7_INVALID_INDEX 0xD402 76 | #define S7COMM_PERRCOD_L7_DGS_CONN_ALREADY_ANNOU 0xD403 77 | #define S7COMM_PERRCOD_L7_MAX_USER_NB 0xD404 78 | #define S7COMM_PERRCOD_L7_DGS_FKT_PAR_SYNTAX_ERR 0xD405 79 | #define S7COMM_PERRCOD_L7_NO_INFO 0xD406 80 | #define S7COMM_PERRCOD_L7_PRT_FKT_PAR_SYNTAX_ERR 0xD601 81 | #define S7COMM_PERRCOD_L7_INVALID_VAR_ADDR 0xD801 82 | #define S7COMM_PERRCOD_L7_UNKNOWN_REQ 0xD802 83 | #define S7COMM_PERRCOD_L7_INVALID_REQ_STATUS 0xD803 84 | 85 | /************************************************************************** 86 | * Function codes in parameter part 87 | */ 88 | #define S7COMM_SERV_CPU 0x00 89 | #define S7COMM_SERV_SETUPCOMM 0xF0 90 | #define S7COMM_SERV_READVAR 0x04 91 | #define S7COMM_SERV_WRITEVAR 0x05 92 | 93 | #define S7COMM_FUNCREQUESTDOWNLOAD 0x1A 94 | #define S7COMM_FUNCDOWNLOADBLOCK 0x1B 95 | #define S7COMM_FUNCDOWNLOADENDED 0x1C 96 | #define S7COMM_FUNCSTARTUPLOAD 0x1D 97 | #define S7COMM_FUNCUPLOAD 0x1E 98 | #define S7COMM_FUNCENDUPLOAD 0x1F 99 | #define S7COMM_FUNC_PLC_CONTROL 0x28 100 | #define S7COMM_FUNC_PLC_STOP 0x29 101 | 102 | /************************************************************************** 103 | * Area names 104 | */ 105 | #define S7COMM_AREA_SYSINFO 0x03 /* System info of 200 family */ 106 | #define S7COMM_AREA_SYSFLAGS 0x05 /* System flags of 200 family */ 107 | #define S7COMM_AREA_ANAIN 0x06 /* analog inputs of 200 family */ 108 | #define S7COMM_AREA_ANAOUT 0x07 /* analog outputs of 200 family */ 109 | #define S7COMM_AREA_P 0x80 /* direct peripheral access */ 110 | #define S7COMM_AREA_INPUTS 0x81 111 | #define S7COMM_AREA_OUTPUTS 0x82 112 | #define S7COMM_AREA_FLAGS 0x83 113 | #define S7COMM_AREA_DB 0x84 /* data blocks */ 114 | #define S7COMM_AREA_DI 0x85 /* instance data blocks */ 115 | #define S7COMM_AREA_LOCAL 0x86 /* local data (should not be accessible over network) */ 116 | #define S7COMM_AREA_V 0x87 /* previous (Vorgaenger) local data (should not be accessible over network) */ 117 | #define S7COMM_AREA_COUNTER 28 /* S7 counters */ 118 | #define S7COMM_AREA_TIMER 29 /* S7 timers */ 119 | #define S7COMM_AREA_COUNTER200 30 /* IEC counters (200 family) */ 120 | #define S7COMM_AREA_TIMER200 31 /* IEC timers (200 family) */ 121 | 122 | /************************************************************************** 123 | * Transport sizes in item data 124 | */ 125 | /* types of 1 byte length */ 126 | #define S7COMM_TRANSPORT_SIZE_BIT 1 127 | #define S7COMM_TRANSPORT_SIZE_BYTE 2 128 | #define S7COMM_TRANSPORT_SIZE_CHAR 3 129 | /* types of 2 bytes length */ 130 | #define S7COMM_TRANSPORT_SIZE_WORD 4 131 | #define S7COMM_TRANSPORT_SIZE_INT 5 132 | /* types of 4 bytes length */ 133 | #define S7COMM_TRANSPORT_SIZE_DWORD 6 134 | #define S7COMM_TRANSPORT_SIZE_DINT 7 135 | #define S7COMM_TRANSPORT_SIZE_REAL 8 136 | /* Special types */ 137 | #define S7COMM_TRANSPORT_SIZE_DATE 9 138 | #define S7COMM_TRANSPORT_SIZE_TOD 10 139 | #define S7COMM_TRANSPORT_SIZE_TIME 11 140 | #define S7COMM_TRANSPORT_SIZE_S5TIME 12 141 | #define S7COMM_TRANSPORT_SIZE_DT 15 142 | /* Timer or counter */ 143 | #define S7COMM_TRANSPORT_SIZE_COUNTER 28 144 | #define S7COMM_TRANSPORT_SIZE_TIMER 29 145 | #define S7COMM_TRANSPORT_SIZE_IEC_COUNTER 30 146 | #define S7COMM_TRANSPORT_SIZE_IEC_TIMER 31 147 | #define S7COMM_TRANSPORT_SIZE_HS_COUNTER 32 148 | 149 | /************************************************************************** 150 | * Syntax Ids of variable specification 151 | */ 152 | #define S7COMM_SYNTAXID_S7ANY 0x10 /* Adress data S7-Any pointer-like DB1.DBX10.2 */ 153 | #define S7COMM_SYNTAXID_DRIVEESANY 0xa2 /* seen on Drive ES Starter with routing over S7 */ 154 | #define S7COMM_SYNTAXID_1200SYM 0xb2 /* Symbolic address mode of S7-1200 */ 155 | #define S7COMM_SYNTAXID_DBREAD 0xb0 /* Kind of DB block read, seen only at an S7-400 */ 156 | 157 | /************************************************************************** 158 | * Transport sizes in data 159 | */ 160 | #define S7COMM_DATA_TRANSPORT_SIZE_NULL 0 161 | #define S7COMM_DATA_TRANSPORT_SIZE_BBIT 3 /* bit access, len is in bits */ 162 | #define S7COMM_DATA_TRANSPORT_SIZE_BBYTE 4 /* byte/word/dword acces, len is in bits */ 163 | #define S7COMM_DATA_TRANSPORT_SIZE_BINT 5 /* integer access, len is in bits */ 164 | #define S7COMM_DATA_TRANSPORT_SIZE_BREAL 7 /* real access, len is in bytes */ 165 | #define S7COMM_DATA_TRANSPORT_SIZE_BSTR 9 /* octet string, len is in bytes */ 166 | 167 | /************************************************************************** 168 | * Returnvalues of an item response 169 | */ 170 | 171 | /************************************************************************** 172 | * Block Types 173 | */ 174 | #define S7COMM_BLOCKTYPE_OB '8' 175 | #define S7COMM_BLOCKTYPE_DB 'A' 176 | #define S7COMM_BLOCKTYPE_SDB 'B' 177 | #define S7COMM_BLOCKTYPE_FC 'C' 178 | #define S7COMM_BLOCKTYPE_SFC 'D' 179 | #define S7COMM_BLOCKTYPE_FB 'E' 180 | #define S7COMM_BLOCKTYPE_SFB 'F' 181 | 182 | 183 | /************************************************************************** 184 | * Subblk types 185 | */ 186 | #define S7COMM_SUBBLKTYPE_OB 0x08 187 | #define S7COMM_SUBBLKTYPE_DB 0x0a 188 | #define S7COMM_SUBBLKTYPE_SDB 0x0b 189 | #define S7COMM_SUBBLKTYPE_FC 0x0c 190 | #define S7COMM_SUBBLKTYPE_SFC 0x0d 191 | #define S7COMM_SUBBLKTYPE_FB 0x0e 192 | #define S7COMM_SUBBLKTYPE_SFB 0x0f 193 | 194 | 195 | /************************************************************************** 196 | * Block security 197 | */ 198 | #define S7COMM_BLOCKSECURITY_OFF 0 199 | #define S7COMM_BLOCKSECURITY_KNOWHOWPROTECT 3 200 | 201 | /************************************************************************** 202 | * Names of types in userdata parameter part 203 | */ 204 | 205 | #define S7COMM_UD_TYPE_PUSH 0x00 206 | #define S7COMM_UD_TYPE_REQ 0x04 207 | #define S7COMM_UD_TYPE_RES 0x08 208 | 209 | 210 | /************************************************************************** 211 | * Userdata Parameter, last data unit 212 | */ 213 | #define S7COMM_UD_LASTDATAUNIT_YES 0x00 214 | #define S7COMM_UD_LASTDATAUNIT_NO 0x01 215 | 216 | /************************************************************************** 217 | * Names of Function groups in userdata parameter part 218 | */ 219 | #define S7COMM_UD_FUNCGROUP_PROG 0x1 220 | #define S7COMM_UD_FUNCGROUP_CYCLIC 0x2 221 | #define S7COMM_UD_FUNCGROUP_BLOCK 0x3 222 | #define S7COMM_UD_FUNCGROUP_CPU 0x4 223 | #define S7COMM_UD_FUNCGROUP_SEC 0x5 /* Security funnctions e.g. plc password */ 224 | #define S7COMM_UD_FUNCGROUP_TIME 0x7 225 | 226 | 227 | /************************************************************************** 228 | * Vartab: Typ of data in data part, first two bytes 229 | */ 230 | #define S7COMM_UD_SUBF_PROG_VARTAB_TYPE_REQ 0x14 231 | #define S7COMM_UD_SUBF_PROG_VARTAB_TYPE_RES 0x04 232 | 233 | /************************************************************************** 234 | * Vartab: area of data request 235 | * 236 | * Low Hi 237 | * 0=M 1=BYTE 238 | * 1=E 2=WORD 239 | * 2=A 3=DWORD 240 | * 3=PEx 241 | * 7=DB 242 | * 54=TIMER 243 | * 64=COUNTER 244 | */ 245 | #define S7COMM_UD_SUBF_PROG_VARTAB_AREA_MB 0x01 246 | #define S7COMM_UD_SUBF_PROG_VARTAB_AREA_MW 0x02 247 | #define S7COMM_UD_SUBF_PROG_VARTAB_AREA_MD 0x03 248 | #define S7COMM_UD_SUBF_PROG_VARTAB_AREA_EB 0x11 249 | #define S7COMM_UD_SUBF_PROG_VARTAB_AREA_EW 0x12 250 | #define S7COMM_UD_SUBF_PROG_VARTAB_AREA_ED 0x13 251 | #define S7COMM_UD_SUBF_PROG_VARTAB_AREA_AB 0x21 252 | #define S7COMM_UD_SUBF_PROG_VARTAB_AREA_AW 0x22 253 | #define S7COMM_UD_SUBF_PROG_VARTAB_AREA_AD 0x23 254 | #define S7COMM_UD_SUBF_PROG_VARTAB_AREA_PEB 0x31 255 | #define S7COMM_UD_SUBF_PROG_VARTAB_AREA_PEW 0x32 256 | #define S7COMM_UD_SUBF_PROG_VARTAB_AREA_PED 0x33 257 | #define S7COMM_UD_SUBF_PROG_VARTAB_AREA_DBB 0x71 258 | #define S7COMM_UD_SUBF_PROG_VARTAB_AREA_DBW 0x72 259 | #define S7COMM_UD_SUBF_PROG_VARTAB_AREA_DBD 0x73 260 | #define S7COMM_UD_SUBF_PROG_VARTAB_AREA_T 0x54 261 | #define S7COMM_UD_SUBF_PROG_VARTAB_AREA_C 0x64 262 | 263 | 264 | /************************************************************************** 265 | * Names of userdata subfunctions in group 1 (Programmer commands) 266 | */ 267 | #define S7COMM_UD_SUBF_PROG_REQDIAGDATA1 0x01 268 | #define S7COMM_UD_SUBF_PROG_VARTAB1 0x02 269 | #define S7COMM_UD_SUBF_PROG_ERASE 0x0c 270 | #define S7COMM_UD_SUBF_PROG_READDIAGDATA 0x0e 271 | #define S7COMM_UD_SUBF_PROG_REMOVEDIAGDATA 0x0f 272 | #define S7COMM_UD_SUBF_PROG_FORCE 0x10 273 | #define S7COMM_UD_SUBF_PROG_REQDIAGDATA2 0x13 274 | 275 | 276 | /************************************************************************** 277 | * Names of userdata subfunctions in group 2 (cyclic data) 278 | */ 279 | #define S7COMM_UD_SUBF_CYCLIC_MEM 0x01 280 | #define S7COMM_UD_SUBF_CYCLIC_UNSUBSCRIBE 0x04 281 | 282 | 283 | /************************************************************************** 284 | * Names of userdata subfunctions in group 3 (Block functions) 285 | */ 286 | #define S7COMM_UD_SUBF_BLOCK_LIST 0x01 287 | #define S7COMM_UD_SUBF_BLOCK_LISTTYPE 0x02 288 | #define S7COMM_UD_SUBF_BLOCK_BLOCKINFO 0x03 289 | 290 | /************************************************************************** 291 | * Names of userdata subfunctions in group 4 (CPU functions) 292 | */ 293 | #define S7COMM_UD_SUBF_CPU_READSZL 0x01 294 | #define S7COMM_UD_SUBF_CPU_MSGS 0x02 295 | #define S7COMM_UD_SUBF_CPU_TRANSSTOP 0x03 296 | #define S7COMM_UD_SUBF_CPU_ALARMIND 0x11 297 | #define S7COMM_UD_SUBF_CPU_ALARMINIT 0x13 298 | #define S7COMM_UD_SUBF_CPU_ALARMACK1 0x0b 299 | #define S7COMM_UD_SUBF_CPU_ALARMACK2 0x0c 300 | 301 | /************************************************************************** 302 | * Names of userdata subfunctions in group 5 (Security?) 303 | */ 304 | #define S7COMM_UD_SUBF_SEC_PASSWD 0x01 305 | 306 | /************************************************************************** 307 | * Names of userdata subfunctions in group 7 (Time functions) 308 | */ 309 | #define S7COMM_UD_SUBF_TIME_READ 0x01 310 | #define S7COMM_UD_SUBF_TIME_SET 0x02 311 | #define S7COMM_UD_SUBF_TIME_READF 0x03 312 | #define S7COMM_UD_SUBF_TIME_SET2 0x04 313 | 314 | /************************************************************************** 315 | * Flags for LID access 316 | */ 317 | #define S7COMM_TIA1200_VAR_ENCAPS_LID 0x2 318 | #define S7COMM_TIA1200_VAR_ENCAPS_IDX 0x3 319 | #define S7COMM_TIA1200_VAR_OBTAIN_LID 0x4 320 | #define S7COMM_TIA1200_VAR_OBTAIN_IDX 0x5 321 | #define S7COMM_TIA1200_VAR_PART_START 0x6 322 | #define S7COMM_TIA1200_VAR_PART_LEN 0x7 323 | 324 | /************************************************************************** 325 | * TIA 1200 Area Names for variable access 326 | */ 327 | #define S7COMM_TIA1200_VAR_ITEM_AREA1_DB 0x8a0e /* Reading DB, 2 byte DB-Number following */ 328 | #define S7COMM_TIA1200_VAR_ITEM_AREA1_IQMCT 0x0000 /* Reading I/Q/M/C/T, 2 Byte detail area following */ 329 | 330 | #define S7COMM_TIA1200_VAR_ITEM_AREA2_I 0x50 331 | #define S7COMM_TIA1200_VAR_ITEM_AREA2_Q 0x51 332 | #define S7COMM_TIA1200_VAR_ITEM_AREA2_M 0x52 333 | #define S7COMM_TIA1200_VAR_ITEM_AREA2_C 0x53 334 | #define S7COMM_TIA1200_VAR_ITEM_AREA2_T 0x54 335 | 336 | #endif 337 | -------------------------------------------------------------------------------- /src/events.bif: -------------------------------------------------------------------------------- 1 | ### 2 | ## Events published by the S7 protocol analyzer plugin 3 | ## Author: Gyorgy Miru 4 | ## Date: 2015.11.04. 5 | ## Version: 0.4 6 | 7 | # Information about regular S7 packets 8 | event siemenss7_packet%(c: connection, msgtype: count, functype: count, errno: count%); 9 | # Information about UserData S7 packets 10 | event siemenss7_ud_packet%(c: connection, msgtype: count, functionmode: count, functiontype: count, subfunction: count, errno: count%); 11 | 12 | # Information about the underlying iso-cotp protocol 13 | event iso_cotp_packet%(c: connection, msg: string, cdt: count%); 14 | 15 | # variable values read from or written to the PLC 16 | event siemenss7_read_data_unsigned%(c: connection, area: count, db: count, s7type: count, address: count, data: count%); 17 | event siemenss7_read_data_signed%(c: connection, area: count, db: count, s7type: count, address: count, data: int%); 18 | event siemenss7_read_data_real%(c: connection, area: count, db: count, s7type: count, address: count, data: double%); 19 | 20 | event siemenss7_write_data_unsigned%(c: connection, area: count, db: count, s7type: count, address: count, data: count%); 21 | event siemenss7_write_data_signed%(c: connection, area: count, db: count, s7type: count, address: count, data: int%); 22 | event siemenss7_write_data_real%(c: connection, area: count, db: count, s7type: count, address: count, data: double%); 23 | 24 | -------------------------------------------------------------------------------- /tests/Makefile: -------------------------------------------------------------------------------- 1 | 2 | test: 3 | @btest 4 | -------------------------------------------------------------------------------- /tests/Scripts/get-bro-env: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | # 3 | # BTest helper for getting values for Bro-related environment variables. 4 | 5 | base=`dirname $0` 6 | bro=`cat ${base}/../../build/CMakeCache.txt | grep BRO_DIST | cut -d = -f 2` 7 | 8 | if [ "$1" = "brobase" ]; then 9 | echo ${bro} 10 | elif [ "$1" = "bropath" ]; then 11 | ${bro}/build/bro-path-dev 12 | elif [ "$1" = "bro_plugin_path" ]; then 13 | ( cd ${base}/../.. && pwd ) 14 | elif [ "$1" = "bro_seed_file" ]; then 15 | echo ${bro}/testing/btest/random.seed 16 | elif [ "$1" = "path" ]; then 17 | echo ${bro}/build/src:${bro}/aux/btest:${base}/:${bro}/aux/bro-cut:$PATH 18 | else 19 | echo "usage: `basename $0` " >&2 20 | exit 1 21 | fi 22 | -------------------------------------------------------------------------------- /tests/btest.cfg: -------------------------------------------------------------------------------- 1 | [btest] 2 | TestDirs = s7comm 3 | TmpDir = %(testbase)s/.tmp 4 | BaselineDir = %(testbase)s/Baseline 5 | IgnoreDirs = .svn CVS .tmp 6 | IgnoreFiles = *.tmp *.swp #* *.trace .DS_Store 7 | 8 | [environment] 9 | BROBASE=`%(testbase)s/Scripts/get-bro-env brobase` 10 | BROPATH=`%(testbase)s/Scripts/get-bro-env bropath` 11 | BRO_PLUGIN_PATH=`%(testbase)s/Scripts/get-bro-env bro_plugin_path` 12 | BRO_SEED_FILE=`%(testbase)s/Scripts/get-bro-env bro_seed_file` 13 | PATH=`%(testbase)s/Scripts/get-bro-env path` 14 | TZ=UTC 15 | LC_ALL=C 16 | TRACES=%(testbase)s/Traces 17 | TMPDIR=%(testbase)s/.tmp 18 | BRO_TRACES=`%(testbase)s/Scripts/get-bro-env brobase`/testing/btest/Traces 19 | TEST_DIFF_CANONIFIER=`%(testbase)s/Scripts/get-bro-env brobase`/testing/scripts/diff-canonifier 20 | -------------------------------------------------------------------------------- /tests/s7comm/show-plugin.bro: -------------------------------------------------------------------------------- 1 | # @TEST-EXEC: bro -NN Crysys::S7comm >output 2 | # @TEST-EXEC: btest-diff output 3 | --------------------------------------------------------------------------------