diff options
Diffstat (limited to 'framework/src/suricata/src/detect-engine-content-inspection.c')
-rw-r--r-- | framework/src/suricata/src/detect-engine-content-inspection.c | 576 |
1 files changed, 576 insertions, 0 deletions
diff --git a/framework/src/suricata/src/detect-engine-content-inspection.c b/framework/src/suricata/src/detect-engine-content-inspection.c new file mode 100644 index 00000000..a434ca5a --- /dev/null +++ b/framework/src/suricata/src/detect-engine-content-inspection.c @@ -0,0 +1,576 @@ +/* Copyright (C) 2007-2011 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 Anoop Saldanha <anoopsaldanha@gmail.com> + * + * Performs content inspection on any buffer supplied. + */ + +#include "suricata-common.h" +#include "suricata.h" + +#include "decode.h" + +#include "detect.h" +#include "detect-engine.h" +#include "detect-parse.h" +#include "detect-content.h" +#include "detect-pcre.h" +#include "detect-isdataat.h" +#include "detect-bytetest.h" +#include "detect-bytejump.h" +#include "detect-byte-extract.h" +#include "detect-replace.h" +#include "detect-engine-content-inspection.h" +#include "detect-uricontent.h" +#include "detect-urilen.h" +#include "detect-lua.h" + +#include "app-layer-dcerpc.h" + +#include "util-spm.h" +#include "util-spm-bm.h" +#include "util-debug.h" +#include "util-print.h" + +#include "util-unittest.h" +#include "util-unittest-helper.h" +#include "util-profiling.h" + +#ifdef HAVE_LUA +#include "util-lua.h" +#endif + +/** + * \brief Run the actual payload match functions + * + * The following keywords are inspected: + * - content, including all the http and dce modified contents + * - isdaatat + * - pcre + * - bytejump + * - bytetest + * - byte_extract + * - urilen + * - + * + * All keywords are evaluated against the buffer with buffer_len. + * + * For accounting the last match in relative matching the + * det_ctx->buffer_offset int is used. + * + * \param de_ctx Detection engine context + * \param det_ctx Detection engine thread context + * \param s Signature to inspect + * \param sm SigMatch to inspect + * \param f Flow (for pcre flowvar storage) + * \param buffer Ptr to the buffer to inspect + * \param buffer_len Length of the payload + * \param stream_start_offset Indicates the start of the current buffer in + * the whole buffer stream inspected. This + * applies if the current buffer is inspected + * in chunks. + * \param inspection_mode Refers to the engine inspection mode we are currently + * inspecting. Can be payload, stream, one of the http + * buffer inspection modes or dce inspection mode. + * \param data Used to send some custom data. For example in + * payload inspection mode, data contains packet ptr, + * and under dce inspection mode, contains dce state. + * + * \retval 0 no match + * \retval 1 match + */ +int DetectEngineContentInspection(DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx, + Signature *s, SigMatch *sm, + Flow *f, + uint8_t *buffer, uint32_t buffer_len, + uint32_t stream_start_offset, + uint8_t inspection_mode, void *data) +{ + SCEnter(); + KEYWORD_PROFILING_START; + + det_ctx->inspection_recursion_counter++; + + if (det_ctx->inspection_recursion_counter == de_ctx->inspection_recursion_limit) { + det_ctx->discontinue_matching = 1; + KEYWORD_PROFILING_END(det_ctx, sm->type, 0); + SCReturnInt(0); + } + + if (sm == NULL || buffer_len == 0) { + KEYWORD_PROFILING_END(det_ctx, sm->type, 0); + SCReturnInt(0); + } + + /* \todo unify this which is phase 2 of payload inspection unification */ + if (sm->type == DETECT_CONTENT) { + + DetectContentData *cd = (DetectContentData *)sm->ctx; + SCLogDebug("inspecting content %"PRIu32" buffer_len %"PRIu32, cd->id, buffer_len); + + /* we might have already have this content matched by the mpm. + * (if there is any other reason why we'd want to avoid checking + * it here, please fill it in) */ + //if (cd->flags & DETECT_CONTENT_NO_DOUBLE_INSPECTION_REQUIRED) { + // goto match; + //} + + /* rule parsers should take care of this */ +#ifdef DEBUG + BUG_ON(cd->depth != 0 && cd->depth <= cd->offset); +#endif + + /* search for our pattern, checking the matches recursively. + * if we match we look for the next SigMatch as well */ + uint8_t *found = NULL; + uint32_t offset = 0; + uint32_t depth = buffer_len; + uint32_t prev_offset = 0; /**< used in recursive searching */ + uint32_t prev_buffer_offset = det_ctx->buffer_offset; + + do { + if ((cd->flags & DETECT_CONTENT_DISTANCE) || + (cd->flags & DETECT_CONTENT_WITHIN)) { + SCLogDebug("det_ctx->buffer_offset %"PRIu32, det_ctx->buffer_offset); + + offset = prev_buffer_offset; + depth = buffer_len; + + int distance = cd->distance; + if (cd->flags & DETECT_CONTENT_DISTANCE) { + if (cd->flags & DETECT_CONTENT_DISTANCE_BE) { + distance = det_ctx->bj_values[cd->distance]; + } + if (distance < 0 && (uint32_t)(abs(distance)) > offset) + offset = 0; + else + offset += distance; + + SCLogDebug("cd->distance %"PRIi32", offset %"PRIu32", depth %"PRIu32, + distance, offset, depth); + } + + if (cd->flags & DETECT_CONTENT_WITHIN) { + if (cd->flags & DETECT_CONTENT_WITHIN_BE) { + if ((int32_t)depth > (int32_t)(prev_buffer_offset + det_ctx->bj_values[cd->within] + distance)) { + depth = prev_buffer_offset + det_ctx->bj_values[cd->within] + distance; + } + } else { + if ((int32_t)depth > (int32_t)(prev_buffer_offset + cd->within + distance)) { + depth = prev_buffer_offset + cd->within + distance; + } + + SCLogDebug("cd->within %"PRIi32", det_ctx->buffer_offset %"PRIu32", depth %"PRIu32, + cd->within, prev_buffer_offset, depth); + } + + if (stream_start_offset != 0 && prev_buffer_offset == 0) { + if (depth <= stream_start_offset) { + goto no_match; + } else if (depth >= (stream_start_offset + buffer_len)) { + ; + } else { + depth = depth - stream_start_offset; + } + } + } + + if (cd->flags & DETECT_CONTENT_DEPTH_BE) { + if ((det_ctx->bj_values[cd->depth] + prev_buffer_offset) < depth) { + depth = prev_buffer_offset + det_ctx->bj_values[cd->depth]; + } + } else { + if (cd->depth != 0) { + if ((cd->depth + prev_buffer_offset) < depth) { + depth = prev_buffer_offset + cd->depth; + } + + SCLogDebug("cd->depth %"PRIu32", depth %"PRIu32, cd->depth, depth); + } + } + + if (cd->flags & DETECT_CONTENT_OFFSET_BE) { + if (det_ctx->bj_values[cd->offset] > offset) + offset = det_ctx->bj_values[cd->offset]; + } else { + if (cd->offset > offset) { + offset = cd->offset; + SCLogDebug("setting offset %"PRIu32, offset); + } + } + } else { /* implied no relative matches */ + /* set depth */ + if (cd->flags & DETECT_CONTENT_DEPTH_BE) { + depth = det_ctx->bj_values[cd->depth]; + } else { + if (cd->depth != 0) { + depth = cd->depth; + } + } + + if (stream_start_offset != 0 && cd->flags & DETECT_CONTENT_DEPTH) { + if (depth <= stream_start_offset) { + goto no_match; + } else if (depth >= (stream_start_offset + buffer_len)) { + ; + } else { + depth = depth - stream_start_offset; + } + } + + /* set offset */ + if (cd->flags & DETECT_CONTENT_OFFSET_BE) + offset = det_ctx->bj_values[cd->offset]; + else + offset = cd->offset; + prev_buffer_offset = 0; + } + + /* update offset with prev_offset if we're searching for + * matches after the first occurence. */ + SCLogDebug("offset %"PRIu32", prev_offset %"PRIu32, offset, prev_offset); + if (prev_offset != 0) + offset = prev_offset; + + SCLogDebug("offset %"PRIu32", depth %"PRIu32, offset, depth); + + if (depth > buffer_len) + depth = buffer_len; + + /* if offset is bigger than depth we can never match on a pattern. + * We can however, "match" on a negated pattern. */ + if (offset > depth || depth == 0) { + if (cd->flags & DETECT_CONTENT_NEGATED) { + goto match; + } else { + goto no_match; + } + } + + uint8_t *sbuffer = buffer + offset; + uint32_t sbuffer_len = depth - offset; + uint32_t match_offset = 0; + SCLogDebug("sbuffer_len %"PRIu32, sbuffer_len); +#ifdef DEBUG + BUG_ON(sbuffer_len > buffer_len); +#endif + + /* \todo Add another optimization here. If cd->content_len is + * greater than sbuffer_len found is anyways NULL */ + + /* do the actual search */ + if (cd->flags & DETECT_CONTENT_NOCASE) + found = BoyerMooreNocase(cd->content, cd->content_len, sbuffer, sbuffer_len, cd->bm_ctx); + else + found = BoyerMoore(cd->content, cd->content_len, sbuffer, sbuffer_len, cd->bm_ctx); + + /* next we evaluate the result in combination with the + * negation flag. */ + SCLogDebug("found %p cd negated %s", found, cd->flags & DETECT_CONTENT_NEGATED ? "true" : "false"); + + if (found == NULL && !(cd->flags & DETECT_CONTENT_NEGATED)) { + goto no_match; + } else if (found == NULL && (cd->flags & DETECT_CONTENT_NEGATED)) { + goto match; + } else if (found != NULL && (cd->flags & DETECT_CONTENT_NEGATED)) { + SCLogDebug("content %"PRIu32" matched at offset %"PRIu32", but negated so no match", cd->id, match_offset); + /* don't bother carrying recursive matches now, for preceding + * relative keywords */ + if (DETECT_CONTENT_IS_SINGLE(cd)) + det_ctx->discontinue_matching = 1; + goto no_match; + } else { + match_offset = (uint32_t)((found - buffer) + cd->content_len); + SCLogDebug("content %"PRIu32" matched at offset %"PRIu32"", cd->id, match_offset); + det_ctx->buffer_offset = match_offset; + + /* Match branch, add replace to the list if needed */ + if (cd->flags & DETECT_CONTENT_REPLACE) { + if (inspection_mode == DETECT_ENGINE_CONTENT_INSPECTION_MODE_PAYLOAD) { + /* we will need to replace content if match is confirmed */ + det_ctx->replist = DetectReplaceAddToList(det_ctx->replist, found, cd); + } else { + SCLogWarning(SC_ERR_INVALID_VALUE, "Can't modify payload without packet"); + } + } + if (!(cd->flags & DETECT_CONTENT_RELATIVE_NEXT)) { + SCLogDebug("no relative match coming up, so this is a match"); + goto match; + } + + /* bail out if we have no next match. Technically this is an + * error, as the current cd has the DETECT_CONTENT_RELATIVE_NEXT + * flag set. */ + if (sm->next == NULL) { + goto no_match; + } + + SCLogDebug("content %"PRIu32, cd->id); + KEYWORD_PROFILING_END(det_ctx, sm->type, 1); + + /* see if the next buffer keywords match. If not, we will + * search for another occurence of this content and see + * if the others match then until we run out of matches */ + int r = DetectEngineContentInspection(de_ctx, det_ctx, s, sm->next, f, buffer, buffer_len, stream_start_offset, inspection_mode, data); + if (r == 1) { + SCReturnInt(1); + } + + if (det_ctx->discontinue_matching) + goto no_match; + + /* set the previous match offset to the start of this match + 1 */ + prev_offset = (match_offset - (cd->content_len - 1)); + SCLogDebug("trying to see if there is another match after prev_offset %"PRIu32, prev_offset); + } + + } while(1); + + } else if (sm->type == DETECT_ISDATAAT) { + SCLogDebug("inspecting isdataat"); + + DetectIsdataatData *id = (DetectIsdataatData *)sm->ctx; + if (id->flags & ISDATAAT_RELATIVE) { + if (det_ctx->buffer_offset + id->dataat > buffer_len) { + SCLogDebug("det_ctx->buffer_offset + id->dataat %"PRIu32" > %"PRIu32, det_ctx->buffer_offset + id->dataat, buffer_len); + if (id->flags & ISDATAAT_NEGATED) + goto match; + goto no_match; + } else { + SCLogDebug("relative isdataat match"); + if (id->flags & ISDATAAT_NEGATED) + goto no_match; + goto match; + } + } else { + if (id->dataat < buffer_len) { + SCLogDebug("absolute isdataat match"); + if (id->flags & ISDATAAT_NEGATED) + goto no_match; + goto match; + } else { + SCLogDebug("absolute isdataat mismatch, id->isdataat %"PRIu32", buffer_len %"PRIu32"", id->dataat, buffer_len); + if (id->flags & ISDATAAT_NEGATED) + goto match; + goto no_match; + } + } + + } else if (sm->type == DETECT_PCRE) { + SCLogDebug("inspecting pcre"); + DetectPcreData *pe = (DetectPcreData *)sm->ctx; + uint32_t prev_buffer_offset = det_ctx->buffer_offset; + uint32_t prev_offset = 0; + int r = 0; + + det_ctx->pcre_match_start_offset = 0; + do { + Packet *p = NULL; + if (inspection_mode == DETECT_ENGINE_CONTENT_INSPECTION_MODE_PAYLOAD) + p = (Packet *)data; + r = DetectPcrePayloadMatch(det_ctx, s, sm, p, f, + buffer, buffer_len); + if (r == 0) { + goto no_match; + } + + if (!(pe->flags & DETECT_PCRE_RELATIVE_NEXT)) { + SCLogDebug("no relative match coming up, so this is a match"); + goto match; + } + KEYWORD_PROFILING_END(det_ctx, sm->type, 1); + + /* save it, in case we need to do a pcre match once again */ + prev_offset = det_ctx->pcre_match_start_offset; + + /* see if the next payload keywords match. If not, we will + * search for another occurence of this pcre and see + * if the others match, until we run out of matches */ + r = DetectEngineContentInspection(de_ctx, det_ctx, s, sm->next, + f, buffer, buffer_len, stream_start_offset, inspection_mode, data); + if (r == 1) { + SCReturnInt(1); + } + + if (det_ctx->discontinue_matching) + goto no_match; + + det_ctx->buffer_offset = prev_buffer_offset; + det_ctx->pcre_match_start_offset = prev_offset; + } while (1); + + } else if (sm->type == DETECT_BYTETEST) { + DetectBytetestData *btd = (DetectBytetestData *)sm->ctx; + uint8_t flags = btd->flags; + int32_t offset = btd->offset; + uint64_t value = btd->value; + if (flags & DETECT_BYTETEST_OFFSET_BE) { + offset = det_ctx->bj_values[offset]; + } + if (flags & DETECT_BYTETEST_VALUE_BE) { + value = det_ctx->bj_values[value]; + } + + /* if we have dce enabled we will have to use the endianness + * specified by the dce header */ + if (flags & DETECT_BYTETEST_DCE) { + DCERPCState *dcerpc_state = (DCERPCState *)data; + /* enable the endianness flag temporarily. once we are done + * processing we reset the flags to the original value*/ + flags |= ((dcerpc_state->dcerpc.dcerpchdr.packed_drep[0] & 0x10) ? + DETECT_BYTETEST_LITTLE: 0); + } + + if (DetectBytetestDoMatch(det_ctx, s, sm->ctx, buffer, buffer_len, flags, + offset, value) != 1) { + goto no_match; + } + + goto match; + + } else if (sm->type == DETECT_BYTEJUMP) { + DetectBytejumpData *bjd = (DetectBytejumpData *)sm->ctx; + uint8_t flags = bjd->flags; + int32_t offset = bjd->offset; + + if (flags & DETECT_BYTEJUMP_OFFSET_BE) { + offset = det_ctx->bj_values[offset]; + } + + /* if we have dce enabled we will have to use the endianness + * specified by the dce header */ + if (flags & DETECT_BYTEJUMP_DCE) { + DCERPCState *dcerpc_state = (DCERPCState *)data; + /* enable the endianness flag temporarily. once we are done + * processing we reset the flags to the original value*/ + flags |= ((dcerpc_state->dcerpc.dcerpchdr.packed_drep[0] & 0x10) ? + DETECT_BYTEJUMP_LITTLE: 0); + } + + if (DetectBytejumpDoMatch(det_ctx, s, sm->ctx, buffer, buffer_len, + flags, offset) != 1) { + goto no_match; + } + + goto match; + + } else if (sm->type == DETECT_BYTE_EXTRACT) { + + DetectByteExtractData *bed = (DetectByteExtractData *)sm->ctx; + uint8_t endian = bed->endian; + + /* if we have dce enabled we will have to use the endianness + * specified by the dce header */ + if ((bed->flags & DETECT_BYTE_EXTRACT_FLAG_ENDIAN) && + endian == DETECT_BYTE_EXTRACT_ENDIAN_DCE) { + + DCERPCState *dcerpc_state = (DCERPCState *)data; + /* enable the endianness flag temporarily. once we are done + * processing we reset the flags to the original value*/ + endian |= ((dcerpc_state->dcerpc.dcerpchdr.packed_drep[0] == 0x10) ? + DETECT_BYTE_EXTRACT_ENDIAN_LITTLE : DETECT_BYTE_EXTRACT_ENDIAN_BIG); + } + + if (DetectByteExtractDoMatch(det_ctx, sm, s, buffer, + buffer_len, + &det_ctx->bj_values[bed->local_id], + endian) != 1) { + goto no_match; + } + + goto match; + + /* we should never get here, but bail out just in case */ + } else if (sm->type == DETECT_AL_URILEN) { + SCLogDebug("inspecting uri len"); + + int r = 0; + DetectUrilenData *urilend = (DetectUrilenData *) sm->ctx; + + switch (urilend->mode) { + case DETECT_URILEN_EQ: + if (buffer_len == urilend->urilen1) + r = 1; + break; + case DETECT_URILEN_LT: + if (buffer_len < urilend->urilen1) + r = 1; + break; + case DETECT_URILEN_GT: + if (buffer_len > urilend->urilen1) + r = 1; + break; + case DETECT_URILEN_RA: + if (buffer_len > urilend->urilen1 && + buffer_len < urilend->urilen2) { + r = 1; + } + break; + } + + if (r == 1) { + goto match; + } + + det_ctx->discontinue_matching = 0; + + goto no_match; +#ifdef HAVE_LUA + } + else if (sm->type == DETECT_LUA) { + SCLogDebug("lua starting"); + /* for flowvar gets and sets we need to know the flow's lock status */ + int flow_lock = LUA_FLOW_LOCKED_BY_PARENT; + if (inspection_mode <= DETECT_ENGINE_CONTENT_INSPECTION_MODE_STREAM) + flow_lock = LUA_FLOW_NOT_LOCKED_BY_PARENT; + + if (DetectLuaMatchBuffer(det_ctx, s, sm, buffer, buffer_len, + det_ctx->buffer_offset, f, flow_lock) != 1) + { + SCLogDebug("lua no_match"); + goto no_match; + } + SCLogDebug("lua match"); + goto match; +#endif /* HAVE_LUA */ + } else { + SCLogDebug("sm->type %u", sm->type); +#ifdef DEBUG + BUG_ON(1); +#endif + } + +no_match: + KEYWORD_PROFILING_END(det_ctx, sm->type, 0); + SCReturnInt(0); + +match: + /* this sigmatch matched, inspect the next one. If it was the last, + * the buffer portion of the signature matched. */ + if (sm->next != NULL) { + KEYWORD_PROFILING_END(det_ctx, sm->type, 1); + int r = DetectEngineContentInspection(de_ctx, det_ctx, s, sm->next, f, buffer, buffer_len, stream_start_offset, inspection_mode, data); + SCReturnInt(r); + } else { + KEYWORD_PROFILING_END(det_ctx, sm->type, 1); + SCReturnInt(1); + } +} |