/* 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 */ /* * 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 *); #ifdef UNITTESTS static void DetectByteMathRegisterTests(void); #endif 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; #ifdef UNITTESTS sigmatch_table[DETECT_BYTEMATH].RegisterTests = DetectByteMathRegisterTests; #endif 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 %" PRIi32 "", 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 %" PRIi32 "", 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 (StringParseI32RangeCheck( &bmd->offset, 10, strlen(tmp_str), (const char *)tmp_str, -65535, 65535) < 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 - 12-13 * string - 14-15 * dce - 16 * bitmask - 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 DetectByteMathParseTest16(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 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 DetectByteMathPacket02(void) { uint8_t buf[] = { 0x38, 0x35, 0x6d, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 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 --> 0x38 * byte_math: Extract 1 byte from offset 1 (0x38) * Add 0x38 + 0x38 = 112 (0x70) * byte_test: Compare 2 bytes at offset 13 bytes from last * match and compare with 0x70 */ 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, relative;" "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, relative;" "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) < 0x70 (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, relative;" "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; } static void DetectByteMathRegisterTests(void) { 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("DetectByteMathParseTest16", DetectByteMathParseTest16); UtRegisterTest("DetectByteMathPacket01", DetectByteMathPacket01); UtRegisterTest("DetectByteMathPacket02", DetectByteMathPacket02); UtRegisterTest("DetectByteMathContext01", DetectByteMathContext01); } #endif /* UNITTESTS */