diff --git a/src/Makefile.am b/src/Makefile.am index ad4daa5309..4f52d37f9b 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -94,6 +94,7 @@ detect-http-cookie.c detect-http-cookie.h \ detect-http-method.c detect-http-method.h \ detect-tls-version.c detect-tls-version.h \ detect-icmp-id.c detect-icmp-id.h \ +detect-icmp-seq.c detect-icmp-seq.h \ detect-dce-iface.c detect-dce-iface.h \ detect-dce-opnum.c detect-dce-opnum.h \ detect-dce-stub-data.c detect-dce-stub-data.h \ diff --git a/src/decode-icmpv4.h b/src/decode-icmpv4.h index 12318dcae6..a864aa60b9 100644 --- a/src/decode-icmpv4.h +++ b/src/decode-icmpv4.h @@ -196,7 +196,7 @@ typedef struct ICMPV4Vars_ #define ICMPV4_GET_ID(p) (p)->icmpv4vars.id /** macro for icmpv4 "seq" access */ /* #define ICMPV4_GET_SEQ(p) (p)->icmpv4h->icmpv4b.icmpv4i.seq */ -#define ICMPV4_GET_SEQ(p) (p)->icmpv4vars.seq +#define ICMPV4_GET_SEQ(p) (ntohs((p)->icmpv4vars.seq)) /** If message is Error */ /** macro for icmpv4 "unused" access */ diff --git a/src/decode-icmpv6.c b/src/decode-icmpv6.c index c20749815a..36512aec3d 100644 --- a/src/decode-icmpv6.c +++ b/src/decode-icmpv6.c @@ -695,7 +695,7 @@ static int ICMPV6EchoReqTest01(void) SCLogDebug("ID: %u seq: %u", ICMPV6_GET_ID(&p), ICMPV6_GET_SEQ(&p)); if (ICMPV6_GET_TYPE(&p) != 128 || ICMPV6_GET_CODE(&p) != 0 || - ICMPV6_GET_ID(&p) != 61477 || ICMPV6_GET_SEQ(&p) != 9077) { + ICMPV6_GET_ID(&p) != 61477 || ICMPV6_GET_SEQ(&p) != 29987) { SCLogDebug("ICMPv6 Echo request decode failed"); retval = 0; goto end; @@ -745,7 +745,7 @@ static int ICMPV6EchoRepTest01(void) ICMPV6_GET_CODE(&p),ICMPV6_GET_ID(&p), ICMPV6_GET_SEQ(&p)); if (ICMPV6_GET_TYPE(&p) != 129 || ICMPV6_GET_CODE(&p) != 0 || - ICMPV6_GET_ID(&p) != 61477 || ICMPV6_GET_SEQ(&p) != 9077) { + ICMPV6_GET_ID(&p) != 61477 || ICMPV6_GET_SEQ(&p) != 29987) { SCLogDebug("ICMPv6 Echo reply decode failed"); retval = 0; goto end; diff --git a/src/decode-icmpv6.h b/src/decode-icmpv6.h index 8e8446d15b..f453742e38 100644 --- a/src/decode-icmpv6.h +++ b/src/decode-icmpv6.h @@ -61,7 +61,7 @@ #define ICMPV6_GET_ID(p) (p)->icmpv6vars.id /** macro for icmpv6 "seq" access */ /* #define ICMPV6_GET_SEQ(p) (p)->icmpv6h->icmpv6b.icmpv6i.seq */ -#define ICMPV6_GET_SEQ(p) (p)->icmpv6vars.seq +#define ICMPV6_GET_SEQ(p) (ntohs((p)->icmpv6vars.seq)) /** If message is Error */ /** macro for icmpv6 "unused" access */ diff --git a/src/detect-icmp-seq.c b/src/detect-icmp-seq.c new file mode 100644 index 0000000000..cf6aed2347 --- /dev/null +++ b/src/detect-icmp-seq.c @@ -0,0 +1,362 @@ +/* Copyright (c) 2009 Open Information Security Foundation */ + +/** \file + * \author Breno Silva + */ + +#include "suricata-common.h" +#include "debug.h" +#include "decode.h" +#include "detect.h" + +#include "detect-icmp-seq.h" + +#include "util-byte.h" +#include "util-unittest.h" +#include "util-debug.h" + +#define PARSE_REGEX "^\\s*(\"\\s*)?([0-9]+)(\\s*\")?\\s*$" + +static pcre *parse_regex; +static pcre_extra *parse_regex_study; + +int DetectIcmpSeqMatch(ThreadVars *, DetectEngineThreadCtx *, Packet *, Signature *, SigMatch *); +int DetectIcmpSeqSetup(DetectEngineCtx *, Signature *, SigMatch *, char *); +void DetectIcmpSeqRegisterTests(void); +void DetectIcmpSeqFree(void *); + +/** + * \brief Registration function for icmp_seq + */ +void DetectIcmpSeqRegister (void) { + sigmatch_table[DETECT_ICMP_SEQ].name = "icmp_seq"; + sigmatch_table[DETECT_ICMP_SEQ].Match = DetectIcmpSeqMatch; + sigmatch_table[DETECT_ICMP_SEQ].Setup = DetectIcmpSeqSetup; + sigmatch_table[DETECT_ICMP_SEQ].Free = DetectIcmpSeqFree; + sigmatch_table[DETECT_ICMP_SEQ].RegisterTests = DetectIcmpSeqRegisterTests; + + const char *eb; + int eo; + int opts = 0; + + parse_regex = pcre_compile(PARSE_REGEX, opts, &eb, &eo, NULL); + if (parse_regex == NULL) { + SCLogError(SC_PCRE_COMPILE_FAILED,"pcre compile of \"%s\" failed at offset %" PRId32 ": %s", PARSE_REGEX, eo, eb); + goto error; + } + + parse_regex_study = pcre_study(parse_regex, 0, &eb); + if (eb != NULL) { + SCLogError(SC_PCRE_STUDY_FAILED,"pcre study failed: %s", eb); + goto error; + } + return; + +error: + return; +} + +/** + * \brief This function is used to match icmp_seq rule option set on a packet + * + * \param t pointer to thread vars + * \param det_ctx pointer to the pattern matcher thread + * \param p pointer to the current packet + * \param m pointer to the sigmatch that we will cast into DetectIcmpSeqData + * + * \retval 0 no match + * \retval 1 match + */ +int DetectIcmpSeqMatch (ThreadVars *t, DetectEngineThreadCtx *det_ctx, Packet *p, Signature *s, SigMatch *m) { + uint16_t seqn; + DetectIcmpSeqData *iseq = (DetectIcmpSeqData *)m->ctx; + + if (PKT_IS_ICMPV4(p)) { + switch (ICMPV4_GET_TYPE(p)){ + case ICMP_ECHOREPLY: + case ICMP_ECHO: + case ICMP_TIMESTAMP: + case ICMP_TIMESTAMPREPLY: + case ICMP_INFO_REQUEST: + case ICMP_INFO_REPLY: + case ICMP_ADDRESS: + case ICMP_ADDRESSREPLY: + seqn = ICMPV4_GET_SEQ(p); + break; + default: + SCLogDebug("Packet has no seq field"); + return 0; + } + } else if (PKT_IS_ICMPV6(p)) { + switch (ICMPV6_GET_TYPE(p)) { + case ICMP6_ECHO_REQUEST: + case ICMP6_ECHO_REPLY: + seqn = ICMPV6_GET_SEQ(p); + break; + default: + SCLogDebug("Packet has no seq field"); + return 0; + } + } else { + SCLogDebug("Packet not ICMPV4 nor ICMPV6"); + return 0; + } + + if (seqn == iseq->seq) return 1; + + return 0; +} + +/** + * \brief This function is used to parse icmp_seq option passed via icmp_seq: keyword + * + * \param icmpseqstr Pointer to the user provided icmp_seq options + * + * \retval iseq pointer to DetectIcmpSeqData on success + * \retval NULL on failure + */ +DetectIcmpSeqData *DetectIcmpSeqParse (char *icmpseqstr) { + DetectIcmpSeqData *iseq = NULL; + char *substr[3] = {NULL, NULL, NULL}; +#define MAX_SUBSTRINGS 30 + int ret = 0, res = 0; + int ov[MAX_SUBSTRINGS]; + int i; + const char *str_ptr; + + ret = pcre_exec(parse_regex, parse_regex_study, icmpseqstr, strlen(icmpseqstr), 0, 0, ov, MAX_SUBSTRINGS); + if (ret < 1 || ret > 4) { + SCLogError(SC_PCRE_MATCH_FAILED,"Parse error %s", icmpseqstr); + goto error; + } + + for (i = 1; i < ret; i++) { + res = pcre_get_substring((char *)icmpseqstr, ov, MAX_SUBSTRINGS, i, &str_ptr); + if (res < 0) { + SCLogError(SC_PCRE_GET_SUBSTRING_FAILED,"pcre_get_substring failed"); + goto error; + } + substr[i-1] = (char *)str_ptr; + } + + iseq = malloc(sizeof(DetectIcmpSeqData)); + if (iseq == NULL) { + SCLogError(SC_ERR_MEM_ALLOC, "Error allocating memory"); + goto error; + } + + iseq->seq = 0; + + if (strlen(substr[0]) != 0) { + if (substr[2] == NULL) { + SCLogError(SC_ERR_MISSING_QUOTE,"Missing quote in input"); + goto error; + } + } else { + if (substr[2] != NULL) { + SCLogError(SC_ERR_MISSING_QUOTE,"Missing quote in input"); + goto error; + } + } + + ByteExtractStringUint16(&iseq->seq, 10, 0, substr[1]); + + for (i = 0; i < 3; i++) { + if (substr[i] != NULL) free(substr[i]); + } + + return iseq; + +error: + for (i = 0; i < 3; i++) { + if (substr[i] != NULL) free(substr[i]); + } + if (iseq != NULL) DetectIcmpSeqFree(iseq); + return NULL; + +} + +/** + * \brief this function is used to add the parsed icmp_seq data into the current signature + * + * \param de_ctx pointer to the Detection Engine Context + * \param s pointer to the Current Signature + * \param m pointer to the Current SigMatch + * \param icmpseqstr pointer to the user provided icmp_seq option + * + * \retval 0 on Success + * \retval -1 on Failure + */ +int DetectIcmpSeqSetup (DetectEngineCtx *de_ctx, Signature *s, SigMatch *m, char *icmpseqstr) { + DetectIcmpSeqData *iseq = NULL; + SigMatch *sm = NULL; + + iseq = DetectIcmpSeqParse(icmpseqstr); + if (iseq == NULL) goto error; + + sm = SigMatchAlloc(); + if (sm == NULL) goto error; + + sm->type = DETECT_ICMP_SEQ; + sm->ctx = (void *)iseq; + + SigMatchAppend(s, m, sm); + + return 0; + +error: + if (iseq != NULL) DetectIcmpSeqFree(iseq); + if (sm != NULL) free(sm); + return -1; + +} + +/** + * \brief this function will free memory associated with DetectIcmpSeqData + * + * \param ptr pointer to DetectIcmpSeqData + */ +void DetectIcmpSeqFree (void *ptr) { + DetectIcmpSeqData *iseq = (DetectIcmpSeqData *)ptr; + free(iseq); +} + +#ifdef UNITTESTS + +#include "detect-parse.h" +#include "detect-engine.h" +#include "detect-engine-mpm.h" + +/** + * \test DetectIcmpSeqParseTest01 is a test for setting a valid icmp_seq value + */ +int DetectIcmpSeqParseTest01 (void) { + DetectIcmpSeqData *iseq = NULL; + iseq = DetectIcmpSeqParse("300"); + if (iseq != NULL && iseq->seq == 300) { + DetectIcmpSeqFree(iseq); + return 1; + } + return 0; +} + +/** + * \test DetectIcmpSeqParseTest02 is a test for setting a valid icmp_seq value + * with spaces all around + */ +int DetectIcmpSeqParseTest02 (void) { + DetectIcmpSeqData *iseq = NULL; + iseq = DetectIcmpSeqParse(" 300 "); + if (iseq != NULL && iseq->seq == 300) { + DetectIcmpSeqFree(iseq); + return 1; + } + return 0; +} + +/** + * \test DetectIcmpSeqParseTest03 is a test for setting an invalid icmp_seq value + */ +int DetectIcmpSeqParseTest03 (void) { + DetectIcmpSeqData *iseq = NULL; + iseq = DetectIcmpSeqParse("badc"); + if (iseq != NULL) { + DetectIcmpSeqFree(iseq); + return 1; + } + return 0; +} + +/** + * \test DetectIcmpSeqMatchTest01 is a test for checking the working of + * icmp_seq keyword by creating 2 rules and matching a crafted packet + * against them. Only the first one shall trigger. + */ +int DetectIcmpSeqMatchTest01 (void) { + int result = 0; + + uint8_t raw_icmpv4[] = { + 0x08, 0x00, 0x42, 0xb4, 0x02, 0x00, 0x08, 0xa8, + 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, + 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, + 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x61, + 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69}; + Packet p; + Signature *s = NULL; + DecodeThreadVars dtv; + ThreadVars th_v; + DetectEngineThreadCtx *det_ctx = NULL; + IPV4Hdr ip4h; + + memset(&p, 0, sizeof(Packet)); + memset(&ip4h, 0, sizeof(IPV4Hdr)); + memset(&dtv, 0, sizeof(DecodeThreadVars)); + memset(&th_v, 0, sizeof(ThreadVars)); + + FlowInitConfig(FLOW_QUIET); + + p.src.family = AF_INET; + p.dst.family = AF_INET; + p.src.addr_data32[0] = 0x01020304; + p.dst.addr_data32[0] = 0x04030201; + + ip4h.ip_src.s_addr = p.src.addr_data32[0]; + ip4h.ip_dst.s_addr = p.dst.addr_data32[0]; + p.ip4h = &ip4h; + + DecodeICMPV4(&th_v, &dtv, &p, raw_icmpv4, sizeof(raw_icmpv4), NULL); + + DetectEngineCtx *de_ctx = DetectEngineCtxInit(); + if (de_ctx == NULL) { + goto end; + } + + de_ctx->flags |= DE_QUIET; + + s = de_ctx->sig_list = SigInit(de_ctx, "alert icmp any any -> any any (icmp_seq:2216; sid:1;)"); + if (s == NULL) { + goto end; + } + + s = s->next = SigInit(de_ctx, "alert icmp any any -> any any (icmp_seq:5000; sid:2;)"); + if (s == NULL) { + goto end; + } + + SigGroupBuild(de_ctx); + DetectEngineThreadCtxInit(&th_v, (void *)de_ctx, (void *)&det_ctx); + + SigMatchSignatures(&th_v, de_ctx, det_ctx, &p); + if (PacketAlertCheck(&p, 1) == 0) { + printf("sid 1 did not alert, but should have: "); + goto cleanup; + } else if (PacketAlertCheck(&p, 2)) { + printf("sid 2 alerted, but should not have: "); + goto cleanup; + } + + result = 1; + +cleanup: + SigGroupCleanup(de_ctx); + SigCleanSignatures(de_ctx); + + DetectEngineThreadCtxDeinit(&th_v, (void *)det_ctx); + DetectEngineCtxFree(de_ctx); + + FlowShutdown(); +end: + return result; + +} +#endif /* UNITTESTS */ + +void DetectIcmpSeqRegisterTests (void) { +#ifdef UNITTESTS + UtRegisterTest("DetectIcmpSeqParseTest01", DetectIcmpSeqParseTest01, 1); + UtRegisterTest("DetectIcmpSeqParseTest02", DetectIcmpSeqParseTest02, 1); + UtRegisterTest("DetectIcmpSeqParseTest03", DetectIcmpSeqParseTest03, 0); + UtRegisterTest("DetectIcmpSeqMatchTest01", DetectIcmpSeqMatchTest01, 1); +#endif /* UNITTESTS */ +} + diff --git a/src/detect-icmp-seq.h b/src/detect-icmp-seq.h new file mode 100644 index 0000000000..3e4cd6dddc --- /dev/null +++ b/src/detect-icmp-seq.h @@ -0,0 +1,17 @@ +/* Copyright (c) 2009 Open Information Security Foundation */ + +/** \file + * \author Breno Silva + */ + +#ifndef __DETECT_ICMP_SEQ_H__ +#define __DETECT_ICMP_SEQ_H__ + +typedef struct DetectIcmpSeqData_ { + uint16_t seq; +} DetectIcmpSeqData; + +/* prototypes */ +void DetectIcmpSeqRegister(void); + +#endif /* __DETECT_ICMP_SEQ__ */ diff --git a/src/detect.c b/src/detect.c index 1c304bf41a..a85c45702e 100644 --- a/src/detect.c +++ b/src/detect.c @@ -70,6 +70,7 @@ #include "detect-itype.h" #include "detect-icode.h" #include "detect-icmp-id.h" +#include "detect-icmp-seq.h" #include "detect-dce-iface.h" #include "detect-dce-opnum.h" #include "detect-dce-stub-data.h" @@ -3021,6 +3022,7 @@ void SigTableSetup(void) { DetectITypeRegister(); DetectICodeRegister(); DetectIcmpIdRegister(); + DetectIcmpSeqRegister(); DetectDceIfaceRegister(); DetectDceOpnumRegister(); DetectDceStubDataRegister(); diff --git a/src/detect.h b/src/detect.h index 2f3256a5f3..af4bac660c 100644 --- a/src/detect.h +++ b/src/detect.h @@ -485,6 +485,7 @@ enum { DETECT_ITYPE, DETECT_ICODE, DETECT_ICMP_ID, + DETECT_ICMP_SEQ, DETECT_ADDRESS, DETECT_PROTO, diff --git a/src/util-error.c b/src/util-error.c index df6371511b..ca948e5738 100644 --- a/src/util-error.c +++ b/src/util-error.c @@ -74,6 +74,7 @@ const char * SCErrorToString(SCError err) CASE_CODE (SC_ERR_FOPEN_ERROR); CASE_CODE (SC_ERR_LOGDIR_CONFIG); CASE_CODE (SC_ERR_LOGDIR_CMDLINE); + CASE_CODE (SC_ERR_MISSING_QUOTE); default: return "UNKNOWN_ERROR"; } diff --git a/src/util-error.h b/src/util-error.h index 5ca8bd0adc..f4fda33d0a 100644 --- a/src/util-error.h +++ b/src/util-error.h @@ -91,6 +91,7 @@ typedef enum { SC_ERR_LOGDIR_CONFIG, SC_ERR_LOGDIR_CMDLINE, SC_RADIX_TREE_GENERIC_ERROR, + SC_ERR_MISSING_QUOTE, } SCError; const char *SCErrorToString(SCError);