From bd2d73ef4f3ea2e487efc52930b75344f36a2baf Mon Sep 17 00:00:00 2001 From: Patrick Kelley Date: Wed, 7 May 2025 14:05:39 -0400 Subject: [PATCH] Initial --- CMakeLists.txt | 13 +++ CODE_OF_CONDUCT.md | 4 + CONTRIBUTING.md | 61 +++++++++++ Dockerfile | 49 +++++++++ LICENSE | 23 ++++ Makefile | 29 +++++ README.md | 11 ++ VERSION | 1 + configure | 188 +++++++++++++++++++++++++++++++++ configure.plugin | 26 +++++ scripts/TDS/__load__.zeek | 3 + scripts/TDS/consts.zeek | 30 ++++++ scripts/TDS/main.zeek | 217 ++++++++++++++++++++++++++++++++++++++ src/Plugin.cc | 19 ++++ src/Plugin.h | 19 ++++ src/TDS.cc | 50 +++++++++ src/TDS.h | 31 ++++++ src/events.bif | 40 +++++++ src/tds-analyzer.pac | 101 ++++++++++++++++++ src/tds-protocol.pac | 92 ++++++++++++++++ src/tds.pac | 14 +++ zkg.meta | 9 ++ 22 files changed, 1030 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 CODE_OF_CONDUCT.md create mode 100644 CONTRIBUTING.md create mode 100644 Dockerfile create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README.md create mode 100644 VERSION create mode 100755 configure create mode 100644 configure.plugin create mode 100644 scripts/TDS/__load__.zeek create mode 100644 scripts/TDS/consts.zeek create mode 100644 scripts/TDS/main.zeek create mode 100644 src/Plugin.cc create mode 100644 src/Plugin.h create mode 100644 src/TDS.cc create mode 100644 src/TDS.h create mode 100644 src/events.bif create mode 100644 src/tds-analyzer.pac create mode 100644 src/tds-protocol.pac create mode 100644 src/tds.pac create mode 100644 zkg.meta diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..fbb4766 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,13 @@ + +cmake_minimum_required(VERSION 3.28.0-rc5 FATAL_ERROR) + +project(Plugin) + +include(ZeekPlugin) + +zeek_plugin_begin(Zeek TDS) + zeek_plugin_cc(src/TDS.cc src/Plugin.cc) + zeek_plugin_bif(src/events.bif) + zeek_plugin_pac(src/tds.pac src/tds-analyzer.pac src/tds-protocol.pac) + zeek_plugin_dist_files(README CHANGES COPYING VERSION) +zeek_plugin_end() diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..5b627cf --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,4 @@ +## Code of Conduct +This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). +For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact +opensource-codeofconduct@amazon.com with any additional questions or comments. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..3af4953 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,61 @@ +# Contributing Guidelines + +Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional +documentation, we greatly value feedback and contributions from our community. + +Please read through this document before submitting any issues or pull requests to ensure we have all the necessary +information to effectively respond to your bug report or contribution. + + +## Reporting Bugs/Feature Requests + +We welcome you to use the GitHub issue tracker to report bugs or suggest features. + +When filing an issue, please check [existing open](https://github.com/amzn/zeek-plugin-tds/issues), or [recently closed](https://github.com/amzn/zeek-plugin-tds/issues?utf8=%E2%9C%93&q=is%3Aissue%20is%3Aclosed%20), issues to make sure somebody else hasn't already +reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: + +* A reproducible test case or series of steps +* The version of our code being used +* Any modifications you've made relevant to the bug +* Anything unusual about your environment or deployment + + +## Contributing via Pull Requests +Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: + +1. You are working against the latest source on the *master* branch. +2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. +3. You open an issue to discuss any significant work - we would hate for your time to be wasted. + +To send us a pull request, please: + +1. Fork the repository. +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. +3. Ensure local tests pass. +4. Commit to your fork using clear commit messages. +5. Send us a pull request, answering any default questions in the pull request interface. +6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. + +GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and +[creating a pull request](https://help.github.com/articles/creating-a-pull-request/). + + +## Finding contributions to work on +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-tds/labels/help%20wanted) issues is a great place to start. + + +## Code of Conduct +This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). +For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact +opensource-codeofconduct@amazon.com with any additional questions or comments. + + +## Security issue notifications +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. + + +## Licensing + +See the [LICENSE](https://github.com/amzn/zeek-plugin-tds/blob/master/LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. + +We may ask you to sign a [Contributor License Agreement (CLA)](http://en.wikipedia.org/wiki/Contributor_License_Agreement) for larger changes. diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..56016ae --- /dev/null +++ b/Dockerfile @@ -0,0 +1,49 @@ +FROM public.ecr.aws/amazonlinux/amazonlinux:latest +MAINTAINER https://github.com/amzn/ + +# Metadata +LABEL program=zeek + +# Specify program +ENV PROG zeek +# Specify source extension +ENV EXT tar.gz +# Specify Zeek version to download and install (e.g. 3.0.0) +ENV VERS 3.2.4 + +# Specify Cmake version +ENV CMAKEVERSMAIN 3.10 +ENV CMAKEVERSSUB .0 + +# Install directory +ENV PREFIX /opt/zeek +# Path should include prefix +ENV PATH /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:$PREFIX/bin + +# Install dependencies +RUN yum -y update +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 + +# Zeek 3.1.0 needs Cmake 3.0 or higher +WORKDIR /tmp +RUN wget https://cmake.org/files/v$CMAKEVERSMAIN/cmake-$CMAKEVERSMAIN$CMAKEVERSSUB.tar.gz +RUN tar -xvzf cmake-$CMAKEVERSMAIN$CMAKEVERSSUB.tar.gz +WORKDIR /tmp/cmake-$CMAKEVERSMAIN$CMAKEVERSSUB +RUN /tmp/cmake-$CMAKEVERSMAIN$CMAKEVERSSUB/bootstrap +RUN make -j$((`nproc`-1)) +RUN make install + +# Compile and install Zeek +WORKDIR /tmp +RUN wget https://old.zeek.org/downloads/$PROG-$VERS.$EXT && tar -xzf $PROG-$VERS.$EXT +WORKDIR /tmp/$PROG-$VERS +RUN ./configure --build-type=RelWithDebInfo --prefix=$PREFIX --disable-python +RUN make -j$((`nproc`-1)) +RUN make install + +USER root +RUN pip3 install zkg +RUN zkg autoconfig +COPY [--chown=bro:bro] . /tmp/zeek-plugin-tds +WORKDIR /tmp/zeek-plugin-tds +RUN zkg install --force . diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..9d60b0b --- /dev/null +++ b/LICENSE @@ -0,0 +1,23 @@ +Copyright 2019 Amazon.com, Inc. or its affiliates. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * 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. + * 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. + +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 BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..9b06a67 --- /dev/null +++ b/Makefile @@ -0,0 +1,29 @@ +# +# Convenience Makefile providing a few common top-level targets. +# + +cmake_build_dir=build +arch=`uname -s | tr A-Z a-z`-`uname -m` + +all: build-it + +build-it: + @test -e $(cmake_build_dir)/config.status || ./configure + -@test -e $(cmake_build_dir)/CMakeCache.txt && \ + test $(cmake_build_dir)/CMakeCache.txt -ot `cat $(cmake_build_dir)/CMakeCache.txt | grep ZEEK_DIST | cut -d '=' -f 2`/build/CMakeCache.txt && \ + echo Updating stale CMake cache && \ + touch $(cmake_build_dir)/CMakeCache.txt + + ( cd $(cmake_build_dir) && make ) + +install: + ( cd $(cmake_build_dir) && make install ) + +clean: + ( cd $(cmake_build_dir) && make clean ) + +distclean: + rm -rf $(cmake_build_dir) + +test: + if [ -f build/lib/Zeek-* ]; then make -C tests; else echo "Plugin not built."; fi diff --git a/README.md b/README.md new file mode 100644 index 0000000..f6f18c9 --- /dev/null +++ b/README.md @@ -0,0 +1,11 @@ +## Zeek Plugin TDS + +When running as part of your Zeek installation this plugin will produce three log files containing metadata extracted from any [Tabular Data Stream](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-tds/b46a581a-39de-4745-b076-ec4dbb7d13ec) (TDS) traffic observed on TCP port 1433. + +## Installation and Usage + +`zeek-plugin-tds` 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. + +## Sharing and Contributing + +This code is made available under the [BSD-3-Clause license](https://github.com/amzn/zeek-plugin-tds/blob/master/LICENSE). [Guidelines for contributing](https://github.com/amzn/zeek-plugin-tds/blob/master/CONTRIBUTING.md) are available as well as a [pull request template](https://github.com/amzn/zeek-plugin-tds/blob/master/.github/PULL_REQUEST_TEMPLATE.md). A [Dockerfile](https://github.com/amzn/zeek-plugin-tds/blob/master/Dockerfile) has been included in the repository to assist with setting up an environment for testing any changes to the plugin. diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..9459d4b --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +1.1 diff --git a/configure b/configure new file mode 100755 index 0000000..6e26a00 --- /dev/null +++ b/configure @@ -0,0 +1,188 @@ +#!/bin/sh +# +# Wrapper for viewing/setting options that the plugin's CMake +# scripts will recognize. +# +# Don't edit this. Edit configure.plugin to add plugin-specific options. +# + +set -e +command="$0 $*" + +if [ -e `dirname $0`/configure.plugin ]; then + # Include custom additions. + . `dirname $0`/configure.plugin +fi + +usage() { + +cat 1>&2 </dev/null 2>&1; then + plugin_usage 1>&2 +fi + +echo + +exit 1 +} + +# Function to append a CMake cache entry definition to the +# CMakeCacheEntries variable +# $1 is the cache entry variable name +# $2 is the cache entry variable type +# $3 is the cache entry variable value +append_cache_entry () { + CMakeCacheEntries="$CMakeCacheEntries -D $1:$2=$3" +} + +# set defaults +builddir=build +zeekdist="" +installroot="default" +CMakeCacheEntries="" + +while [ $# -ne 0 ]; do + case "$1" in + -*=*) optarg=`echo "$1" | sed 's/[-_a-zA-Z0-9]*=//'` ;; + *) optarg= ;; + esac + + case "$1" in + --help|-h) + usage + ;; + + --cmake=*) + CMakeCommand=$optarg + ;; + + --zeek-dist=*) + zeekdist=`cd $optarg && pwd` + ;; + + --install-root=*) + installroot=$optarg + ;; + + --with-binpac=*) + append_cache_entry BinPAC_ROOT_DIR PATH $optarg + binpac_root=$optarg + ;; + + --with-broker=*) + append_cache_entry BROKER_ROOT_DIR PATH $optarg + broker_root=$optarg + ;; + + --with-caf=*) + append_cache_entry CAF_ROOT_DIR PATH $optarg + caf_root=$optarg + ;; + + --with-bifcl=*) + append_cache_entry BifCl_EXE PATH $optarg + ;; + + --enable-debug) + append_cache_entry BRO_PLUGIN_ENABLE_DEBUG BOOL true + ;; + + *) + if type plugin_option >/dev/null 2>&1; then + plugin_option $1 && shift && continue; + fi + + echo "Invalid option '$1'. Try $0 --help to see available options." + exit 1 + ;; + esac + shift +done + +if [ -z "$CMakeCommand" ]; then + # prefer cmake3 over "regular" cmake (cmake == cmake2 on RHEL) + if command -v cmake3 >/dev/null 2>&1 ; then + CMakeCommand="cmake3" + elif command -v cmake >/dev/null 2>&1 ; then + CMakeCommand="cmake" + else + echo "This package requires CMake, please install it first." + echo "Then you may use this script to configure the CMake build." + echo "Note: pass --cmake=PATH to use cmake in non-standard locations." + exit 1; + fi +fi + +if [ -z "$zeekdist" ]; then + if type zeek-config >/dev/null 2>&1; then + zeek_config="zeek-config" + else + echo "Either 'zeek-config' must be in PATH or '--zeek-dist=' used" + exit 1 + fi + + append_cache_entry BRO_CONFIG_PREFIX PATH `${zeek_config} --prefix` + append_cache_entry BRO_CONFIG_INCLUDE_DIR PATH `${zeek_config} --include_dir` + append_cache_entry BRO_CONFIG_PLUGIN_DIR PATH `${zeek_config} --plugin_dir` + append_cache_entry BRO_CONFIG_CMAKE_DIR PATH `${zeek_config} --cmake_dir` + append_cache_entry CMAKE_MODULE_PATH PATH `${zeek_config} --cmake_dir` + + build_type=`${zeek_config} --build_type` + + if [ "$build_type" = "debug" ]; then + append_cache_entry BRO_PLUGIN_ENABLE_DEBUG BOOL true + fi + + if [ -z "$binpac_root" ]; then + append_cache_entry BinPAC_ROOT_DIR PATH `${zeek_config} --binpac_root` + fi + + if [ -z "$broker_root" ]; then + append_cache_entry BROKER_ROOT_DIR PATH `${zeek_config} --broker_root` + fi + + if [ -z "$caf_root" ]; then + append_cache_entry CAF_ROOT_DIR PATH `${zeek_config} --caf_root` + fi +else + if [ ! -e "$zeekdist/zeek-path-dev.in" ]; then + echo "$zeekdist does not appear to be a valid Zeek source tree." + exit 1 + fi + + # BRO_DIST is the canonical/historical name used by plugin CMake scripts + # ZEEK_DIST doesn't serve a function at the moment, but set/provided anyway + append_cache_entry BRO_DIST PATH $zeekdist + append_cache_entry ZEEK_DIST PATH $zeekdist + append_cache_entry CMAKE_MODULE_PATH PATH $zeekdist/cmake +fi + +if [ "$installroot" != "default" ]; then + mkdir -p $installroot + append_cache_entry BRO_PLUGIN_INSTALL_ROOT PATH $installroot +fi + +echo "Build Directory : $builddir" +echo "Zeek Source Directory : $zeekdist" + +mkdir -p $builddir +cd $builddir + +"$CMakeCommand" $CMakeCacheEntries .. + +echo "# This is the command used to configure this build" > config.status +echo $command >> config.status +chmod u+x config.status diff --git a/configure.plugin b/configure.plugin new file mode 100644 index 0000000..057985d --- /dev/null +++ b/configure.plugin @@ -0,0 +1,26 @@ +#!/bin/sh +# +# Hooks to add custom options to the configure script. +# + +plugin_usage() +{ + : # Do nothing +# cat < 0) { + local params: string_vec; + local params_index: count=0; + local param_index: count=0; + local param_name: string=""; + local param_type: count=0; + local param_data: string; + local param_len: count=0; + while (param_index < params_size) { + param_len = bytestring_to_count(parameters[param_index])*2; + param_index += 1; + param_name = subst_string(parameters[param_index: param_index+param_len], "\x00", ""); + param_index += param_len; + param_index += 1; ##! status + param_type = bytestring_to_count(parameters[param_index]); + param_index += 1; + param_data = ""; + switch (param_type) { + case 0x24, ##! GUID + 0x26, ##! int + 0x68, ##! bit + 0x6d, ##! float + 0x6f: ##! DateTime + param_index += 1; ##! max length + param_len = bytestring_to_count(parameters[param_index]); + param_index += 1; + switch (param_type) { + case 0x24: ##! GUID + param_data = bytestring_to_hexstr(parameters[param_index+3]); + param_data += bytestring_to_hexstr(parameters[param_index+2]); + param_data += bytestring_to_hexstr(parameters[param_index+1]); + param_data += bytestring_to_hexstr(parameters[param_index]); + param_data += "-"; + param_data += bytestring_to_hexstr(parameters[param_index+5]); + param_data += bytestring_to_hexstr(parameters[param_index+4]); + param_data += "-"; + param_data += bytestring_to_hexstr(parameters[param_index+7]); + param_data += bytestring_to_hexstr(parameters[param_index+6]); + param_data += "-"; + param_data += bytestring_to_hexstr(parameters[param_index+8:param_index+10]); + param_data += "-"; + param_data += bytestring_to_hexstr(parameters[param_index+10:param_index+param_len]); + break; + case 0x68: ##! bit + if (bytestring_to_count(parameters[param_index]) == 1) { + param_data = "True"; + } + else { + param_data = "False"; + } + break; + case 0x6d: ##! float + param_data = fmt("%f", bytestring_to_double(parameters[param_index:param_index+param_len])); + break; + default: + if (param_index >= params_size) { + break; + } + param_data = fmt("%d", bytestring_to_count(parameters[param_index:param_index+param_len], T)); + break; + } + param_index += param_len; + params[params_index] = fmt("%s=%s", param_name, param_data); + params_index += 1; + break; + case 0xa7, ##! VarChar + 0xe7: ##! NVarChar + param_index += 2; ##! max length + param_index += 5; ##! collation + param_len = bytestring_to_count(parameters[param_index:param_index+2], T); + param_index += 2; + ##! NULL is 65535 + if (param_len > 0 && param_len < 65535) { + param_data = subst_string(parameters[param_index:param_index+param_len], "\x00", ""); + param_index += param_len; + } + params[params_index] = fmt("%s=%s", param_name, param_data); + params_index += 1; + break; + } + } + + c$tds_rpc$parameters = params; + } + + Log::write(Log_TDS_RPC, c$tds_rpc); + delete c$tds_rpc; + } + +event tds_sql_batch(c: connection, is_orig: bool, + header_type: count, + query: string) { + if(!c?$tds_sql_batch) { + c$tds_sql_batch = [$ts=network_time(), $uid=c$uid, $id=c$id]; + } + + c$tds_sql_batch$ts = network_time(); + c$tds_sql_batch$header_type = header_types[header_type]; + query = subst_string(query, "\x00", ""); + c$tds_sql_batch$query = query; + + Log::write(Log_TDS_SQL_Batch, c$tds_sql_batch); + delete c$tds_sql_batch; + } + +event connection_state_remove(c: connection) &priority=-5 { + if(c?$tds) { + delete c$tds; + } + } diff --git a/src/Plugin.cc b/src/Plugin.cc new file mode 100644 index 0000000..379af1d --- /dev/null +++ b/src/Plugin.cc @@ -0,0 +1,19 @@ +#include "Plugin.h" +#include "zeek/analyzer/Component.h" + +namespace plugin { + namespace Zeek_TDS { + Plugin plugin; + } + } + +using namespace plugin::Zeek_TDS; + +zeek::plugin::Configuration Plugin::Configure() { + AddComponent(new zeek::analyzer::Component("TDS", analyzer::tds::TDS_Analyzer::Instantiate)); + + zeek::plugin::Configuration config; + config.name = "Zeek::TDS"; + config.description = "MS-SQL TDS protocol analyzer"; + return config; + } diff --git a/src/Plugin.h b/src/Plugin.h new file mode 100644 index 0000000..3fd9dae --- /dev/null +++ b/src/Plugin.h @@ -0,0 +1,19 @@ +#ifndef ZEEK_PLUGIN_ZEEK_TDS +#define ZEEK_PLUGIN_ZEEK_TDS + +#include +#include "TDS.h" + +namespace plugin { + namespace Zeek_TDS { + class Plugin : public zeek::plugin::Plugin { + protected: + // Overridden from plugin::Plugin. + virtual zeek::plugin::Configuration Configure(); + }; + + extern Plugin plugin; + } + } + +#endif diff --git a/src/TDS.cc b/src/TDS.cc new file mode 100644 index 0000000..6f57729 --- /dev/null +++ b/src/TDS.cc @@ -0,0 +1,50 @@ +#include "TDS.h" +#include +#include +#include "events.bif.h" + +using namespace analyzer::tds; + +TDS_Analyzer::TDS_Analyzer(zeek::Connection* c): zeek::analyzer::tcp::TCP_ApplicationAnalyzer("TDS", c) { + interp = new binpac::TDS::TDS_Conn(this); + had_gap = false; + } + +TDS_Analyzer::~TDS_Analyzer() { + delete interp; + } + +void TDS_Analyzer::Done() { + zeek::analyzer::tcp::TCP_ApplicationAnalyzer::Done(); + interp->FlowEOF(true); + interp->FlowEOF(false); + } + +void TDS_Analyzer::EndpointEOF(bool is_orig) { + zeek::analyzer::tcp::TCP_ApplicationAnalyzer::EndpointEOF(is_orig); + interp->FlowEOF(is_orig); + } + +void TDS_Analyzer::DeliverStream(int len, const u_char* data, bool orig) { + zeek::analyzer::tcp::TCP_ApplicationAnalyzer::DeliverStream(len, data, orig); + assert(TCP()); + //if(TCP()->IsPartial()) + // return; + // If only one side had a content gap, we could still try to + // deliver data to the other side if the script layer can handle this. + if(had_gap) + return; + + try { + interp->NewData(orig, data, data + len); + } + catch(const binpac::Exception& e) { + AnalyzerViolation(zeek::util::fmt("Binpac exception: %s", e.c_msg())); + } + } + +void TDS_Analyzer::Undelivered(uint64_t seq, int len, bool orig) { + zeek::analyzer::tcp::TCP_ApplicationAnalyzer::Undelivered(seq, len, orig); + had_gap = true; + interp->NewGap(orig, len); + } diff --git a/src/TDS.h b/src/TDS.h new file mode 100644 index 0000000..9a1cbb2 --- /dev/null +++ b/src/TDS.h @@ -0,0 +1,31 @@ +#ifndef ANALYZER_PROTOCOL_TDS_H +#define ANALYZER_PROTOCOL_TDS_H + +#include +#include "tds_pac.h" + +namespace analyzer { + namespace tds { + class TDS_Analyzer : public zeek::analyzer::tcp::TCP_ApplicationAnalyzer { + public: + TDS_Analyzer(zeek::Connection* conn); + virtual ~TDS_Analyzer(); + + virtual void Done(); + virtual void DeliverStream(int len, const u_char* data, bool orig); + virtual void Undelivered(uint64_t seq, int len, bool orig); + + virtual void EndpointEOF(bool is_orig); + + static zeek::analyzer::Analyzer* Instantiate(zeek::Connection* conn) { + return new TDS_Analyzer(conn); + } + + protected: + binpac::TDS::TDS_Conn* interp; + bool had_gap; + }; + } + } + +#endif diff --git a/src/events.bif b/src/events.bif new file mode 100644 index 0000000..af14297 --- /dev/null +++ b/src/events.bif @@ -0,0 +1,40 @@ +## Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +## SPDX-License-Identifier: BSD-3-Clause + +###################################### +## Generated for all the TDS headers # +###################################### +## c: The connection the TDS communication is part of. +## is_orig: True if this reflects originator-side activity. +## command: uint16 of either request/response +## index: index# of the packet. +## +event tds%(c: connection, is_orig: bool, + command: count + %); + +############################################ +## Generated for the remote procedure call # +############################################ +## c: The connection the TDS communication is part of. +## is_orig: True if this reflects originator-side activity. +## product_name: product name. +## serial_number: serial number. +## +event tds_rpc%(c: connection, is_orig: bool, + procedure_name: string, + parameters: string + %); + +#################################### +## Generated for the identity info # +#################################### +## c: The connection the TDS communication is part of. +## is_orig: True if this reflects originator-side activity. +## product_name: product name. +## serial_number: serial number. +## +event tds_sql_batch%(c: connection, is_orig: bool, + header_type: count, + query: string + %); diff --git a/src/tds-analyzer.pac b/src/tds-analyzer.pac new file mode 100644 index 0000000..da3270f --- /dev/null +++ b/src/tds-analyzer.pac @@ -0,0 +1,101 @@ +## Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +## SPDX-License-Identifier: BSD-3-Clause + +connection TDS_Conn(zeek_analyzer: ZeekAnalyzer) { + upflow = TDS_Flow(true); + downflow = TDS_Flow(false); + }; + +%header{ + #define SQL_BATCH 0x01 + #define PRE_TDS7_LOGIN 0x02 + #define REMOTE_PROCEDURE_CALL 0x03 + #define RESPONSE 0x04 + #define UNUSED 0x05 + #define ATTENTION_REQUEST 0x06 + #define BULK_LOAD_DATA 0x07 + #define TRANSACTION_MANAGER 0x0e + #define TDS5_QUERY 0x0F + #define TDS7_LOGIN 0x10 + #define SSPI_MESSAGE 0x11 + #define TDS7_PRELOGIN 0x12 + + #define QUERY_NOTIFICATIONS 0x0001 + #define TRANSACTION_DESCRIPTOR 0x0002 + %} + +flow TDS_Flow(is_orig: bool) { + # flowunit ? + datagram = TDS_PDU(is_orig) withcontext(connection, this); + + function tds(header: TDS): bool %{ + if(::tds) { + if (${header.command} != SQL_BATCH && + ${header.command} != PRE_TDS7_LOGIN && + ${header.command} != REMOTE_PROCEDURE_CALL && + ${header.command} != RESPONSE && + ${header.command} != UNUSED && + ${header.command} != ATTENTION_REQUEST && + ${header.command} != BULK_LOAD_DATA && + ${header.command} != TRANSACTION_MANAGER && + ${header.command} != TDS5_QUERY && + ${header.command} != TDS7_LOGIN && + ${header.command} != SSPI_MESSAGE && + ${header.command} != TDS7_PRELOGIN) { + return false; + } + connection()->zeek_analyzer()->AnalyzerConfirmation(); + zeek::BifEvent::enqueue_tds(connection()->zeek_analyzer(), + connection()->zeek_analyzer()->Conn(), + is_orig(), + ${header.command} + ); + } + + return true; + %} + + function tds_rpc(rpc: TDS_RPC): bool %{ + if(::tds_rpc) { + connection()->zeek_analyzer()->AnalyzerConfirmation(); + zeek::BifEvent::enqueue_tds_rpc(connection()->zeek_analyzer(), + connection()->zeek_analyzer()->Conn(), + is_orig(), + to_stringval(${rpc.procedure_name}), + to_stringval(${rpc.parameters}) + ); + } + + return true; + %} + + function tds_sql_batch(sqlBatch: TDS_SQL_BATCH): bool %{ + if(::tds_sql_batch) { + if (${sqlBatch.stream_header.header_type} != QUERY_NOTIFICATIONS && + ${sqlBatch.stream_header.header_type} != TRANSACTION_DESCRIPTOR) { + return false; + } + connection()->zeek_analyzer()->AnalyzerConfirmation(); + zeek::BifEvent::enqueue_tds_sql_batch(connection()->zeek_analyzer(), + connection()->zeek_analyzer()->Conn(), + is_orig(), + ${sqlBatch.stream_header.header_type}, + to_stringval(${sqlBatch.query}) + ); + } + + return true; + %} + }; + +refine typeattr TDS += &let { + tds: bool = $context.flow.tds(this); + }; + +refine typeattr TDS_RPC += &let { + tds_rpc: bool = $context.flow.tds_rpc(this); + }; + +refine typeattr TDS_SQL_BATCH += &let { + tds_sql_batch: bool = $context.flow.tds_sql_batch(this); + }; diff --git a/src/tds-protocol.pac b/src/tds-protocol.pac new file mode 100644 index 0000000..4c5ca2a --- /dev/null +++ b/src/tds-protocol.pac @@ -0,0 +1,92 @@ +## Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +## SPDX-License-Identifier: BSD-3-Clause + +# +# Binpac for Microsoft TDS analyser. +# More information from https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-tds/b46a581a-39de-4745-b076-ec4dbb7d13ec +# + +############################## +# CONSTANTS # +############################## + +enum cmd_codes { + SQL_BATCH = 0x01, + PRE_TDS7_LOGIN = 0x02, + REMOTE_PROCEDURE_CALL = 0x03, + RESPONSE = 0x04, + UNUSED = 0x05, + ATTENTION_REQUEST = 0x06, + BULK_LOAD_DATA = 0x07, + TRANSACTION_MANAGER = 0x0e, + TDS5_QUERY = 0x0F, + TDS7_LOGIN = 0x10, + SSPI_MESSAGE = 0x11, + TDS7_PRELOGIN = 0x12 + }; + +############################## +## RECORD TYPES # +############################## + +## All multiple byte fields are set in little endian order +## Packets are set in big endian order + +type TDS_PDU(is_orig: bool) = case is_orig of { + true -> request : TDS_Request; + false -> response : TDS_Response; + } &byteorder=bigendian; + +# switch for the request portion +type TDS_Request = record { + header: TDS; + data: case(header.command) of { + REMOTE_PROCEDURE_CALL -> remoteProcedureCall : TDS_RPC; + SQL_BATCH -> sqlBatch : TDS_SQL_BATCH; + default -> unknown : bytestring &restofdata; + }; + } &byteorder=bigendian; + +# switch for the response portion +type TDS_Response = record { + header: TDS; + data: case(header.command) of { + RESPONSE -> response : TDS_RESPONSE; + ##! SQL_BATCH -> sqlBatch : TDS_SQL_BATCH; + default -> unknown : bytestring &restofdata; + }; + } &byteorder=bigendian; + +type TDS = record { + command : uint8; + status : uint8; + len : uint16; + channel : uint16; + packet_number : uint8; + window : uint8; + } &byteorder=bigendian; + +type TDS_RPC = record { + stream_header : Stream_Header; + name_len : uint16 &byteorder=littleendian; + procedure_name : bytestring &length=name_len*2; + option_flags : uint16 &byteorder=littleendian; + parameters : bytestring &restofdata; + } &byteorder=bigendian; + +type TDS_SQL_BATCH = record { + stream_header : Stream_Header; + query : bytestring &restofdata; + } &byteorder=bigendian; + +type Stream_Header = record { + total_len : uint32; + header_len : uint32; + header_type : uint16; + descriptor : uint64; + request_count : uint32; + } &byteorder=littleendian; + +type TDS_RESPONSE = record { + tokens : bytestring &restofdata; + } &byteorder=bigendian; diff --git a/src/tds.pac b/src/tds.pac new file mode 100644 index 0000000..17cda8e --- /dev/null +++ b/src/tds.pac @@ -0,0 +1,14 @@ +%include zeek/binpac.pac +%include zeek/zeek.pac + +%extern{ + #include "events.bif.h" + %} + +analyzer TDS withcontext { + connection: TDS_Conn; + flow: TDS_Flow; + }; + +%include tds-protocol.pac +%include tds-analyzer.pac diff --git a/zkg.meta b/zkg.meta new file mode 100644 index 0000000..7356e94 --- /dev/null +++ b/zkg.meta @@ -0,0 +1,9 @@ +[package] +script_dir = scripts/TDS +build_command = ./configure && make +tags = zeek plugin, protocol analyzer, log writer, tds +description = Plugin that enables parsing of the Tabular Data Stream (TDS) protocol +depends = + zkg >=2.0 + zeek >=3.0.0 +