]> git.ipfire.org Git - thirdparty/suricata.git/commitdiff
detect: Add byte_math detector
authorJeff Lucovsky <jeff@lucovsky.org>
Mon, 27 Jan 2020 00:27:22 +0000 (19:27 -0500)
committerVictor Julien <victor@inliniac.net>
Sat, 11 Jul 2020 06:36:52 +0000 (08:36 +0200)
src/Makefile.am
src/detect-bytemath.c [new file with mode: 0644]
src/detect-bytemath.h [new file with mode: 0644]
src/detect-engine-register.h

index c40274eef7bc7ea8d707262af35c36741d3800a3..839adc31747dd9e321a8c79d89bea18467a5e8b3 100755 (executable)
@@ -99,6 +99,7 @@ detect-bsize.c detect-bsize.h \
 detect-byte.c detect-byte.h \
 detect-byte-extract.c detect-byte-extract.h \
 detect-bytejump.c detect-bytejump.h \
+detect-bytemath.c detect-bytemath.h \
 detect-bytetest.c detect-bytetest.h \
 detect-bypass.c detect-bypass.h \
 detect.c detect.h \
diff --git a/src/detect-bytemath.c b/src/detect-bytemath.c
new file mode 100644 (file)
index 0000000..67a115f
--- /dev/null
@@ -0,0 +1,1199 @@
+/* Copyright (C) 2020 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 <jeff@lucovsky.org>
+ */
+
+/*
+ * Refer to the Snort manual, section 3.5.34 for details.
+ */
+
+#include "suricata-common.h"
+#include "threads.h"
+#include "decode.h"
+
+#include "detect.h"
+#include "detect-parse.h"
+#include "detect-engine.h"
+#include "detect-engine-mpm.h"
+#include "detect-engine-state.h"
+#include "detect-content.h"
+#include "detect-pcre.h"
+#include "detect-byte.h"
+#include "detect-bytemath.h"
+#include "detect-isdataat.h"
+
+#include "app-layer-protos.h"
+
+#include "flow.h"
+#include "flow-var.h"
+#include "flow-util.h"
+
+#include "util-byte.h"
+#include "util-debug.h"
+#include "util-unittest.h"
+#include "util-unittest-helper.h"
+#include "util-spm.h"
+
+/* the default value of endianess to be used, if none's specified */
+#define DETECT_BYTEMATH_ENDIAN_DEFAULT DETECT_BYTEMATH_ENDIAN_BIG
+
+/* the base to be used if string mode is specified.  These options would be
+ * specified in DetectByteMathData->base */
+#define DETECT_BYTEMATH_BASE_NONE  0
+#define DETECT_BYTEMATH_BASE_OCT   8
+#define DETECT_BYTEMATH_BASE_DEC  10
+#define DETECT_BYTEMATH_BASE_HEX  16
+#define DETECT_BYTEMATH_BASE_DEFAULT DETECT_BYTEMATH_BASE_DEC
+
+/* the max no of bytes that can be extracted in string mode - (string, hex)
+ * (string, oct) or (string, dec) */
+#define STRING_MAX_BYTES_TO_EXTRACT_FOR_OCT 23
+#define STRING_MAX_BYTES_TO_EXTRACT_FOR_DEC 20
+#define STRING_MAX_BYTES_TO_EXTRACT_FOR_HEX 14
+
+/* the max no of bytes that can be extracted in non-string mode */
+#define NO_STRING_MAX_BYTES_TO_EXTRACT 4
+
+#define PARSE_REGEX "^" \
+    "\\s*(bytes)\\s*(\\d+)\\s*" \
+    ",\\s*(offset)\\s*(\\d+)\\s*" \
+    ",\\s*(oper)\\s*([-+\\/]{1}|<<|>>)\\s*" \
+    ",\\s*(rvalue)\\s*(\\w+)\\s*" \
+    ",\\s*(result)\\s*(\\w+)\\s*" \
+    "(?:,\\s*(relative)\\s*)?" \
+    "(?:,\\s*(endian)\\s*(big|little)\\s*)?" \
+    "(?:,\\s*(string)\\s*(hex|dec|oct)\\s*)?" \
+    "(?:,\\s*(dce)\\s*)?" \
+    "(?:,\\s*(bitmask)\\s*(0?[xX]?[0-9a-fA-F]{2,8})\\s*)?" \
+    "$"
+
+/* Mandatory value group numbers -- kw values not needed */
+//#define BYTES_KW     1
+#define BYTES_VAL      2
+//#define OFFSET_KW    3
+#define OFFSET_VAL     4
+//#define OPER_KW              5
+#define OPER_VAL       6
+//#define RVALUE_KW    7
+#define RVALUE_VAL     8
+//#define RESULT_KW    9
+#define RESULT_VAL     10
+
+/* Optional value group numbers */
+#define RELATIVE_KW    11
+#define ENDIAN_KW      12
+#define ENDIAN_VAL     13
+#define STRING_KW      14
+#define STRING_VAL     15
+#define DCE_KW         16
+#define BITMASK_KW     17
+#define BITMASK_VAL    18
+
+#define MIN_GROUP      10
+#define MAX_GROUP      19
+
+static DetectParseRegex parse_regex;
+
+static int DetectByteMathSetup(DetectEngineCtx *, Signature *, const char *);
+static void DetectByteMathRegisterTests(void);
+static void DetectByteMathFree(DetectEngineCtx *, void *);
+
+/**
+ * \brief Registers the keyword handlers for the "byte_math" keyword.
+ */
+void DetectBytemathRegister(void)
+{
+    sigmatch_table[DETECT_BYTEMATH].name = "byte_math";
+    sigmatch_table[DETECT_BYTEMATH].Match = NULL;
+    sigmatch_table[DETECT_BYTEMATH].Setup = DetectByteMathSetup;
+    sigmatch_table[DETECT_BYTEMATH].Free = DetectByteMathFree;
+    sigmatch_table[DETECT_BYTEMATH].RegisterTests = DetectByteMathRegisterTests;
+
+    DetectSetupParseRegexes(PARSE_REGEX, &parse_regex);
+}
+
+int DetectByteMathDoMatch(DetectEngineThreadCtx *det_ctx, const SigMatchData *smd,
+                          const Signature *s, const uint8_t *payload,
+                          uint16_t payload_len, uint64_t rvalue, uint64_t *value, uint8_t endian)
+{
+    const DetectByteMathData *data = (DetectByteMathData *)smd->ctx;
+    const uint8_t *ptr;
+    int32_t len;
+    uint64_t val;
+    int extbytes;
+
+    if (payload_len == 0) {
+        return 0;
+    }
+
+    /* Calculate the ptr value for the byte-math op and length remaining in
+     * the packet from that point.
+     */
+    if (data->flags & DETECT_BYTEMATH_FLAG_RELATIVE) {
+        SCLogDebug("relative, working with det_ctx->buffer_offset %"PRIu32", "
+                   "data->offset %"PRIu32"", det_ctx->buffer_offset, data->offset);
+
+        ptr = payload + det_ctx->buffer_offset;
+        len = payload_len - det_ctx->buffer_offset;
+
+        ptr += data->offset;
+        len -= data->offset;
+
+        /* No match if there is no relative base */
+        if (len <= 0) {
+            return 0;
+        }
+    } else {
+        SCLogDebug("absolute, data->offset %"PRIu32"", data->offset);
+
+        ptr = payload + data->offset;
+        len = payload_len - data->offset;
+    }
+
+    /* Validate that the to-be-extracted is within the packet */
+    if (ptr < payload || data->nbytes > len) {
+        SCLogDebug("Data not within payload pkt=%p, ptr=%p, len=%"PRIu32", nbytes=%d",
+                    payload, ptr, len, data->nbytes);
+        return 0;
+    }
+
+    /* Extract the byte data */
+    if (data->flags & DETECT_BYTEMATH_FLAG_STRING) {
+        extbytes = ByteExtractStringUint64(&val, data->base,
+                                           data->nbytes, (const char *)ptr);
+        if (extbytes <= 0) {
+            if (val == 0) {
+                SCLogDebug("No Numeric value");
+                return 0;
+            } else {
+                SCLogDebug("error extracting %d bytes of string data: %d",
+                           data->nbytes, extbytes);
+                return -1;
+            }
+        }
+    } else {
+        int endianness = (endian == DETECT_BYTEMATH_ENDIAN_BIG) ?
+                          BYTE_BIG_ENDIAN : BYTE_LITTLE_ENDIAN;
+        extbytes = ByteExtractUint64(&val, endianness, data->nbytes, ptr);
+        if (extbytes != data->nbytes) {
+            SCLogDebug("error extracting %d bytes of numeric data: %d",
+                       data->nbytes, extbytes);
+            return 0;
+        }
+    }
+
+    BUG_ON(extbytes > len);
+
+    ptr += extbytes;
+    det_ctx->buffer_offset = ptr - payload;
+
+    switch (data->oper) {
+        case DETECT_BYTEMATH_OPERATOR_NONE:
+            break;
+        case DETECT_BYTEMATH_OPERATOR_PLUS:
+            val += rvalue;
+            break;
+        case DETECT_BYTEMATH_OPERATOR_MINUS:
+            val -= rvalue;
+            break;
+        case DETECT_BYTEMATH_OPERATOR_DIVIDE:
+            val /= rvalue;
+            break;
+        case DETECT_BYTEMATH_OPERATOR_MULTIPLY:
+            val *= rvalue;
+            break;
+        case DETECT_BYTEMATH_OPERATOR_LSHIFT:
+            val <<= rvalue;
+            break;
+        case DETECT_BYTEMATH_OPERATOR_RSHIFT:
+            val >>= rvalue;
+            break;
+    }
+
+    if (data->flags & DETECT_BYTEMATH_FLAG_BITMASK) {
+        val &= data->bitmask_val;
+        if (val && data->bitmask_shift_count) {
+            val = val >> data->bitmask_shift_count;
+        }
+    }
+
+    *value = val;
+    return 1;
+}
+
+/**
+ * \internal
+ * \brief Used to parse byte_math arg.
+ *
+ * \param arg The argument to parse.
+ * \param rvalue May be NULL. When non-null, will contain the variable
+ *              name of rvalue (iff rvalue is not a scalar value)
+ *
+ * \retval bmd On success an instance containing the parsed data.
+ *            On failure, NULL.
+ */
+static DetectByteMathData *DetectByteMathParse(DetectEngineCtx *de_ctx, const char *arg, char **rvalue)
+{
+    DetectByteMathData *bmd = NULL;
+    int ret, res;
+#undef MAX_SUBSTRINGS
+#define MAX_SUBSTRINGS 100
+    int ov[MAX_SUBSTRINGS];
+    char tmp_str[128];
+
+    ret = DetectParsePcreExec(&parse_regex, arg, 0, 0, ov, MAX_SUBSTRINGS);
+    if (ret < MIN_GROUP || ret > MAX_GROUP) {
+        SCLogError(SC_ERR_PCRE_PARSE, "byte_math parse error; invalid value: ret %" PRId32
+                   ", string \"%s\"", ret, arg);
+        goto error;
+    }
+
+    bmd = SCCalloc(1, sizeof(DetectByteMathData));
+    if (unlikely(bmd == NULL))
+        goto error;
+
+    /* no of bytes to extract */
+    res = pcre_copy_substring((char *)arg, ov,
+                             MAX_SUBSTRINGS, BYTES_VAL, tmp_str, sizeof(tmp_str));
+    if (res < 0) {
+        SCLogError(SC_ERR_PCRE_GET_SUBSTRING, "pcre_copy_substring failed "
+                   "for \"nbytes\" value: \"%s\"", tmp_str);
+        goto error;
+    }
+
+    res = ByteExtractStringUint8(&bmd->nbytes, 10,
+                                strlen(tmp_str),
+                                (const char *)tmp_str);
+    if (res < 1 || bmd->nbytes > 10) {
+        SCLogError(SC_ERR_INVALID_SIGNATURE, "byte_math invalid bytes "
+                   "value \"%s\" specified.", tmp_str);
+        goto error;
+    }
+
+    /* offset */
+    res = pcre_copy_substring((char *)arg, ov,
+                             MAX_SUBSTRINGS, OFFSET_VAL, tmp_str, sizeof(tmp_str));
+    if (res < 0) {
+        SCLogError(SC_ERR_PCRE_GET_SUBSTRING, "pcre_copy_substring failed "
+                   "for \"offset\" value: \"%s\"", tmp_str);
+        goto error;
+    }
+
+    if (ByteExtractStringUint16(&bmd->offset, 10, strlen(tmp_str), (const char *)tmp_str) < 0) {
+        SCLogError(SC_ERR_INVALID_SIGNATURE, "byte_math invalid offset "
+                   "value \"%s\"", tmp_str);
+        goto error;
+    }
+
+    /* operator */
+    res = pcre_copy_substring((char *)arg, ov,
+                             MAX_SUBSTRINGS, OPER_VAL, tmp_str, sizeof(tmp_str));
+    if (res < 0) {
+        SCLogError(SC_ERR_PCRE_GET_SUBSTRING, "pcre_copy_substring failed "
+                   "for \"operator\" value of byte_math: \"%s\"", tmp_str);
+        goto error;
+    }
+
+    if (strcmp(tmp_str, "+") == 0) {
+        bmd->oper = DETECT_BYTEMATH_OPERATOR_PLUS;
+    } else if (strcmp(tmp_str, "-") == 0) {
+        bmd->oper = DETECT_BYTEMATH_OPERATOR_MINUS;
+    } else if (strcmp(tmp_str, "/") == 0) {
+        bmd->oper = DETECT_BYTEMATH_OPERATOR_DIVIDE;
+    } else if (strcmp(tmp_str, "*") == 0) {
+        bmd->oper = DETECT_BYTEMATH_OPERATOR_MULTIPLY;
+    } else if (strcmp(tmp_str, "<<") == 0) {
+        bmd->oper = DETECT_BYTEMATH_OPERATOR_LSHIFT;
+    } else if (strcmp(tmp_str, ">>") == 0) {
+        bmd->oper = DETECT_BYTEMATH_OPERATOR_RSHIFT;
+    }
+
+    /* rvalue */
+    res = pcre_copy_substring((char *)arg, ov, MAX_SUBSTRINGS,
+                              RVALUE_VAL, tmp_str, sizeof(tmp_str));
+    if (res < 0) {
+        SCLogError(SC_ERR_PCRE_GET_SUBSTRING, "pcre_copy_substring failed "
+                   "for \"rvalue\" to byte_math: \"%s\"", tmp_str);
+        goto error;
+    }
+
+    if (*tmp_str != '-' && isalpha((unsigned char)*tmp_str)) {
+        if (rvalue == NULL) {
+            SCLogError(SC_ERR_INVALID_SIGNATURE, "byte_math supplied with "
+                       "var name for rvalue. \"rvalue\" argument supplied to "
+                       "this function must be non-NULL");
+            goto error;
+        }
+        *rvalue = SCStrdup(tmp_str);
+        if (*rvalue == NULL) {
+            goto error;
+        }
+    } else {
+        if (ByteExtractStringUint32(&bmd->rvalue, 10, strlen(tmp_str), (const char *)tmp_str) < 0) {
+            SCLogError(SC_ERR_INVALID_SIGNATURE, "byte_math invalid rvalue "
+                       "value \"%s\"", tmp_str);
+            goto error;
+        }
+    }
+
+    /* result */
+    res = pcre_copy_substring((char *)arg, ov, MAX_SUBSTRINGS,
+                              RESULT_VAL, tmp_str, sizeof(tmp_str));
+    if (res < 0) {
+        SCLogError(SC_ERR_PCRE_GET_SUBSTRING, "pcre_copy_substring failed "
+                   "for \"result\" to byte_math");
+        goto error;
+    }
+    if (!isalpha(*tmp_str)) {
+        SCLogError(SC_ERR_INVALID_SIGNATURE, "byte_math result must be "
+                   "a variable name. Unable to find \"%s\"", tmp_str);
+        goto error;
+    }
+
+    bmd->result = SCStrdup(tmp_str);
+    if (bmd->result == NULL)
+        goto error;
+
+    /* optional value handling:
+     * relative - 11
+     * endian <val> - 12-13
+     * string <val> - 14-15
+     * dce - 16
+     * bitmask <val> - 17-18
+     */
+
+    if (ret > RELATIVE_KW) {
+        res = pcre_copy_substring((char *)arg, ov, MAX_SUBSTRINGS,
+                                  RELATIVE_KW, tmp_str, sizeof(tmp_str));
+
+        if (res < 0) {
+            SCLogError(SC_ERR_PCRE_GET_SUBSTRING, "pcre_copy_substring failed "
+                       "for byte_math \"relative\" arg");
+            goto error;
+        }
+
+        if (tmp_str[0] != '\0') {
+            bmd->flags |= DETECT_BYTEMATH_FLAG_RELATIVE;
+        }
+    }
+
+    if (ret > ENDIAN_VAL) {
+        res = pcre_copy_substring((char *)arg, ov, MAX_SUBSTRINGS,
+                                  ENDIAN_KW, tmp_str, sizeof(tmp_str));
+        if (res < 0) {
+            SCLogError(SC_ERR_PCRE_GET_SUBSTRING, "pcre_copy_substring failed "
+                       "for byte_math \"endian\" arg");
+            goto error;
+        }
+
+        if (tmp_str[0] != '\0') {
+            bmd->flags |= DETECT_BYTEMATH_FLAG_ENDIAN;
+        }
+
+        res = pcre_copy_substring((char *)arg, ov, MAX_SUBSTRINGS,
+                                 ENDIAN_VAL, tmp_str, sizeof(tmp_str));
+        if (res < 0) {
+            SCLogError(SC_ERR_PCRE_GET_SUBSTRING, "pcre_copy_substring failed "
+                       "for byte_math \"endian\" value");
+            goto error;
+        }
+
+        /* Since the parse succeeded, there's a value */
+        if (strcmp("big", tmp_str) == 0)
+            bmd->endian = DETECT_BYTEMATH_ENDIAN_BIG;
+        else if (strcmp("little", tmp_str) == 0)
+            bmd->endian = DETECT_BYTEMATH_ENDIAN_LITTLE;
+    }
+
+    if (ret > STRING_VAL) {
+        res = pcre_copy_substring((char *)arg, ov, MAX_SUBSTRINGS,
+                                  STRING_KW, tmp_str, sizeof(tmp_str));
+        if (res < 0) {
+            SCLogError(SC_ERR_PCRE_GET_SUBSTRING, "pcre_copy_substring failed "
+                       "for byte_math \"string\" arg");
+            goto error;
+        }
+
+        if (tmp_str[0] != '\0') {
+            bmd->flags |= DETECT_BYTEMATH_FLAG_STRING;
+        }
+
+        res = pcre_copy_substring((char *)arg, ov, MAX_SUBSTRINGS,
+                                  STRING_VAL, tmp_str, sizeof(tmp_str));
+        if (res < 0) {
+            SCLogError(SC_ERR_PCRE_GET_SUBSTRING, "pcre_copy_substring failed "
+                       "for byte_math \"string\" value");
+            goto error;
+        }
+
+        /* Since the parse succeeded, there's a value */
+        if (strcmp("hex", tmp_str) == 0) {
+            bmd->base = DETECT_BYTEMATH_BASE_HEX;
+        } else if (strcmp("oct", tmp_str) == 0) {
+            bmd->base = DETECT_BYTEMATH_BASE_OCT;
+        } else if (strcmp("dec", tmp_str) == 0) {
+            bmd->base = DETECT_BYTEMATH_BASE_DEC;
+        }
+    }
+
+    if (ret > DCE_KW) {
+        res = pcre_copy_substring((char *)arg, ov, MAX_SUBSTRINGS,
+                                  DCE_KW, tmp_str, sizeof(tmp_str));
+        if (res < 0) {
+            SCLogError(SC_ERR_PCRE_GET_SUBSTRING, "pcre_copy_substring failed "
+                       "for byte_math \"dce\" arg");
+            goto error;
+        }
+
+        if (tmp_str[0] != '\0') {
+            bmd->flags |= DETECT_BYTEMATH_FLAG_ENDIAN;
+            bmd->endian = DETECT_BYTEMATH_ENDIAN_DCE;
+        }
+    }
+
+    if (ret > BITMASK_VAL) {
+        res = pcre_copy_substring((char *)arg, ov, MAX_SUBSTRINGS,
+                                  BITMASK_KW, tmp_str, sizeof(tmp_str));
+        if (res < 0) {
+            SCLogError(SC_ERR_PCRE_GET_SUBSTRING, "pcre_copy_substring failed "
+                       "for byte_math \"bitmask\" arg");
+            goto error;
+        }
+
+        if (tmp_str[0] != '\0') {
+            bmd->flags |= DETECT_BYTEMATH_FLAG_BITMASK;
+        }
+
+        /* bitmask value*/
+        res = pcre_copy_substring((char *)arg, ov, MAX_SUBSTRINGS,
+                                  BITMASK_VAL, tmp_str, sizeof(tmp_str));
+        if (res < 0) {
+            SCLogError(SC_ERR_PCRE_GET_SUBSTRING, "pcre_copy_substring failed "
+                   "for bitmask value: \"%s\"", tmp_str);
+            goto error;
+        }
+
+        res = ByteExtractStringUint32(&bmd->bitmask_val, 16, strlen(tmp_str), tmp_str);
+        if (res < 0) {
+            SCLogError(SC_ERR_INVALID_SIGNATURE, "Unable to extract bitmask "
+                       "value: \"%s\"", tmp_str);
+            goto error;
+        }
+
+        /* determine how many trailing 0's are in the bitmask. This will be used
+         * to rshift the value after applying the bitmask
+         */
+        bmd->bitmask_shift_count = 0;
+        if (bmd->bitmask_val) {
+            uint32_t bmask = bmd->bitmask_val;
+            while (!(bmask & 0x1)){
+                bmask = bmask >> 1;
+                bmd->bitmask_shift_count++;
+            }
+        }
+    }
+
+    if (bmd->endian == DETECT_BYTEMATH_ENDIAN_DCE) {
+        switch (bmd->nbytes) {
+            case 1:
+            case 2:
+            case 4:
+                break;
+            default:
+                SCLogError(SC_ERR_INVALID_SIGNATURE, "nbytes must be 1, 2, or 4 "
+                           "when used with \"dce\"; %d is not valid", bmd->nbytes);
+                goto error;
+                break;
+        }
+    }
+
+    switch (bmd->oper) {
+        case DETECT_BYTEMATH_OPERATOR_LSHIFT:
+        case DETECT_BYTEMATH_OPERATOR_RSHIFT:
+            /* nbytes has already been validated to be in the range [1, 10] */
+            if (bmd->nbytes > 4) {
+                SCLogError(SC_ERR_INVALID_SIGNATURE, "nbytes must be 1 through 4 (inclusive) "
+                           "when used with \"<<\" or \">>\"; %d is not valid", bmd->nbytes);
+                goto error;
+            }
+            break;
+
+        default:
+            break;
+    }
+
+    /* Set defaults for endian and base if needed */
+    if (!(bmd->flags & DETECT_BYTEMATH_FLAG_ENDIAN)) {
+        bmd->endian = DETECT_BYTEMATH_ENDIAN_DEFAULT;
+    }
+    if (!(bmd->flags & DETECT_BYTEMATH_FLAG_STRING)) {
+        bmd->base = DETECT_BYTEMATH_BASE_DEFAULT;
+    }
+
+    return bmd;
+
+ error:
+    if (bmd != NULL)
+        DetectByteMathFree(de_ctx, bmd);
+    return NULL;
+}
+
+/**
+ * \brief The setup function for the byte_math keyword for a signature.
+ *
+ * \param de_ctx Pointer to the detection engine context.
+ * \param s      Pointer to signature for the current Signature being parsed
+ *               from the rules.
+ * \param arg    Pointer to the string holding the keyword value.
+ *
+ * \retval  0 On success.
+ * \retval -1 On failure.
+ */
+static int DetectByteMathSetup(DetectEngineCtx *de_ctx, Signature *s, const char *arg)
+{
+    SigMatch *sm = NULL;
+    SigMatch *prev_pm = NULL;
+    DetectByteMathData *data;
+    char *rvalue = NULL;
+    int ret = -1;
+
+    data = DetectByteMathParse(de_ctx, arg, &rvalue);
+    if (data == NULL)
+        goto error;
+
+    int sm_list;
+    if (s->init_data->list != DETECT_SM_LIST_NOTSET) {
+        if (DetectBufferGetActiveList(de_ctx, s) == -1)
+            goto error;
+
+        sm_list = s->init_data->list;
+
+        if (data->flags & DETECT_BYTEMATH_FLAG_RELATIVE) {
+            prev_pm = DetectGetLastSMFromLists(s, DETECT_CONTENT, DETECT_PCRE, -1);
+            if (!prev_pm) {
+                SCLogError(SC_ERR_INVALID_SIGNATURE, "relative specified without "
+                           "previous pattern match");
+                goto error;
+            }
+        }
+    } else if (data->endian == DETECT_BYTEMATH_ENDIAN_DCE) {
+        if (data->flags & DETECT_BYTEMATH_FLAG_RELATIVE) {
+            prev_pm = DetectGetLastSMFromLists(s, DETECT_CONTENT, DETECT_PCRE,
+                                               DETECT_BYTETEST, DETECT_BYTEJUMP,
+                                               DETECT_BYTE_EXTRACT,
+                                               DETECT_BYTEMATH,
+                                               DETECT_ISDATAAT, -1);
+            if (prev_pm == NULL) {
+                sm_list = DETECT_SM_LIST_PMATCH;
+            } else {
+                sm_list = SigMatchListSMBelongsTo(s, prev_pm);
+                if (sm_list < 0)
+                    goto error;
+            }
+        } else {
+            sm_list = DETECT_SM_LIST_PMATCH;
+        }
+
+        if (DetectSignatureSetAppProto(s, ALPROTO_DCERPC) < 0)
+            goto error;
+        s->flags |= SIG_FLAG_APPLAYER;
+
+    } else if (data->flags & DETECT_BYTEMATH_FLAG_RELATIVE) {
+        prev_pm = DetectGetLastSMFromLists(s, DETECT_CONTENT, DETECT_PCRE,
+                                           DETECT_BYTETEST, DETECT_BYTEJUMP,
+                                           DETECT_BYTE_EXTRACT, DETECT_BYTEMATH,
+                                           DETECT_ISDATAAT, -1);
+        if (prev_pm == NULL) {
+            sm_list = DETECT_SM_LIST_PMATCH;
+        } else {
+            sm_list = SigMatchListSMBelongsTo(s, prev_pm);
+            if (sm_list < 0)
+                goto error;
+            if (sm_list != DETECT_SM_LIST_PMATCH)
+                s->flags |= SIG_FLAG_APPLAYER;
+        }
+
+    } else {
+        sm_list = DETECT_SM_LIST_PMATCH;
+    }
+
+    if (data->endian == DETECT_BYTEMATH_ENDIAN_DCE) {
+        if (DetectSignatureSetAppProto(s, ALPROTO_DCERPC) != 0)
+            goto error;
+
+        if ((data->flags & DETECT_BYTEMATH_FLAG_STRING) ||
+            (data->base == DETECT_BYTEMATH_BASE_DEC) ||
+            (data->base == DETECT_BYTEMATH_BASE_HEX) ||
+            (data->base == DETECT_BYTEMATH_BASE_OCT) ) {
+            SCLogError(SC_ERR_CONFLICTING_RULE_KEYWORDS, "Invalid option. "
+                       "A bytemath keyword with dce holds other invalid modifiers.");
+            goto error;
+        }
+    }
+
+    if (rvalue != NULL) {
+        DetectByteIndexType index;
+        if (!DetectByteRetrieveSMVar(rvalue, s, &index)) {
+            SCLogError(SC_ERR_INVALID_SIGNATURE, "unknown byte_ keyword var "
+                       "seen in byte_math - %s\n", rvalue);
+            goto error;
+        }
+        data->rvalue = index;
+        data->flags |= DETECT_BYTEMATH_RVALUE_VAR;
+        SCFree(rvalue);
+        rvalue = NULL;
+    }
+
+    SigMatch *prev_bmd_sm = DetectGetLastSMByListId(s, sm_list,
+            DETECT_BYTEMATH, -1);
+    if (prev_bmd_sm == NULL) {
+        data->local_id = 0;
+    } else {
+        data->local_id = ((DetectByteMathData *)prev_bmd_sm->ctx)->local_id + 1;
+    }
+    if (data->local_id > de_ctx->byte_extract_max_local_id) {
+        de_ctx->byte_extract_max_local_id = data->local_id;
+    }
+
+    sm = SigMatchAlloc();
+    if (sm == NULL)
+        goto error;
+    sm->type = DETECT_BYTEMATH;
+    sm->ctx = (void *)data;
+    SigMatchAppendSMToList(s, sm, sm_list);
+
+    if (!(data->flags & DETECT_BYTEMATH_FLAG_RELATIVE))
+        goto okay;
+
+    if (prev_pm == NULL)
+        goto okay;
+
+    if (prev_pm->type == DETECT_CONTENT) {
+        DetectContentData *cd = (DetectContentData *)prev_pm->ctx;
+        cd->flags |= DETECT_CONTENT_RELATIVE_NEXT;
+    } else if (prev_pm->type == DETECT_PCRE) {
+        DetectPcreData *pd = (DetectPcreData *)prev_pm->ctx;
+        pd->flags |= DETECT_PCRE_RELATIVE_NEXT;
+    }
+
+ okay:
+    return 0;
+
+ error:
+    if (rvalue)
+        SCFree(rvalue);
+    DetectByteMathFree(de_ctx, data);
+    return ret;
+}
+
+/**
+ * \brief Used to free instances of DetectByteMathractData.
+ *
+ * \param ptr Instance of DetectByteMathData to be freed.
+ */
+static void DetectByteMathFree(DetectEngineCtx *de_ctx, void *ptr)
+{
+    if (ptr != NULL) {
+        DetectByteMathData *bmd = ptr;
+        if (bmd->result != NULL)
+            SCFree((void *)bmd->result);
+        SCFree(bmd);
+    }
+
+    return;
+}
+
+/**
+ * \brief Lookup the SigMatch for a named byte_math variable.
+ *
+ * \param arg The name of the byte_math variable to lookup.
+ * \param s Pointer the signature to look in.
+ *
+ * \retval A pointer to the SigMatch if found, otherwise NULL.
+ */
+SigMatch *DetectByteMathRetrieveSMVar(const char *arg, const Signature *s)
+{
+    const int nlists = s->init_data->smlists_array_size;
+    for (int list = 0; list < nlists; list++) {
+        SigMatch *sm = s->init_data->smlists[list];
+        while (sm != NULL) {
+            if (sm->type == DETECT_BYTEMATH) {
+                const DetectByteMathData *bmd = (const DetectByteMathData *)sm->ctx;
+                if (strcmp(bmd->result, arg) == 0) {
+                    SCLogDebug("Retrieved SM for \"%s\"", arg);
+                    return sm;
+                }
+            }
+            sm = sm->next;
+        }
+    }
+
+    return NULL;
+}
+
+/*************************************Unittests********************************/
+#ifdef UNITTESTS
+
+#include "app-layer-parser.h"
+
+static int DetectByteMathParseTest01(void)
+{
+
+    DetectByteMathData *bmd = DetectByteMathParse(NULL, "bytes 4, offset 2, oper +,"
+                                                  "rvalue 10, result bar", NULL);
+    FAIL_IF(bmd == NULL);
+
+    FAIL_IF_NOT(bmd->nbytes == 4);
+    FAIL_IF_NOT(bmd->offset == 2);
+    FAIL_IF_NOT(bmd->oper == DETECT_BYTEMATH_OPERATOR_PLUS);
+    FAIL_IF_NOT(bmd->rvalue == 10);
+    FAIL_IF_NOT(strcmp(bmd->result, "bar") == 0);
+    FAIL_IF_NOT(bmd->endian == DETECT_BYTEMATH_ENDIAN_DEFAULT);
+    FAIL_IF_NOT(bmd->base == DETECT_BYTEMATH_BASE_DEFAULT);
+
+    DetectByteMathFree(NULL, bmd);
+
+    PASS;
+}
+
+static int DetectByteMathParseTest02(void)
+{
+    /* bytes value invalid */
+    DetectByteMathData *bmd = DetectByteMathParse(NULL, "bytes 257, offset 2, oper +, "
+                                                  "rvalue 39, result bar", NULL);
+
+    FAIL_IF_NOT(bmd == NULL);
+
+    PASS;
+}
+
+static int DetectByteMathParseTest03(void)
+{
+    /* bytes value invalid */
+    DetectByteMathData *bmd = DetectByteMathParse(NULL, "bytes 11, offset 2, oper +, "
+                                                  "rvalue 39, result bar", NULL);
+    FAIL_IF_NOT(bmd == NULL);
+
+    PASS;
+}
+
+static int DetectByteMathParseTest04(void)
+{
+    /* offset value invalid */
+    DetectByteMathData *bmd = DetectByteMathParse(NULL, "bytes 4, offset 70000, oper +,"
+                                                  " rvalue 39, result bar", NULL);
+
+    FAIL_IF_NOT(bmd == NULL);
+
+    PASS;
+}
+
+static int DetectByteMathParseTest05(void)
+{
+    /* oper value invalid */
+    DetectByteMathData *bmd = DetectByteMathParse(NULL, "bytes 11, offset 16, oper &,"
+                                                  "rvalue 39, result bar", NULL);
+    FAIL_IF_NOT(bmd == NULL);
+
+    PASS;
+}
+
+static int DetectByteMathParseTest06(void)
+{
+    uint8_t flags = DETECT_BYTEMATH_FLAG_RELATIVE;
+    char *rvalue = NULL;
+
+    DetectByteMathData *bmd = DetectByteMathParse(NULL, "bytes 4, offset 0, oper +,"
+                                                  "rvalue 248, result var, relative",
+                                                  &rvalue);
+
+    FAIL_IF(bmd == NULL);
+    FAIL_IF_NOT(bmd->nbytes == 4);
+    FAIL_IF_NOT(bmd->offset == 0);
+    FAIL_IF_NOT(bmd->oper == DETECT_BYTEMATH_OPERATOR_PLUS);
+    FAIL_IF_NOT(bmd->rvalue == 248);
+    FAIL_IF_NOT(strcmp(bmd->result, "var") == 0);
+    FAIL_IF_NOT(bmd->flags == flags);
+    FAIL_IF_NOT(bmd->endian == DETECT_BYTEMATH_ENDIAN_DEFAULT);
+    FAIL_IF_NOT(bmd->base == DETECT_BYTEMATH_BASE_DEFAULT);
+
+    DetectByteMathFree(NULL, bmd);
+
+    PASS;
+}
+
+static int DetectByteMathParseTest07(void)
+{
+    char *rvalue = NULL;
+
+    DetectByteMathData *bmd = DetectByteMathParse(NULL, "bytes 4, offset 2, oper +,"
+                                                  "rvalue foo, result bar",
+                                                  &rvalue);
+    FAIL_IF_NOT(rvalue);
+    FAIL_IF_NOT(bmd->nbytes == 4);
+    FAIL_IF_NOT(bmd->offset == 2);
+    FAIL_IF_NOT(bmd->oper == DETECT_BYTEMATH_OPERATOR_PLUS);
+    FAIL_IF_NOT(strcmp(rvalue, "foo") == 0);
+    FAIL_IF_NOT(strcmp(bmd->result, "bar") == 0);
+    FAIL_IF_NOT(bmd->endian == DETECT_BYTEMATH_ENDIAN_DEFAULT);
+    FAIL_IF_NOT(bmd->base == DETECT_BYTEMATH_BASE_DEFAULT);
+
+    DetectByteMathFree(NULL, bmd);
+
+    SCFree(rvalue);
+
+    PASS;
+}
+
+static int DetectByteMathParseTest08(void)
+{
+    /* ensure Parse checks the pointer value when rvalue is a var */
+    DetectByteMathData *bmd = DetectByteMathParse(NULL, "bytes 4, offset 2, oper +,"
+                                                  "rvalue foo, result bar", NULL);
+    FAIL_IF_NOT(bmd == NULL);
+
+    PASS;
+}
+
+static int DetectByteMathParseTest09(void)
+{
+    uint8_t flags = DETECT_BYTEMATH_FLAG_RELATIVE;
+
+    DetectByteMathData *bmd = DetectByteMathParse(NULL, "bytes 4, offset 2, oper +,"
+                                                  "rvalue 39, result bar, relative",
+                                                  NULL);
+    FAIL_IF(bmd == NULL);
+
+    FAIL_IF_NOT(bmd->nbytes == 4);
+    FAIL_IF_NOT(bmd->offset == 2);
+    FAIL_IF_NOT(bmd->oper == DETECT_BYTEMATH_OPERATOR_PLUS);
+    FAIL_IF_NOT(bmd->rvalue == 39);
+    FAIL_IF_NOT(strcmp(bmd->result, "bar") == 0);
+    FAIL_IF_NOT(bmd->flags == flags);
+    FAIL_IF_NOT(bmd->endian == DETECT_BYTEMATH_ENDIAN_DEFAULT);
+    FAIL_IF_NOT(bmd->base == DETECT_BYTEMATH_BASE_DEFAULT);
+
+    DetectByteMathFree(NULL, bmd);
+
+    PASS;
+}
+
+static int DetectByteMathParseTest10(void)
+{
+    uint8_t flags = DETECT_BYTEMATH_FLAG_ENDIAN;
+
+    DetectByteMathData *bmd = DetectByteMathParse(NULL, "bytes 4, offset 2, oper +,"
+                                                  "rvalue 39, result bar, endian"
+                                                  " big", NULL);
+
+    FAIL_IF(bmd == NULL);
+    FAIL_IF_NOT(bmd->nbytes == 4);
+    FAIL_IF_NOT(bmd->offset == 2);
+    FAIL_IF_NOT(bmd->oper == DETECT_BYTEMATH_OPERATOR_PLUS);
+    FAIL_IF_NOT(bmd->rvalue == 39);
+    FAIL_IF_NOT(strcmp(bmd->result, "bar") == 0);
+    FAIL_IF_NOT(bmd->flags == flags);
+    FAIL_IF_NOT(bmd->endian == DETECT_BYTEMATH_ENDIAN_BIG);
+    FAIL_IF_NOT(bmd->base == DETECT_BYTEMATH_BASE_DEFAULT);
+
+    DetectByteMathFree(NULL, bmd);
+
+    PASS;
+}
+
+static int DetectByteMathParseTest11(void)
+{
+    uint8_t flags = DETECT_BYTEMATH_FLAG_ENDIAN;
+
+    DetectByteMathData *bmd = DetectByteMathParse(NULL, "bytes 4, offset 2, oper +, "
+                                                  "rvalue 39, result bar, dce",
+                                                  NULL);
+
+    FAIL_IF(bmd == NULL);
+    FAIL_IF_NOT(bmd->nbytes == 4);
+    FAIL_IF_NOT(bmd->offset == 2);
+    FAIL_IF_NOT(bmd->oper == DETECT_BYTEMATH_OPERATOR_PLUS);
+    FAIL_IF_NOT(bmd->rvalue == 39);
+    FAIL_IF_NOT(strcmp(bmd->result, "bar") == 0);
+    FAIL_IF_NOT(bmd->flags == flags);
+    FAIL_IF_NOT(bmd->endian == DETECT_BYTEMATH_ENDIAN_DCE);
+    FAIL_IF_NOT(bmd->base == DETECT_BYTEMATH_BASE_DEFAULT);
+
+    DetectByteMathFree(NULL, bmd);
+
+    PASS;
+}
+
+static int DetectByteMathParseTest12(void)
+{
+    uint8_t flags = DETECT_BYTEMATH_FLAG_RELATIVE | DETECT_BYTEMATH_FLAG_STRING;
+
+    DetectByteMathData *bmd = DetectByteMathParse(NULL, "bytes 4, offset 2, oper +,"
+                                                  "rvalue 39, result bar, "
+                                                  "relative, string dec", NULL);
+
+    FAIL_IF(bmd == NULL);
+    FAIL_IF_NOT(bmd->nbytes == 4);
+    FAIL_IF_NOT(bmd->offset == 2);
+    FAIL_IF_NOT(bmd->oper == DETECT_BYTEMATH_OPERATOR_PLUS);
+    FAIL_IF_NOT(bmd->rvalue == 39);
+    FAIL_IF_NOT(strcmp(bmd->result, "bar") == 0);
+    FAIL_IF_NOT(bmd->flags == flags);
+    FAIL_IF_NOT(bmd->endian == DETECT_BYTEMATH_ENDIAN_BIG);
+    FAIL_IF_NOT(bmd->base == DETECT_BYTEMATH_BASE_DEC);
+
+    DetectByteMathFree(NULL, bmd);
+
+    PASS;
+}
+
+static int DetectByteMathParseTest13(void)
+{
+    uint8_t flags = DETECT_BYTEMATH_FLAG_STRING |
+                    DETECT_BYTEMATH_FLAG_RELATIVE |
+                    DETECT_BYTEMATH_FLAG_BITMASK;
+
+    DetectByteMathData *bmd = DetectByteMathParse(NULL, "bytes 4, offset 2, oper +, "
+                                                  "rvalue 39, result bar, "
+                                                  "relative,  string dec, bitmask "
+                                                  "0x8f40", NULL);
+
+    FAIL_IF(bmd == NULL);
+    FAIL_IF_NOT(bmd->nbytes == 4);
+    FAIL_IF_NOT(bmd->offset == 2);
+    FAIL_IF_NOT(bmd->oper == DETECT_BYTEMATH_OPERATOR_PLUS);
+    FAIL_IF_NOT(bmd->rvalue == 39);
+    FAIL_IF_NOT(strcmp(bmd->result, "bar") == 0);
+    FAIL_IF_NOT(bmd->bitmask_val == 0x8f40);
+    FAIL_IF_NOT(bmd->bitmask_shift_count == 6);
+    FAIL_IF_NOT(bmd->flags == flags);
+    FAIL_IF_NOT(bmd->endian == DETECT_BYTEMATH_ENDIAN_BIG);
+    FAIL_IF_NOT(bmd->base == DETECT_BYTEMATH_BASE_DEC);
+
+    DetectByteMathFree(NULL, bmd);
+
+    PASS;
+}
+
+
+static int DetectByteMathParseTest14(void)
+{
+    /* incomplete */
+    DetectByteMathData *bmd = DetectByteMathParse(NULL, "bytes 4, offset 2, oper +,"
+                                                  "rvalue foo", NULL);
+
+    FAIL_IF_NOT(bmd == NULL);
+
+    PASS;
+}
+
+static int DetectByteMathParseTest15(void)
+{
+
+    /* incomplete */
+    DetectByteMathData *bmd = DetectByteMathParse(NULL, "bytes 4, offset 2, oper +, "
+                                                  "result bar", NULL);
+
+    FAIL_IF_NOT(bmd == NULL);
+
+    PASS;
+}
+
+static int DetectByteMathPacket01(void)
+{
+    uint8_t buf[] = { 0x38, 0x35, 0x6d, 0x00, 0x00, 0x01,
+                      0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                      0x00, 0x00, 0x6d, 0x00, 0x01, 0x00 };
+    Flow f;
+    void *dns_state = NULL;
+    Packet *p = NULL;
+    Signature *s = NULL;
+    ThreadVars tv;
+    DetectEngineThreadCtx *det_ctx = NULL;
+    AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc();
+
+    memset(&tv, 0, sizeof(ThreadVars));
+    memset(&f, 0, sizeof(Flow));
+
+    p = UTHBuildPacketReal(buf, sizeof(buf), IPPROTO_UDP,
+                           "192.168.1.5", "192.168.1.1",
+                           41424, 53);
+    FAIL_IF_NULL(p);
+
+    FLOW_INITIALIZE(&f);
+    f.flags |= FLOW_IPV4;
+    f.proto = IPPROTO_UDP;
+    f.protomap = FlowGetProtoMapping(f.proto);
+
+    p->flow = &f;
+    p->flags |= PKT_HAS_FLOW;
+    p->flowflags |= FLOW_PKT_TOSERVER;
+    f.alproto = ALPROTO_DNS;
+
+    DetectEngineCtx *de_ctx = DetectEngineCtxInit();
+    FAIL_IF_NULL(de_ctx);
+
+    de_ctx->mpm_matcher = mpm_default_matcher;
+    de_ctx->flags |= DE_QUIET;
+
+    /*
+     * byte_extract: Extract 1 byte from offset 0 --> 0x0038
+     * byte_math: Extract 1 byte from offset 2 (0x35)
+     *            Add 0x35 + 0x38 = 109 (0x6d)
+     * byte_test: Compare 2 bytes at offset 13 bytes from last
+     *            match and compare with 0x6d
+     */
+    s = DetectEngineAppendSig(de_ctx, "alert udp any any -> any any "
+                              "(byte_extract: 1, 0, extracted_val, relative;"
+                              "byte_math: bytes 1, offset 1, oper +, rvalue extracted_val, result var;"
+                              "byte_test: 2, =, var, 13;"
+                              "msg:\"Byte extract and byte math with byte test verification\";"
+                              "sid:1;)");
+    FAIL_IF_NULL(s);
+
+    /* this rule should not alert */
+    s = DetectEngineAppendSig(de_ctx, "alert udp any any -> any any "
+                              "(byte_extract: 1, 0, extracted_val, relative;"
+                              "byte_math: bytes 1, offset 1, oper +, rvalue extracted_val, result var;"
+                              "byte_test: 2, !=, var, 13;"
+                              "msg:\"Byte extract and byte math with byte test verification\";"
+                              "sid:2;)");
+    FAIL_IF_NULL(s);
+
+    /*
+     * this rule should alert:
+     * compares offset 15 with var ... 1 (offset 15) < 0x6d (var)
+     */
+    s = DetectEngineAppendSig(de_ctx, "alert udp any any -> any any "
+                              "(byte_extract: 1, 0, extracted_val, relative;"
+                              "byte_math: bytes 1, offset 1, oper +, rvalue extracted_val, result var;"
+                              "byte_test: 2, <, var, 15;"
+                              "msg:\"Byte extract and byte math with byte test verification\";"
+                              "sid:3;)");
+    FAIL_IF_NULL(s);
+
+    SigGroupBuild(de_ctx);
+    DetectEngineThreadCtxInit(&tv, (void *)de_ctx, (void *)&det_ctx);
+    FAIL_IF_NULL(det_ctx);
+
+    int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_DNS,
+                                STREAM_TOSERVER, buf, sizeof(buf));
+    FAIL_IF_NOT(r == 0);
+
+    dns_state = f.alstate;
+    FAIL_IF_NULL(dns_state);
+
+    /* do detect */
+    SigMatchSignatures(&tv, de_ctx, det_ctx, p);
+
+    /* ensure sids 1 & 3 alerted */
+    FAIL_IF_NOT(PacketAlertCheck(p, 1));
+    FAIL_IF(PacketAlertCheck(p, 2));
+    FAIL_IF_NOT(PacketAlertCheck(p, 3));
+
+    AppLayerParserThreadCtxFree(alp_tctx);
+    DetectEngineThreadCtxDeinit(&tv, det_ctx);
+    DetectEngineCtxFree(de_ctx);
+
+    FLOW_DESTROY(&f);
+    UTHFreePacket(p);
+
+    PASS;
+}
+
+static int DetectByteMathContext01(void)
+{
+    DetectEngineCtx *de_ctx = NULL;
+    Signature *s = NULL;
+    SigMatch *sm = NULL;
+    DetectContentData *cd = NULL;
+    DetectByteMathData *bmd = NULL;
+
+    de_ctx = DetectEngineCtxInit();
+    FAIL_IF(de_ctx == NULL);
+
+    de_ctx->flags |= DE_QUIET;
+    s = de_ctx->sig_list = SigInit(de_ctx, "alert tcp any any -> any any "
+                                   "(msg:\"Testing bytemath_body\"; "
+                                   "content:\"|00 04 93 F3|\"; "
+                                   "content:\"|00 00 00 07|\"; distance:4; within:4;"
+                                   "byte_math:bytes 4, offset 0, oper +, rvalue"
+                                   "248, result var, relative; sid:1;)");
+
+    FAIL_IF(de_ctx->sig_list == NULL);
+
+    FAIL_IF(s->sm_lists_tail[DETECT_SM_LIST_PMATCH] == NULL);
+
+    sm = s->sm_lists[DETECT_SM_LIST_PMATCH];
+    FAIL_IF(sm->type != DETECT_CONTENT);
+    cd = (DetectContentData *)sm->ctx;
+    FAIL_IF(cd->flags & DETECT_CONTENT_WITHIN);
+    FAIL_IF(cd->flags & DETECT_CONTENT_DISTANCE);
+    FAIL_IF(cd->content_len != 4);
+
+    sm = sm->next;
+    FAIL_IF(sm->type != DETECT_CONTENT);
+    sm = sm->next;
+    FAIL_IF(sm->type != DETECT_BYTEMATH);
+
+    FAIL_IF(sm->ctx == NULL);
+
+    bmd = (DetectByteMathData *)sm->ctx;
+    FAIL_IF_NOT(bmd->nbytes == 4);
+    FAIL_IF_NOT(bmd->offset == 0);
+    FAIL_IF_NOT(bmd->rvalue == 248);
+    FAIL_IF_NOT(strcmp(bmd->result, "var") == 0);
+    FAIL_IF_NOT(bmd->flags == DETECT_BYTEMATH_FLAG_RELATIVE);
+    FAIL_IF_NOT(bmd->endian == DETECT_BYTEMATH_ENDIAN_BIG);
+    FAIL_IF_NOT(bmd->oper == DETECT_BYTEMATH_OPERATOR_PLUS);
+    FAIL_IF_NOT(bmd->base == DETECT_BYTEMATH_BASE_DEC);
+
+    DetectEngineCtxFree(de_ctx);
+
+    PASS;
+}
+
+#endif /* UNITTESTS */
+
+static void DetectByteMathRegisterTests(void)
+{
+#ifdef UNITTESTS
+    UtRegisterTest("DetectByteMathParseTest01", DetectByteMathParseTest01);
+    UtRegisterTest("DetectByteMathParseTest02", DetectByteMathParseTest02);
+    UtRegisterTest("DetectByteMathParseTest03", DetectByteMathParseTest03);
+    UtRegisterTest("DetectByteMathParseTest04", DetectByteMathParseTest04);
+    UtRegisterTest("DetectByteMathParseTest05", DetectByteMathParseTest05);
+    UtRegisterTest("DetectByteMathParseTest06", DetectByteMathParseTest06);
+    UtRegisterTest("DetectByteMathParseTest07", DetectByteMathParseTest07);
+    UtRegisterTest("DetectByteMathParseTest08", DetectByteMathParseTest08);
+    UtRegisterTest("DetectByteMathParseTest09", DetectByteMathParseTest09);
+    UtRegisterTest("DetectByteMathParseTest10", DetectByteMathParseTest10);
+    UtRegisterTest("DetectByteMathParseTest11", DetectByteMathParseTest11);
+    UtRegisterTest("DetectByteMathParseTest12", DetectByteMathParseTest12);
+    UtRegisterTest("DetectByteMathParseTest13", DetectByteMathParseTest13);
+    UtRegisterTest("DetectByteMathParseTest14", DetectByteMathParseTest14);
+    UtRegisterTest("DetectByteMathParseTest15", DetectByteMathParseTest15);
+    UtRegisterTest("DetectByteMathPacket01",    DetectByteMathPacket01);
+    UtRegisterTest("DetectByteMathContext01", DetectByteMathContext01);
+#endif /* UNITTESTS */
+
+    return;
+}
diff --git a/src/detect-bytemath.h b/src/detect-bytemath.h
new file mode 100644 (file)
index 0000000..0f9de69
--- /dev/null
@@ -0,0 +1,81 @@
+/* Copyright (C) 2020 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 <jeff@lucovsky.org>
+ */
+
+#ifndef __DETECT_BYTEMATH_H__
+#define __DETECT_BYTEMATH_H__
+
+/* flags */
+#define DETECT_BYTEMATH_FLAG_RELATIVE   0x01
+#define DETECT_BYTEMATH_FLAG_STRING     0x02
+#define DETECT_BYTEMATH_FLAG_BITMASK    0x04
+#define DETECT_BYTEMATH_FLAG_ENDIAN     0x08
+#define DETECT_BYTEMATH_RVALUE_VAR      0x10
+
+/* endian value to be used.  Would be stored in DetectByteMathData->endian */
+#define DETECT_BYTEMATH_ENDIAN_NONE    0
+#define DETECT_BYTEMATH_ENDIAN_BIG     1
+#define DETECT_BYTEMATH_ENDIAN_LITTLE  2
+#define DETECT_BYTEMATH_ENDIAN_DCE     3
+
+#define DETECT_BYTEMATH_OPERATOR_NONE     1
+#define DETECT_BYTEMATH_OPERATOR_PLUS     2
+#define DETECT_BYTEMATH_OPERATOR_MINUS    3
+#define DETECT_BYTEMATH_OPERATOR_DIVIDE   4
+#define DETECT_BYTEMATH_OPERATOR_MULTIPLY 5
+#define DETECT_BYTEMATH_OPERATOR_LSHIFT   6
+#define DETECT_BYTEMATH_OPERATOR_RSHIFT   7
+
+/**
+ * \brief Holds data related to byte_math keyword.
+ */
+typedef struct DetectByteMathData_ {
+    /* local id used by other keywords in the sig to reference this */
+    uint8_t local_id;
+    uint8_t nbytes;
+    uint16_t offset;
+
+    uint32_t rvalue;
+
+    /* "result" variable, if present */
+    const char *result; /* consumed */
+
+    uint8_t flags;
+    uint8_t endian;
+    uint8_t base;
+    uint8_t oper;
+
+    uint32_t bitmask_val;
+
+    uint16_t bitmask_shift_count;
+    /* unique id used to reference this byte_math keyword */
+    uint16_t id;
+
+} DetectByteMathData;
+
+void DetectBytemathRegister(void);
+
+SigMatch *DetectByteMathRetrieveSMVar(const char *, const Signature *);
+int DetectByteMathDoMatch(DetectEngineThreadCtx *, const SigMatchData *, const Signature *,
+                             const uint8_t *, uint16_t, uint64_t, uint64_t *, uint8_t);
+
+#endif /* __DETECT_BYTEMATH_H__ */
index fe00c8ff5e724f08b9e2a1db9f8d06f4fa1c138d..0ca46809dc3f5038da7bb563a4d64848e0999bf7 100644 (file)
@@ -71,6 +71,7 @@ enum DetectKeywordId {
     DETECT_RAWBYTES,
     DETECT_BYTETEST,
     DETECT_BYTEJUMP,
+    DETECT_BYTEMATH,
     DETECT_SAMEIP,
     DETECT_GEOIP,
     DETECT_IPPROTO,