mirror of https://github.com/OISF/suricata
Merge b05dfe3318 into 9a37bb659b
commit
4a59eba35b
@ -0,0 +1,107 @@
|
|||||||
|
Detection Inspection Tracing
|
||||||
|
============================
|
||||||
|
|
||||||
|
The content-inspection engine (``DetectEngineContentInspection``) can emit a
|
||||||
|
human-readable trace of every keyword it evaluates against a buffer. For each
|
||||||
|
inspected keyword it prints the signature being evaluated, the keyword and its
|
||||||
|
parameters, and -- on a match or a definitive no-match -- a hexdump of the
|
||||||
|
buffer windowed around the current inspection offset, with the bytes the
|
||||||
|
keyword matched called out in the dump. It is a developer aid for answering
|
||||||
|
"why did (or didn't) this rule match this traffic?".
|
||||||
|
|
||||||
|
The facility is intended for interactive debugging with a single pcap and a
|
||||||
|
small rule set. It writes directly to ``stdout`` and is not meant for
|
||||||
|
production traffic.
|
||||||
|
|
||||||
|
Enabling
|
||||||
|
--------
|
||||||
|
|
||||||
|
Tracing is compiled in only when Suricata is configured with::
|
||||||
|
|
||||||
|
./configure --enable-detect-trace
|
||||||
|
|
||||||
|
This defines the ``DETECT_TRACE`` preprocessor macro. When the macro is not
|
||||||
|
defined the trace call sites expand to nothing, so a normal build carries no
|
||||||
|
overhead at all.
|
||||||
|
|
||||||
|
Even in a trace-enabled build the output stays off until it is switched on at
|
||||||
|
runtime through an environment variable::
|
||||||
|
|
||||||
|
SURICATA_DETECT_TRACE=1 suricata -S rules.rules -r input.pcap -k none -l ./log
|
||||||
|
|
||||||
|
When the variable is unset, each trace call is guarded by a single predictable
|
||||||
|
branch, so a trace-enabled build can be used normally with negligible cost.
|
||||||
|
|
||||||
|
Output
|
||||||
|
------
|
||||||
|
|
||||||
|
Each inspected keyword produces a block such as::
|
||||||
|
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
TRACE (4) MSG: trace demo deep body
|
||||||
|
TRACE (4) Mode: state AppProto: http_any Buffer: file_data Match Type: content Recursion: 1/3000
|
||||||
|
TRACE (4) Inspecting content:"</html>" (len 7)
|
||||||
|
TRACE (4) -> MATCH! <- (content)
|
||||||
|
TRACE (4) post-detect offset: 4259
|
||||||
|
TRACE buffer window +4000..+4259 of 4259 (match +4252..+4259, cursor +4259 past end):
|
||||||
|
00000FA0 3E 20 6C 6F 77 3C 62 72 20 2F 3E 3C 65 6D 3E 50 > low<br /><em>P
|
||||||
|
...
|
||||||
|
00001080 0D 0A 0D 0A 09 09 3C 2F 64 69 76 3E 0D 0A 0D 0A ......</ div>....
|
||||||
|
>00001090 09 3C 2F 62 6F 64 79 3E 0D 0A 0D 0A 3C 2F 68 74 .</body> ....</ht
|
||||||
|
>000010A0 6D 6C 3E ml>
|
||||||
|
|
||||||
|
Reading the dump
|
||||||
|
~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Row offsets are absolute within the inspected buffer, so they can be compared
|
||||||
|
directly against the ``post-detect offset`` and against the ``offset``,
|
||||||
|
``depth`` and ``distance`` values printed for the keyword. Only a window around
|
||||||
|
the current offset is dumped -- 256 bytes on either side -- so that a large
|
||||||
|
stream or body buffer does not flood the terminal; the header line gives the
|
||||||
|
window bounds and the buffer's full length.
|
||||||
|
|
||||||
|
Two things are called out in the dump. The bytes the keyword just matched are
|
||||||
|
shown as a span, and the offset the next keyword will start from is shown as a
|
||||||
|
single cursor byte. They are distinct because a content match leaves the
|
||||||
|
inspection offset just *past* the bytes it matched, so ``match +4252..+4259``
|
||||||
|
and ``cursor +4259`` in the example above describe the seven bytes of
|
||||||
|
``</html>`` and the position after them. A match span is only reported for a
|
||||||
|
positive, non-negated ``content``; every other keyword, and every no-match,
|
||||||
|
leaves the offset as a bare cursor with no span to show. Rows containing either
|
||||||
|
marker carry a ``>`` in the left gutter.
|
||||||
|
|
||||||
|
When ``stdout`` is a terminal the span and the cursor are colored, in both the
|
||||||
|
hex and the ascii column. Otherwise -- output redirected to a file or a pager,
|
||||||
|
or ``NO_COLOR`` set in the environment -- the whole trace is emitted without
|
||||||
|
escape sequences and the markers appear on a row of their own beneath the hex
|
||||||
|
bytes, with ``^`` under the matched span and ``*`` under the cursor::
|
||||||
|
|
||||||
|
TRACE buffer window +0..+27 of 27 (cursor +10) [^ match, * cursor]:
|
||||||
|
>00000000 2F 66 69 6C 65 32 70 63 61 70 2F 62 6F 64 79 74 /file2pc ap/bodyt
|
||||||
|
**
|
||||||
|
00000010 65 78 74 74 65 73 74 2E 74 78 74 exttest. txt
|
||||||
|
|
||||||
|
The ``Buffer`` name is only shown when Suricata is additionally built with
|
||||||
|
``--enable-profiling`` (the current inspection buffer id is only tracked on the
|
||||||
|
thread context in profiling builds); otherwise it is reported as ``(null)``.
|
||||||
|
|
||||||
|
Per-keyword parameter detail is printed for ``content``, ``pcre``, ``isdataat``,
|
||||||
|
``byte_test``, ``byte_jump`` and ``base64_decode``. Other keywords are still
|
||||||
|
traced by name via the ``Match Type`` field.
|
||||||
|
|
||||||
|
Implementation
|
||||||
|
--------------
|
||||||
|
|
||||||
|
All of the tracing lives in ``src/detect-engine-inspect-trace.{c,h}`` and is
|
||||||
|
invoked from ``DetectEngineContentInspection`` through the ``DETECT_TRACE_*``
|
||||||
|
macros, which are no-ops unless ``DETECT_TRACE`` is defined. To keep the trace
|
||||||
|
readable, ``content`` values are rendered from the parsed ``DetectContentData``
|
||||||
|
(no signature re-parsing), and the original ``pcre`` pattern text is retained on
|
||||||
|
the ``DetectParseRegex`` only in trace builds.
|
||||||
|
|
||||||
|
The dump is written by ``DetectTraceHexdump()`` rather than by the shared
|
||||||
|
``PrintRawDataFp()`` helper, since it needs absolute row offsets and per-byte
|
||||||
|
markers that the shared dumper does not provide. Color is decided once in
|
||||||
|
``DetectTraceInit()``: the ANSI sequences are held in file-scope pointers that
|
||||||
|
stay empty strings unless ``stdout`` is a tty and ``NO_COLOR`` is unset, so the
|
||||||
|
same format strings produce both forms of output.
|
||||||
@ -0,0 +1,446 @@
|
|||||||
|
/* Copyright (C) 2026 Open Information Security Foundation
|
||||||
|
*
|
||||||
|
* You can copy, redistribute or modify this Program under the terms of
|
||||||
|
* the GNU General Public License version 2 as published by the Free
|
||||||
|
* Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* version 2 along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||||
|
* 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \file
|
||||||
|
*
|
||||||
|
* \author Jeff Lucovsky <jlucovsky@oisf.net>
|
||||||
|
*
|
||||||
|
* See detect-engine-inspect-trace.h for an overview.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "suricata-common.h"
|
||||||
|
#include "detect-engine-inspect-trace.h"
|
||||||
|
|
||||||
|
#ifdef DETECT_TRACE
|
||||||
|
|
||||||
|
#include "decode.h"
|
||||||
|
#include "detect.h"
|
||||||
|
#include "detect-parse.h"
|
||||||
|
#include "detect-engine.h"
|
||||||
|
|
||||||
|
#include "detect-content.h"
|
||||||
|
#include "detect-pcre.h"
|
||||||
|
#include "detect-isdataat.h"
|
||||||
|
#include "detect-bytetest.h"
|
||||||
|
#include "detect-bytejump.h"
|
||||||
|
#include "detect-urilen.h"
|
||||||
|
#include "detect-base64-decode.h"
|
||||||
|
|
||||||
|
#include "app-layer-protos.h"
|
||||||
|
#include "util-print.h"
|
||||||
|
|
||||||
|
/* ANSI colors used to make the trace readable on a terminal. They are empty
|
||||||
|
* strings unless DetectTraceInit() finds an interactive stdout, so the same
|
||||||
|
* format strings serve both the colored and the plain output. */
|
||||||
|
static const char *c_rst = "";
|
||||||
|
static const char *c_red = "";
|
||||||
|
static const char *c_grn = "";
|
||||||
|
static const char *c_blu = "";
|
||||||
|
static const char *c_mag = "";
|
||||||
|
static const char *c_cyn = "";
|
||||||
|
/* the bytes a keyword just matched, and the byte the next keyword starts at */
|
||||||
|
static const char *c_match = "";
|
||||||
|
static const char *c_cursor = "";
|
||||||
|
|
||||||
|
bool g_detect_trace_enabled = false;
|
||||||
|
static bool g_detect_trace_color = false;
|
||||||
|
|
||||||
|
void DetectTraceInit(void)
|
||||||
|
{
|
||||||
|
g_detect_trace_enabled = (getenv("SURICATA_DETECT_TRACE") != NULL);
|
||||||
|
if (!g_detect_trace_enabled)
|
||||||
|
return;
|
||||||
|
|
||||||
|
/* Escape sequences only help on a terminal: they are noise in a file and
|
||||||
|
* they would swamp a hexdump. NO_COLOR (https://no-color.org/) turns them
|
||||||
|
* off even there. */
|
||||||
|
const char *no_color = getenv("NO_COLOR");
|
||||||
|
#ifndef OS_WIN32
|
||||||
|
g_detect_trace_color = isatty(fileno(stdout)) && (no_color == NULL || no_color[0] == '\0');
|
||||||
|
#endif
|
||||||
|
if (g_detect_trace_color) {
|
||||||
|
c_rst = "\033[0m";
|
||||||
|
c_red = "\033[31m";
|
||||||
|
c_grn = "\033[32m";
|
||||||
|
c_blu = "\033[34m";
|
||||||
|
c_mag = "\033[35m";
|
||||||
|
c_cyn = "\033[36m";
|
||||||
|
/* explicit foreground and background so the markers render the same
|
||||||
|
* whatever the terminal's own palette is */
|
||||||
|
c_match = "\033[30;102m";
|
||||||
|
c_cursor = "\033[30;103m";
|
||||||
|
}
|
||||||
|
|
||||||
|
SCLogNotice("detection-engine inspection tracing enabled "
|
||||||
|
"(SURICATA_DETECT_TRACE set); output goes to stdout");
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *DetectTraceModeName(enum DetectContentInspectionType mode)
|
||||||
|
{
|
||||||
|
switch (mode) {
|
||||||
|
case DETECT_ENGINE_CONTENT_INSPECTION_MODE_PAYLOAD:
|
||||||
|
return "payload";
|
||||||
|
case DETECT_ENGINE_CONTENT_INSPECTION_MODE_HEADER:
|
||||||
|
return "header";
|
||||||
|
case DETECT_ENGINE_CONTENT_INSPECTION_MODE_STREAM:
|
||||||
|
return "stream";
|
||||||
|
case DETECT_ENGINE_CONTENT_INSPECTION_MODE_FRAME:
|
||||||
|
return "frame";
|
||||||
|
case DETECT_ENGINE_CONTENT_INSPECTION_MODE_STATE:
|
||||||
|
return "state";
|
||||||
|
}
|
||||||
|
return "unknown";
|
||||||
|
}
|
||||||
|
|
||||||
|
/** \brief how a byte of the dump is called out, if at all */
|
||||||
|
enum DetectTraceMark {
|
||||||
|
TRACE_MARK_NONE = 0,
|
||||||
|
TRACE_MARK_MATCH,
|
||||||
|
TRACE_MARK_CURSOR,
|
||||||
|
};
|
||||||
|
|
||||||
|
/** \brief width of the gutter and offset column preceding the hex bytes */
|
||||||
|
#define TRACE_DUMP_PREFIX 11
|
||||||
|
|
||||||
|
/** \brief classify byte \p i of the buffer against the marked region */
|
||||||
|
static enum DetectTraceMark DetectTraceByteMark(
|
||||||
|
uint32_t i, uint32_t hl_start, uint32_t hl_end, uint32_t cursor, bool has_cursor)
|
||||||
|
{
|
||||||
|
if (has_cursor && i == cursor)
|
||||||
|
return TRACE_MARK_CURSOR;
|
||||||
|
if (i >= hl_start && i < hl_end)
|
||||||
|
return TRACE_MARK_MATCH;
|
||||||
|
return TRACE_MARK_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *DetectTraceMarkColor(enum DetectTraceMark mark)
|
||||||
|
{
|
||||||
|
switch (mark) {
|
||||||
|
case TRACE_MARK_MATCH:
|
||||||
|
return c_match;
|
||||||
|
case TRACE_MARK_CURSOR:
|
||||||
|
return c_cursor;
|
||||||
|
case TRACE_MARK_NONE:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Whether the highlight should run through the separator after byte \p i.
|
||||||
|
*
|
||||||
|
* It continues only into a neighbour on the same row carrying the same mark, so
|
||||||
|
* a span reads as one bar instead of as detached pairs, without the background
|
||||||
|
* bleeding past the end of the row.
|
||||||
|
*/
|
||||||
|
static bool DetectTraceMarkJoins(uint32_t i, uint32_t rowend, enum DetectTraceMark mark,
|
||||||
|
uint32_t hl_start, uint32_t hl_end, uint32_t cursor, bool has_cursor)
|
||||||
|
{
|
||||||
|
if (mark == TRACE_MARK_NONE || i + 1 >= rowend)
|
||||||
|
return false;
|
||||||
|
return DetectTraceByteMark(i + 1, hl_start, hl_end, cursor, has_cursor) == mark;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** \brief the caret glyph standing in for a color when there is no terminal */
|
||||||
|
static char DetectTraceMarkGlyph(enum DetectTraceMark mark)
|
||||||
|
{
|
||||||
|
switch (mark) {
|
||||||
|
case TRACE_MARK_MATCH:
|
||||||
|
return '^';
|
||||||
|
case TRACE_MARK_CURSOR:
|
||||||
|
return '*';
|
||||||
|
case TRACE_MARK_NONE:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return ' ';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Hexdump of \p buf around the current inspection \p cursor.
|
||||||
|
*
|
||||||
|
* Large payload/stream buffers are windowed so the trace doesn't flood the
|
||||||
|
* terminal. Row offsets are absolute within the buffer, so they can be read
|
||||||
|
* against the offsets the rest of the trace prints without any arithmetic.
|
||||||
|
*
|
||||||
|
* \p match_len is the number of bytes the keyword just consumed. Those bytes
|
||||||
|
* end at \p cursor rather than starting there, so they are highlighted as the
|
||||||
|
* span [cursor - match_len, cursor) and \p cursor itself is marked separately
|
||||||
|
* as the point the next keyword starts from.
|
||||||
|
*/
|
||||||
|
static void DetectTraceHexdump(
|
||||||
|
const uint8_t *buf, uint32_t len, uint32_t cursor, uint32_t match_len)
|
||||||
|
{
|
||||||
|
const uint32_t window = 256; /* bytes of context on each side of the cursor */
|
||||||
|
|
||||||
|
uint32_t start = (cursor > window) ? cursor - window : 0;
|
||||||
|
start &= ~0xFU; /* align rows to 16 so the columns stay in fixed positions */
|
||||||
|
uint32_t end = (cursor + window < len) ? cursor + window : len;
|
||||||
|
if (start >= end) {
|
||||||
|
start = 0;
|
||||||
|
end = len;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool span = (match_len > 0 && match_len <= cursor);
|
||||||
|
const uint32_t hl_start = span ? cursor - match_len : 0;
|
||||||
|
const uint32_t hl_end = span ? cursor : 0; /* exclusive */
|
||||||
|
const bool has_cursor = (cursor < len);
|
||||||
|
|
||||||
|
printf("TRACE buffer window +%u..+%u of %u (", start, end, len);
|
||||||
|
if (span)
|
||||||
|
printf("match +%u..+%u, ", hl_start, hl_end);
|
||||||
|
printf("cursor +%u%s)", cursor, has_cursor ? "" : " past end");
|
||||||
|
if (!g_detect_trace_color)
|
||||||
|
printf(" [^ match, * cursor]");
|
||||||
|
printf(":\n");
|
||||||
|
|
||||||
|
for (uint32_t u = start; u < end; u += 16) {
|
||||||
|
const uint32_t rowend = (u + 16 < end) ? u + 16 : end;
|
||||||
|
|
||||||
|
bool marked = false;
|
||||||
|
for (uint32_t i = u; i < rowend; i++) {
|
||||||
|
if (DetectTraceByteMark(i, hl_start, hl_end, cursor, has_cursor) != TRACE_MARK_NONE) {
|
||||||
|
marked = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("%c%08X ", marked ? '>' : ' ', u);
|
||||||
|
|
||||||
|
uint32_t ch;
|
||||||
|
for (ch = 0; u + ch < rowend; ch++) {
|
||||||
|
const enum DetectTraceMark mark =
|
||||||
|
DetectTraceByteMark(u + ch, hl_start, hl_end, cursor, has_cursor);
|
||||||
|
const bool joins = DetectTraceMarkJoins(
|
||||||
|
u + ch, rowend, mark, hl_start, hl_end, cursor, has_cursor);
|
||||||
|
const char *esc = (mark == TRACE_MARK_NONE) ? "" : DetectTraceMarkColor(mark);
|
||||||
|
|
||||||
|
printf("%s%02X%s ", esc, buf[u + ch], (mark != TRACE_MARK_NONE && !joins) ? c_rst : "");
|
||||||
|
if (ch == 7)
|
||||||
|
putchar(' ');
|
||||||
|
if (joins)
|
||||||
|
printf("%s", c_rst);
|
||||||
|
}
|
||||||
|
/* pad a short final row so the ascii column stays where it belongs */
|
||||||
|
for (uint32_t pad = ch; pad < 16; pad++) {
|
||||||
|
printf(" ");
|
||||||
|
if (pad == 7)
|
||||||
|
putchar(' ');
|
||||||
|
}
|
||||||
|
printf(" ");
|
||||||
|
|
||||||
|
for (ch = 0; u + ch < rowend; ch++) {
|
||||||
|
const uint8_t c = buf[u + ch];
|
||||||
|
const char pc = isprint(c) ? (char)c : '.';
|
||||||
|
const enum DetectTraceMark mark =
|
||||||
|
DetectTraceByteMark(u + ch, hl_start, hl_end, cursor, has_cursor);
|
||||||
|
const bool joins = DetectTraceMarkJoins(
|
||||||
|
u + ch, rowend, mark, hl_start, hl_end, cursor, has_cursor);
|
||||||
|
const char *esc = (mark == TRACE_MARK_NONE) ? "" : DetectTraceMarkColor(mark);
|
||||||
|
|
||||||
|
printf("%s%c%s", esc, pc, (mark != TRACE_MARK_NONE && !joins) ? c_rst : "");
|
||||||
|
if (ch == 7)
|
||||||
|
putchar(' ');
|
||||||
|
if (joins)
|
||||||
|
printf("%s", c_rst);
|
||||||
|
}
|
||||||
|
putchar('\n');
|
||||||
|
|
||||||
|
/* with no color to carry it, the marked bytes get a row of their own */
|
||||||
|
if (!g_detect_trace_color && marked) {
|
||||||
|
for (int i = 0; i < TRACE_DUMP_PREFIX; i++)
|
||||||
|
putchar(' ');
|
||||||
|
for (ch = 0; u + ch < rowend; ch++) {
|
||||||
|
const char glyph = DetectTraceMarkGlyph(
|
||||||
|
DetectTraceByteMark(u + ch, hl_start, hl_end, cursor, has_cursor));
|
||||||
|
printf("%c%c ", glyph, glyph);
|
||||||
|
if (ch == 7)
|
||||||
|
putchar(' ');
|
||||||
|
}
|
||||||
|
putchar('\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Number of bytes the keyword consumed, ending at the post-detect offset.
|
||||||
|
*
|
||||||
|
* A positive, non-negated content match is the only case that moves
|
||||||
|
* det_ctx->buffer_offset to the end of the bytes it matched (match_offset in
|
||||||
|
* DetectEngineContentInspectionInternal()); every other keyword and every
|
||||||
|
* no-match leaves the offset as a bare cursor with no span to show.
|
||||||
|
*/
|
||||||
|
static uint32_t DetectTraceMatchLen(const SigMatchData *smd, int result)
|
||||||
|
{
|
||||||
|
if (result != 1 || smd->type != DETECT_CONTENT)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
const DetectContentData *cd = (const DetectContentData *)smd->ctx;
|
||||||
|
if (cd->flags & DETECT_CONTENT_NEGATED)
|
||||||
|
return 0;
|
||||||
|
return cd->content_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** \brief append " name:value" for a set flag, into a fixed buffer */
|
||||||
|
static void DetectTraceAppend(char *dst, size_t dstlen, const char *text)
|
||||||
|
{
|
||||||
|
strlcat(dst, text, dstlen);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** \brief render the keyword-specific parameters into \p out */
|
||||||
|
static void DetectTraceKeywordDetail(const SigMatchData *smd, char *out, size_t outlen)
|
||||||
|
{
|
||||||
|
out[0] = '\0';
|
||||||
|
|
||||||
|
switch (smd->type) {
|
||||||
|
case DETECT_CONTENT: {
|
||||||
|
const DetectContentData *cd = (const DetectContentData *)smd->ctx;
|
||||||
|
char content[1024] = "";
|
||||||
|
uint32_t o = 0;
|
||||||
|
PrintRawUriBuf(content, &o, sizeof(content), cd->content, cd->content_len);
|
||||||
|
|
||||||
|
char mods[256] = "";
|
||||||
|
char tmp[64];
|
||||||
|
if (cd->flags & DETECT_CONTENT_OFFSET) {
|
||||||
|
snprintf(tmp, sizeof(tmp), " offset:%u", cd->offset);
|
||||||
|
DetectTraceAppend(mods, sizeof(mods), tmp);
|
||||||
|
}
|
||||||
|
if (cd->flags & DETECT_CONTENT_DEPTH) {
|
||||||
|
snprintf(tmp, sizeof(tmp), " depth:%u", cd->depth);
|
||||||
|
DetectTraceAppend(mods, sizeof(mods), tmp);
|
||||||
|
}
|
||||||
|
if (cd->flags & DETECT_CONTENT_DISTANCE) {
|
||||||
|
snprintf(tmp, sizeof(tmp), " distance:%d", cd->distance);
|
||||||
|
DetectTraceAppend(mods, sizeof(mods), tmp);
|
||||||
|
}
|
||||||
|
if (cd->flags & DETECT_CONTENT_WITHIN) {
|
||||||
|
snprintf(tmp, sizeof(tmp), " within:%d", cd->within);
|
||||||
|
DetectTraceAppend(mods, sizeof(mods), tmp);
|
||||||
|
}
|
||||||
|
if (cd->flags & DETECT_CONTENT_STARTS_WITH)
|
||||||
|
DetectTraceAppend(mods, sizeof(mods), " startswith");
|
||||||
|
if (cd->flags & DETECT_CONTENT_ENDS_WITH)
|
||||||
|
DetectTraceAppend(mods, sizeof(mods), " endswith");
|
||||||
|
if (cd->flags & DETECT_CONTENT_FAST_PATTERN)
|
||||||
|
DetectTraceAppend(mods, sizeof(mods), " fast_pattern");
|
||||||
|
if (cd->flags & DETECT_CONTENT_RAWBYTES)
|
||||||
|
DetectTraceAppend(mods, sizeof(mods), " rawbytes");
|
||||||
|
|
||||||
|
snprintf(out, outlen, "content:%s\"%s%s%s\"%s (len %u)",
|
||||||
|
(cd->flags & DETECT_CONTENT_NEGATED) ? "!" : "", c_grn, content, c_rst, mods,
|
||||||
|
cd->content_len);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case DETECT_PCRE: {
|
||||||
|
const DetectPcreData *pe = (const DetectPcreData *)smd->ctx;
|
||||||
|
char mods[64] = "";
|
||||||
|
if (pe->flags & DETECT_PCRE_RELATIVE)
|
||||||
|
DetectTraceAppend(mods, sizeof(mods), " relative");
|
||||||
|
if (pe->flags & DETECT_PCRE_CASELESS)
|
||||||
|
DetectTraceAppend(mods, sizeof(mods), " nocase");
|
||||||
|
if (pe->flags & DETECT_PCRE_RAWBYTES)
|
||||||
|
DetectTraceAppend(mods, sizeof(mods), " rawbytes");
|
||||||
|
snprintf(out, outlen, "pcre:%s\"%s%s%s\"%s",
|
||||||
|
(pe->flags & DETECT_PCRE_NEGATE) ? "!" : "", c_grn,
|
||||||
|
pe->parse_regex.regexstr ? pe->parse_regex.regexstr : "(unknown)", c_rst, mods);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case DETECT_ISDATAAT: {
|
||||||
|
const DetectIsdataatData *id = (const DetectIsdataatData *)smd->ctx;
|
||||||
|
snprintf(out, outlen, "isdataat:%s%s%u%s%s", (id->flags & ISDATAAT_NEGATED) ? "!" : "",
|
||||||
|
c_grn, id->dataat, c_rst, (id->flags & ISDATAAT_RELATIVE) ? " relative" : "");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case DETECT_BYTETEST: {
|
||||||
|
const DetectBytetestData *btd = (const DetectBytetestData *)smd->ctx;
|
||||||
|
snprintf(out, outlen,
|
||||||
|
"byte_test:%sbytes %u, op %u, value %" PRIu64 ", offset %d, bitmask 0x%x%s",
|
||||||
|
c_grn, btd->nbytes, btd->op, btd->value, btd->offset, btd->bitmask, c_rst);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case DETECT_BYTEJUMP: {
|
||||||
|
const DetectBytejumpData *bjd = (const DetectBytejumpData *)smd->ctx;
|
||||||
|
snprintf(out, outlen, "byte_jump:%sbytes %u, offset %d, post_offset %d%s", c_grn,
|
||||||
|
bjd->nbytes, bjd->offset, bjd->post_offset, c_rst);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case DETECT_BASE64_DECODE: {
|
||||||
|
const DetectBase64Decode *bd = (const DetectBase64Decode *)smd->ctx;
|
||||||
|
snprintf(out, outlen, "base64_decode:%sbytes %u, offset %u, relative %s%s", c_grn,
|
||||||
|
bd->bytes, bd->offset, bd->relative ? "true" : "false", c_rst);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
/* no dedicated formatter; the keyword name is shown in the header */
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DetectTraceKeyword(const DetectEngineThreadCtx *det_ctx, const Signature *s,
|
||||||
|
const SigMatchData *smd, const Packet *p, enum DetectContentInspectionType inspection_mode,
|
||||||
|
uint32_t recursion, uint32_t recursion_limit)
|
||||||
|
{
|
||||||
|
char detail[1400];
|
||||||
|
DetectTraceKeywordDetail(smd, detail, sizeof(detail));
|
||||||
|
|
||||||
|
/* The current buffer/list id is only tracked on det_ctx when profiling is
|
||||||
|
* compiled in; without it we simply omit the buffer name. */
|
||||||
|
const char *bufname = NULL;
|
||||||
|
#ifdef PROFILING
|
||||||
|
if (det_ctx->de_ctx != NULL) {
|
||||||
|
bufname = DetectEngineBufferTypeGetNameById(det_ctx->de_ctx, det_ctx->keyword_perf_list);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
flockfile(stdout);
|
||||||
|
for (int i = 0; i < 78; i++)
|
||||||
|
putchar('-');
|
||||||
|
printf("\nTRACE (%u) MSG: %s\n", s->id, s->msg ? s->msg : "");
|
||||||
|
printf("TRACE (%u)", s->id);
|
||||||
|
if (p != NULL)
|
||||||
|
printf(" Packet: %" PRIu64, PcapPacketCntGet(p));
|
||||||
|
printf(" Mode: %s%s%s", c_red, DetectTraceModeName(inspection_mode), c_rst);
|
||||||
|
if (s->alproto != ALPROTO_UNKNOWN)
|
||||||
|
printf(" AppProto: %s%s%s", c_red, AppProtoToString(s->alproto), c_rst);
|
||||||
|
printf(" Buffer: %s%s%s", c_mag, bufname ? bufname : "(null)", c_rst);
|
||||||
|
printf(" Match Type: %s%s%s", c_blu, sigmatch_table[smd->type].name, c_rst);
|
||||||
|
printf(" Recursion: %s%u/%u%s\n", c_grn, recursion, recursion_limit, c_rst);
|
||||||
|
if (detail[0] != '\0')
|
||||||
|
printf("TRACE (%u) Inspecting %s\n", s->id, detail);
|
||||||
|
fflush(stdout);
|
||||||
|
funlockfile(stdout);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DetectTraceResult(const Signature *s, const SigMatchData *smd, const uint8_t *buffer,
|
||||||
|
uint32_t buffer_len, uint32_t offset, int result)
|
||||||
|
{
|
||||||
|
flockfile(stdout);
|
||||||
|
if (result == 1) {
|
||||||
|
printf("TRACE (%u) -> %sMATCH!%s <- (%s)\n", s->id, c_cyn, c_rst,
|
||||||
|
sigmatch_table[smd->type].name);
|
||||||
|
} else {
|
||||||
|
printf("TRACE (%u) -> %sNO MATCH!%s <-%s (%s)\n", s->id, c_red, c_rst,
|
||||||
|
(result == -1) ? " [discontinue]" : "", sigmatch_table[smd->type].name);
|
||||||
|
}
|
||||||
|
printf("TRACE (%u) post-detect offset: %s%u%s\n", s->id, c_grn, offset, c_rst);
|
||||||
|
if (buffer != NULL)
|
||||||
|
DetectTraceHexdump(buffer, buffer_len, offset, DetectTraceMatchLen(smd, result));
|
||||||
|
fflush(stdout);
|
||||||
|
funlockfile(stdout);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* DETECT_TRACE */
|
||||||
@ -0,0 +1,93 @@
|
|||||||
|
/* Copyright (C) 2026 Open Information Security Foundation
|
||||||
|
*
|
||||||
|
* You can copy, redistribute or modify this Program under the terms of
|
||||||
|
* the GNU General Public License version 2 as published by the Free
|
||||||
|
* Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* version 2 along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||||
|
* 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \file
|
||||||
|
*
|
||||||
|
* \author Jeff Lucovsky <jlucovsky@oisf.net>
|
||||||
|
*
|
||||||
|
* Optional, human-readable tracing of the content-inspection engine. Prints, per
|
||||||
|
* inspected keyword, the rule being evaluated, the keyword parameters and a
|
||||||
|
* hexdump of the buffer around the current inspection offset, followed by the
|
||||||
|
* match/no-match outcome. The dump calls out the bytes the keyword matched and
|
||||||
|
* the offset the next keyword starts from, with color on an interactive stdout
|
||||||
|
* and with a caret row otherwise.
|
||||||
|
*
|
||||||
|
* The whole facility is compiled in only when configured with
|
||||||
|
* --enable-detect-trace (which defines DETECT_TRACE). Even then it stays silent
|
||||||
|
* unless enabled at runtime via the SURICATA_DETECT_TRACE environment variable,
|
||||||
|
* so a trace-enabled build pays nothing more than a single predictable branch
|
||||||
|
* per keyword when tracing is off.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef SURICATA_DETECT_ENGINE_INSPECT_TRACE_H
|
||||||
|
#define SURICATA_DETECT_ENGINE_INSPECT_TRACE_H
|
||||||
|
|
||||||
|
#include "detect.h"
|
||||||
|
#include "detect-engine-content-inspection.h"
|
||||||
|
|
||||||
|
#ifdef DETECT_TRACE
|
||||||
|
|
||||||
|
/** \brief runtime toggle; set once from the environment by DetectTraceInit() */
|
||||||
|
extern bool g_detect_trace_enabled;
|
||||||
|
|
||||||
|
/** \brief read the SURICATA_DETECT_TRACE environment variable once at startup */
|
||||||
|
void DetectTraceInit(void);
|
||||||
|
|
||||||
|
/** \internal implementation of DETECT_TRACE_KEYWORD() */
|
||||||
|
void DetectTraceKeyword(const DetectEngineThreadCtx *det_ctx, const Signature *s,
|
||||||
|
const SigMatchData *smd, const Packet *p, enum DetectContentInspectionType inspection_mode,
|
||||||
|
uint32_t recursion, uint32_t recursion_limit);
|
||||||
|
|
||||||
|
/** \internal implementation of DETECT_TRACE_RESULT() */
|
||||||
|
void DetectTraceResult(const Signature *s, const SigMatchData *smd, const uint8_t *buffer,
|
||||||
|
uint32_t buffer_len, uint32_t offset, int result);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Trace the keyword about to be inspected (rule, parameters, buffer).
|
||||||
|
*/
|
||||||
|
#define DETECT_TRACE_KEYWORD(det_ctx, s, smd, p, mode, rec, lim) \
|
||||||
|
do { \
|
||||||
|
if (unlikely(g_detect_trace_enabled)) \
|
||||||
|
DetectTraceKeyword((det_ctx), (s), (smd), (p), (mode), (rec), (lim)); \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Trace the outcome of the current keyword. \p result is 1 for a match,
|
||||||
|
* 0 for no match and -1 for no match with "discontinue".
|
||||||
|
*/
|
||||||
|
#define DETECT_TRACE_RESULT(s, smd, buffer, buffer_len, offset, result) \
|
||||||
|
do { \
|
||||||
|
if (unlikely(g_detect_trace_enabled)) \
|
||||||
|
DetectTraceResult((s), (smd), (buffer), (buffer_len), (offset), (result)); \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
#else /* !DETECT_TRACE */
|
||||||
|
|
||||||
|
static inline void DetectTraceInit(void)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
#define DETECT_TRACE_KEYWORD(det_ctx, s, smd, p, mode, rec, lim) \
|
||||||
|
do { \
|
||||||
|
} while (0)
|
||||||
|
#define DETECT_TRACE_RESULT(s, smd, buffer, buffer_len, offset, result) \
|
||||||
|
do { \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
#endif /* DETECT_TRACE */
|
||||||
|
|
||||||
|
#endif /* SURICATA_DETECT_ENGINE_INSPECT_TRACE_H */
|
||||||
Loading…
Reference in New Issue