/* Simple content match part of the detection engine. * * Copyright (C) 2008 by Victor Julien */ /* This is a very important part of the detection engine, and certainly one * of the most complex parts. String searching is complex and expensive, * and thus worth optimizing. The way that is done here is by only running * the pattern matcher once for every packet. In this search, all search words, * the 'content' matches, are looked for. All results, of all the search words * are stored in a array of lists. The array is an array of MpmMatchBucket's, * that can be entered through the DetectContentData id field. There, it finds * the bucket containing a list of 0, 1, or more matches of that content match. * The list contains MpmMatch items, that contain an offset field. This field * is the possition of the last character in the match. * * 03/22/2008 -- VJ: * Recursive capture runs do something special to the depth and offset: the * settings are only considered for the initial match. For the next matches, * they are not. The reason is that this way we can still anchor the first * match to a specific part of the payload, while the rest can be handled * by content and pcre matches. * * 06/11/2009 -- PR: * Now Patterns that exceed the max_pattern_length allowed by the current mpm * are split into multiple chunk. The modifiers must be set in the first * chunk of a group of chunks, and after a modifier is set, the modifiers of the * next chunks must be recalculated (propagated). This way, each DETECT_CONTENT * installed should be completely independent, as if it were loaded in another * content option of the signature. * * TODO: add a 'recursive depth' to limit the depth to do the recursion on... * * XXX more later.... * */ #include "suricata-common.h" #include "decode.h" #include "detect.h" #include "detect-content.h" #include "detect-uricontent.h" #include "detect-engine-mpm.h" #include "detect-engine.h" #include "detect-parse.h" #include "util-mpm.h" #include "flow.h" #include "flow-var.h" #include "detect-flow.h" #include "util-unittest.h" #include "util-print.h" #include "util-debug.h" #include "threads.h" int DetectContentMatch (ThreadVars *, DetectEngineThreadCtx *, Packet *, Signature *, SigMatch *); int DetectContentSetup (DetectEngineCtx *, Signature *, SigMatch *, char *); void DetectContentRegisterTests(void); void DetectContentRegister (void) { sigmatch_table[DETECT_CONTENT].name = "content"; sigmatch_table[DETECT_CONTENT].Match = DetectContentMatch; sigmatch_table[DETECT_CONTENT].Setup = DetectContentSetup; sigmatch_table[DETECT_CONTENT].Free = DetectContentFree; sigmatch_table[DETECT_CONTENT].RegisterTests = DetectContentRegisterTests; sigmatch_table[DETECT_CONTENT].flags |= SIGMATCH_PAYLOAD; } /* pass on the content_max_id */ uint32_t DetectContentMaxId(DetectEngineCtx *de_ctx) { //SCLogDebug("DetectContentMaxId: %" PRIu32 "", de_ctx->content_max_id); return de_ctx->content_max_id; } #ifdef DEBUG static void DetectContentDebugPrint(DetectContentData *co) { char buf[2048] = ""; char tmp[4] = ""; uint16_t u = 0; for (u = 0; u < co->content_len; u++) { if (isprint((char)co->content[u])) { snprintf(tmp,sizeof(tmp),"%c", (char)co->content[u]); } else { snprintf(tmp,sizeof(tmp),"\\%02x", co->content[u]); } strncat(buf,tmp,sizeof(buf)); } SCLogDebug("content \"%s\"",buf); } static void DetectContentPrintMatches(DetectEngineThreadCtx *det_ctx, DetectContentData *co) { DetectContentDebugPrint(co); if (det_ctx->mtc.match[co->id].len == 0) SCLogDebug("pattern did not match"); else SCLogDebug("matched %" PRIu32 " time(s) at offsets: ", det_ctx->mtc.match[co->id].len); MpmMatch *tmpm = NULL; for (tmpm = det_ctx->mtc.match[co->id].top; tmpm != NULL; tmpm = tmpm->next) { SCLogDebug("pattern matched at offset %" PRIu32 " ", tmpm->offset); } } #endif static inline int TestOffsetDepth(MpmMatch *m, DetectContentData *co, uint16_t pktoff) { if (m->offset >= pktoff) { if (co->offset == 0 || (m->offset >= co->offset)) { if (co->depth == 0 || ((m->offset + co->content_len) <= co->depth)) { SCLogDebug("depth %" PRIu32 ", offset %" PRIu32 ", m->offset " "%" PRIu32 ", return 1", co->depth, co->offset, m->offset); /* If we reach this point, it means we have obtained a depth and * offset match, which indicates that we have a FAILURE if the * content is negated, and SUCCESS if the content is not negated */ if (co->negated == 1) return 0; else return 1; } else { /* We have success so far with offset, but a failure with * depth. We can return a match at the bottom of this function * for negated_content, provided offset is 0. If offset * isn't 0 for negated_content, we have a failure and we return * a no match here. If the content is not negated, we have a no * match, which we return at the end of this function. */ if (co->offset && co->negated == 1) return 0; } } else { /* If offset fails, and if the content is negated, we check if depth * succeeds. If it succeeds, we have a no match for negated content. * Else we have a success for negated content. If the content is * not negated, we go down till the end and return a no match. */ if (co->negated == 1 && (co->depth && (m->offset+co->content_len) <= co->depth)) { return 0; } } } SCLogDebug("depth %" PRIu32 ", offset %" PRIu32 ", m->offset %" PRIu32 ", " "return 0", co->depth, co->offset, m->offset); /* If we reach this point, we have a match for negated content and no match * otherwise */ if (co->negated == 1) return 1; else return 0; } /** * \brief test the within, distance, offset and depth of a match * * This function is called recursively (if nescessary) to be able * to determine whether or not a chain of content matches connected * with 'within' and 'distance' options fully matches. The reason it * was done like this is to make sure we can handle partial matches * that turn out to fail being followed by full matches later in the * packet. This adds some runtime complexity however. * * WITHIN * The within check, if enabled, works as follows. The check is done * against the current match "m". This is the pattern that we check * the next against. So we will figure out if the next pattern exists * within X bytes of "m". * * To do this, we take the next pattern (nsm) and loop through all * matches of it. We then for each of the matches "nm" below, see if * it is in the within limit. * * The within limit is checked as follows. It's checked against the * current match "m". "m->offset" indicates the start of that match. * So we need to consider m->offset + co->content_len. This will give * us the end of the match "m". The next match then needs to occur * before that point + the lenght of the pattern we're checking, * nco->content_len. * * \param t thread vars * \param det_ctx thread local data of the detection engine ctx * \param m match we are inspecting * \param nm current sigmatch to work with * \param nsm next sigmatch to work with * \param pktoff packet offset * * \retval 1 On success. * \retval 0 On failure because of non-negated content. * \retval -1 On failure because of negated content. */ int TestWithinDistanceOffsetDepth(ThreadVars *t, DetectEngineThreadCtx *det_ctx, MpmMatch *m, SigMatch *sm, SigMatch *nsm, uint16_t pktoff) { int neg_success_flag = 0; if (nsm == NULL) { SCLogDebug("No next sigmatch, all sigmatches matched."); return 1; } /** content match of current pattern */ DetectContentData *co = (DetectContentData *)sm->ctx; if (!(co->flags & DETECT_CONTENT_DISTANCE_NEXT) && !(co->flags & DETECT_CONTENT_WITHIN_NEXT)) { SCLogDebug("Next content does not need distance/within checking."); return 1; } /** content match of next pattern */ DetectContentData *nco = (DetectContentData *)nsm->ctx; #ifdef DEBUG if (SCLogDebugEnabled()) { SCLogDebug("printing matches"); DetectContentPrintMatches(det_ctx, nco); } #endif /** list of matches of the next pattern */ MpmMatch *nm = det_ctx->mtc.match[nco->id].top; /* if we have no matches and the content is negated, we can return a success */ if (nm == NULL) { SCLogDebug("no nm to inspect"); if (nco->negated == 1) return 1; else return 0; } /* recursively check if we have a next pattern that matches */ for ( ; nm != NULL; nm = nm->next) { SCLogDebug("nm->offset %" PRIu32 ", m->offset %" PRIu32 ", pktoff " "%" PRIu32 "", nm->offset, m->offset, pktoff); SCLogDebug("nm->offset + nco->content_len = %"PRIu32" + %"PRIu32" = " "%"PRIu32"", nm->offset, nco->content_len, nm->offset + nco->content_len); SCLogDebug("within (0 if disabled) = %"PRIu32" (nco->within " "%"PRIu32" + co->content_len %"PRIu32")", (nco->flags & DETECT_CONTENT_WITHIN) ? (nco->within + co->content_len) : 0, nco->within, co->content_len); if (nm->offset >= pktoff) { if ((!(nco->flags & DETECT_CONTENT_WITHIN) || (nco->within > 0 && (nm->offset > m->offset) && (((nm->offset + nco->content_len) - m->offset) <= (nco->within + co->content_len))))) { SCLogDebug("MATCH: %" PRIu32 " <= WITHIN(%" PRIu32 ")", (nm->offset + nco->content_len) - m->offset, nco->within + co->content_len); if (!(nco->flags & DETECT_CONTENT_DISTANCE) || ((nm->offset >= (m->offset + co->content_len)) && ((nm->offset - (m->offset + co->content_len)) >= nco->distance))) { SCLogDebug("MATCH: %" PRIu32 " >= DISTANCE(%" PRIu32 ")", nm->offset - (m->offset + co->content_len), nco->distance); if (TestOffsetDepth(nm, nco, pktoff) == 0) { /* if the content is not negated, we have to return a 0 * under all circumstances, because we can't afford for * the offset and depth match to fail. If the content * is negated, we have 2 cases. First case is when we * have a distance or within and TestOffsetDepth() fails. * In this case we have to return a 0, irrespective of * whether we have a depth or offset, because we seem to * be having a match for within or distance. If we don't * have distance and within, and if the depth/offset * check failed, then we still have a failure because of * the obvious reason that in the absence of within and * distance, offset/depth check has to succeed. */ if (nco->negated == 1) return -1; else return 0; } else { /* if the content is negated and we had a within or a * distance, it indicates that we passed through the * within/distance, which is a failure */ if (nco->negated == 1 && ((nco->flags & DETECT_CONTENT_WITHIN) || (nco->flags & DETECT_CONTENT_DISTANCE))) { return -1; } else { return TestWithinDistanceOffsetDepth(t, det_ctx, nm, nsm, nsm->next, pktoff); } } } else { SCLogDebug("NO MATCH: %" PRIu32 " < DISTANCE(%" PRIu32 ")", nm->offset - (m->offset + co->content_len), nco->distance); /* looks like we got through within, but failed at distance. * An obvious failure in case of non-negated content, in which * case we move on to the next match. * In case of negated content, if there was a within * previously, it indicates that we got through the within * and we have a nomatch now with distance. If we didn't * have within, we made it through, but we check for depth * and offset now. If depth/offset check succeeds we have a * temporary success and we move on to the next match. If * it fails, we check if it failed because we didn't have * offset and depth, in which case, it is not a failure and * we on to the nextmatch. Otherwise it is a failure */ if (nco->negated == 1 && (nm->offset >= (m->offset + co->content_len))) { if (nco->flags & DETECT_CONTENT_WITHIN) return -1; if (TestOffsetDepth(nm, nco, pktoff) == 1) { neg_success_flag = 1; } else { if (nco->offset == 0 && nco->depth == 0) neg_success_flag = 1; else return -1; } } } } else { /* We have failed at within. If the content is not negated we * have an obvious failure and we move on to the next match. If * the content is negated, we check if distance exists. If it * does, and if the distance check succeeds, we have a failure * for negated content. If we don't have a failure or if distance * doesn't exist, we move on to test offset/depth check. The * offset/depth test is the same as in the previous else. */ if (nco->negated == 1 && nm->offset > m->offset) { if ((nco->flags & DETECT_CONTENT_DISTANCE) && ((nm->offset - (m->offset + co->content_len)) >= nco->distance)) { return -1; /* distance check meets non-negated requirements. Let * us move on and check depth/offset */ } if (TestOffsetDepth(nm, nco, pktoff) == 1) { /* offset, depth success for negated content. A temp * success for us. Let us set the flag indicating * this and move on to the next match. */ neg_success_flag = 1; } else { /* looks like offset/depth failed. If it failed because * both offset and depth weren't present, then it is not * precisely a failure, because the existance of negated * content is governed by the presence of within/distance. * So we set the flag and move on to the next content. * But if offset and depth do exist, then it indicates * that the negated content doesn't meet the requirements * of offset/depth and we have a failure. */ if (nco->offset == 0 && nco->depth == 0) neg_success_flag = 1; else return -1; } } } } else { SCLogDebug("pktoff %"PRIu16" > nm->offset %"PRIu32"", pktoff, nm->offset); } } if (neg_success_flag == 1) { return 1; } SCLogDebug("no match found, returning 0"); return 0; } int DoDetectContent(ThreadVars *t, DetectEngineThreadCtx *det_ctx, Packet *p, Signature *s, SigMatch *sm, DetectContentData *co) { int ret = 0; char match = 0; uint16_t pkt_off = det_ctx->pkt_off; MpmMatch *temp_m = NULL; /* Get the top match, we already know we have one. */ MpmMatch *m = det_ctx->mtc.match[co->id].top; SCLogDebug("det_ctx->mtc.match[co->id].len %"PRIu32"", det_ctx->mtc.match[co->id].len); /* reset de_checking_distancewithin */ if (!(co->flags & DETECT_CONTENT_WITHIN) && !(co->flags & DETECT_CONTENT_DISTANCE)) { det_ctx->de_checking_distancewithin = 0; /* only use pkt offset of previous matches * on relative matches. */ pkt_off = 0; } SCLogDebug("using pkt_off %"PRIu16"", pkt_off); /* if we have within or distance coming up next, check this match * for distance and/or within and check the rest of this match * chain as well. */ if ((co->flags & DETECT_CONTENT_WITHIN_NEXT || co->flags & DETECT_CONTENT_DISTANCE_NEXT) && det_ctx->de_checking_distancewithin == 0) { SCLogDebug("DETECT_CONTENT_WITHIN_NEXT is %s", co->flags & DETECT_CONTENT_WITHIN_NEXT ? "true":"false"); SCLogDebug("DETECT_CONTENT_DISTANCE_NEXT is %s", co->flags & DETECT_CONTENT_DISTANCE_NEXT ? "true":"false"); /* indicate to the detection engine the next sigmatch(es) * are part of this match chain */ det_ctx->de_checking_distancewithin = 1; for (; m != NULL; m = m->next) { /* first check our match for offset and depth */ if (TestOffsetDepth(m, co, pkt_off) == 1) { SCLogDebug("TestOffsetDepth returned 1, for co->id %"PRIu32"", co->id); SigMatch *real_sm_next = DetectContentFindNextApplicableSM(sm->next); ret = TestWithinDistanceOffsetDepth(t, det_ctx, m, sm, real_sm_next, pkt_off); if (ret == 1) { SCLogDebug("TestWithinDistanceOffsetDepth returned 1"); det_ctx->pkt_ptr = p->payload + m->offset; /* update both the local and ctx pkt_off */ pkt_off = det_ctx->pkt_off = m->offset; match = 1; break; } else if (ret == -1) { SCLogDebug("TestWithinDistanceOffsetDepth returned -1"); break; } } else { SCLogDebug("TestOffsetDepth returned 0, for co->id %"PRIu32"", co->id); } } /* Okay, this is complicated... on the first match of a match chain, * we do the whole match of that chain (a chain here means a number * of consecutive content matches that relate to each other with * 'within and/or 'distance options'). But we still get to the next * sigmatches. We have already inspected this sigmatch, even for * offset and depth. Since the fact that we get there means we have * had a match, we return match here too. */ } else if (co->flags & DETECT_CONTENT_WITHIN || co->flags & DETECT_CONTENT_DISTANCE) { SCLogDebug("distance/within checking already done, returning 1"); det_ctx->de_checking_distancewithin = 0; match = 1; /* Getting here means we are not in checking an within/distance chain. * This means we can just inspect this content match on it's own. So * Let's see if at least one of the matches within the offset and depth * settings. If so, return a match. */ } else { SCLogDebug("no distance/within checking"); /* if we have no matches, we return MATCH if the content is negated, or * NOMATCH if the content is not negated */ if (m == NULL) { if (co->negated == 1) match = 1; else match = 0; SCLogDebug("returning %d", match); return match; } /* when in recursive capture mode don't check depth and offset * after the first match */ if (s->flags & SIG_FLAG_RECURSIVE && det_ctx->pkt_cnt) { for (; m != NULL; m = m->next) { if (m->offset >= det_ctx->pkt_off) { /* update pkt ptrs, content doesn't use this, * but pcre does */ det_ctx->pkt_ptr = p->payload + m->offset; det_ctx->pkt_off = m->offset; match = 1; break; } } } else { temp_m = m; for (; m != NULL; m = m->next) { /* no offset as we inspect each match on it's own */ ret = TestOffsetDepth(m, co, 0); /* If ret is 0 and content is negated, we have a failure and we * break. If ret is 0 and content is not negated, we have a * failure for this match, so we will continue in this loop * testing other matches. If ret is 1, and the content is * negated we have a success and we will continue along the loop * to check that other matches also return 1 for TestOffsetDepth() * with the negated content. But if ret is 1, and the content * is not negated, we have a match, which is sufficient for us to * return with a break, with match = 1. */ if (ret == 0) { if (co->negated == 1) { match = 0; break; } } else { if (co->negated == 0) { /* update pkt ptrs, this content run doesn't * use this, but pcre does */ det_ctx->pkt_ptr = p->payload + m->offset; det_ctx->pkt_off = m->offset; match = 1; break; } } } /* If there were matches, with the content being negated, and all of * them passed TestOffsetDepth(), we have a match. This is the * reason why we continue in the else part if ret == 1, if the * content is negated */ if (temp_m != NULL && ret == 1 && co->negated == 1) { SCLogDebug("setting match to true"); match = 1; } } } /* If it has matched, check if it's set a "isdataat" option and process it */ /* If it has matched, check if it's set a "isdataat" option and process it */ if (match == 1 && (co->flags & DETECT_CONTENT_ISDATAAT_RELATIVE) && co->negated == 0) { /* if the rest of the payload (from the last match) is less than the "isdataat" there is no data where the rule expected so match=0 */ SCLogDebug("isdataat: payload_len: %u, used %u, rest %u, isdataat? %u", p->payload_len, (m->offset + co->content_len),p->payload_len - (m->offset + co->content_len), co->isdataat); if ( ((p->payload_len - (m->offset + co->content_len)) < co->isdataat) ) match = 0; if (match) { SCLogDebug("still matching after isdataat check"); } } SCLogDebug("returning %d", match); return match; } /* * returns 0: no match * 1: match * -1: error */ int DetectContentMatch (ThreadVars *t, DetectEngineThreadCtx *det_ctx, Packet *p, Signature *s, SigMatch *m) { if (p->payload_len == 0) return 0; DetectContentData *co = (DetectContentData *)m->ctx; #ifdef DEBUG if (SCLogDebugEnabled()) { SCLogDebug("printing matches"); DetectContentPrintMatches(det_ctx, co); } #endif return DoDetectContent(t, det_ctx, p, s, m, co); } DetectContentData *DetectContentParse (char *contentstr) { DetectContentData *cd = NULL; char *str = NULL; char *temp = NULL; uint16_t len; if ((temp = strdup(contentstr)) == NULL) goto error; if (strlen(temp) == 0) { if (temp) free(temp); return NULL; } cd = malloc(sizeof(DetectContentData)); if (cd == NULL) { printf("DetectContentParse malloc failed\n"); goto error; } memset(cd, 0, sizeof(DetectContentData)); if (temp[0] == '!') { free(temp); if ( (temp = strdup(contentstr + 1)) == NULL) goto error; cd->negated = 1; } if (temp[0] == '\"' && temp[strlen(temp)-1] == '\"') { if ( (str = strdup(temp + 1)) == NULL) goto error; str[strlen(temp) - 2] = '\0'; } else { if ( (str = strdup(temp)) == NULL) goto error; } free(temp); if (str[0] == '!') { if (cd->negated == 1) { SCLogDebug("Invalid negated content. \"!\" located twice at the " "start of the contet string: %s", contentstr); goto error; } else { temp = str; if ( (str = strdup(temp + 1)) == NULL) goto error; cd->negated = 1; free(temp); } } len = strlen(str); if (len == 0) goto error; //SCLogDebug("DetectContentParse: \"%s\", len %" PRIu32 "", str, len); char converted = 0; { uint16_t i, x; uint8_t bin = 0; uint8_t escape = 0; uint8_t binstr[3] = ""; uint8_t binpos = 0; for (i = 0, x = 0; i < len; i++) { // SCLogDebug("str[%02u]: %c", i, str[i]); if (str[i] == '|') { if (bin) { bin = 0; } else { bin = 1; } } else if(!escape && str[i] == '\\') { escape = 1; } else { if (bin) { if (isdigit(str[i]) || str[i] == 'A' || str[i] == 'a' || str[i] == 'B' || str[i] == 'b' || str[i] == 'C' || str[i] == 'c' || str[i] == 'D' || str[i] == 'd' || str[i] == 'E' || str[i] == 'e' || str[i] == 'F' || str[i] == 'f') { // SCLogDebug("part of binary: %c", str[i]); binstr[binpos] = (char)str[i]; binpos++; if (binpos == 2) { uint8_t c = strtol((char *)binstr, (char **) NULL, 16) & 0xFF; binpos = 0; str[x] = c; x++; converted = 1; } } else if (str[i] == ' ') { // SCLogDebug("space as part of binary string"); } } else if (escape) { if (str[i] == ':' || str[i] == ';' || str[i] == '\\' || str[i] == '\"') { str[x] = str[i]; x++; } else { //SCLogDebug("Can't escape %c", str[i]); goto error; } escape = 0; converted = 1; } else { str[x] = str[i]; x++; } } } #if 0//def DEBUG if (SCLogDebugEnabled()) { for (i = 0; i < x; i++) { if (isprint(str[i])) SCLogDebug("%c", str[i]); else SCLogDebug("\\x%02u", str[i]); } SCLogDebug(""); } #endif if (converted) { len = x; } } cd->content = malloc(len); if (cd->content == NULL) goto error; memcpy(cd->content, str, len); cd->content_len = len; cd->depth = 0; cd->offset = 0; cd->within = 0; cd->distance = 0; cd->flags = 0; free(str); return cd; error: free(str); if (cd != NULL) { if (cd->content != NULL) free(cd->content); free(cd); } return NULL; } /** * \brief Helper function to print a DetectContentData */ void DetectContentPrint(DetectContentData *cd) { int i = 0; if (cd == NULL) { SCLogDebug("DetectContentData \"cd\" is NULL"); return; } char *tmpstr=malloc(sizeof(char) * cd->content_len + 1); if (tmpstr != NULL) { for (i = 0; i < cd->content_len; i++) tmpstr[i] = cd->content[i]; tmpstr[i] = '\0'; SCLogDebug("Content: \"%s\"", tmpstr); free(tmpstr); } else { SCLogDebug("Content: "); for (i = 0; i < cd->content_len; i++) SCLogDebug("%c", cd->content[i]); } SCLogDebug("Content_id: %u ", cd->id); SCLogDebug("Content_len: %u ", cd->content_len); SCLogDebug("Depth: %u ", cd->depth); SCLogDebug("Offset: %u ", cd->offset); SCLogDebug("Within: %u ", cd->within); SCLogDebug("Distance: %u ", cd->distance); SCLogDebug("Isdataat: %u ", cd->isdataat); SCLogDebug("flags: %u ", cd->flags); /** If it's a chunk, print the data related */ if (cd->flags & DETECT_CONTENT_IS_CHUNK) { SCLogDebug("* Is_Chunk: is set"); SCLogDebug("chunk_group_id: %u ", cd->chunk_group_id); SCLogDebug("chunk_id: %u ", cd->chunk_id); } else { SCLogDebug("Is_Chunk: is not set"); } SCLogDebug("-----------"); } /** * \brief Function that return chunks of a original DetectContentData * that need to be split * \param origcd pointer to the real DetectContentData * \param remaining_content_length to the real DetectContentData * * \retval NULL if something goes wrong * \retval DetectContentData pointer to the new chunk */ DetectContentData *DetectContentSplitChunk(DetectContentData *origcd, uint8_t remaining_content_len, uint8_t index, int32_t mpl) { DetectContentData *cd = malloc(sizeof(DetectContentData)); if (cd == NULL) { SCLogError(SC_ERR_MEM_ALLOC, "DetectContentData malloc failed"); goto error; } memset(cd,0,sizeof(DetectContentData)); /* Get the length for this chunk */ if (remaining_content_len < mpl) cd->content_len = remaining_content_len; else cd->content_len = mpl; if (cd->content_len <= 0) goto error; cd->content = (uint8_t*) malloc(sizeof(uint8_t) * cd->content_len); if (cd->content == NULL) { SCLogError(SC_ERR_MEM_ALLOC, "string for content malloc failed"); goto error; } memcpy(cd->content, origcd->content + index * sizeof(uint8_t), cd->content_len); return cd; error: if (cd != NULL) { if (cd->content != NULL) free(cd->content); free(cd); } return NULL; } /** * \brief Search the next applicable DETECT_CONTENT SigMatch (includes the current sm) * * \param sm pointer to the current SigMatch of a parsing process * * \retval null if no applicable DetectContent was found * \retval pointer to the SigMatch next DETECT_CONTENT SigMatch */ SigMatch *DetectContentFindNextApplicableSM(SigMatch *sm) { if (sm == NULL) return NULL; while ( sm != NULL && sm->type != DETECT_CONTENT) sm = sm->next; return sm; } /** * \brief Helper function to determine if there are patterns before this one, * this is used before installing a new within or distance modifier * because if this return NULL, it will never match! * * \param sm pointer to the current SigMatch of a parsing process * * \retval null if no applicable SigMatch pattern was found * \retval pointer to the SigMatch that has the previous SigMatch * of type DetectContent, (and is the first chunk if * the pattern was splitted) * * \todo: should we add here DETECT_PCRE, DETECT_URI_CONTENT, etc? */ SigMatch *DetectContentHasPrevSMPattern(SigMatch *sm) { if (sm == NULL) return NULL; /* the current SM doesn't apply */ sm = sm->prev; while (sm != NULL && sm->type != DETECT_CONTENT) sm = sm->prev; return sm; } /** * \brief Search the first DETECT_CONTENT chunk of the last group in the * previous SigMatches or the first DETECT_CONTENT not chunked * \retval pointer to the SigMatch holding the DetectContent * \param sm pointer to the current SigMatch of a parsing process * \retval null if no applicable DetectContent was found * \retval pointer to the SigMatch that has the previous SigMatch * of type DetectContent, (and is the first chunk if * the pattern was splitted) */ SigMatch *DetectContentFindPrevApplicableSM(SigMatch *sm) { if (sm == NULL) return NULL; while (sm != NULL && sm->type != DETECT_CONTENT) sm = sm->prev; if (sm == NULL) return NULL; DetectContentData *cd = (DetectContentData*) sm->ctx; if (cd == NULL) return NULL; /** It's not a chunk, so its the only DetectContent for this pattern */ if (!(cd->flags & DETECT_CONTENT_IS_CHUNK)) return sm; /** Else search for the first chunk in this group of chunks */ uint8_t chunk_group_id = cd->chunk_group_id; while (sm != NULL && sm->type == DETECT_CONTENT) { cd = (DetectContentData*) sm->ctx; if ( !(cd->flags & DETECT_CONTENT_IS_CHUNK)) return NULL; /** Weird case, this means that the chunk are not consecutive * or not likned correctly */ if (cd->chunk_group_id != chunk_group_id) return NULL; /** If we get the first one, return the SimMatch */ if (cd->chunk_id == 0) return sm; sm = sm->prev; } /* We should not be here */ return NULL; } /** * \brief Count the number of chunks of a specified chunk group * \param sm pointer to a SigMatch that belong to this chunk group * \param chunk_group_id id of the group of chunks (to ensure) * \retval -1 if something fail * \retval count of the chunks of this group */ int DetectContentCountChunksInGroup(SigMatch *sm, uint8_t chunk_group_id) { int count = 0; if (sm == NULL || sm->type != DETECT_CONTENT) return -1; DetectContentData *cd = NULL; SigMatch *first_sm = DetectContentFindPrevApplicableSM(sm); for (; first_sm != NULL && first_sm->type == DETECT_CONTENT && first_sm->ctx != NULL && (cd = (DetectContentData*) first_sm->ctx) && (cd->flags & DETECT_CONTENT_IS_CHUNK) && cd->chunk_group_id == chunk_group_id ; first_sm = first_sm->next, count++); return count; } /** * \brief Get the remaining legth of a splitted pattern * (current content not included!) * \param first_sm pointer to a SigMatch that belong to this chunk group * \retval pointer to the SigMatch holding the DetectContent * \retval -1 if fail * \retval length if every thing was ok */ int DetectContentChunksGetRemainingLength(SigMatch *first_sm) { int length = 0; if (first_sm == NULL) return -1; if (first_sm->type != DETECT_CONTENT || first_sm->ctx == NULL) return -1; DetectContentData *cd = (DetectContentData*) first_sm->ctx; uint8_t chunk_group_id = cd->chunk_group_id; /** Skip the current content (not included) */ first_sm = first_sm->next; /** sum the content_len's */ for (; first_sm != NULL && first_sm->type == DETECT_CONTENT && first_sm->ctx != NULL && (cd = (DetectContentData*) first_sm->ctx) && (cd->flags & DETECT_CONTENT_IS_CHUNK) && cd->chunk_group_id == chunk_group_id ; first_sm = first_sm->next, length += cd->content_len); return length; } /** * \brief Get the previous legth of a splitted pattern (current content not included!) * \param first_sm pointer to a SigMatch that belong to this chunk group * \retval pointer to the SigMatch holding the DetectContent * \retval length if every thing was ok * \retval -1 if fail */ int DetectContentChunksGetPreviousLength(SigMatch *sm) { int length = 0; if (sm == NULL) return -1; if (sm->type != DETECT_CONTENT || sm->ctx == NULL) return -1; DetectContentData *cd = (DetectContentData*) sm->ctx; uint8_t chunk_group_id = cd->chunk_group_id; uint8_t chunk_id = cd->chunk_id; SigMatch *first_sm = DetectContentFindPrevApplicableSM(sm); for (; first_sm != NULL && first_sm->type == DETECT_CONTENT && first_sm->ctx != NULL && (cd = (DetectContentData*) first_sm->ctx) && (cd->flags & DETECT_CONTENT_IS_CHUNK) && cd->chunk_group_id == chunk_group_id && cd->chunk_id != chunk_id ; first_sm = first_sm->next, length += cd->content_len); if (cd != NULL && cd->chunk_id == chunk_id) return length; return 0; } /** * \brief Get the total legth of a splitted pattern * \param first_sm pointer to a SigMatch that belong to this chunk group * \retval pointer to the SigMatch holding the DetectContent * \retval length if every thing was ok * \retval -1 if fail */ int DetectContentChunksGetTotalLength(SigMatch *sm) { int length = 0; if (sm == NULL) return -1; if (sm->type != DETECT_CONTENT || sm->ctx == NULL) return -1; DetectContentData *cd = (DetectContentData*) sm->ctx; uint8_t chunk_group_id = cd->chunk_group_id; /** Go to the first SigMatch of this Chunk group */ SigMatch *first_sm = DetectContentFindPrevApplicableSM(sm); for (; first_sm != NULL && first_sm->type == DETECT_CONTENT && first_sm->ctx != NULL && (cd = (DetectContentData*) first_sm->ctx) && (cd->flags & DETECT_CONTENT_IS_CHUNK) && cd->chunk_group_id == chunk_group_id ; first_sm = first_sm->next, length += cd->content_len); return length; } /** * \brief Print list of DETECT_CONTENT SigMatch's allocated in a * SigMatch list, from the current sm to the end * \param sm pointer to the current SigMatch to start printing from */ void DetectContentPrintAll(SigMatch *sm) { #ifdef DEBUG int i = 0; if (SCLogDebugEnabled()) { if (sm == NULL) return; /** Go to the first SigMatch of this Chunk group */ SigMatch *first_sm = sm; /* Print all of them */ for (; first_sm != NULL; first_sm = first_sm->next) { if (first_sm->type == DETECT_CONTENT) { SCLogDebug("Printing SigMatch DETECT_CONTENT %d", ++i); DetectContentPrint(first_sm->ctx); } } } #endif /* DEBUG */ } /** * \brief Function to update modifiers of a chunk group after setting depth * \param first_sm pointer to the head of this group of chunks to update * \retval -1 if error * \retval 1 if all was ok */ int DetectContentPropagateDepth(SigMatch *first_sm) { int res = -1; DetectContentData *cd = first_sm->ctx; if (first_sm == NULL || first_sm->ctx == NULL) return -1; if (cd->chunk_flags & CHUNK_UPDATED_DEPTH) { SCLogDebug("Depth already set for this pattern!!"); return res; } res = DetectContentPropagateModifiers(first_sm); if (res == 1) { cd->chunk_flags |= CHUNK_UPDATED_DEPTH; } return res; } /** * \brief Function to update modifiers of a chunk group after setting isdataat * \param first_sm pointer to the head of this group of chunks to update * \retval -1 if error * \retval 1 if all was ok */ int DetectContentPropagateIsdataat(SigMatch *first_sm) { int res = -1; DetectContentData *cd = first_sm->ctx; if (first_sm == NULL || first_sm->ctx == NULL) return -1; if (cd->chunk_flags & CHUNK_UPDATED_ISDATAAT) { SCLogDebug("Depth already set for this pattern!!"); return res; } res = DetectContentPropagateModifiers(first_sm); if (res == 1) { cd->chunk_flags |= CHUNK_UPDATED_ISDATAAT; } return res; } /** * \brief Function to update modifiers of a chunk group after setting within * \param first_sm pointer to the head of this group of chunks to update * \retval -1 if error * \retval 1 if all was ok */ int DetectContentPropagateWithin(SigMatch *first_sm) { int res = -1; DetectContentData *cd = first_sm->ctx; if (first_sm == NULL || first_sm->ctx == NULL) return -1; if (cd->chunk_flags & CHUNK_UPDATED_WITHIN) { SCLogDebug("Depth already set for this pattern!!"); return res; } res = DetectContentPropagateModifiers(first_sm); if (res == 1) { cd->chunk_flags |= CHUNK_UPDATED_WITHIN; } return res; } /** * \brief Function to update modifiers of a chunk group after setting distance * \param first_sm pointer to the head of this group of chunks to update * \retval -1 if error * \retval 1 if all was ok */ int DetectContentPropagateDistance(SigMatch *first_sm) { int res = -1; DetectContentData *cd = first_sm->ctx; if (first_sm == NULL || first_sm->ctx == NULL) return -1; if (cd->chunk_flags & CHUNK_UPDATED_DISTANCE) { SCLogDebug("Depth already set for this pattern!!"); return res; } res = DetectContentPropagateModifiers(first_sm); if (res == 1) { cd->chunk_flags |= CHUNK_UPDATED_DISTANCE; } return res; } /** * \brief Function to update modifiers of a chunk group after setting Offset * \param first_sm pointer to the head of this group of chunks to update * \retval -1 if error * \retval 1 if all was ok */ int DetectContentPropagateOffset(SigMatch *first_sm) { int res = -1; DetectContentData *cd = first_sm->ctx; if (first_sm == NULL || first_sm->ctx == NULL) return -1; if (cd->chunk_flags & CHUNK_UPDATED_OFFSET) { SCLogDebug("Depth already set for this pattern!!"); return res; } res = DetectContentPropagateModifiers(first_sm); if (res == 1) { cd->chunk_flags |= CHUNK_UPDATED_OFFSET; } return res; } /** * \brief Function to update modifiers of a chunk group after setting a modifier * This function should not be called directly from outside detect-content.c ! * * \param first_sm pointer to the head of this group of chunks to update * \retval -1 if error * \retval 1 if all was ok */ int DetectContentPropagateModifiers(SigMatch *first_sm) { if (first_sm == NULL) return -1; /** Rewind the pointer to the start of the chunk if we have a chunk group */ first_sm = DetectContentFindPrevApplicableSM(first_sm); if (first_sm->ctx == NULL) return -1; DetectContentData *first_chunk = (DetectContentData*)first_sm->ctx; if (first_chunk == NULL) return -1; if ( !(first_chunk->flags & DETECT_CONTENT_IS_CHUNK)) /** No modifiers to update */ return 1; uint8_t chunk_group_id = first_chunk->chunk_group_id; uint8_t num_chunks = DetectContentCountChunksInGroup(first_sm, chunk_group_id); int16_t total_len = DetectContentChunksGetTotalLength(first_sm); if (num_chunks < 1 || total_len < 1) return -1; DetectContentData *cur_chunk = NULL; DetectContentData *last_chunk = first_chunk; SigMatch *cur_sm = NULL; /** The first chunk has the real modifiers that we want to propagate */ for (cur_sm = first_sm; cur_sm != NULL && cur_sm->type == DETECT_CONTENT && (cur_chunk = (DetectContentData*) cur_sm->ctx) != NULL && cur_chunk->chunk_group_id == chunk_group_id ; cur_sm = cur_sm->next) { //SCLogDebug("Cur: %u %s Last: %u %s", cur_chunk->offset, cur_chunk->content, last_chunk->offset, last_chunk->content); int16_t remaining_len = DetectContentChunksGetRemainingLength(cur_sm); int16_t previous_len = DetectContentChunksGetRemainingLength(cur_sm); if (previous_len < 0 || remaining_len < 0) return -1; /** If we are in the first chunk */ if (cur_chunk->chunk_id == 0) { /** Reset the first depth removing the length of the remaining chunks */ SCLogDebug("CUR depth = %u remain_len %d ", cur_chunk->depth, remaining_len); if ( !(cur_chunk->chunk_flags & CHUNK_UPDATED_DEPTH) && cur_chunk->depth > 0) cur_chunk->depth -= remaining_len; /** Reset the first within removing the length of the remaining chunks */ if ( !(cur_chunk->chunk_flags & CHUNK_UPDATED_WITHIN) && cur_chunk->within > 0) cur_chunk->within -= remaining_len; /** Reset the first isdataat adding the length of the remaining chunks */ if ( !(cur_chunk->chunk_flags & CHUNK_UPDATED_ISDATAAT) && cur_chunk->isdataat > 0) cur_chunk->isdataat += remaining_len; /** * The offseth for the first chunk is the real offset, * so no need to update it here * The same is applicable here to offset and distance */ /** If it's not the first chunk we need to propagate the changes */ } else { /** Propagate the flags */ cur_chunk->flags = last_chunk->flags; /** Update the depth adding the content_len of the current chunk * to the previous chunk depth */ if ( !(cur_chunk->chunk_flags & CHUNK_UPDATED_DEPTH) && last_chunk->depth > 0) cur_chunk->depth = last_chunk->depth + cur_chunk->content_len; /** Update the offset adding the content_len of the last chunk * to the previous chunk offset */ if ( !(cur_chunk->chunk_flags & CHUNK_UPDATED_OFFSET) && last_chunk->offset > 0) cur_chunk->offset = last_chunk->offset + last_chunk->content_len; /** We are iterating in the chunks after the first one, so within is * relative to the previous chunks and should be exactly the size of * its content_len since they are consecutive */ if ( !(cur_chunk->chunk_flags & CHUNK_UPDATED_WITHIN)) { /** Even if they don't specify a within option, * we set the flag, since they are relative to the chunks * of the same pattern */ cur_chunk->flags |= DETECT_CONTENT_WITHIN; cur_chunk->within = cur_chunk->content_len; /* set the within next flag on the previous chunk */ if (last_chunk != NULL) last_chunk->flags |= DETECT_CONTENT_WITHIN_NEXT; } /** We are iterating in the chunks after the first one, so distance * must be 0 between the chunks, since they are consecutive * splitted chunks */ if ( !(cur_chunk->chunk_flags & CHUNK_UPDATED_DISTANCE)) { /** Even if they don't specify a within option, * we set the flag, since they are relative to the chunks * of the same pattern */ cur_chunk->flags |= DETECT_CONTENT_DISTANCE; cur_chunk->distance = 0; /* set the distance next flag on the previous chunk */ if (last_chunk != NULL) last_chunk->flags |= DETECT_CONTENT_DISTANCE_NEXT; } /** The isdataat (relative) is updated to the * last_chunk isdataat - the content_len of the current * chunk content_len */ if ( !(cur_chunk->chunk_flags & CHUNK_UPDATED_ISDATAAT) && last_chunk->isdataat > 0) cur_chunk->isdataat = last_chunk->isdataat - cur_chunk->content_len; } last_chunk = cur_chunk; } return 1; } /** * \brief Function to setup a content pattern. Patterns that doesn't fit the * current max_pattern_length, are splitted into multiple chunks in independent * DetectContentData structures with it's own modifiers. Each modifier must be * recalculated for each chunk from the modifiers of the head of the chunk * group, and will act as independent patterns * * \param de_ctx pointer to the current detection_engine * \param s pointer to the current Signature * \param m pointer to the last parsed SigMatch * \param contentstr pointer to the current keyword content string * \retval -1 if error * \retval 0 if all was ok */ int DetectContentSetup (DetectEngineCtx *de_ctx, Signature *s, SigMatch *m, char *contentstr) { DetectContentData *cd = NULL; SigMatch *sm = NULL; /* max_pattern_length */ int32_t mpltmp = -1; uint8_t mpl = 0; uint8_t index = 0; cd = DetectContentParse(contentstr); if (cd == NULL) goto error; mpltmp = MpmMatcherGetMaxPatternLength(de_ctx->mpm_matcher); if (mpltmp < 0) { SCLogDebug("Unknown Matcher type. Exiting..."); exit(EXIT_FAILURE); } mpl = mpltmp; SCLogDebug("Matcher type: %"PRIu16" max_pattern_length: %"PRIu32"", de_ctx->mpm_matcher, mpl); /** We are going to assign a chunk group to all the DetectContents, even * if it's not splitted. This will give us the number of loaded patterns * in this signature */ if (s != NULL) { s->nchunk_groups++; } /** Check if we need to split the content into chunks */ if (mpl > 0 && cd->content_len > mpl) { DetectContentData *aux = NULL; SigMatch *first = NULL; uint8_t chunk_id = 0; /** Split it from DetectContentSplitChunk() */ for (index = 0; index < cd->content_len; index += mpl) { aux = DetectContentSplitChunk(cd, (uint8_t)(cd->content_len - index), index, mpl); if ( aux == NULL) { SCLogDebug("Couldn't split pattern chunks. Exiting..."); exit(EXIT_FAILURE); } aux->flags |= DETECT_CONTENT_IS_CHUNK; /** If we load a signature, assing the internal * chunk_group_id of the sig */ if (s != NULL) { /** each group of chunks has it's own internal id in the sig */ aux->chunk_group_id = s->nchunk_groups; /** * The first chunk will have id = 0 * we need to search for applying the content modifiers */ aux->chunk_id = chunk_id++; } /** Allocate it as a normal SigMatch */ sm = SigMatchAlloc(); if (sm == NULL) goto error; sm->type = DETECT_CONTENT; sm->ctx = (void *)aux; SigMatchAppend(s,m,sm); m = sm; aux->id = de_ctx->content_max_id; de_ctx->content_max_id++; s->flags |= SIG_FLAG_MPM; /** We need to setup the modifiers for the chunks respect * the last chunk installed inmediatelly before * so do the propagation from the first one * The function DetectContentPropagate*Modifier*() should * be called when a new content modifier is * parsed/installed */ if (aux->chunk_id == 0) first = sm; DetectContentPropagateModifiers(first); //DetectContentPrint(aux); } /** Free the original pattern */ DetectContentFree(cd); /** * If we dont need to split it is because the matcher has no length limit * or the payload fit in the current max pattern length, so no chunks here */ } else { sm = SigMatchAlloc(); if (sm == NULL) goto error; sm->type = DETECT_CONTENT; sm->ctx = (void *)cd; SigMatchAppend(s,m,sm); if (s != NULL) { /** each group of chunks has it's own internal id in the sig, * if the content is not splitted we will assign a chunk group id * anyway, so we know the real number of detect_content * patterns loaded */ cd->chunk_group_id = s->nchunk_groups; } cd->id = de_ctx->content_max_id; de_ctx->content_max_id++; s->flags |= SIG_FLAG_MPM; if (cd->negated == 1) s->flags |= SIG_FLAG_MPM_NEGCONTENT; //DetectContentPrint(cd); } return 0; error: if (cd != NULL) DetectContentFree(cd); if (sm != NULL) free(sm); return -1; } /** * \brief this function will free memory associated with DetectContentData * * \param cd pointer to DetectCotentData */ void DetectContentFree(void *ptr) { DetectContentData *cd = (DetectContentData *)ptr; if (cd == NULL) return; if (cd->content != NULL) free(cd->content); free(cd); } #ifdef UNITTESTS /* UNITTESTS */ /** * \test DetectCotentParseTest01 this is a test to make sure we can deal with escaped colons */ int DetectContentParseTest01 (void) { int result = 1; DetectContentData *cd = NULL; char *teststring = "\"abc\\:def\""; char *teststringparsed = "abc:def"; cd = DetectContentParse(teststring); if (cd != NULL) { if (memcmp(cd->content, teststringparsed, strlen(teststringparsed)) != 0) { SCLogDebug("expected %s got ", teststringparsed); PrintRawUriFp(stdout,cd->content,cd->content_len); SCLogDebug(": "); result = 0; DetectContentFree(cd); } } else { SCLogDebug("expected %s got NULL: ", teststringparsed); result = 0; } return result; } /** * \test DetectCotentParseTest02 this is a test to make sure we can deal with escaped semi-colons */ int DetectContentParseTest02 (void) { int result = 1; DetectContentData *cd = NULL; char *teststring = "\"abc\\;def\""; char *teststringparsed = "abc;def"; cd = DetectContentParse(teststring); if (cd != NULL) { if (memcmp(cd->content, teststringparsed, strlen(teststringparsed)) != 0) { SCLogDebug("expected %s got ", teststringparsed); PrintRawUriFp(stdout,cd->content,cd->content_len); SCLogDebug(": "); result = 0; DetectContentFree(cd); } } else { SCLogDebug("expected %s got NULL: ", teststringparsed); result = 0; } return result; } /** * \test DetectCotentParseTest03 this is a test to make sure we can deal with escaped double-quotes */ int DetectContentParseTest03 (void) { int result = 1; DetectContentData *cd = NULL; char *teststring = "\"abc\\\"def\""; char *teststringparsed = "abc\"def"; cd = DetectContentParse(teststring); if (cd != NULL) { if (memcmp(cd->content, teststringparsed, strlen(teststringparsed)) != 0) { SCLogDebug("expected %s got ", teststringparsed); PrintRawUriFp(stdout,cd->content,cd->content_len); SCLogDebug(": "); result = 0; DetectContentFree(cd); } } else { SCLogDebug("expected %s got NULL: ", teststringparsed); result = 0; } return result; } /** * \test DetectCotentParseTest04 this is a test to make sure we can deal with escaped backslashes */ int DetectContentParseTest04 (void) { int result = 1; DetectContentData *cd = NULL; char *teststring = "\"abc\\\\def\""; char *teststringparsed = "abc\\def"; cd = DetectContentParse(teststring); if (cd != NULL) { uint16_t len = (cd->content_len > strlen(teststringparsed)); if (memcmp(cd->content, teststringparsed, len) != 0) { SCLogDebug("expected %s got ", teststringparsed); PrintRawUriFp(stdout,cd->content,cd->content_len); SCLogDebug(": "); result = 0; DetectContentFree(cd); } } else { SCLogDebug("expected %s got NULL: ", teststringparsed); result = 0; } return result; } /** * \test DetectCotentParseTest05 test illegal escape */ int DetectContentParseTest05 (void) { int result = 1; DetectContentData *cd = NULL; char *teststring = "\"abc\\def\""; cd = DetectContentParse(teststring); if (cd != NULL) { SCLogDebug("expected NULL got "); PrintRawUriFp(stdout,cd->content,cd->content_len); SCLogDebug(": "); result = 0; DetectContentFree(cd); } return result; } /** * \test DetectCotentParseTest06 test a binary content */ int DetectContentParseTest06 (void) { int result = 1; DetectContentData *cd = NULL; char *teststring = "\"a|42|c|44|e|46|\""; char *teststringparsed = "abcdef"; cd = DetectContentParse(teststring); if (cd != NULL) { uint16_t len = (cd->content_len > strlen(teststringparsed)); if (memcmp(cd->content, teststringparsed, len) != 0) { SCLogDebug("expected %s got ", teststringparsed); PrintRawUriFp(stdout,cd->content,cd->content_len); SCLogDebug(": "); result = 0; DetectContentFree(cd); } } else { SCLogDebug("expected %s got NULL: ", teststringparsed); result = 0; } return result; } /** * \test DetectCotentParseTest07 test an empty content */ int DetectContentParseTest07 (void) { int result = 1; DetectContentData *cd = NULL; char *teststring = "\"\""; cd = DetectContentParse(teststring); if (cd != NULL) { SCLogDebug("expected NULL got %p: ", cd); result = 0; DetectContentFree(cd); } return result; } /** * \test DetectCotentParseTest08 test an empty content */ int DetectContentParseTest08 (void) { int result = 1; DetectContentData *cd = NULL; char *teststring = ""; cd = DetectContentParse(teststring); if (cd != NULL) { SCLogDebug("expected NULL got %p: ", cd); result = 0; DetectContentFree(cd); } return result; } /** * \test DetectCotentParseChunksTest01B2G test split process */ int DetectContentChunkTestB2G01 (void) { int result = 0; DetectContentData *cd = NULL; Signature *s = NULL; SigMatch *m = NULL; DetectEngineCtx *de_ctx = DetectEngineCtxInit(); if (de_ctx == NULL) goto end; /** MPM_B2G is currently 32 bytes word, so the number of chunks * created should be 0, since the pattern is 32 bytes and fit in a word */ de_ctx->mpm_matcher = MPM_B2G; char *sigstr = "alert tcp any any -> any any (msg:\"This content is exactly" "32 bytes lentgh\"; content:\"12345678901234567890123456789012\"; sid:1;)"; s = de_ctx->sig_list = SigInit(de_ctx, sigstr); if (s == NULL) goto end; if (de_ctx->sig_list->match == NULL) goto end; m = de_ctx->sig_list->match; if (m->type == DETECT_CONTENT && m->ctx != NULL) cd = m->ctx; else goto end; if ( !(cd->flags & DETECT_CONTENT_IS_CHUNK) && m->next == NULL) result = 1; end: SigCleanSignatures(de_ctx); if (de_ctx != NULL) DetectEngineCtxFree(de_ctx); return result; } /** * \test DetectCotentParseChunksTest01B3G test split process */ int DetectContentChunkTestB3G01 (void) { int result = 0; DetectContentData *cd = NULL; Signature *s = NULL; SigMatch *m = NULL; DetectEngineCtx *de_ctx = DetectEngineCtxInit(); if (de_ctx == NULL) goto end; /** MPM_B3G is currently 32 bytes word, so the number of chunks * created should be 0, since the pattern is 32 bytes and fit in a word */ de_ctx->mpm_matcher = MPM_B3G; char *sigstr = "alert tcp any any -> any any (msg:\"This content is exactly" "32 bytes lentgh\"; content:\"12345678901234567890123456789012\"; sid:1;)"; s = de_ctx->sig_list = SigInit(de_ctx, sigstr); if (s == NULL) goto end; if (de_ctx->sig_list->match == NULL) goto end; m = de_ctx->sig_list->match; if (m->type == DETECT_CONTENT && m->ctx != NULL) cd = m->ctx; else goto end; if ( !(cd->flags & DETECT_CONTENT_IS_CHUNK) && m->next == NULL) result = 1; end: SigCleanSignatures(de_ctx); if (de_ctx != NULL) DetectEngineCtxFree(de_ctx); return result; } /** * \test DetectCotentParseChunksTestB2G02 test split process */ int DetectContentChunkTestB2G02 (void) { int result = 0; DetectContentData *cd = NULL; Signature *s = NULL; SigMatch *m = NULL; DetectEngineCtx *de_ctx = DetectEngineCtxInit(); if (de_ctx == NULL) goto end; /** MPM_B2G is currently 33 bytes word, so the number of chunks * created should be 2, since the pattern is 33 bytes and * wont fit in a word */ de_ctx->mpm_matcher = MPM_B2G; char *sigstr = "alert tcp any any -> any any (msg:\"This content is exactly 33 bytes length, so it should be splitted\"; content:\"123456789012345678901234567890123\"; sid:1;)"; s = de_ctx->sig_list = SigInit(de_ctx, sigstr); if (s == NULL) goto end; if (de_ctx->sig_list->match == NULL) goto end; m = de_ctx->sig_list->match; if (m->type == DETECT_CONTENT && m->ctx != NULL) cd = m->ctx; else goto end; if ( !(cd->flags & DETECT_CONTENT_IS_CHUNK && m->next != NULL && cd->content_len == 32 && cd->chunk_id == 0)) goto end; m = m->next; if (m != NULL && m->type == DETECT_CONTENT && m->ctx != NULL) cd = m->ctx; if ( cd->flags & DETECT_CONTENT_IS_CHUNK && m->next == NULL && cd->content_len == 1 && cd->chunk_id == 1) result = 1; end: SigCleanSignatures(de_ctx); if (de_ctx != NULL) DetectEngineCtxFree(de_ctx); return result; } /** * \test DetectCotentParseChunksTestB3G02 test split proccess */ int DetectContentChunkTestB3G02 (void) { int result = 0; DetectContentData *cd = NULL; Signature *s = NULL; SigMatch *m = NULL; DetectEngineCtx *de_ctx = DetectEngineCtxInit(); if (de_ctx == NULL) goto end; /** MPM_B3G is currently 33 bytes word, so the number of chunks * created should be 2, since the pattern is 33 bytes and * wont fit in a word */ de_ctx->mpm_matcher = MPM_B3G; char *sigstr = "alert tcp any any -> any any (msg:\"This content is exactly 33 bytes length, so it should be splitted\"; content:\"123456789012345678901234567890123\"; sid:1;)"; s = de_ctx->sig_list = SigInit(de_ctx, sigstr); if (s == NULL) goto end; if (de_ctx->sig_list->match == NULL) goto end; m = de_ctx->sig_list->match; if (m->type == DETECT_CONTENT && m->ctx != NULL) cd = m->ctx; else goto end; if ( !(cd->flags & DETECT_CONTENT_IS_CHUNK && m->next != NULL && cd->content_len == 32 && cd->chunk_id == 0)) goto end; m = m->next; if (m != NULL && m->type == DETECT_CONTENT && m->ctx != NULL) cd = m->ctx; if ( cd->flags & DETECT_CONTENT_IS_CHUNK && m->next == NULL && cd->content_len == 1 && cd->chunk_id == 1) result = 1; end: SigCleanSignatures(de_ctx); if (de_ctx != NULL) DetectEngineCtxFree(de_ctx); return result; } /** * \test DetectCotentParseChunksTestB2G03 test split proccess */ int DetectContentChunkTestB2G03 (void) { int result = 0; DetectContentData *cd = NULL; Signature *s = NULL; SigMatch *m = NULL; DetectEngineCtx *de_ctx = DetectEngineCtxInit(); if (de_ctx == NULL) goto end; de_ctx->mpm_matcher = MPM_B2G; /** content_len = 100, so 3 chunks of 32 and the last chunk length == 4 */ char *sigstr = "alert tcp any any -> any any (msg:\"This content is exactly 100 bytes length, so it should be splitted\"; content:\"1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890\"; sid:1;)"; s = de_ctx->sig_list = SigInit(de_ctx, sigstr); if (s == NULL) goto end; if (de_ctx->sig_list->match == NULL) goto end; m = de_ctx->sig_list->match; uint8_t chunk_id = 0; do { if (m->type == DETECT_CONTENT && m->ctx != NULL) cd = m->ctx; else goto end; if ( !(cd->flags & DETECT_CONTENT_IS_CHUNK && m->next != NULL && cd->content_len == 32 && cd->chunk_id == chunk_id++)) goto end; } while ((m = m->next) && m != NULL && m->next != NULL); /** Now let's see if the last Chunk hast the content_len of 4 */ if (m == NULL || m->next != NULL) goto end; if (m->type == DETECT_CONTENT && m->ctx != NULL) cd = m->ctx; else goto end; if (cd->content_len != 4 || cd->chunk_id != chunk_id) goto end; result = 1; end: SigCleanSignatures(de_ctx); if (de_ctx != NULL) DetectEngineCtxFree(de_ctx); return result; } /** * \test DetectCotentParseChunksTestB3G03 test split proccess */ int DetectContentChunkTestB3G03 (void) { int result = 0; DetectContentData *cd = NULL; Signature *s = NULL; SigMatch *m = NULL; DetectEngineCtx *de_ctx = DetectEngineCtxInit(); if (de_ctx == NULL) goto end; de_ctx->mpm_matcher = MPM_B3G; /** content_len = 100, so 3 chunks of 32 and the last chunk length == 4 */ char *sigstr = "alert tcp any any -> any any (msg:\"This content is exactly 100 bytes length, so it should be splitted\"; content:\"1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890\"; sid:1;)"; s = de_ctx->sig_list = SigInit(de_ctx, sigstr); if (s == NULL) goto end; if (de_ctx->sig_list->match == NULL) goto end; m = de_ctx->sig_list->match; uint8_t chunk_id = 0; do { if (m->type == DETECT_CONTENT && m->ctx != NULL) cd = m->ctx; else goto end; if ( !(cd->flags & DETECT_CONTENT_IS_CHUNK && m->next != NULL && cd->content_len == 32 && cd->chunk_id == chunk_id++)) goto end; } while ((m = m->next) && m != NULL && m->next != NULL); /** Now let's see if the last Chunk hast the content_len of 4 */ if (m == NULL || m->next != NULL) goto end; if (m->type == DETECT_CONTENT && m->ctx != NULL) cd = m->ctx; else goto end; if (cd->content_len != 4 || cd->chunk_id != chunk_id) goto end; result = 1; end: SigCleanSignatures(de_ctx); if (de_ctx != NULL) DetectEngineCtxFree(de_ctx); return result; } /** * \test DetectContentChunkTestModifiers01 test modifiers propagation * given a signature with just one pattern */ int DetectContentChunkModifiersTest01 (void) { int result = 0; DetectContentData *cd = NULL; Signature *s = NULL; SigMatch *m = NULL; DetectEngineCtx *de_ctx = DetectEngineCtxInit(); if (de_ctx == NULL) goto end; de_ctx->mpm_matcher = MPM_B2G; /** content_len = 43, so 1 chunk of length 32 and another chunk of length == 11 */ char *sigstr = "alert tcp any any -> any any (msg:\"This content is exactly 43 bytes length, so it should be splitted\"; content:\"1234567890123456789012345678901234567890123\"; depth:50; offset:10; isdataat:10, relative; sid:1;)"; s = de_ctx->sig_list = SigInit(de_ctx, sigstr); if (s == NULL) goto end; if (de_ctx->sig_list->match == NULL) goto end; m = de_ctx->sig_list->match; if (m->type == DETECT_CONTENT && m->ctx != NULL) cd = m->ctx; else goto end; SCLogDebug("---DetectContentChunkModifiersTest01---"); DetectContentPrintAll(m); uint8_t chunk_id = 0; if ( !(cd->flags & DETECT_CONTENT_IS_CHUNK && m->next != NULL && cd->content_len == 32 && cd->chunk_id == chunk_id++)) goto end; /** Check modifiers for the first chunk */ if (cd->offset != 10 || cd->depth != 39 || cd->isdataat != 21 || cd->within != 0 || cd->distance != 0) { SCLogDebug("First Chunk has bad modifiers"); goto end; } /** Check specified flags (offset and depth have no flags) */ if ( !(cd->flags & DETECT_CONTENT_ISDATAAT_RELATIVE)) goto end; /** Check not specified flags (should not be set * automatically set for the first chunk) */ if ( (cd->flags & DETECT_CONTENT_WITHIN) || (cd->flags & DETECT_CONTENT_DISTANCE)) goto end; /** Now let's see if the last Chunk of this first group has * the content_len of 11 and the modifiers correctly set */ m = m->next; if (m == NULL) goto end; if (m->type == DETECT_CONTENT && m->ctx != NULL) cd = m->ctx; else goto end; if (!(cd->flags & DETECT_CONTENT_IS_CHUNK)) goto end; if (cd->content_len != 11 || cd->chunk_id != chunk_id) goto end; /** Check modifiers for the second chunk */ if (cd->offset != 42 || cd->depth != 50 || cd->isdataat != 10 || cd->within != 11 || cd->distance != 0) { SCLogDebug("Second Chunk has bad modifiers"); goto end; } /** Check specified flags */ if ( !(cd->flags & DETECT_CONTENT_ISDATAAT_RELATIVE)) goto end; /** Check flags, the second chunk should have a distance and depth * relative to the first chunk, so flags should be automatically set */ if ( !(cd->flags & DETECT_CONTENT_DISTANCE) || !(cd->flags & DETECT_CONTENT_WITHIN)) goto end; /* Great! */ result = 1; end: SigCleanSignatures(de_ctx); if (de_ctx != NULL) DetectEngineCtxFree(de_ctx); return result; } /** * \test DetectContentChunkTestModifiers01 test modifiers propagation * mixing splitted patterns with non splitted */ int DetectContentChunkModifiersTest02 (void) { int result = 0; DetectContentData *cd = NULL; Signature *s = NULL; SigMatch *m = NULL; DetectEngineCtx *de_ctx = DetectEngineCtxInit(); if (de_ctx == NULL) goto end; de_ctx->mpm_matcher = MPM_B2G; /** content 1: content_len = 3, so not splitted * content 2: content_len = 43, so 1 chunk of length 32 and another chunk of length == 11 * content 3: content_len = 4, so not splitted * content 4: content_len = 43, so 1 chunk of length 32 and another chunk of length == 11 */ char *sigstr = "alert tcp any any -> any any (msg:\"Lot of contents\"; content:\"GET\"; depth:3; offset:0; isdataat:43,relative ; content:\"1234567890123456789012345678901234567890123\"; distance: 1; within: 50; depth:50; offset:10; isdataat:10, relative; content:\"HTTP\"; distance:10; within:20; content:\"1234567890123456789012345678901234567890123\"; distance: 10; within: 50; depth:1000; offset:50; isdataat:20, relative; sid:1;)"; s = de_ctx->sig_list = SigInit(de_ctx, sigstr); if (s == NULL) goto end; if (de_ctx->sig_list->match == NULL) goto end; m = de_ctx->sig_list->match; if (m->type == DETECT_CONTENT && m->ctx != NULL) cd = m->ctx; else goto end; SCLogDebug("---DetectContentChunkModifiersTest02---"); DetectContentPrintAll(m); //uint8_t num_chunks = DetectContentCountChunksInGroup(first_sm, chunk_group_id); /** The first DetectContent should not be splitted */ if ( (cd->flags & DETECT_CONTENT_IS_CHUNK) || m->next == NULL || cd->depth != 3 || cd->isdataat != 43 || cd->offset!= 0 || cd->within != 0 || cd->distance != 0 || cd->content_len != 3) goto end; /** First detect content ok, now let's see the first group of chunks */ m = m->next; if (m->type == DETECT_CONTENT && m->ctx != NULL) cd = m->ctx; else goto end; if ( !(cd->flags & DETECT_CONTENT_IS_CHUNK && m->next != NULL && cd->content_len == 32 && cd->chunk_id == 0)) goto end; /** Check modifiers for the first chunk */ if (cd->offset != 10 || cd->depth != 39 || cd->isdataat != 21 || cd->within != 39 || cd->distance != 1) { SCLogDebug("First Chunk has bad modifiers"); goto end; } /** Check specified flags (offset and depth have no flags) */ if ( !(cd->flags & DETECT_CONTENT_ISDATAAT_RELATIVE)) goto end; /** Check specified flags relative to the previous DetectContent * are correctly set for the first chunk) */ if ( !(cd->flags & DETECT_CONTENT_WITHIN) || !(cd->flags & DETECT_CONTENT_DISTANCE)) goto end; /** Now let's see if the last Chunk of this first group has * the content_len of 11 and the modifiers correctly set */ m = m->next; if (m == NULL) goto end; if (m->type == DETECT_CONTENT && m->ctx != NULL) cd = m->ctx; else goto end; if (!(cd->flags & DETECT_CONTENT_IS_CHUNK)) goto end; if (cd->content_len != 11 || cd->chunk_id != 1) goto end; /** Check modifiers for the second chunk */ if (cd->offset != 42 || cd->depth != 50 || cd->isdataat != 10 || cd->within != 11 || cd->distance != 0) { SCLogDebug("Second Chunk has bad modifiers"); goto end; } /** Check specified flags */ if ( !(cd->flags & DETECT_CONTENT_ISDATAAT_RELATIVE)) goto end; /** Check flags, the second chunk should have a distance and depth * relative to the first chunk, so flags should be automatically set */ if ( !(cd->flags & DETECT_CONTENT_DISTANCE) || !(cd->flags & DETECT_CONTENT_WITHIN)) goto end; /** The next DetectContent should not be splitted (pattern "HTTP") */ m = m->next; if (m == NULL) goto end; if (m->type == DETECT_CONTENT && m->ctx != NULL) cd = m->ctx; else goto end; /** Should not be a chunk */ if (cd->flags & DETECT_CONTENT_IS_CHUNK) goto end; if (cd->content_len != 4) goto end; /** Check modifiers for the second chunk */ if (cd->offset != 0 || cd->depth != 0 || cd->isdataat != 0 || cd->within != 20 || cd->distance != 10) { SCLogDebug("Second Chunk has bad modifiers"); goto end; } /** Check not specified flags */ if ( cd->flags & DETECT_CONTENT_ISDATAAT_RELATIVE) goto end; /** Check specified flags */ if ( !(cd->flags & DETECT_CONTENT_DISTANCE) || !(cd->flags & DETECT_CONTENT_WITHIN)) goto end; /** Ok, now the last group of chunks */ m = m->next; if (m->type == DETECT_CONTENT && m->ctx != NULL) cd = m->ctx; else goto end; if ( !((cd->flags & DETECT_CONTENT_IS_CHUNK) && m->next != NULL && cd->content_len == 32 && cd->chunk_id == 0 && cd->chunk_group_id == 4)) goto end; /** Check modifiers for the first chunk */ if (cd->offset != 50 || cd->depth != 989 || cd->isdataat != 31 || cd->within != 39 || cd->distance != 10) { SCLogDebug("First Chunk of last group has bad modifiers"); goto end; } /** Check specified flags (offset and depth have no flags) */ if ( !(cd->flags & DETECT_CONTENT_ISDATAAT_RELATIVE)) goto end; /** Check specified flags relative to the previous DetectContent * are correctly set for the first chunk) */ if ( !(cd->flags & DETECT_CONTENT_WITHIN) || !(cd->flags & DETECT_CONTENT_DISTANCE)) goto end; /** Now let's see if the last Chunk of this last group has * the content_len of 11 and the modifiers correctly set */ m = m->next; if (m == NULL) goto end; if (m->type == DETECT_CONTENT && m->ctx != NULL) cd = m->ctx; else goto end; if (!(cd->flags & DETECT_CONTENT_IS_CHUNK)) goto end; if (cd->content_len != 11 || cd->chunk_id != 1 || cd->chunk_group_id != 4) goto end; /** Check modifiers for the second chunk */ if (cd->offset != 82 || cd->depth != 1000 || cd->isdataat != 20 || cd->within != 11 || cd->distance != 0) { SCLogDebug("Second Chunk of last group has bad modifiers"); goto end; } /** Check specified flags */ if ( !(cd->flags & DETECT_CONTENT_ISDATAAT_RELATIVE)) goto end; /** Check flags, the second chunk should have a distance and depth * relative to the first chunk, so flags should be automatically set */ if ( !(cd->flags & DETECT_CONTENT_DISTANCE) || !(cd->flags & DETECT_CONTENT_WITHIN)) goto end; /** Great!!! */ result = 1; end: SigCleanSignatures(de_ctx); if (de_ctx != NULL) DetectEngineCtxFree(de_ctx); return result; } /** * \test Test packet Matches * \param raw_eth_pkt pointer to the ethernet packet * \param pktsize size of the packet * \param sig pointer to the signature to test * \param sid sid number of the signature * \retval return 1 if match * \retval return 0 if not */ int DetectContentChunkMatchTest(uint8_t *raw_eth_pkt, uint16_t pktsize, char *sig, uint32_t sid) { int result = 1; Packet p; DecodeThreadVars dtv; ThreadVars th_v; DetectEngineThreadCtx *det_ctx = NULL; memset(&p, 0, sizeof(Packet)); memset(&dtv, 0, sizeof(DecodeThreadVars)); memset(&th_v, 0, sizeof(th_v)); FlowInitConfig(FLOW_QUIET); DecodeEthernet(&th_v, &dtv, &p, raw_eth_pkt, pktsize, NULL); DetectEngineCtx *de_ctx = DetectEngineCtxInit(); if (de_ctx == NULL) { result=0; goto end; } de_ctx->flags |= DE_QUIET; de_ctx->sig_list = SigInit(de_ctx, sig); if (de_ctx->sig_list == NULL) { result = 0; goto end; } de_ctx->sig_list->next = NULL; SCLogDebug("---DetectContentChunkMatchTest---"); DetectContentPrintAll(de_ctx->sig_list->match); SigGroupBuild(de_ctx); //PatternMatchPrepare(mpm_ctx, MPM_B2G); DetectEngineThreadCtxInit(&th_v, (void *)de_ctx, (void *)&det_ctx); SigMatchSignatures(&th_v, de_ctx, det_ctx, &p); if (PacketAlertCheck(&p, sid) != 1) { result = 0; goto end; } end: if (de_ctx != NULL) { //PatternMatchDestroy(mpm_ctx); SigGroupCleanup(de_ctx); SigCleanSignatures(de_ctx); if (det_ctx != NULL) DetectEngineThreadCtxDeinit(&th_v, (void *)det_ctx); DetectEngineCtxFree(de_ctx); } FlowShutdown(); return result; } /** * \brief Wrapper for DetectContentChunkMatchTest */ int DetectContentChunkMatchTestWrp(char *sig, uint32_t sid) { /** Real packet with the following tcp data: * "Hi, this is a big test to check content matches of splitted" * "patterns between multiple chunks!" * (without quotes! :) ) */ uint8_t raw_eth_pkt[] = { 0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00, 0x00,0x00,0x00,0x00,0x08,0x00,0x45,0x00, 0x00,0x85,0x00,0x01,0x00,0x00,0x40,0x06, 0x7c,0x70,0x7f,0x00,0x00,0x01,0x7f,0x00, 0x00,0x01,0x00,0x14,0x00,0x50,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x50,0x02, 0x20,0x00,0xc9,0xad,0x00,0x00,0x48,0x69, 0x2c,0x20,0x74,0x68,0x69,0x73,0x20,0x69, 0x73,0x20,0x61,0x20,0x62,0x69,0x67,0x20, 0x74,0x65,0x73,0x74,0x20,0x74,0x6f,0x20, 0x63,0x68,0x65,0x63,0x6b,0x20,0x63,0x6f, 0x6e,0x74,0x65,0x6e,0x74,0x20,0x6d,0x61, 0x74,0x63,0x68,0x65,0x73,0x20,0x6f,0x66, 0x20,0x73,0x70,0x6c,0x69,0x74,0x74,0x65, 0x64,0x20,0x70,0x61,0x74,0x74,0x65,0x72, 0x6e,0x73,0x20,0x62,0x65,0x74,0x77,0x65, 0x65,0x6e,0x20,0x6d,0x75,0x6c,0x74,0x69, 0x70,0x6c,0x65,0x20,0x63,0x68,0x75,0x6e, 0x6b,0x73,0x21 }; /* end raw_eth_pkt */ return DetectContentChunkMatchTest(raw_eth_pkt, (uint16_t)sizeof(raw_eth_pkt), sig, sid); } /** * \test Check if we match a normal pattern (not splitted) */ int DetectContentChunkMatchTest01() { char *sig = "alert tcp any any -> any any (msg:\"Nothing..\";" " content:\"Hi, this is a big test\"; sid:1;)"; return DetectContentChunkMatchTestWrp(sig, 1); } /** * \test Check if we match a splitted pattern */ int DetectContentChunkMatchTest02() { char *sig = "alert tcp any any -> any any (msg:\"Nothing..\";" " content:\"Hi, this is a big test to check content matches of" " splitted patterns between multiple chunks!\"; sid:1;)"; return DetectContentChunkMatchTestWrp(sig, 1); } /** * \test Check that we don't match the signature if one of the splitted * chunks doesn't match the packet */ int DetectContentChunkMatchTest03() { /** The last chunk of the content should not match */ char *sig = "alert tcp any any -> any any (msg:\"Nothing..\";" " content:\"Hi, this is a big test to check content matches of" " splitted patterns between multiple splitted chunks!\"; sid:1;)"; return (DetectContentChunkMatchTestWrp(sig, 1) == 0) ? 1: 0; } /** * \test Check if we match multiple content (not splitted) */ int DetectContentChunkMatchTest04() { char *sig = "alert tcp any any -> any any (msg:\"Nothing..\"; " " content:\"Hi, this is\"; depth:15 ;content:\"a big test\"; " " within:15; content:\"to check content matches of\"; " " within:30; content:\"splitted patterns\"; distance:1; " " within:30; depth:400;" " sid:1;)"; return DetectContentChunkMatchTestWrp(sig, 1); } /** * \test Check that we match packets with multiple chunks and not chunks * Here we should specify only contents that fit in 32 bytes * Each of them with their modifier values */ int DetectContentChunkMatchTest05() { char *sig = "alert tcp any any -> any any (msg:\"Nothing..\"; " " content:\"Hi, this is a big\"; depth:17; " " isdataat:30, relative; " " content:\"test\"; within: 5; distance:1; depth:22; " " isdataat:15, relative; offset:18; " " content:\"of splitted\"; within:37; distance:15; " " depth:60; isdataat:20,relative; offset: 48; " " content:\"patterns\"; within:9; distance:1; depth:69; " " isdataat:10, relative; offset:60; " " sid:1;)"; return DetectContentChunkMatchTestWrp(sig, 1); } /** * \test Check that we match packets with multiple chunks and not chunks * Here we should specify contents that fit and contents that must be splitted * Each of them with their modifier values */ int DetectContentChunkMatchTest06() { char *sig = "alert tcp any any -> any any (msg:\"Nothing..\"; " " content:\"Hi, this is a big test to check cont\"; depth:36;" " content:\"ent matches\"; within:11; distance:0; " " content:\"of splitted patterns between multiple\"; " " within:38; distance:1; offset:47; depth:85; " " content:\"chunks!\"; within: 8; distance:1; " " depth:94; offset: 50; " " sid:1;)"; return DetectContentChunkMatchTestWrp(sig, 1); } /** * \test Check if we match contents that are in the payload * but not in the same order as specified in the signature */ int DetectContentChunkMatchTest07() { char *sig = "alert tcp any any -> any any (msg:\"Nothing..\"; " " content:\"chunks!\"; " " content:\"content matches\"; offset:32; depth:47; " " content:\"of splitted patterns between multiple\"; " " content:\"Hi, this is a big\"; offset:0; depth:17; " " sid:1;)"; return DetectContentChunkMatchTestWrp(sig, 1); } /** * \test Check if we match contents that are in the payload * but not in the same order as specified in the signature */ int DetectContentChunkMatchTest08() { char *sig = "alert tcp any any -> any any (msg:\"Nothing..\"; " " content:\"ent matches\"; " " content:\"of splitted patterns between multiple\"; " " within:38; distance:1; offset:47; depth:85; " " content:\"chunks!\"; within: 8; distance:1; " " depth:94; offset: 50; " " content:\"Hi, this is a big test to check cont\"; depth:36;" " sid:1;)"; return DetectContentChunkMatchTestWrp(sig, 1); } /** * \test Check if we match contents that are in the payload * but not in the same order as specified in the signature */ int DetectContentChunkMatchTest09() { char *sig = "alert tcp any any -> any any (msg:\"Nothing..\"; " " content:\"ent matches\"; " " content:\"of splitted patterns between multiple\"; " " within:38; distance:1; offset:47; depth:85; " " content:\"chunks!\"; within: 8; distance:1; " " depth:94; offset: 50; " " content:\"Hi, this is a big test to chec\"; depth:36;" " content:\"k cont\"; distance:0; within:6;" " sid:1;)"; return DetectContentChunkMatchTestWrp(sig, 1); } int DetectContentParseTest09(void) { int result = 0; DetectContentData *cd = NULL; char *teststring = "!boo"; cd = DetectContentParse(teststring); if (cd != NULL) { result = (cd->negated == 1); DetectContentFree(cd); } return result; } int DetectContentParseTest10(void) { int result = 0; DetectContentData *cd = NULL; char *teststring = "!\"boo\""; cd = DetectContentParse(teststring); if (cd != NULL) { result = (cd->negated == 1); DetectContentFree(cd); } return result; } int DetectContentParseNegTest11(void) { int result = 0; DetectContentData *cd = NULL; char *teststring = "boo"; cd = DetectContentParse(teststring); if (cd != NULL) { result = (cd->negated == 0); DetectContentFree(cd); } return result; } int DetectContentParseNegTest12(void) { int result = 0; DetectContentData *cd = NULL; char *teststring = "\"boo\""; cd = DetectContentParse(teststring); if (cd != NULL) { result = (cd->negated == 0); DetectContentFree(cd); } return result; } int DetectContentParseNegTest13(void) { int result = 0; DetectContentData *cd = NULL; char *teststring = "\"!boo\""; cd = DetectContentParse(teststring); if (cd != NULL) { result = (cd->negated == 1); DetectContentFree(cd); } return result; } static int SigTestPositiveTestContent(char *rule, uint8_t *buf) { uint16_t buflen = strlen((char *)buf); Packet p; ThreadVars th_v; DetectEngineThreadCtx *det_ctx; int result = 0; memset(&th_v, 0, sizeof(th_v)); memset(&p, 0, sizeof(p)); p.src.family = AF_INET; p.dst.family = AF_INET; p.payload = buf; p.payload_len = buflen; p.proto = IPPROTO_TCP; DetectEngineCtx *de_ctx = DetectEngineCtxInit(); if (de_ctx == NULL) goto end; de_ctx->flags |= DE_QUIET; de_ctx->sig_list = SigInit(de_ctx, rule); if (de_ctx->sig_list == NULL) { result = 0; goto end; } SigGroupBuild(de_ctx); //PatternMatchPrepare(mpm_ctx, MPM_B2G); DetectEngineThreadCtxInit(&th_v, (void *)de_ctx, (void *)&det_ctx); SigMatchSignatures(&th_v, de_ctx, det_ctx, &p); if (PacketAlertCheck(&p, 1) == 1) { result = 1; goto end; } SigGroupCleanup(de_ctx); SigCleanSignatures(de_ctx); DetectEngineThreadCtxDeinit(&th_v, (void *)det_ctx); //PatternMatchDestroy(mpm_ctx); DetectEngineCtxFree(de_ctx); end: return result; } static int SigTestNegativeTestContent(char *rule, uint8_t *buf) { uint16_t buflen = strlen((char *)buf); Packet p; ThreadVars th_v; DetectEngineThreadCtx *det_ctx; int result = 0; memset(&th_v, 0, sizeof(th_v)); memset(&p, 0, sizeof(p)); p.src.family = AF_INET; p.dst.family = AF_INET; p.payload = buf; p.payload_len = buflen; p.proto = IPPROTO_TCP; DetectEngineCtx *de_ctx = DetectEngineCtxInit(); if (de_ctx == NULL) goto end; de_ctx->flags |= DE_QUIET; de_ctx->sig_list = SigInit(de_ctx, rule); if (de_ctx->sig_list == NULL) { result = 0; goto end; } SigGroupBuild(de_ctx); //PatternMatchPrepare(mpm_ctx, MPM_B2G); DetectEngineThreadCtxInit(&th_v, (void *)de_ctx, (void *)&det_ctx); SigMatchSignatures(&th_v, de_ctx, det_ctx, &p); if (PacketAlertCheck(&p, 1) == 0) { result = 1; goto end; } SigGroupCleanup(de_ctx); SigCleanSignatures(de_ctx); DetectEngineThreadCtxDeinit(&th_v, (void *)det_ctx); //PatternMatchDestroy(mpm_ctx); DetectEngineCtxFree(de_ctx); end: return result; } /** * \test A positive test that checks that the content string doesn't contain * the negated content */ static int SigTest41TestNegatedContent(void) { return SigTestPositiveTestContent("alert tcp any any -> any any (msg:\"HTTP URI cap\"; content:!GES; sid:1;)", (uint8_t *)"GET /one/ HTTP/1.1\r\n Host: one.example.org\r\n\r\n\r\nGET /two/ HTTP/1.1\r\nHost: two.example.org\r\n\r\n\r\n"); } /** * \test A positive test that checks that the content string doesn't contain * the negated content within the specified depth */ static int SigTest42TestNegatedContent(void) { return SigTestPositiveTestContent("alert tcp any any -> any any (msg:\"HTTP URI cap\"; content:!twentythree; depth:22; offset:35; sid:1;)", (uint8_t *)"one four nine fourteen twentythree thirtyfive fourtysix fiftysix"); } /** * \test A negative test that checks that the content string doesn't contain * the negated content within the specified depth, and also after the * specified offset. If it is present in the depth we get a failure * anyways, and we don't do a check on the offset */ static int SigTest43TestNegatedContent(void) { return SigTestNegativeTestContent("alert tcp any any -> any any (msg:\"HTTP URI cap\"; content:!twentythree; depth:15; offset:22; sid:1;)", (uint8_t *)"one four nine fourteen twentythree thirtyfive fourtysix fiftysix"); } /** * \test A negative test that checks that the content string doesn't contain * the negated content after the specified offset and within the specified * depth. */ static int SigTest44TestNegatedContent(void) { return SigTestNegativeTestContent("alert tcp any any -> any any (msg:\"HTTP URI cap\"; content:!twentythree; offset:40; depth:35; sid:1;)", (uint8_t *)"one four nine fourteen twentythree thirtyfive fourtysix fiftysix"); } /** * \test A positive test that uses a combination of content string with negated * content string */ static int SigTest45TestNegatedContent(void) { return SigTestPositiveTestContent("alert tcp any any -> any any (msg:\"HTTP URI cap\"; content:one; depth:5; content:!twentythree; depth:23; sid:1;)", (uint8_t *)"one four nine fourteen twentythree thirtyfive fourtysix fiftysix"); } /** * \test A negative test that uses a combination of content string with negated * content string, with we receiving a failure for 'onee' itself. */ static int SigTest46TestNegatedContent(void) { return SigTestNegativeTestContent("alert tcp any any -> any any (msg:\"HTTP URI cap\"; content:onee; content:!twentythree; depth:23; sid:1;)", (uint8_t *)"one four nine fourteen twentythree thirtyfive fourtysix fiftysix"); } /** * \test A negative test that uses a combination of content string with negated * content string, with we receiving a failure of first content's offset * condition */ static int SigTest47TestNegatedContent(void) { return SigTestNegativeTestContent("alert tcp any any -> any any (msg:\"HTTP URI cap\"; content:one; offset:5; content:!twentythree; depth:23; sid:1;)", (uint8_t *)"one four nine fourteen twentythree thirtyfive fourtysix fiftysix"); } /** * \test A positive test that checks that we don't have a negated content within * the specified length from the previous content match. */ static int SigTest48TestNegatedContent(void) { return SigTestPositiveTestContent("alert tcp any any -> any any (msg:\"HTTP URI cap\"; content:GET; content:!GES; within:26; sid:1;)", (uint8_t *)"GET /one/ HTTP/1.1\r\n Host: one.example.org\r\n\r\n\r\nGET /two/ HTTP/1.1\r\nHost: two.example.org\r\n\r\n\r\n"); } /** * \test A negative test that checks the combined use of content and negated * content with the use of within */ static int SigTest49TestNegatedContent(void) { return SigTestNegativeTestContent("alert tcp any any -> any any (msg:\"HTTP URI cap\"; content:GET; content:!Host; within:26; sid:1;)", (uint8_t *)"GET /one/ HTTP/1.1\r\n Host: one.example.org\r\n\r\n\r\nGET /two/ HTTP/1.1\r\nHost: two.example.org\r\n\r\n\r\n"); } /** * \test A positive test that checks the combined use of content and negated * content with the use of distance */ static int SigTest50TestNegatedContent(void) { return SigTestPositiveTestContent("alert tcp any any -> any any (msg:\"HTTP URI cap\"; content:GET; content:!GES; distance:25; sid:1;)", (uint8_t *)"GET /one/ HTTP/1.1\r\n Host: one.example.org\r\n\r\n\r\nGET /two/ HTTP/1.1\r\nHost: two.example.org\r\n\r\n\r\n"); } /** * \test A negative test that checks the combined use of content and negated * content with the use of distance */ static int SigTest51TestNegatedContent(void) { return SigTestNegativeTestContent("alert tcp any any -> any any (msg:\"HTTP URI cap\"; content:GET; content:!Host; distance:18; sid:1;)", (uint8_t *)"GET /one/ HTTP/1.1\r\n Host: one.example.org\r\n\r\n\r\nGET /two/ HTTP/1.1\r\nHost: two.example.org\r\n\r\n\r\n"); } /** * \test A negative test that checks the combined use of content and negated * content, with the content not being present */ static int SigTest52TestNegatedContent(void) { return SigTestNegativeTestContent("alert tcp any any -> any any (msg:\"HTTP URI cap\"; content:GES; content:!BOO; sid:1;)", (uint8_t *)"GET /one/ HTTP/1.1\r\n Host: one.example.org\r\n\r\n\r\nGET /two/ HTTP/1.1\r\nHost: two.example.org\r\n\r\n\r\n"); } /** * \test A negative test that checks the combined use of content and negated * content, in the presence of within */ static int SigTest53TestNegatedContent(void) { return SigTestNegativeTestContent("alert tcp any any -> any any (msg:\"HTTP URI cap\"; content:one; content:!fourty; within:56; sid:1;)", (uint8_t *)"one four nine fourteen twentythree thirtyfive fourtysix fiftysix"); } /** * \test A positive test that checks the combined use of content and negated * content, in the presence of within */ static int SigTest54TestNegatedContent(void) { return SigTestPositiveTestContent("alert tcp any any -> any any (msg:\"HTTP URI cap\"; content:one; content:!fourty; within:20; sid:1;)", (uint8_t *)"one four nine fourteen twentythree thirtyfive fourtysix fiftysix"); } /** * \test A negative test that checks the use of negated content along with * the presence of depth */ static int SigTest55TestNegatedContent(void) { return SigTestNegativeTestContent("alert tcp any any -> any any (msg:\"HTTP URI cap\"; content:!one; depth:5; sid:1;)", (uint8_t *)"one four nine fourteen twentythree thirtyfive fourtysix fiftysix"); } /** * \test A positive test that checks the combined use of 2 contents in the * presence of within */ static int SigTest56TestNegatedContent(void) { return SigTestPositiveTestContent("alert tcp any any -> any any (msg:\"HTTP URI cap\"; content:one; content:fourty; within:56; sid:1;)", (uint8_t *)"one four nine fourteen twentythree thirtyfive fourtysix fiftysix"); } /** * \test A negative test that checks the combined use of content and negated * content, in the presence of within */ static int SigTest57TestNegatedContent(void) { return SigTestNegativeTestContent("alert tcp any any -> any any (msg:\"HTTP URI cap\"; content:one; content:!fourty; within:56; sid:1;)", (uint8_t *)"one four nine fourteen twentythree thirtyfive fourtysix fiftysix"); } /** * \test A positive test that checks the combined use of content and negated * content, in the presence of distance */ static int SigTest58TestNegatedContent(void) { return SigTestPositiveTestContent("alert tcp any any -> any any (msg:\"HTTP URI cap\"; content:one; content:!fourty; distance:57; sid:1;)", (uint8_t *)"one four nine fourteen twentythree thirtyfive fourtysix fiftysix"); } /** * \test A negative test that checks the combined use of content and negated * content, in the presence of distance */ static int SigTest59TestNegatedContent(void) { return SigTestNegativeTestContent("alert tcp any any -> any any (msg:\"HTTP URI cap\"; content:one; content:!fourty; distance:30; sid:1;)", (uint8_t *)"one four nine fourteen twentythree thirtyfive fourtysix fiftysix"); } static int SigTest60TestNegatedContent(void) { return SigTestNegativeTestContent("alert tcp any any -> any any (msg:\"HTTP URI cap\"; content:!one; content:fourty; sid:1;)", (uint8_t *)"one four nine fourteen twentythree thirtyfive fourtysix fiftysix"); } static int SigTest61TestNegatedContent(void) { return SigTestPositiveTestContent("alert tcp any any -> any any (msg:\"HTTP URI cap\"; content:one; depth:10; content:!fourty; within:30; sid:1;)", (uint8_t *)"one four nine fourteen twentythree thirtyfive fourtysix fiftysix"); } static int SigTest62TestNegatedContent(void) { return SigTestNegativeTestContent("alert tcp any any -> any any (msg:\"HTTP URI cap\"; content:one; depth:10; content:!fourty; within:30; depth:56; sid:1;)", (uint8_t *)"one four nine fourteen twentythree thirtyfive fourtysix fiftysix"); } static int SigTest63TestNegatedContent(void) { return SigTestNegativeTestContent("alert tcp any any -> any any (msg:\"HTTP URI cap\"; content:one; depth:10; content:!fourty; within:56; sid:1;)", (uint8_t *)"one four nine fourteen twentythree thirtyfive fourtysix fiftysix"); } static int SigTest64TestNegatedContent(void) { return SigTestPositiveTestContent("alert tcp any any -> any any (msg:\"HTTP URI cap\"; content:one; depth:10; content:!fourty; within:30; depth:30; sid:1;)", (uint8_t *)"one four nine fourteen twentythree thirtyfive fourtysix fiftysix"); } static int SigTest65TestNegatedContent(void) { return SigTestNegativeTestContent("alert tcp any any -> any any (msg:\"HTTP URI cap\"; content:one; depth:10; content:!fourty; within:30; offset:30; sid:1;)", (uint8_t *)"one four nine fourteen twentythree thirtyfive fourtysix fiftysix"); } static int SigTest66TestNegatedContent(void) { return SigTestPositiveTestContent("alert tcp any any -> any any (msg:\"HTTP URI cap\"; content:one; depth:10; content:!fourty; within:30; offset:56; sid:1;)", (uint8_t *)"one four nine fourteen twentythree thirtyfive fourtysix fiftysix"); } static int SigTest67TestNegatedContent(void) { return SigTestNegativeTestContent("alert tcp any any -> any any (msg:\"HTTP URI cap\"; content:one; depth:10; content:!four; within:56; sid:1;)", (uint8_t *)"one four nine fourteen twentythree thirtyfive fourtysix fiftysix"); } static int SigTest68TestNegatedContent(void) { return SigTestPositiveTestContent("alert tcp any any -> any any (msg:\"HTTP URI cap\"; content:one; depth:10; content:nine; offset:8; content:!fourty; within:28; content:fiftysix; sid:1;)", (uint8_t *)"one four nine fourteen twentythree thirtyfive fourtysix fiftysix"); } static int SigTest69TestNegatedContent(void) { return SigTestNegativeTestContent("alert tcp any any -> any any (msg:\"HTTP URI cap\"; content:one; depth:10; content:nine; offset:8; content:!fourty; within:48; content:fiftysix; sid:1;)", (uint8_t *)"one four nine fourteen twentythree thirtyfive fourtysix fiftysix"); } static int SigTest70TestNegatedContent(void) { return SigTestNegativeTestContent("alert tcp any any -> any any (msg:\"HTTP URI cap\"; content:one; content:!fourty; within:52; distance:45 sid:1;)", (uint8_t *)"one four nine fourteen twentythree thirtyfive fourtysix fiftysix"); } static int SigTest71TestNegatedContent(void) { return SigTestNegativeTestContent("alert tcp any any -> any any (msg:\"HTTP URI cap\"; content:one; content:!fourty; within:40; distance:43; sid:1;)", (uint8_t *)"one four nine fourteen twentythree thirtyfive fourtysix fiftysix"); } static int SigTest72TestNegatedContent(void) { return SigTestNegativeTestContent("alert tcp any any -> any any (msg:\"HTTP URI cap\"; content:one; content:!fourty; within:52; distance:47; sid:1;)", (uint8_t *)"one four nine fourteen twentythree thirtyfive fourtysix fiftysix"); } static int SigTest73TestNegatedContent(void) { return SigTestNegativeTestContent("alert tcp any any -> any any (msg:\"HTTP URI cap\"; content:one; depth:5; content:!twentythree; depth:35; sid:1;)", (uint8_t *)"one four nine fourteen twentythree thirtyfive fourtysix fiftysix"); } static int SigTest74TestNegatedContent(void) { return SigTestPositiveTestContent("alert tcp any any -> any any (msg:\"HTTP URI cap\"; content:\"USER\"; content:!\"PASS\"; sid:1;)", (uint8_t *)"USER apple"); } static int SigTest75TestNegatedContent(void) { return SigTestPositiveTestContent("alert tcp any any -> any any (msg:\"HTTP URI cap\"; content:\"USER\"; content:\"!PASS\"; sid:1;)", (uint8_t *)"USER apple"); } #endif /* UNITTESTS */ /** * \brief this function registers unit tests for DetectContent */ void DetectContentRegisterTests(void) { #ifdef UNITTESTS /* UNITTESTS */ UtRegisterTest("DetectContentParseTest01", DetectContentParseTest01, 1); UtRegisterTest("DetectContentParseTest02", DetectContentParseTest02, 1); UtRegisterTest("DetectContentParseTest03", DetectContentParseTest03, 1); UtRegisterTest("DetectContentParseTest04", DetectContentParseTest04, 1); UtRegisterTest("DetectContentParseTest05", DetectContentParseTest05, 1); UtRegisterTest("DetectContentParseTest06", DetectContentParseTest06, 1); UtRegisterTest("DetectContentParseTest07", DetectContentParseTest07, 1); UtRegisterTest("DetectContentParseTest08", DetectContentParseTest08, 1); UtRegisterTest("DetectContentParseTest09", DetectContentParseTest09, 1); UtRegisterTest("DetectContentParseTest10", DetectContentParseTest10, 1); UtRegisterTest("DetectContentParseTest11", DetectContentParseNegTest11, 1); UtRegisterTest("DetectContentParseTest12", DetectContentParseNegTest12, 1); UtRegisterTest("DetectContentParseTest13", DetectContentParseNegTest13, 1); UtRegisterTest("DetectContentChunkTestB2G01 l=32", DetectContentChunkTestB2G01, 1); UtRegisterTest("DetectContentChunkTestB3G01 l=32", DetectContentChunkTestB3G01, 1); UtRegisterTest("DetectContentChunkTestB2G02 l=33", DetectContentChunkTestB2G02, 1); UtRegisterTest("DetectContentChunkTestB3G02 l=33", DetectContentChunkTestB3G02, 1); UtRegisterTest("DetectContentChunkTestB2G03 l=100", DetectContentChunkTestB2G03, 1); UtRegisterTest("DetectContentChunkTestB3G03 l=100", DetectContentChunkTestB3G03, 1); UtRegisterTest("DetectContentChunkModifiersTest01", DetectContentChunkModifiersTest01, 1); UtRegisterTest("DetectContentChunkModifiersTest02", DetectContentChunkModifiersTest02, 1); /* The reals */ UtRegisterTest("DetectContentChunkMatchTest01", DetectContentChunkMatchTest01, 1); UtRegisterTest("DetectContentChunkMatchTest02", DetectContentChunkMatchTest02, 1); UtRegisterTest("DetectContentChunkMatchTest03", DetectContentChunkMatchTest03, 1); UtRegisterTest("DetectContentChunkMatchTest04", DetectContentChunkMatchTest04, 1); UtRegisterTest("DetectContentChunkMatchTest05", DetectContentChunkMatchTest05, 1); UtRegisterTest("DetectContentChunkMatchTest06", DetectContentChunkMatchTest06, 1); UtRegisterTest("DetectContentChunkMatchTest07", DetectContentChunkMatchTest07, 1); UtRegisterTest("DetectContentChunkMatchTest08", DetectContentChunkMatchTest08, 1); UtRegisterTest("DetectContentChunkMatchTest09", DetectContentChunkMatchTest09, 1); /* Negated content tests */ UtRegisterTest("SigTest41TestNegatedContent", SigTest41TestNegatedContent, 1); UtRegisterTest("SigTest42TestNegatedContent", SigTest42TestNegatedContent, 1); UtRegisterTest("SigTest43TestNegatedContent", SigTest43TestNegatedContent, 1); UtRegisterTest("SigTest44TestNegatedContent", SigTest44TestNegatedContent, 1); UtRegisterTest("SigTest45TestNegatedContent", SigTest45TestNegatedContent, 1); UtRegisterTest("SigTest46TestNegatedContent", SigTest46TestNegatedContent, 1); UtRegisterTest("SigTest47TestNegatedContent", SigTest47TestNegatedContent, 1); UtRegisterTest("SigTest48TestNegatedContent", SigTest48TestNegatedContent, 1); UtRegisterTest("SigTest49TestNegatedContent", SigTest49TestNegatedContent, 1); UtRegisterTest("SigTest50TestNegatedContent", SigTest50TestNegatedContent, 1); UtRegisterTest("SigTest51TestNegatedContent", SigTest51TestNegatedContent, 1); UtRegisterTest("SigTest52TestNegatedContent", SigTest52TestNegatedContent, 1); UtRegisterTest("SigTest53TestNegatedContent", SigTest53TestNegatedContent, 1); UtRegisterTest("SigTest54TestNegatedContent", SigTest54TestNegatedContent, 1); UtRegisterTest("SigTest55TestNegatedContent", SigTest55TestNegatedContent, 1); UtRegisterTest("SigTest56TestNegatedContent", SigTest56TestNegatedContent, 1); UtRegisterTest("SigTest57TestNegatedContent", SigTest57TestNegatedContent, 1); UtRegisterTest("SigTest58TestNegatedContent", SigTest58TestNegatedContent, 1); UtRegisterTest("SigTest59TestNegatedContent", SigTest59TestNegatedContent, 1); UtRegisterTest("SigTest60TestNegatedContent", SigTest60TestNegatedContent, 1); UtRegisterTest("SigTest61TestNegatedContent", SigTest61TestNegatedContent, 1); UtRegisterTest("SigTest62TestNegatedContent", SigTest62TestNegatedContent, 1); UtRegisterTest("SigTest63TestNegatedContent", SigTest63TestNegatedContent, 1); UtRegisterTest("SigTest64TestNegatedContent", SigTest64TestNegatedContent, 1); UtRegisterTest("SigTest65TestNegatedContent", SigTest65TestNegatedContent, 1); UtRegisterTest("SigTest66TestNegatedContent", SigTest66TestNegatedContent, 1); UtRegisterTest("SigTest67TestNegatedContent", SigTest67TestNegatedContent, 1); UtRegisterTest("SigTest68TestNegatedContent", SigTest68TestNegatedContent, 1); UtRegisterTest("SigTest69TestNegatedContent", SigTest69TestNegatedContent, 1); UtRegisterTest("SigTest70TestNegatedContent", SigTest70TestNegatedContent, 1); UtRegisterTest("SigTest71TestNegatedContent", SigTest71TestNegatedContent, 1); UtRegisterTest("SigTest72TestNegatedContent", SigTest72TestNegatedContent, 1); UtRegisterTest("SigTest73TestNegatedContent", SigTest73TestNegatedContent, 1); UtRegisterTest("SigTest74TestNegatedContent", SigTest74TestNegatedContent, 1); UtRegisterTest("SigTest75TestNegatedContent", SigTest75TestNegatedContent, 1); #endif /* UNITTESTS */ }