From ecbcccf355c53db10bc76c3447455aeb424bf0e9 Mon Sep 17 00:00:00 2001 From: Victor Julien Date: Sat, 17 May 2025 21:37:50 +0200 Subject: [PATCH] detect: add tcp.wscale keyword Allows matching on wscale option value in TCP header options. Ticket: #7713. --- doc/userguide/rules/header-keywords.rst | 20 +++ src/Makefile.am | 2 + src/detect-engine-register.c | 2 + src/detect-engine-register.h | 1 + src/detect-tcp-wscale.c | 172 ++++++++++++++++++++++++ src/detect-tcp-wscale.h | 29 ++++ 6 files changed, 226 insertions(+) create mode 100644 src/detect-tcp-wscale.c create mode 100644 src/detect-tcp-wscale.h diff --git a/doc/userguide/rules/header-keywords.rst b/doc/userguide/rules/header-keywords.rst index 2962e0253a..7f204b8ee7 100644 --- a/doc/userguide/rules/header-keywords.rst +++ b/doc/userguide/rules/header-keywords.rst @@ -454,6 +454,26 @@ Example rule: alert tcp $EXTERNAL_NET any -> $HOME_NET any (flow:stateless; flags:S,12; :example-rule-emphasis:`tcp.mss:<536;` sid:1234; rev:5;) +tcp.wscale +^^^^^^^^^^ + +Match on the TCP window scaling option value. Will not match if the option is not +present. + +``tcp.wscale`` uses an :ref:`unsigned 8-bit integer `. + +The format of the keyword is:: + + tcp.wscale:-; + tcp.wscale:[<|>]; + tcp.wscale:; + +Example rule: + +.. container:: example-rule + + alert tcp $EXTERNAL_NET any -> $HOME_NET any (flow:stateless; flags:S,12; :example-rule-emphasis:`tcp.wscale:>10;` sid:1234; rev:5;) + tcp.hdr ^^^^^^^ diff --git a/src/Makefile.am b/src/Makefile.am index ed279d4770..c70254f3f3 100755 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -299,6 +299,7 @@ noinst_HEADERS = \ detect-tcp-flags.h \ detect-tcp-seq.h \ detect-tcp-window.h \ + detect-tcp-wscale.h \ detect-tcphdr.h \ detect-tcpmss.h \ detect-template.h \ @@ -896,6 +897,7 @@ libsuricata_c_a_SOURCES = \ detect-tcp-flags.c \ detect-tcp-seq.c \ detect-tcp-window.c \ + detect-tcp-wscale.c \ detect-tcphdr.c \ detect-tcpmss.c \ detect-template.c \ diff --git a/src/detect-engine-register.c b/src/detect-engine-register.c index 5e0023fd89..81da9fa1ee 100644 --- a/src/detect-engine-register.c +++ b/src/detect-engine-register.c @@ -123,6 +123,7 @@ #include "detect-flow-pkts.h" #include "detect-requires.h" #include "detect-tcp-window.h" +#include "detect-tcp-wscale.h" #include "detect-ftpbounce.h" #include "detect-ftp-dynamic-port.h" #include "detect-isdataat.h" @@ -707,6 +708,7 @@ void SigTableSetup(void) DetectTcphdrRegister(); DetectUdphdrRegister(); DetectTcpmssRegister(); + DetectTcpWscaleRegister(); DetectICMPv6hdrRegister(); DetectICMPv6mtuRegister(); DetectIPAddrBufferRegister(); diff --git a/src/detect-engine-register.h b/src/detect-engine-register.h index 65a1c192ed..0e5e52242c 100644 --- a/src/detect-engine-register.h +++ b/src/detect-engine-register.h @@ -289,6 +289,7 @@ enum DetectKeywordId { DETECT_TCPHDR, DETECT_UDPHDR, DETECT_TCPMSS, + DETECT_TCP_WSCALE, DETECT_FTPDATA, DETECT_TARGET, DETECT_QUIC_VERSION, diff --git a/src/detect-tcp-wscale.c b/src/detect-tcp-wscale.c new file mode 100644 index 0000000000..747a041255 --- /dev/null +++ b/src/detect-tcp-wscale.c @@ -0,0 +1,172 @@ +/* Copyright (C) 2007-2025 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 Victor Julien + * + */ + +#include "suricata-common.h" + +#include "detect.h" +#include "detect-parse.h" +#include "detect-engine-prefilter-common.h" +#include "detect-engine-uint.h" +#include "util-byte.h" + +#include "detect-tcp-wscale.h" + +/* prototypes */ +static int DetectTcpWscaleMatch( + DetectEngineThreadCtx *, Packet *, const Signature *, const SigMatchCtx *); +static int DetectTcpWscaleSetup(DetectEngineCtx *, Signature *, const char *); +void DetectTcpWscaleFree(DetectEngineCtx *, void *); +static int PrefilterSetupTcpWscale(DetectEngineCtx *de_ctx, SigGroupHead *sgh); +static bool PrefilterTcpWscaleIsPrefilterable(const Signature *s); + +/** + * \brief Registration function for tcp.wscale keyword + */ + +void DetectTcpWscaleRegister(void) +{ + sigmatch_table[DETECT_TCP_WSCALE].name = "tcp.wscale"; + sigmatch_table[DETECT_TCP_WSCALE].desc = "match on TCP WSCALE option field"; + sigmatch_table[DETECT_TCP_WSCALE].url = "/rules/header-keywords.html#tcpwscale"; + sigmatch_table[DETECT_TCP_WSCALE].Match = DetectTcpWscaleMatch; + sigmatch_table[DETECT_TCP_WSCALE].Setup = DetectTcpWscaleSetup; + sigmatch_table[DETECT_TCP_WSCALE].Free = DetectTcpWscaleFree; + sigmatch_table[DETECT_TCP_WSCALE].SupportsPrefilter = PrefilterTcpWscaleIsPrefilterable; + sigmatch_table[DETECT_TCP_WSCALE].SetupPrefilter = PrefilterSetupTcpWscale; + sigmatch_table[DETECT_TCP_WSCALE].flags = SIGMATCH_SUPPORT_FIREWALL; +} + +/** + * \brief This function is used to match WSCALE rule option on a packet with those passed via + * tcp.wscale: + * + * \param det_ctx pointer to the pattern matcher thread + * \param p pointer to the current packet + * \param ctx pointer to the sigmatch that we will cast into DetectU8Data + * + * \retval 0 no match + * \retval 1 match + */ +static int DetectTcpWscaleMatch( + DetectEngineThreadCtx *det_ctx, Packet *p, const Signature *s, const SigMatchCtx *ctx) +{ + DEBUG_VALIDATE_BUG_ON(PKT_IS_PSEUDOPKT(p)); + + if (!(PacketIsTCP(p))) + return 0; + + if (!(TCP_HAS_WSCALE(p))) + return 0; + + uint8_t v = TCP_GET_WSCALE(p); + + const DetectU8Data *ws = (const DetectU8Data *)ctx; + return DetectU8Match(v, ws); +} + +/** + * \brief this function is used to attach the parsed tcp.wscale data into the current signature + * + * \param de_ctx pointer to the Detection Engine Context + * \param s pointer to the Current Signature + * \param str pointer to the user provided options + * + * \retval 0 on Success + * \retval -1 on Failure + */ +static int DetectTcpWscaleSetup(DetectEngineCtx *de_ctx, Signature *s, const char *str) +{ + DetectU8Data *ws = DetectU8Parse(str); + if (ws == NULL) + return -1; + + if (SCSigMatchAppendSMToList( + de_ctx, s, DETECT_TCP_WSCALE, (SigMatchCtx *)ws, DETECT_SM_LIST_MATCH) == NULL) { + DetectTcpWscaleFree(de_ctx, ws); + return -1; + } + s->flags |= SIG_FLAG_REQUIRE_PACKET; + + return 0; +} + +/** + * \brief this function will free memory associated with DetectU8Data + * + * \param ptr pointer to DetectU8Data + */ +void DetectTcpWscaleFree(DetectEngineCtx *de_ctx, void *ptr) +{ + SCDetectU8Free(ptr); +} + +/* prefilter code */ + +static void PrefilterPacketTcpWscaleMatch( + DetectEngineThreadCtx *det_ctx, Packet *p, const void *pectx) +{ + DEBUG_VALIDATE_BUG_ON(PKT_IS_PSEUDOPKT(p)); + if (!(PacketIsTCP(p))) + return; + + if (!(TCP_HAS_WSCALE(p))) + return; + + const uint8_t v = TCP_GET_WSCALE(p); + + /* during setup Suricata will automatically see if there is another + * check that can be added: alproto, sport or dport */ + const PrefilterPacketHeaderCtx *ctx = pectx; + if (!PrefilterPacketHeaderExtraMatch(ctx, p)) + return; + + DetectU8Data du8; + du8.mode = ctx->v1.u8[0]; + du8.arg1 = ctx->v1.u8[1]; + du8.arg2 = ctx->v1.u8[2]; + /* if we match, add all the sigs that use this prefilter. This means + * that these will be inspected further */ + if (DetectU8Match(v, &du8)) { + SCLogDebug("packet matches wscale %u", v); + PrefilterAddSids(&det_ctx->pmq, ctx->sigs_array, ctx->sigs_cnt); + } +} + +static int PrefilterSetupTcpWscale(DetectEngineCtx *de_ctx, SigGroupHead *sgh) +{ + return PrefilterSetupPacketHeader(de_ctx, sgh, DETECT_TCP_WSCALE, SIG_MASK_REQUIRE_REAL_PKT, + PrefilterPacketU8Set, PrefilterPacketU8Compare, PrefilterPacketTcpWscaleMatch); +} + +static bool PrefilterTcpWscaleIsPrefilterable(const Signature *s) +{ + const SigMatch *sm; + for (sm = s->init_data->smlists[DETECT_SM_LIST_MATCH]; sm != NULL; sm = sm->next) { + switch (sm->type) { + case DETECT_TCP_WSCALE: + return true; + } + } + return false; +} diff --git a/src/detect-tcp-wscale.h b/src/detect-tcp-wscale.h new file mode 100644 index 0000000000..c7f15ae93b --- /dev/null +++ b/src/detect-tcp-wscale.h @@ -0,0 +1,29 @@ +/* Copyright (C) 2007-2019 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 Victor Julien + */ + +#ifndef SURICARA_DETECT_TCP_WSCALE_H +#define SURICARA_DETECT_TCP_WSCALE_H + +void DetectTcpWscaleRegister(void); + +#endif /* SURICARA_DETECT_TCP_WSCALE_H */ -- 2.47.2