├── .github └── PULL_REQUEST_TEMPLATE.md ├── CMakeLists.txt ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── THIRD-PARTY ├── VERSION ├── configure ├── configure.plugin ├── scripts └── S7comm │ ├── __load__.zeek │ ├── consts.zeek │ └── main.zeek ├── src ├── Plugin.cc ├── Plugin.h ├── S7comm.cc ├── S7comm.h ├── consts.pac ├── events.bif ├── s7comm-analyzer.pac ├── s7comm-protocol.pac └── s7comm.pac └── zkg.meta /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | *Issue #, if available:* 2 | 3 | *Description of changes:* 4 | 5 | 6 | By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice. 7 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.0 FATAL_ERROR) 2 | 3 | project(Plugin) 4 | 5 | include(ZeekPlugin) 6 | 7 | zeek_plugin_begin(Zeek S7comm) 8 | zeek_plugin_cc(src/S7comm.cc src/Plugin.cc) 9 | zeek_plugin_bif(src/events.bif) 10 | zeek_plugin_pac(src/s7comm.pac src/s7comm-analyzer.pac src/s7comm-protocol.pac) 11 | zeek_plugin_dist_files(README CHANGES COPYING VERSION) 12 | zeek_plugin_end() 13 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional 4 | documentation, we greatly value feedback and contributions from our community. 5 | 6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary 7 | information to effectively respond to your bug report or contribution. 8 | 9 | 10 | ## Reporting Bugs/Feature Requests 11 | 12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 13 | 14 | When filing an issue, please check [existing open](https://github.com/amzn/zeek-plugin-s7comm/issues), or [recently closed](https://github.com/amzn/zeek-plugin-s7comm/issues?utf8=%E2%9C%93&q=is%3Aissue%20is%3Aclosed%20), issues to make sure somebody else hasn't already 15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 16 | 17 | * A reproducible test case or series of steps 18 | * The version of our code being used 19 | * Any modifications you've made relevant to the bug 20 | * Anything unusual about your environment or deployment 21 | 22 | 23 | ## Contributing via Pull Requests 24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 25 | 26 | 1. You are working against the latest source on the *master* branch. 27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. 29 | 30 | To send us a pull request, please: 31 | 32 | 1. Fork the repository. 33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. 34 | 3. Ensure local tests pass. 35 | 4. Commit to your fork using clear commit messages. 36 | 5. Send us a pull request, answering any default questions in the pull request interface. 37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. 38 | 39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and 40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 41 | 42 | 43 | ## Finding contributions to work on 44 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any ['help wanted'](https://github.com/amzn/zeek-plugin-s7comm/labels/help%20wanted) issues is a great place to start. 45 | 46 | 47 | ## Code of Conduct 48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 50 | opensource-codeofconduct@amazon.com with any additional questions or comments. 51 | 52 | 53 | ## Security issue notifications 54 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. 55 | 56 | 57 | ## Licensing 58 | 59 | See the [LICENSE](https://github.com/amzn/zeek-plugin-s7comm/blob/master/LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. 60 | 61 | We may ask you to sign a [Contributor License Agreement (CLA)](http://en.wikipedia.org/wiki/Contributor_License_Agreement) for larger changes. 62 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM public.ecr.aws/amazonlinux/amazonlinux:latest 2 | MAINTAINER https://github.com/amzn/ 3 | 4 | # Metadata 5 | LABEL program=zeek 6 | 7 | # Specify program 8 | ENV PROG zeek 9 | # Specify source extension 10 | ENV EXT tar.gz 11 | # Specify Zeek version to download and install (e.g. 3.0.0) 12 | ENV VERS 3.2.4 13 | 14 | # Specify Cmake version 15 | ENV CMAKEVERSMAIN 3.10 16 | ENV CMAKEVERSSUB .0 17 | 18 | # Install directory 19 | ENV PREFIX /opt/zeek 20 | # Path should include prefix 21 | ENV PATH /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:$PREFIX/bin 22 | 23 | # Install dependencies 24 | RUN yum -y update 25 | RUN yum -y install cronie epel-release gcc gcc-c++ make libpcap-devel openssl-devel bind-devel zlib-devel git perl libcurl-devel GeoIP-devel python-devel jemalloc-devel swig libpcap bind-libs zlib bash python3 libcurl gawk GeoIP jemalloc wget flex bison python3-pip tar iproute procps-ng kernel-devel clang gdb && yum clean all 26 | 27 | # Zeek 3.1.0 needs Cmake 3.0 or higher 28 | WORKDIR /tmp 29 | RUN wget https://cmake.org/files/v$CMAKEVERSMAIN/cmake-$CMAKEVERSMAIN$CMAKEVERSSUB.tar.gz 30 | RUN tar -xvzf cmake-$CMAKEVERSMAIN$CMAKEVERSSUB.tar.gz 31 | WORKDIR /tmp/cmake-$CMAKEVERSMAIN$CMAKEVERSSUB 32 | RUN /tmp/cmake-$CMAKEVERSMAIN$CMAKEVERSSUB/bootstrap 33 | RUN make -j$((`nproc`-1)) 34 | RUN make install 35 | 36 | # Compile and install Zeek 37 | WORKDIR /tmp 38 | RUN wget https://old.zeek.org/downloads/$PROG-$VERS.$EXT && tar -xzf $PROG-$VERS.$EXT 39 | WORKDIR /tmp/$PROG-$VERS 40 | RUN ./configure --build-type=RelWithDebInfo --prefix=$PREFIX --disable-python 41 | RUN make -j$((`nproc`-1)) 42 | RUN make install 43 | 44 | USER root 45 | RUN pip3 install zkg 46 | RUN zkg autoconfig 47 | COPY [--chown=bro:bro] . /tmp/zeek-plugin-s7comm 48 | WORKDIR /tmp/zeek-plugin-s7comm 49 | RUN zkg install --force . 50 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2019 Amazon.com, Inc. or its affiliates. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are met: 5 | * Redistributions of source code must retain the above copyright 6 | notice, this list of conditions and the following disclaimer. 7 | * Redistributions in binary form must reproduce the above copyright 8 | notice, this list of conditions and the following disclaimer in the 9 | documentation and/or other materials provided with the distribution. 10 | * Neither the name of the copyright holder nor the 11 | names of its contributors may be used to endorse or promote products 12 | derived from this software without specific prior written permission. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY 18 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 21 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 23 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /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 ZEEK_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 | if [ -f build/lib/Zeek-* ]; then make -C tests; else echo "Plugin not built."; fi 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Zeek Plugin S7comm 2 | 3 | When running as part of your Zeek installation this plugin will produce two log files containing metadata extracted from any ISO COTP and Siemens S7 traffic observed on TCP port 102. S7 uses COTP as transport. 4 | 5 | ## Installation and Usage 6 | 7 | `zeek-plugin-s7comm` is distributed as a Zeek package and is compatible with the [`zkg`](https://docs.zeek.org/projects/package-manager/en/stable/zkg.html) command line tool. 8 | 9 | ## Sharing and Contributing 10 | 11 | This code is made available under the [BSD-3-Clause license](https://github.com/amzn/zeek-plugin-s7comm/blob/master/LICENSE). [Guidelines for contributing](https://github.com/amzn/zeek-plugin-s7comm/blob/master/CONTRIBUTING.md) are available as well as a [pull request template](https://github.com/amzn/zeek-plugin-s7comm/blob/master/.github/PULL_REQUEST_TEMPLATE.md). A [Dockerfile](https://github.com/amzn/zeek-plugin-s7comm/blob/master/Dockerfile) has been included in the repository to assist with setting up an environment for testing any changes to the plugin. 12 | 13 | ## Acknowledgements 14 | 15 | * [Earlier work](https://github.com/CrySyS/bro-step7-plugin) on S7 parsing by the [Laboratory of Cryptography and System Security](https://www.crysys.hu/) 16 | -------------------------------------------------------------------------------- /THIRD-PARTY: -------------------------------------------------------------------------------- 1 | ** zeek; version 3.0.0 -- https://www.zeek.org/ 2 | Copyright (c) 1995-2018, The Regents of the University of California through 3 | the Lawrence Berkeley National Laboratory and the International Computer 4 | Science Institute. All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | (1) Redistributions of source code must retain the above copyright 10 | notice, this list of conditions and the following disclaimer. 11 | 12 | (2) Redistributions in binary form must reproduce the above copyright 13 | notice, this list of conditions and the following disclaimer in the 14 | documentation and/or other materials provided with the distribution. 15 | 16 | (3) Neither the name of the University of California, Lawrence Berkeley 17 | National Laboratory, U.S. Dept. of Energy, International Computer 18 | Science Institute, nor the names of contributors may be used to endorse 19 | or promote products derived from this software without specific prior 20 | written permission. 21 | 22 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 23 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 24 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 25 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 26 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 27 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 28 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 29 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 30 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 31 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 32 | POSSIBILITY OF SUCH DAMAGE. 33 | 34 | Note that some files in the distribution may carry their own copyright 35 | notices. 36 | 37 | ------ 38 | 39 | ** bro-step7-plugin; version 0.8 -- https://github.com/CrySyS/bro-step7-plugin 40 | Copyright (c) 2016, Laboratory of Cryptography and System Security 41 | All rights reserved. 42 | 43 | Redistribution and use in source and binary forms, with or without 44 | modification, are permitted provided that the following conditions are met: 45 | 46 | 1. Redistributions of source code must retain the above copyright notice, this 47 | list of conditions and the following disclaimer. 48 | 49 | 2. Redistributions in binary form must reproduce the above copyright notice, 50 | this list of conditions and the following disclaimer in the documentation 51 | and/or other materials provided with the distribution. 52 | 53 | 3. Neither the name of the copyright holder nor the names of its contributors 54 | may be used to endorse or promote products derived from this software without 55 | specific prior written permission. 56 | 57 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 58 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 59 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 60 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 61 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 62 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 63 | SERVICES; LOSS OF USE, DATA, OR 64 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 65 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 66 | OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 67 | ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 68 | 69 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 1.0 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 | usage() { 18 | 19 | cat 1>&2 </dev/null 2>&1; then 34 | plugin_usage 1>&2 35 | fi 36 | 37 | echo 38 | 39 | exit 1 40 | } 41 | 42 | # Function to append a CMake cache entry definition to the 43 | # CMakeCacheEntries variable 44 | # $1 is the cache entry variable name 45 | # $2 is the cache entry variable type 46 | # $3 is the cache entry variable value 47 | append_cache_entry () { 48 | CMakeCacheEntries="$CMakeCacheEntries -D $1:$2=$3" 49 | } 50 | 51 | # set defaults 52 | builddir=build 53 | zeekdist="" 54 | installroot="default" 55 | CMakeCacheEntries="" 56 | 57 | while [ $# -ne 0 ]; do 58 | case "$1" in 59 | -*=*) optarg=`echo "$1" | sed 's/[-_a-zA-Z0-9]*=//'` ;; 60 | *) optarg= ;; 61 | esac 62 | 63 | case "$1" in 64 | --help|-h) 65 | usage 66 | ;; 67 | 68 | --cmake=*) 69 | CMakeCommand=$optarg 70 | ;; 71 | 72 | --zeek-dist=*) 73 | zeekdist=`cd $optarg && pwd` 74 | ;; 75 | 76 | --install-root=*) 77 | installroot=$optarg 78 | ;; 79 | 80 | --with-binpac=*) 81 | append_cache_entry BinPAC_ROOT_DIR PATH $optarg 82 | binpac_root=$optarg 83 | ;; 84 | 85 | --with-broker=*) 86 | append_cache_entry BROKER_ROOT_DIR PATH $optarg 87 | broker_root=$optarg 88 | ;; 89 | 90 | --with-caf=*) 91 | append_cache_entry CAF_ROOT_DIR PATH $optarg 92 | caf_root=$optarg 93 | ;; 94 | 95 | --with-bifcl=*) 96 | append_cache_entry BifCl_EXE PATH $optarg 97 | ;; 98 | 99 | --enable-debug) 100 | append_cache_entry BRO_PLUGIN_ENABLE_DEBUG BOOL true 101 | ;; 102 | 103 | *) 104 | if type plugin_option >/dev/null 2>&1; then 105 | plugin_option $1 && shift && continue; 106 | fi 107 | 108 | echo "Invalid option '$1'. Try $0 --help to see available options." 109 | exit 1 110 | ;; 111 | esac 112 | shift 113 | done 114 | 115 | if [ -z "$CMakeCommand" ]; then 116 | # prefer cmake3 over "regular" cmake (cmake == cmake2 on RHEL) 117 | if command -v cmake3 >/dev/null 2>&1 ; then 118 | CMakeCommand="cmake3" 119 | elif command -v cmake >/dev/null 2>&1 ; then 120 | CMakeCommand="cmake" 121 | else 122 | echo "This package requires CMake, please install it first." 123 | echo "Then you may use this script to configure the CMake build." 124 | echo "Note: pass --cmake=PATH to use cmake in non-standard locations." 125 | exit 1; 126 | fi 127 | fi 128 | 129 | if [ -z "$zeekdist" ]; then 130 | if type zeek-config >/dev/null 2>&1; then 131 | zeek_config="zeek-config" 132 | else 133 | echo "Either 'zeek-config' must be in PATH or '--zeek-dist=' used" 134 | exit 1 135 | fi 136 | 137 | append_cache_entry BRO_CONFIG_PREFIX PATH `${zeek_config} --prefix` 138 | append_cache_entry BRO_CONFIG_INCLUDE_DIR PATH `${zeek_config} --include_dir` 139 | append_cache_entry BRO_CONFIG_PLUGIN_DIR PATH `${zeek_config} --plugin_dir` 140 | append_cache_entry BRO_CONFIG_CMAKE_DIR PATH `${zeek_config} --cmake_dir` 141 | append_cache_entry CMAKE_MODULE_PATH PATH `${zeek_config} --cmake_dir` 142 | 143 | build_type=`${zeek_config} --build_type` 144 | 145 | if [ "$build_type" = "debug" ]; then 146 | append_cache_entry BRO_PLUGIN_ENABLE_DEBUG BOOL true 147 | fi 148 | 149 | if [ -z "$binpac_root" ]; then 150 | append_cache_entry BinPAC_ROOT_DIR PATH `${zeek_config} --binpac_root` 151 | fi 152 | 153 | if [ -z "$broker_root" ]; then 154 | append_cache_entry BROKER_ROOT_DIR PATH `${zeek_config} --broker_root` 155 | fi 156 | 157 | if [ -z "$caf_root" ]; then 158 | append_cache_entry CAF_ROOT_DIR PATH `${zeek_config} --caf_root` 159 | fi 160 | else 161 | if [ ! -e "$zeekdist/zeek-path-dev.in" ]; then 162 | echo "$zeekdist does not appear to be a valid Zeek source tree." 163 | exit 1 164 | fi 165 | 166 | # BRO_DIST is the canonical/historical name used by plugin CMake scripts 167 | # ZEEK_DIST doesn't serve a function at the moment, but set/provided anyway 168 | append_cache_entry BRO_DIST PATH $zeekdist 169 | append_cache_entry ZEEK_DIST PATH $zeekdist 170 | append_cache_entry CMAKE_MODULE_PATH PATH $zeekdist/cmake 171 | fi 172 | 173 | if [ "$installroot" != "default" ]; then 174 | mkdir -p $installroot 175 | append_cache_entry BRO_PLUGIN_INSTALL_ROOT PATH $installroot 176 | fi 177 | 178 | echo "Build Directory : $builddir" 179 | echo "Zeek Source Directory : $zeekdist" 180 | 181 | mkdir -p $builddir 182 | cd $builddir 183 | 184 | "$CMakeCommand" $CMakeCacheEntries .. 185 | 186 | echo "# This is the command used to configure this build" > config.status 187 | echo $command >> config.status 188 | chmod u+x config.status 189 | -------------------------------------------------------------------------------- /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 < 0) { 92 | ##! DB number 93 | data_block_num = bytestring_to_count(data[data_index:data_index+2]); 94 | data_index += 2; 95 | ##! area 96 | area = bytestring_to_count(data[data_index]); 97 | data_index += 1; 98 | ##! address, comprised of byte and bit address 99 | address = bytestring_to_count("\x00"+data[data_index:data_index+3]); 100 | ##! wireshark format of (area data_block_num.DBX address transport_size length) 101 | output[output_index] = fmt("%s %d.DBX %s %s %d", 102 | areas[area], 103 | data_block_num, 104 | fmt("%d.%d", address/8, address%8), 105 | item_transport_sizes[transport_size], 106 | data_length); 107 | } 108 | break; 109 | case 2: 110 | ##! return code 111 | data_index += 1; 112 | ##! transport size 113 | transport_size = bytestring_to_count(data[data_index]); 114 | data_index += 1; 115 | ##! data length 116 | data_length = bytestring_to_count(data[data_index:data_index+2]); 117 | data_index += 2; 118 | if (data_length > 0) { 119 | ##! based on data type, switch and format using bytestring_to_double/int/count 120 | switch (transport_size) { 121 | case 4: ##! BYTE/WORK/DWORD 122 | data_value = fmt("0x%s", bytestring_to_hexstr(data[data_index:data_index+data_length/8])); 123 | break; 124 | case 5, ##! INTEGER 125 | 6: ##! DOUBLE_INTEGER 126 | data_value = fmt("%d", bytestring_to_count(data[data_index:data_index+data_length/8])); 127 | break; 128 | case 7: ##! REAL 129 | data_value = fmt("%f", bytestring_to_double("\x00\x00\x00\x00"+data[data_index:data_index+4])); 130 | break; 131 | default: ##! display hex as default 132 | data_value = fmt("0x%s", bytestring_to_hexstr(data[data_index:data_index+data_length])); 133 | break; 134 | } 135 | ##! fill byte for data_length 136 | if (data_length == 1) { 137 | data_index += 1; 138 | } 139 | } 140 | ##! custom format 141 | output[output_index] = fmt("%s %s", 142 | data_transport_sizes[transport_size], 143 | data_value); 144 | break; 145 | case 3: 146 | ##! memory area 147 | area = bytestring_to_count(data[data_index]); 148 | data_index += 1; 149 | ##! repetition factor 150 | repetition_factor = bytestring_to_count(data[data_index]); 151 | data_index += 1; 152 | ##! DB number 153 | data_block_num = bytestring_to_count(data[data_index:data_index+2]); 154 | data_index += 2; 155 | ##! start address 156 | address = bytestring_to_count(data[data_index:data_index+2]); 157 | data_index += 2; 158 | ##! wireshark format of (area data_block_num.DBX address transport_size length) 159 | output[output_index] = fmt("%s %d.%d %d", vartab_areas[area], 160 | address, 161 | data_block_num, 162 | repetition_factor); 163 | break; 164 | } 165 | return output; 166 | } 167 | 168 | event iso_cotp(c: connection, is_orig: bool, 169 | pdu_type: count) &priority=5 { 170 | if(!c?$iso_cotp) { 171 | c$iso_cotp = [$ts=network_time(), $uid=c$uid, $id=c$id]; 172 | add c$service["iso_cotp"]; 173 | } 174 | 175 | c$iso_cotp$ts = network_time(); 176 | c$iso_cotp$pdu_type = cotp_types[pdu_type]; 177 | 178 | Log::write(S7comm::LOG_ISO_COTP, c$iso_cotp); 179 | } 180 | 181 | event s7comm_data(c:connection, is_orig:bool, 182 | data:string) { 183 | if(!c?$s7comm) { 184 | c$s7comm = [$ts=network_time(), $uid=c$uid, $id=c$id]; 185 | add c$service["s7comm"]; 186 | } 187 | 188 | local data_index: count=0; 189 | data_index += 1; 190 | ##! ROSCTR 191 | local rosctr: count = bytestring_to_count(data[data_index]); 192 | c$s7comm$rosctr = rosctrs[rosctr]; 193 | data_index += 1; 194 | ##! redundancy id 195 | data_index += 2; 196 | ##! protocol data unit reference 197 | data_index += 2; 198 | ##! parameter length 199 | data_index += 2; 200 | ##! data length 201 | data_index += 2; 202 | local transport_size: count; 203 | local data_length: count; 204 | local parameter: string_vec; 205 | local parameter_index: count=0; 206 | local data_info: string_vec; 207 | local data_info_index: count=0; 208 | local item_count: count=0; 209 | switch(rosctr) { 210 | case 0x01, ##! job 211 | 0x03: ##! Ack_Data 212 | ##! ACK_DATA only 213 | if (rosctr == 0x03) { 214 | ##! info from header section 215 | ##! error class 216 | parameter[parameter_index] = fmt("class=%s", error_classes[bytestring_to_count(data[data_index])]); 217 | parameter_index += 1; 218 | data_index += 1; 219 | ##! error code 220 | parameter[parameter_index] = fmt("code=%d", bytestring_to_count(data[data_index])); 221 | parameter_index += 1; 222 | data_index += 1; 223 | } 224 | ##! function 225 | local function_type: count = bytestring_to_count(data[data_index]); 226 | parameter[parameter_index] = fmt("type=%s", functions[function_type]); 227 | parameter_index += 1; 228 | data_index += 1; 229 | switch(function_type) { 230 | case 0x04: ##! Read Var 231 | ##! item count 232 | item_count = bytestring_to_count(data[data_index]); 233 | c$s7comm$item_count = item_count; 234 | data_index += 1; 235 | ##! use item_count to get all data instead of 1st entry only? 236 | switch(rosctr) { 237 | case 0x01: ##! job 238 | data_info = parse_data(data, data_index, 1, item_count); 239 | break; 240 | case 0x03: ##! Ack_Data 241 | data_info = parse_data(data, data_index, 2, item_count); 242 | break; 243 | } 244 | break; 245 | case 0x05: ##! Write Var 246 | ##! item count 247 | item_count = bytestring_to_count(data[data_index]); 248 | c$s7comm$item_count = item_count; 249 | data_index += 1; 250 | ##! use item_count to get all data instead of 1st entry only? 251 | switch(rosctr) { 252 | case 0x01: ##! job 253 | data_info = parse_data(data, data_index, 1, item_count); 254 | break; 255 | case 0x03: ##! Ack_Data 256 | data_info[0] = return_codes[bytestring_to_count(data[data_index])]; 257 | break; 258 | } 259 | break; 260 | } 261 | break; 262 | case 0x07: ##! Userdata 263 | ##! parameter head 264 | data_index += 3; 265 | ##! parameter length 266 | data_index += 1; 267 | ##! method: req:0x11, resp:0x12 268 | data_index += 1; 269 | ##! function mode/group 270 | local function_data: count=bytestring_to_count(data[data_index]); 271 | local mode: count=function_data/16; 272 | parameter[parameter_index] = fmt("mode=%s", user_data_function_modes[mode]); 273 | parameter_index += 1; 274 | local function_group: count=function_data%16; 275 | parameter[parameter_index] = fmt("group=%s", user_data_function_groups[function_group]); 276 | parameter_index += 1; 277 | data_index += 1; 278 | ##! subfunction 279 | local subfunction: count=bytestring_to_count(data[data_index]); 280 | data_index += 1; 281 | switch (function_group) { 282 | case 0: 283 | break; 284 | case 1: ##! programmer commands 285 | parameter[parameter_index] = fmt("sub=%s", user_data_programmer_subfunctions[subfunction]); 286 | parameter_index += 1; 287 | break; 288 | case 2: ##! cyclic data 289 | parameter[parameter_index] = fmt("sub=%s", user_data_cyclic_subfunctions[subfunction]); 290 | parameter_index += 1; 291 | break; 292 | case 3: ##! block functions 293 | parameter[parameter_index] = fmt("sub=%s", user_data_block_subfunctions[subfunction]); 294 | parameter_index += 1; 295 | break; 296 | case 4: ##! CPU functions 297 | parameter[parameter_index] = fmt("sub=%s", user_data_cpu_subfunctions[subfunction]); 298 | parameter_index += 1; 299 | break; 300 | case 5: ##! security 301 | parameter[parameter_index] = fmt("sub=%s", user_data_sec_subfunctions[subfunction]); 302 | parameter_index += 1; 303 | break; 304 | case 6: ##! PBC send/receive 305 | parameter[parameter_index] = fmt("sub=%s", user_data_time_subfunctions[subfunction]); 306 | parameter_index += 1; 307 | break; 308 | case 7: ##! time functions 309 | parameter[parameter_index] = fmt("sub=%s", user_data_time_subfunctions[subfunction]); 310 | parameter_index += 1; 311 | break; 312 | default: 313 | parameter[parameter_index] = fmt("sub=(%d)", subfunction); 314 | parameter_index += 1; 315 | break; 316 | } 317 | ##! sequence number 318 | data_index += 1; 319 | ##! response mode 320 | if (mode == 0x00 || mode == 0x08 || function_group == 0x01) { 321 | ##! data unit reference number 322 | data_index += 1; 323 | ##! last data unit 324 | data_index += 1; 325 | ##! error code 326 | parameter[parameter_index] = fmt("code=%s", bytestring_to_count(data[data_index])); 327 | parameter_index += 1; 328 | data_index += 2; 329 | } 330 | switch (function_group) { 331 | case 1: ##! programmer commands 332 | ##! return code 333 | data_index += 1; 334 | ##! transport size 335 | ##!data_info[data_info_index] = fmt("%s", data_transport_sizes[transport_size]); 336 | ##!data_info_index += 1; 337 | data_index += 1; 338 | ##! data length 339 | data_length = bytestring_to_count(data[data_index:data_index+2]); 340 | data_index += 2; 341 | if (data_length > 0) { 342 | ##! type of data 343 | local data_type: count=bytestring_to_count(data[data_index:data_index+2]); 344 | data_index += 2; 345 | ##! byte count of total data 346 | data_length = bytestring_to_count(data[data_index:data_index+2]); 347 | data_index += 2; 348 | ##! vartab info 349 | data_index += data_type; 350 | ##! item count 351 | item_count = bytestring_to_count(data[data_index:data_index+2]); 352 | c$s7comm$item_count = item_count; 353 | data_index += 2; 354 | if (mode == 0x04) { 355 | data_info = parse_data(data, data_index, 3, item_count); 356 | } 357 | else if (mode == 0x00 || mode == 0x08) { 358 | data_info = parse_data(data, data_index, 2, item_count); 359 | } 360 | } 361 | break; 362 | case 2: ##! cyclic data 363 | ##!data_index += 2; 364 | switch (subfunction) { 365 | case 1: ##! memory 366 | ##! return code 367 | data_index += 1; 368 | ##! transport size 369 | ##!data_info[data_info_index] = fmt("%s", data_transport_sizes[transport_size]); 370 | ##!data_info_index += 1; 371 | data_index += 1; 372 | ##! data length 373 | ##!data_length = bytestring_to_count(data[data_index:data_index+2]); 374 | data_index += 2; 375 | ##! item count 376 | item_count = bytestring_to_count(data[data_index:data_index+2]); 377 | c$s7comm$item_count = item_count; 378 | data_index += 2; 379 | switch (mode) { 380 | case 0x04: ##! request 381 | ##! interval timebase 382 | data_index += 1; 383 | ##! interval time 384 | data_index += 1; 385 | data_info = parse_data(data, data_index, 1, item_count); 386 | break; 387 | case 0x00, ##! push 388 | 0x08: ##! response 389 | data_info = parse_data(data, data_index, 2, item_count); 390 | break; 391 | } 392 | break; 393 | case 4: ##! unsubscribe 394 | data_info = parse_data(data, data_index, 2, 1); 395 | break; 396 | } 397 | break; 398 | case 4: ##! CPU functions 399 | ##! return code 400 | data_index += 1; 401 | ##! transport size 402 | transport_size = bytestring_to_count(data[data_index]); 403 | ##!data_info[data_info_index] = fmt("%s", data_transport_sizes[transport_size]); 404 | ##!data_info_index += 1; 405 | data_index += 1; 406 | ##! data length 407 | data_length = bytestring_to_count(data[data_index:data_index+2]); 408 | data_index += 2; 409 | if (data_length > 0) { 410 | switch (subfunction) { 411 | case 1: ##! read szl 412 | data_info[data_info_index] = fmt("%s id=0x%s index=%s", 413 | data_transport_sizes[transport_size], 414 | bytestring_to_hexstr(data[data_index:data_index+2]), 415 | bytestring_to_hexstr(data[data_index+2:data_index+4])); 416 | data_info_index += 1; 417 | break; 418 | case 2: ##! message service 419 | ##! subscribed event 420 | data_index += 1; 421 | ##! reserved 422 | data_index += 1; 423 | ##! username 424 | break; 425 | } 426 | } 427 | break; 428 | } 429 | break; 430 | } 431 | c$s7comm$parameter = parameter; 432 | c$s7comm$data_info = data_info; 433 | 434 | Log::write(LOG_S7COMM, c$s7comm); 435 | delete c$s7comm; 436 | } 437 | 438 | event connection_state_remove(c: connection) &priority=-5 { 439 | if(c?$s7comm) { 440 | delete c$s7comm; 441 | } 442 | } 443 | -------------------------------------------------------------------------------- /src/Plugin.cc: -------------------------------------------------------------------------------- 1 | #include "Plugin.h" 2 | #include "zeek/analyzer/Component.h" 3 | 4 | namespace plugin { 5 | namespace Zeek_S7comm { 6 | Plugin plugin; 7 | } 8 | } 9 | 10 | using namespace plugin::Zeek_S7comm; 11 | 12 | zeek::plugin::Configuration Plugin::Configure() { 13 | AddComponent(new zeek::analyzer::Component("S7comm", analyzer::s7comm::S7comm_Analyzer::Instantiate)); 14 | 15 | zeek::plugin::Configuration config; 16 | config.name = "Zeek::S7comm"; 17 | config.description = "S7 communnication protocol analyzer"; 18 | return config; 19 | } 20 | -------------------------------------------------------------------------------- /src/Plugin.h: -------------------------------------------------------------------------------- 1 | #ifndef ZEEK_PLUGIN_ZEEK_S7COMM 2 | #define ZEEK_PLUGIN_ZEEK_S7COMM 3 | 4 | #include 5 | #include "S7comm.h" 6 | 7 | namespace plugin { 8 | namespace Zeek_S7comm { 9 | class Plugin : public zeek::plugin::Plugin { 10 | protected: 11 | // Overridden from plugin::Plugin. 12 | virtual zeek::plugin::Configuration Configure(); 13 | }; 14 | 15 | extern Plugin plugin; 16 | } 17 | } 18 | 19 | #endif 20 | -------------------------------------------------------------------------------- /src/S7comm.cc: -------------------------------------------------------------------------------- 1 | #include "S7comm.h" 2 | #include 3 | #include 4 | #include "events.bif.h" 5 | 6 | using namespace analyzer::s7comm; 7 | 8 | S7comm_Analyzer::S7comm_Analyzer(zeek::Connection* c): zeek::analyzer::tcp::TCP_ApplicationAnalyzer("S7comm", c) { 9 | interp = new binpac::S7comm::S7comm_Conn(this); 10 | had_gap = false; 11 | } 12 | 13 | S7comm_Analyzer::~S7comm_Analyzer() { 14 | delete interp; 15 | } 16 | 17 | void S7comm_Analyzer::Done() { 18 | zeek::analyzer::tcp::TCP_ApplicationAnalyzer::Done(); 19 | interp->FlowEOF(true); 20 | interp->FlowEOF(false); 21 | } 22 | 23 | void S7comm_Analyzer::EndpointEOF(bool is_orig) { 24 | zeek::analyzer::tcp::TCP_ApplicationAnalyzer::EndpointEOF(is_orig); 25 | interp->FlowEOF(is_orig); 26 | } 27 | 28 | void S7comm_Analyzer::DeliverStream(int len, const u_char* data, bool orig) { 29 | zeek::analyzer::tcp::TCP_ApplicationAnalyzer::DeliverStream(len, data, orig); 30 | assert(TCP()); 31 | //if(TCP()->IsPartial()) 32 | // return; 33 | // If only one side had a content gap, we could still try to 34 | // deliver data to the other side if the script layer can handle this. 35 | if(had_gap) 36 | return; 37 | 38 | try { 39 | interp->NewData(orig, data, data + len); 40 | } 41 | catch(const binpac::Exception& e) { 42 | ProtocolViolation(zeek::util::fmt("Binpac exception: %s", e.c_msg())); 43 | } 44 | } 45 | 46 | void S7comm_Analyzer::Undelivered(uint64_t seq, int len, bool orig) { 47 | zeek::analyzer::tcp::TCP_ApplicationAnalyzer::Undelivered(seq, len, orig); 48 | had_gap = true; 49 | interp->NewGap(orig, len); 50 | } 51 | -------------------------------------------------------------------------------- /src/S7comm.h: -------------------------------------------------------------------------------- 1 | #ifndef ANALYZER_PROTOCOL_S7COMM_H 2 | #define ANALYZER_PROTOCOL_S7COMM_H 3 | 4 | #include 5 | #include "s7comm_pac.h" 6 | 7 | namespace analyzer { 8 | namespace s7comm { 9 | class S7comm_Analyzer : public zeek::analyzer::tcp::TCP_ApplicationAnalyzer { 10 | public: 11 | S7comm_Analyzer(zeek::Connection* conn); 12 | virtual ~S7comm_Analyzer(); 13 | 14 | virtual void Done(); 15 | virtual void DeliverStream(int len, const u_char* data, bool orig); 16 | virtual void Undelivered(uint64_t seq, int len, bool orig); 17 | 18 | virtual void EndpointEOF(bool is_orig); 19 | 20 | static zeek::analyzer::Analyzer* Instantiate(zeek::Connection* conn) { 21 | return new S7comm_Analyzer(conn); 22 | } 23 | 24 | protected: 25 | binpac::S7comm::S7comm_Conn* interp; 26 | bool had_gap; 27 | }; 28 | } 29 | } 30 | 31 | #endif 32 | -------------------------------------------------------------------------------- /src/consts.pac: -------------------------------------------------------------------------------- 1 | ## Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | ## SPDX-License-Identifier: BSD-3-Clause 3 | 4 | // COTP PDU types 5 | #define CR 0xe0 6 | #define CC 0xd0 7 | #define DR 0x80 8 | #define DC 0xc0 9 | #define DT 0xf0 10 | #define ED 0x10 11 | #define AK 0x60 12 | #define EA 0x20 13 | #define RJ 0x50 14 | #define ERR 0x70 15 | 16 | // CR CC class 0 variable parameter types 17 | #define SRC_TSAP 0xc1 18 | #define DST_TSAP 0xc2 19 | #define TPDU_LEN 0xc0 20 | 21 | #define ANALYZER_ERROR_UNSUPPORTED_DATA_TYPE 0x4141 22 | #define ANALYZER_ERROR_UNEXPECTED_ITEM_COUNT 0x4242 23 | #define ANALYZER_ERROR_UNEXPECTED_LENGTH 0x4343 24 | #define ANALYZER_ERROR_UNSOPPORTED_ADDRESSING 0x4444 25 | #define S7COMM_MIN_TELEGRAM_LENGTH 10 26 | /************************************************************************** 27 | * PDU types 28 | */ 29 | #define S7COMM_ROSCTR_JOB 0x01 30 | #define S7COMM_ROSCTR_ACK 0x02 31 | #define S7COMM_ROSCTR_ACK_DATA 0x03 32 | #define S7COMM_ROSCTR_USERDATA 0x07 33 | 34 | #define S7COMM_MAX_AGE 3 35 | 36 | /************************************************************************** 37 | * Error classes in header 38 | */ 39 | #define S7COMM_ERRCLS_NONE 0x00 40 | #define S7COMM_ERRCLS_APPREL 0x81 41 | #define S7COMM_ERRCLS_OBJDEF 0x82 42 | #define S7COMM_ERRCLS_RESSOURCE 0x83 43 | #define S7COMM_ERRCLS_SERVICE 0x84 44 | #define S7COMM_ERRCLS_SUPPLIES 0x85 45 | #define S7COMM_ERRCLS_ACCESS 0x87 46 | 47 | 48 | /************************************************************************** 49 | * Error code in parameter part 50 | */ 51 | #define S7COMM_PERRCOD_NO_ERROR 0x0000 52 | #define S7COMM_PERRCOD_INVALID_BLOCK_TYPE_NUM 0x0110 53 | #define S7COMM_PERRCOD_INVALID_PARAM 0x0112 54 | #define S7COMM_PERRCOD_PG_RESOURCE_ERROR 0x011A 55 | #define S7COMM_PERRCOD_PLC_RESOURCE_ERROR 0x011B 56 | #define S7COMM_PERRCOD_PROTOCOL_ERROR 0x011C 57 | #define S7COMM_PERRCOD_USER_BUFFER_TOO_SHORT 0x011F 58 | #define S7COMM_PERRCOD_REQ_INI_ERR 0x0141 59 | #define S7COMM_PERRCOD_VERSION_MISMATCH 0x01C0 60 | #define S7COMM_PERRCOD_NOT_IMPLEMENTED 0x01F0 61 | #define S7COMM_PERRCOD_L7_INVALID_CPU_STATE 0x8001 62 | #define S7COMM_PERRCOD_L7_PDU_SIZE_ERR 0x8500 63 | #define S7COMM_PERRCOD_L7_INVALID_SZL_ID 0xD401 64 | #define S7COMM_PERRCOD_L7_INVALID_INDEX 0xD402 65 | #define S7COMM_PERRCOD_L7_DGS_CONN_ALREADY_ANNOU 0xD403 66 | #define S7COMM_PERRCOD_L7_MAX_USER_NB 0xD404 67 | #define S7COMM_PERRCOD_L7_DGS_FKT_PAR_SYNTAX_ERR 0xD405 68 | #define S7COMM_PERRCOD_L7_NO_INFO 0xD406 69 | #define S7COMM_PERRCOD_L7_PRT_FKT_PAR_SYNTAX_ERR 0xD601 70 | #define S7COMM_PERRCOD_L7_INVALID_VAR_ADDR 0xD801 71 | #define S7COMM_PERRCOD_L7_UNKNOWN_REQ 0xD802 72 | #define S7COMM_PERRCOD_L7_INVALID_REQ_STATUS 0xD803 73 | 74 | /************************************************************************** 75 | * Function codes in parameter part 76 | */ 77 | #define S7COMM_SERV_CPU 0x00 78 | #define S7COMM_SERV_SETUPCOMM 0xF0 79 | #define S7COMM_SERV_READVAR 0x04 80 | #define S7COMM_SERV_WRITEVAR 0x05 81 | 82 | #define S7COMM_FUNCREQUESTDOWNLOAD 0x1A 83 | #define S7COMM_FUNCDOWNLOADBLOCK 0x1B 84 | #define S7COMM_FUNCDOWNLOADENDED 0x1C 85 | #define S7COMM_FUNCSTARTUPLOAD 0x1D 86 | #define S7COMM_FUNCUPLOAD 0x1E 87 | #define S7COMM_FUNCENDUPLOAD 0x1F 88 | #define S7COMM_FUNC_PLC_CONTROL 0x28 89 | #define S7COMM_FUNC_PLC_STOP 0x29 90 | 91 | /************************************************************************** 92 | * Area names 93 | */ 94 | #define S7COMM_AREA_SYSINFO 0x03 /* System info of 200 family */ 95 | #define S7COMM_AREA_SYSFLAGS 0x05 /* System flags of 200 family */ 96 | #define S7COMM_AREA_ANAIN 0x06 /* analog inputs of 200 family */ 97 | #define S7COMM_AREA_ANAOUT 0x07 /* analog outputs of 200 family */ 98 | #define S7COMM_AREA_P 0x80 /* direct peripheral access */ 99 | #define S7COMM_AREA_INPUTS 0x81 100 | #define S7COMM_AREA_OUTPUTS 0x82 101 | #define S7COMM_AREA_FLAGS 0x83 102 | #define S7COMM_AREA_DB 0x84 /* data blocks */ 103 | #define S7COMM_AREA_DI 0x85 /* instance data blocks */ 104 | #define S7COMM_AREA_LOCAL 0x86 /* local data (should not be accessible over network) */ 105 | #define S7COMM_AREA_V 0x87 /* previous (Vorgaenger) local data (should not be accessible over network) */ 106 | #define S7COMM_AREA_COUNTER 28 /* S7 counters */ 107 | #define S7COMM_AREA_TIMER 29 /* S7 timers */ 108 | #define S7COMM_AREA_COUNTER200 30 /* IEC counters (200 family) */ 109 | #define S7COMM_AREA_TIMER200 31 /* IEC timers (200 family) */ 110 | 111 | /************************************************************************** 112 | * Transport sizes in item data 113 | */ 114 | /* types of 1 byte length */ 115 | #define S7COMM_TRANSPORT_SIZE_BIT 1 116 | #define S7COMM_TRANSPORT_SIZE_BYTE 2 117 | #define S7COMM_TRANSPORT_SIZE_CHAR 3 118 | /* types of 2 bytes length */ 119 | #define S7COMM_TRANSPORT_SIZE_WORD 4 120 | #define S7COMM_TRANSPORT_SIZE_INT 5 121 | /* types of 4 bytes length */ 122 | #define S7COMM_TRANSPORT_SIZE_DWORD 6 123 | #define S7COMM_TRANSPORT_SIZE_DINT 7 124 | #define S7COMM_TRANSPORT_SIZE_REAL 8 125 | /* Special types */ 126 | #define S7COMM_TRANSPORT_SIZE_DATE 9 127 | #define S7COMM_TRANSPORT_SIZE_TOD 10 128 | #define S7COMM_TRANSPORT_SIZE_TIME 11 129 | #define S7COMM_TRANSPORT_SIZE_S5TIME 12 130 | #define S7COMM_TRANSPORT_SIZE_DT 15 131 | /* Timer or counter */ 132 | #define S7COMM_TRANSPORT_SIZE_COUNTER 28 133 | #define S7COMM_TRANSPORT_SIZE_TIMER 29 134 | #define S7COMM_TRANSPORT_SIZE_IEC_COUNTER 30 135 | #define S7COMM_TRANSPORT_SIZE_IEC_TIMER 31 136 | #define S7COMM_TRANSPORT_SIZE_HS_COUNTER 32 137 | 138 | /************************************************************************** 139 | * Syntax Ids of variable specification 140 | */ 141 | #define S7COMM_SYNTAXID_S7ANY 0x10 /* Adress data S7-Any pointer-like DB1.DBX10.2 */ 142 | #define S7COMM_SYNTAXID_DRIVEESANY 0xa2 /* seen on Drive ES Starter with routing over S7 */ 143 | #define S7COMM_SYNTAXID_1200SYM 0xb2 /* Symbolic address mode of S7-1200 */ 144 | #define S7COMM_SYNTAXID_DBREAD 0xb0 /* Kind of DB block read, seen only at an S7-400 */ 145 | 146 | /************************************************************************** 147 | * Transport sizes in data 148 | */ 149 | #define S7COMM_DATA_TRANSPORT_SIZE_NULL 0 150 | #define S7COMM_DATA_TRANSPORT_SIZE_BBIT 3 /* bit access, len is in bits */ 151 | #define S7COMM_DATA_TRANSPORT_SIZE_BBYTE 4 /* byte/word/dword acces, len is in bits */ 152 | #define S7COMM_DATA_TRANSPORT_SIZE_BINT 5 /* integer access, len is in bits */ 153 | #define S7COMM_DATA_TRANSPORT_SIZE_BREAL 7 /* real access, len is in bytes */ 154 | #define S7COMM_DATA_TRANSPORT_SIZE_BSTR 9 /* octet string, len is in bytes */ 155 | 156 | /************************************************************************** 157 | * Returnvalues of an item response 158 | */ 159 | 160 | /************************************************************************** 161 | * Block Types 162 | */ 163 | #define S7COMM_BLOCKTYPE_OB '8' 164 | #define S7COMM_BLOCKTYPE_DB 'A' 165 | #define S7COMM_BLOCKTYPE_SDB 'B' 166 | #define S7COMM_BLOCKTYPE_FC 'C' 167 | #define S7COMM_BLOCKTYPE_SFC 'D' 168 | #define S7COMM_BLOCKTYPE_FB 'E' 169 | #define S7COMM_BLOCKTYPE_SFB 'F' 170 | 171 | 172 | /************************************************************************** 173 | * Subblk types 174 | */ 175 | #define S7COMM_SUBBLKTYPE_OB 0x08 176 | #define S7COMM_SUBBLKTYPE_DB 0x0a 177 | #define S7COMM_SUBBLKTYPE_SDB 0x0b 178 | #define S7COMM_SUBBLKTYPE_FC 0x0c 179 | #define S7COMM_SUBBLKTYPE_SFC 0x0d 180 | #define S7COMM_SUBBLKTYPE_FB 0x0e 181 | #define S7COMM_SUBBLKTYPE_SFB 0x0f 182 | 183 | 184 | /************************************************************************** 185 | * Block security 186 | */ 187 | #define S7COMM_BLOCKSECURITY_OFF 0 188 | #define S7COMM_BLOCKSECURITY_KNOWHOWPROTECT 3 189 | 190 | /************************************************************************** 191 | * Names of types in userdata parameter part 192 | */ 193 | 194 | #define S7COMM_UD_TYPE_PUSH 0x00 195 | #define S7COMM_UD_TYPE_REQ 0x04 196 | #define S7COMM_UD_TYPE_RES 0x08 197 | 198 | 199 | /************************************************************************** 200 | * Userdata Parameter, last data unit 201 | */ 202 | #define S7COMM_UD_LASTDATAUNIT_YES 0x00 203 | #define S7COMM_UD_LASTDATAUNIT_NO 0x01 204 | 205 | /************************************************************************** 206 | * Names of Function groups in userdata parameter part 207 | */ 208 | #define S7COMM_UD_FUNCGROUP_PROG 0x1 209 | #define S7COMM_UD_FUNCGROUP_CYCLIC 0x2 210 | #define S7COMM_UD_FUNCGROUP_BLOCK 0x3 211 | #define S7COMM_UD_FUNCGROUP_CPU 0x4 212 | #define S7COMM_UD_FUNCGROUP_SEC 0x5 /* Security funnctions e.g. plc password */ 213 | #define S7COMM_UD_FUNCGROUP_TIME 0x7 214 | 215 | 216 | /************************************************************************** 217 | * Vartab: Typ of data in data part, first two bytes 218 | */ 219 | #define S7COMM_UD_SUBF_PROG_VARTAB_TYPE_REQ 0x14 220 | #define S7COMM_UD_SUBF_PROG_VARTAB_TYPE_RES 0x04 221 | 222 | /************************************************************************** 223 | * Vartab: area of data request 224 | * 225 | * Low Hi 226 | * 0=M 1=BYTE 227 | * 1=E 2=WORD 228 | * 2=A 3=DWORD 229 | * 3=PEx 230 | * 7=DB 231 | * 54=TIMER 232 | * 64=COUNTER 233 | */ 234 | #define S7COMM_UD_SUBF_PROG_VARTAB_AREA_MB 0x01 235 | #define S7COMM_UD_SUBF_PROG_VARTAB_AREA_MW 0x02 236 | #define S7COMM_UD_SUBF_PROG_VARTAB_AREA_MD 0x03 237 | #define S7COMM_UD_SUBF_PROG_VARTAB_AREA_EB 0x11 238 | #define S7COMM_UD_SUBF_PROG_VARTAB_AREA_EW 0x12 239 | #define S7COMM_UD_SUBF_PROG_VARTAB_AREA_ED 0x13 240 | #define S7COMM_UD_SUBF_PROG_VARTAB_AREA_AB 0x21 241 | #define S7COMM_UD_SUBF_PROG_VARTAB_AREA_AW 0x22 242 | #define S7COMM_UD_SUBF_PROG_VARTAB_AREA_AD 0x23 243 | #define S7COMM_UD_SUBF_PROG_VARTAB_AREA_PEB 0x31 244 | #define S7COMM_UD_SUBF_PROG_VARTAB_AREA_PEW 0x32 245 | #define S7COMM_UD_SUBF_PROG_VARTAB_AREA_PED 0x33 246 | #define S7COMM_UD_SUBF_PROG_VARTAB_AREA_DBB 0x71 247 | #define S7COMM_UD_SUBF_PROG_VARTAB_AREA_DBW 0x72 248 | #define S7COMM_UD_SUBF_PROG_VARTAB_AREA_DBD 0x73 249 | #define S7COMM_UD_SUBF_PROG_VARTAB_AREA_T 0x54 250 | #define S7COMM_UD_SUBF_PROG_VARTAB_AREA_C 0x64 251 | 252 | 253 | /************************************************************************** 254 | * Names of userdata subfunctions in group 1 (Programmer commands) 255 | */ 256 | #define S7COMM_UD_SUBF_PROG_REQDIAGDATA1 0x01 257 | #define S7COMM_UD_SUBF_PROG_VARTAB1 0x02 258 | #define S7COMM_UD_SUBF_PROG_ERASE 0x0c 259 | #define S7COMM_UD_SUBF_PROG_READDIAGDATA 0x0e 260 | #define S7COMM_UD_SUBF_PROG_REMOVEDIAGDATA 0x0f 261 | #define S7COMM_UD_SUBF_PROG_FORCE 0x10 262 | #define S7COMM_UD_SUBF_PROG_REQDIAGDATA2 0x13 263 | 264 | 265 | /************************************************************************** 266 | * Names of userdata subfunctions in group 2 (cyclic data) 267 | */ 268 | #define S7COMM_UD_SUBF_CYCLIC_MEM 0x01 269 | #define S7COMM_UD_SUBF_CYCLIC_UNSUBSCRIBE 0x04 270 | 271 | 272 | /************************************************************************** 273 | * Names of userdata subfunctions in group 3 (Block functions) 274 | */ 275 | #define S7COMM_UD_SUBF_BLOCK_LIST 0x01 276 | #define S7COMM_UD_SUBF_BLOCK_LISTTYPE 0x02 277 | #define S7COMM_UD_SUBF_BLOCK_BLOCKINFO 0x03 278 | 279 | /************************************************************************** 280 | * Names of userdata subfunctions in group 4 (CPU functions) 281 | */ 282 | #define S7COMM_UD_SUBF_CPU_READSZL 0x01 283 | #define S7COMM_UD_SUBF_CPU_MSGS 0x02 284 | #define S7COMM_UD_SUBF_CPU_TRANSSTOP 0x03 285 | #define S7COMM_UD_SUBF_CPU_ALARMIND 0x11 286 | #define S7COMM_UD_SUBF_CPU_ALARMINIT 0x13 287 | #define S7COMM_UD_SUBF_CPU_ALARMACK1 0x0b 288 | #define S7COMM_UD_SUBF_CPU_ALARMACK2 0x0c 289 | 290 | /************************************************************************** 291 | * Names of userdata subfunctions in group 5 (Security?) 292 | */ 293 | #define S7COMM_UD_SUBF_SEC_PASSWD 0x01 294 | 295 | /************************************************************************** 296 | * Names of userdata subfunctions in group 7 (Time functions) 297 | */ 298 | #define S7COMM_UD_SUBF_TIME_READ 0x01 299 | #define S7COMM_UD_SUBF_TIME_SET 0x02 300 | #define S7COMM_UD_SUBF_TIME_READF 0x03 301 | #define S7COMM_UD_SUBF_TIME_SET2 0x04 302 | 303 | /************************************************************************** 304 | * Flags for LID access 305 | */ 306 | #define S7COMM_TIA1200_VAR_ENCAPS_LID 0x2 307 | #define S7COMM_TIA1200_VAR_ENCAPS_IDX 0x3 308 | #define S7COMM_TIA1200_VAR_OBTAIN_LID 0x4 309 | #define S7COMM_TIA1200_VAR_OBTAIN_IDX 0x5 310 | #define S7COMM_TIA1200_VAR_PART_START 0x6 311 | #define S7COMM_TIA1200_VAR_PART_LEN 0x7 312 | 313 | /************************************************************************** 314 | * TIA 1200 Area Names for variable access 315 | */ 316 | #define S7COMM_TIA1200_VAR_ITEM_AREA1_DB 0x8a0e /* Reading DB, 2 byte DB-Number following */ 317 | #define S7COMM_TIA1200_VAR_ITEM_AREA1_IQMCT 0x0000 /* Reading I/Q/M/C/T, 2 Byte detail area following */ 318 | 319 | #define S7COMM_TIA1200_VAR_ITEM_AREA2_I 0x50 320 | #define S7COMM_TIA1200_VAR_ITEM_AREA2_Q 0x51 321 | #define S7COMM_TIA1200_VAR_ITEM_AREA2_M 0x52 322 | #define S7COMM_TIA1200_VAR_ITEM_AREA2_C 0x53 323 | #define S7COMM_TIA1200_VAR_ITEM_AREA2_T 0x54 324 | -------------------------------------------------------------------------------- /src/events.bif: -------------------------------------------------------------------------------- 1 | ################################## 2 | ## Generated for iso_cotp events # 3 | ################################## 4 | ## c: The connection the S7 communication is part of 5 | ## is_orig: True if this reflects originator-side activity 6 | ## pdu_type: iso_cotp types 7 | ## 8 | event iso_cotp%(c: connection, is_orig: bool, 9 | pdu_type: count 10 | %); 11 | 12 | #################################################### 13 | ## Generated for communication and data to the PLC # 14 | #################################################### 15 | ## c: The connection the S7 communication is part of 16 | ## is_orig: True if this reflects originator-side activity 17 | ## self-explanatory parameters 18 | ## 19 | event s7comm_data%(c: connection, is_orig: bool, 20 | data: string 21 | %); 22 | -------------------------------------------------------------------------------- /src/s7comm-analyzer.pac: -------------------------------------------------------------------------------- 1 | ## Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | ## SPDX-License-Identifier: BSD-3-Clause 3 | 4 | connection S7comm_Conn(zeek_analyzer: ZeekAnalyzer) { 5 | upflow = S7comm_Flow(true); 6 | downflow = S7comm_Flow(false); 7 | }; 8 | 9 | %header{ 10 | #define S7_HEADER 0x32 11 | //cotp_types 12 | #define EXPEDITED_DATA 0x10 13 | #define CLTP_USER_DATA 0x20 14 | #define EXPEDITED_DATA_ACK 0x40 15 | #define REJECT 0x50 16 | // #define ACK_DATA 0x60 17 | #define ACK_DATA 0x70 18 | #define DISCONNECT_REQUEST 0x80 19 | #define DISCONNECT_CONFIRM 0xc0 20 | #define CONNECT_CONFIRM 0xd0 21 | #define CONNECT_REQUEST 0xe0 22 | #define DATA 0xf0 23 | 24 | //rosctrs 25 | #define ROSCTR_JOB 0x01 26 | #define ROSCTR_ACK 0x02 27 | #define ROSCTR_ACK_DATA 0x03 28 | #define ROSCTR_USER_DATA 0x07 29 | %} 30 | 31 | flow S7comm_Flow(is_orig: bool) { 32 | # flowunit = S7comm_PDU(is_orig) withcontext(connection, this); 33 | datagram = S7comm_PDU(is_orig) withcontext(connection, this); 34 | 35 | function iso_cotp(header: ISO_COTP): bool %{ 36 | if(::iso_cotp) { 37 | // connection()->zeek_analyzer()->ProtocolConfirmation(); 38 | zeek::BifEvent::enqueue_iso_cotp(connection()->zeek_analyzer(), 39 | connection()->zeek_analyzer()->Conn(), 40 | is_orig(), 41 | ${header.cotp_type}); 42 | } 43 | return true; 44 | %} 45 | 46 | ##! handles s7comm.log and s7data.log 47 | function s7comm_data(s7comm_data: S7comm_Data): bool %{ 48 | if(::s7comm_data) { 49 | // check protocol for 0x32 50 | if (${s7comm_data.data[0]} != S7_HEADER) { 51 | return false; 52 | } 53 | connection()->zeek_analyzer()->ProtocolConfirmation(); 54 | zeek::BifEvent::enqueue_s7comm_data(connection()->zeek_analyzer(), 55 | connection()->zeek_analyzer()->Conn(), 56 | is_orig(), 57 | to_stringval(${s7comm_data.data})); 58 | } 59 | return true; 60 | %} 61 | }; 62 | 63 | refine typeattr ISO_COTP += &let { 64 | proc: bool = $context.flow.iso_cotp(this); 65 | }; 66 | 67 | refine typeattr S7comm_Data += &let { 68 | proc: bool = $context.flow.s7comm_data(this); 69 | }; 70 | -------------------------------------------------------------------------------- /src/s7comm-protocol.pac: -------------------------------------------------------------------------------- 1 | ## Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | ## SPDX-License-Identifier: BSD-3-Clause 3 | 4 | ############################## 5 | # CONSTANTS # 6 | ############################## 7 | 8 | enum cotp_codes { 9 | EXPEDITED_DATA = 0x10, 10 | CLTP_USER_DATA = 0x20, 11 | EXPEDITED_DATA_ACK = 0x40, 12 | REJECT = 0x50, 13 | ##! ACK_DATA = 0x60, 14 | ACK_DATA = 0x70, 15 | DISCONNECT_REQUEST = 0x80, 16 | DISCONNECT_CONFIRM = 0xc0, 17 | CONNECT_CONFIRM = 0xd0, 18 | CONNECT_REQUEST = 0xe0, 19 | DATA = 0xf0 20 | }; 21 | 22 | ############################## 23 | ## RECORD TYPES # 24 | ############################## 25 | type S7comm_PDU(is_orig: bool) = case is_orig of { 26 | true -> request : S7comm_Request; 27 | false -> response : S7comm_Response; 28 | } &byteorder=littleendian; 29 | 30 | ##! switch for the request portion 31 | type S7comm_Request = record { 32 | header: ISO_COTP; 33 | data: case(header.cotp_type) of { 34 | CONNECT_REQUEST, 35 | CONNECT_CONFIRM -> connect : Connection_Header(header); 36 | DATA -> dataInfo : S7comm_Data; 37 | default -> unknown : bytestring &restofdata; 38 | }; 39 | } &byteorder=bigendian; 40 | 41 | ##! switch for the response portion 42 | type S7comm_Response = record { 43 | header: ISO_COTP; 44 | data: case(header.cotp_type) of { 45 | CONNECT_REQUEST, 46 | CONNECT_CONFIRM -> connect : Connection_Header(header); 47 | DATA -> dataInfo : S7comm_Data; 48 | default -> unknown : bytestring &restofdata; 49 | }; 50 | } &byteorder=bigendian; 51 | 52 | ##! Structure of the iso-cotp, used for iso_cotp.log 53 | type ISO_COTP = record { 54 | tpkt_version : uint8; 55 | tpkt_reserved : uint8; 56 | tpkt_length : uint16; 57 | cotp_length : uint8; 58 | cotp_type : uint8; 59 | something : case cotp_type of { ##! only in DATA messages 60 | DATA -> something_value : uint8; 61 | default -> something_default : empty; 62 | }; 63 | } &byteorder=bigendian; 64 | 65 | ##! iso-cotp connection info 66 | type Connection_Info = record { 67 | src_tsap: uint16; 68 | dst_tsap: uint16; 69 | tpdu_len: uint8; 70 | } &byteorder=bigendian; 71 | 72 | ##! iso-cotp connection header 73 | type Connection_Header(header: ISO_COTP) = record { 74 | destination_reference : uint16; 75 | source_reference : uint16; 76 | class_options : uint8; 77 | ##! check Param_Info.code to verify order of tpdu_size 78 | source_tsap : Param_Info; ##! code 0xc1 79 | destination_tsap : Param_Info; ##! code 0xc2 80 | tpdu_size : Param_Info; ##! code 0xc0 81 | } &byteorder=bigendian; 82 | 83 | ##! iso-cotp connection info 84 | type Param_Info = record { 85 | code : uint8; 86 | len : uint8; 87 | value : bytestring &length=len; 88 | } &byteorder=bigendian; 89 | 90 | ##! parse out data to generate s7comm.log and s7data.log 91 | type S7comm_Data = record { 92 | data : bytestring &restofdata; 93 | } &byteorder=bigendian; 94 | -------------------------------------------------------------------------------- /src/s7comm.pac: -------------------------------------------------------------------------------- 1 | %include zeek/binpac.pac 2 | %include zeek/zeek.pac 3 | 4 | %extern{ 5 | #include "events.bif.h" 6 | %} 7 | 8 | analyzer S7comm withcontext { 9 | connection: S7comm_Conn; 10 | flow: S7comm_Flow; 11 | }; 12 | 13 | %include s7comm-protocol.pac 14 | %include s7comm-analyzer.pac 15 | -------------------------------------------------------------------------------- /zkg.meta: -------------------------------------------------------------------------------- 1 | [package] 2 | script_dir = scripts/S7comm 3 | build_command = ./configure && make 4 | tags = zeek plugin, protocol analyzer, log writer, ics, s7 5 | description = Plugin that enables parsing of the S7 protocol 6 | depends = 7 | zkg >=2.0 8 | zeek >=3.0.0 9 | --------------------------------------------------------------------------------