commit bd2d73ef4f3ea2e487efc52930b75344f36a2baf Author: Patrick Kelley Date: Wed May 7 14:05:39 2025 -0400 Initial 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 +