This commit is contained in:
Patrick Kelley 2025-05-07 14:05:39 -04:00
commit bd2d73ef4f
22 changed files with 1030 additions and 0 deletions

13
CMakeLists.txt Normal file
View File

@ -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()

4
CODE_OF_CONDUCT.md Normal file
View File

@ -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.

61
CONTRIBUTING.md Normal file
View File

@ -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.

49
Dockerfile Normal file
View File

@ -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 .

23
LICENSE Normal file
View File

@ -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.

29
Makefile Normal file
View File

@ -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

11
README.md Normal file
View File

@ -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.

1
VERSION Normal file
View File

@ -0,0 +1 @@
1.1

188
configure vendored Executable file
View File

@ -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 <<EOF
Usage: $0 [OPTIONS]
Plugin Options:
--cmake=PATH Path to CMake binary
--zeek-dist=DIR Path to Zeek source tree
--install-root=DIR Path where to install plugin into
--with-binpac=DIR Path to BinPAC installation root
--with-broker=DIR Path to Broker installation root
--with-caf=DIR Path to CAF installation root
--with-bifcl=PATH Path to bifcl executable
--enable-debug Compile in debugging mode
EOF
if type plugin_usage >/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=<path>' 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

26
configure.plugin Normal file
View File

@ -0,0 +1,26 @@
#!/bin/sh
#
# Hooks to add custom options to the configure script.
#
plugin_usage()
{
: # Do nothing
# cat <<EOF
# --with-foo=DIR Path to foo
# EOF
}
plugin_option()
{
case "$1" in
# --with-foo=*)
# append_cache_entry FOO_DIR PATH $optarg
# return 0
# ;;
*)
return 1;
;;
esac
}

View File

@ -0,0 +1,3 @@
@load ./consts.zeek
@load ./main.zeek

30
scripts/TDS/consts.zeek Normal file
View File

@ -0,0 +1,30 @@
## Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
## SPDX-License-Identifier: BSD-3-Clause
module TDS;
export {
## TDS default commands
const commands = {
[0x00] = "NOP",
[0x01] = "SQL Batch",
[0x02] = "Pre TDS7 Login",
[0x03] = "Remote Procedure Call",
[0x04] = "SQL Batch Server Response",
[0x05] = "Unused",
[0x06] = "Attention Request",
[0x07] = "Bulk Load Data",
[0x0E] = "Transcation Manager Request",
[0x0F] = "TDS5 Query",
[0x10] = "TDS7 Login",
[0x11] = "SSPI Message",
[0x12] = "Pre-Login Request",
} &default=function(i: count):string { return fmt("command (%d)", i); } &redef;
## TDS header types
const header_types = {
[0x0000] = "NOP",
[0x0001] = "Query notifications",
[0x0002] = "Transaction Descriptor",
} &default=function(i: count):string { return fmt("command (%d)", i); } &redef;
}

217
scripts/TDS/main.zeek Normal file
View File

@ -0,0 +1,217 @@
##! Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
##! SPDX-License-Identifier: BSD-3-Clause
##! Implements base functionality for TDS analysis.
##! Generates the tds.log file, containing some information about the TDS headers.
##! Generates the tds_rpc.log file, containing some information about rpc data.
##! Generates the tds_sql_batch.log file, containing some information about sql batch data.
module TDS;
export {
redef enum Log::ID += {
Log_TDS,
Log_TDS_RPC,
Log_TDS_SQL_Batch
};
## header info
type TDS: record {
ts : time &log; ## Timestamp for when the event happened.
uid : string &log; ## Unique ID for the connection.
id : conn_id &log; ## The connection's 4-tuple of endpoint addresses/ports.
command : string &optional &log; ## Name of the sent TDS command.
};
## Event that can be handled to access the tds record as it is sent to the loggin framework.
global log_tds: event(rec: TDS);
## Remote Procedure Call
type TDS_RPC: record {
ts : time &log; ## Timestamp for when the event happened.
uid : string &log; ## Unique ID for the connection.
id : conn_id &log; ## The connection's 4-tuple of endpoint addresses/ports.
procedure_name : string &optional &log;
parameters : string_vec &optional &log;
};
## Event that can be handled to access the tds record as it is sent to the loggin framework.
global log_tds_rpc: event(rec: TDS_RPC);
## SQL Batch
type TDS_SQL_Batch: record {
ts : time &log; ## Timestamp for when the event happened.
uid : string &log; ## Unique ID for the connection.
id : conn_id &log; ## The connection's 4-tuple of endpoint addresses/ports.
header_type : string &optional &log;
query : string &optional &log;
};
## Event that can be handled to access the tds record as it is sent to the loggin framework.
global log_tds_sql_batch: event(rec: TDS_SQL_Batch);
}
redef record connection += {
tds : TDS &optional;
tds_rpc : TDS_RPC &optional;
tds_sql_batch : TDS_SQL_Batch &optional;
};
## define listening ports
const ports = {
1433/tcp
};
redef likely_server_ports += {
ports
};
event zeek_init() &priority=5 {
Log::create_stream(TDS::Log_TDS,
[$columns=TDS,
$ev=log_tds,
$path="tds"]);
Log::create_stream(TDS::Log_TDS_RPC,
[$columns=TDS_RPC,
$ev=log_tds_rpc,
$path="tds_rpc"]);
Log::create_stream(TDS::Log_TDS_SQL_Batch,
[$columns=TDS_SQL_Batch,
$ev=log_tds_sql_batch,
$path="tds_sql_batch"]);
Analyzer::register_for_ports(Analyzer::ANALYZER_TDS, ports);
}
##! general tds header
event tds(c: connection, is_orig: bool, command: count) {
if(!c?$tds) {
c$tds = [$ts=network_time(), $uid=c$uid, $id=c$id];
}
c$tds$ts = network_time();
c$tds$command = commands[command];
Log::write(Log_TDS, c$tds);
delete c$tds;
}
##! general tds rpc
event tds_rpc(c: connection, is_orig: bool,
procedure_name: string,
parameters: string) {
if(!c?$tds_rpc) {
c$tds_rpc = [$ts=network_time(), $uid=c$uid, $id=c$id];
}
c$tds_rpc$ts = network_time();
c$tds_rpc$procedure_name = subst_string(procedure_name, "\x00", "");
local params_size: count = |parameters|;
if (params_size > 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;
}
}

19
src/Plugin.cc Normal file
View File

@ -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;
}

19
src/Plugin.h Normal file
View File

@ -0,0 +1,19 @@
#ifndef ZEEK_PLUGIN_ZEEK_TDS
#define ZEEK_PLUGIN_ZEEK_TDS
#include <zeek/plugin/Plugin.h>
#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

50
src/TDS.cc Normal file
View File

@ -0,0 +1,50 @@
#include "TDS.h"
#include <zeek/analyzer/protocol/tcp/TCP_Reassembler.h>
#include <zeek/Reporter.h>
#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);
}

31
src/TDS.h Normal file
View File

@ -0,0 +1,31 @@
#ifndef ANALYZER_PROTOCOL_TDS_H
#define ANALYZER_PROTOCOL_TDS_H
#include <zeek/analyzer/protocol/tcp/TCP.h>
#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

40
src/events.bif Normal file
View File

@ -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
%);

101
src/tds-analyzer.pac Normal file
View File

@ -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);
};

92
src/tds-protocol.pac Normal file
View File

@ -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;

14
src/tds.pac Normal file
View File

@ -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

9
zkg.meta Normal file
View File

@ -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