diff options
Diffstat (limited to '')
-rw-r--r-- | src/detect-base64-decode.c | 617 |
1 files changed, 617 insertions, 0 deletions
diff --git a/src/detect-base64-decode.c b/src/detect-base64-decode.c new file mode 100644 index 0000000..25fdf10 --- /dev/null +++ b/src/detect-base64-decode.c @@ -0,0 +1,617 @@ +/* Copyright (C) 2020-2022 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. + */ + +#include "suricata-common.h" +#include "detect.h" +#include "detect-parse.h" +#include "detect-base64-decode.h" +#include "util-base64.h" +#include "util-byte.h" +#include "util-print.h" +#include "detect-engine-build.h" + +/* Arbitrary maximum buffer size for decoded base64 data. */ +#define BASE64_DECODE_MAX 65535 + +typedef struct DetectBase64Decode_ { + uint32_t bytes; + uint32_t offset; + uint8_t relative; +} DetectBase64Decode; + +static const char decode_pattern[] = "\\s*(bytes\\s+(\\d+),?)?" + "\\s*(offset\\s+(\\d+),?)?" + "\\s*(\\w+)?"; + +static DetectParseRegex decode_pcre; + +static int DetectBase64DecodeSetup(DetectEngineCtx *, Signature *, const char *); +static void DetectBase64DecodeFree(DetectEngineCtx *, void *); +#ifdef UNITTESTS +static void DetectBase64DecodeRegisterTests(void); +#endif + +void DetectBase64DecodeRegister(void) +{ + sigmatch_table[DETECT_BASE64_DECODE].name = "base64_decode"; + sigmatch_table[DETECT_BASE64_DECODE].desc = + "Decodes base64 encoded data."; + sigmatch_table[DETECT_BASE64_DECODE].url = + "/rules/base64-keywords.html#base64-decode"; + sigmatch_table[DETECT_BASE64_DECODE].Setup = DetectBase64DecodeSetup; + sigmatch_table[DETECT_BASE64_DECODE].Free = DetectBase64DecodeFree; +#ifdef UNITTESTS + sigmatch_table[DETECT_BASE64_DECODE].RegisterTests = + DetectBase64DecodeRegisterTests; +#endif + sigmatch_table[DETECT_BASE64_DECODE].flags |= SIGMATCH_OPTIONAL_OPT; + + DetectSetupParseRegexes(decode_pattern, &decode_pcre); +} + +int DetectBase64DecodeDoMatch(DetectEngineThreadCtx *det_ctx, const Signature *s, + const SigMatchData *smd, const uint8_t *payload, uint32_t payload_len) +{ + DetectBase64Decode *data = (DetectBase64Decode *)smd->ctx; + int decode_len; + +#if 0 + printf("Input data:\n"); + PrintRawDataFp(stdout, payload, payload_len); +#endif + + if (data->relative) { + payload += det_ctx->buffer_offset; + payload_len -= det_ctx->buffer_offset; + } + + if (data->offset) { + if (data->offset >= payload_len) { + return 0; + } + payload = payload + data->offset; + payload_len -= data->offset; + } + + decode_len = MIN(payload_len, data->bytes); + +#if 0 + printf("Decoding:\n"); + PrintRawDataFp(stdout, payload, decode_len); +#endif + + uint32_t consumed = 0, num_decoded = 0; + (void)DecodeBase64(det_ctx->base64_decoded, det_ctx->base64_decoded_len_max, payload, + decode_len, &consumed, &num_decoded, BASE64_MODE_RFC4648); + det_ctx->base64_decoded_len = num_decoded; + SCLogDebug("Decoded %d bytes from base64 data.", + det_ctx->base64_decoded_len); +#if 0 + if (det_ctx->base64_decoded_len) { + printf("Decoded data:\n"); + PrintRawDataFp(stdout, det_ctx->base64_decoded, + det_ctx->base64_decoded_len); + } +#endif + + return det_ctx->base64_decoded_len > 0; +} + +static int DetectBase64DecodeParse(const char *str, uint32_t *bytes, + uint32_t *offset, uint8_t *relative) +{ + const char *bytes_str = NULL; + const char *offset_str = NULL; + const char *relative_str = NULL; + int retval = 0; + + *bytes = 0; + *offset = 0; + *relative = 0; + size_t pcre2_len; + pcre2_match_data *match = NULL; + + int pcre_rc = DetectParsePcreExec(&decode_pcre, &match, str, 0, 0); + if (pcre_rc < 3) { + goto error; + } + + if (pcre_rc >= 3) { + if (pcre2_substring_get_bynumber(match, 2, (PCRE2_UCHAR8 **)&bytes_str, &pcre2_len) == 0) { + if (StringParseUint32(bytes, 10, 0, bytes_str) <= 0) { + SCLogError("Bad value for bytes: \"%s\"", bytes_str); + goto error; + } + } + } + + if (pcre_rc >= 5) { + if (pcre2_substring_get_bynumber(match, 4, (PCRE2_UCHAR8 **)&offset_str, &pcre2_len) == 0) { + if (StringParseUint32(offset, 10, 0, offset_str) <= 0) { + SCLogError("Bad value for offset: \"%s\"", offset_str); + goto error; + } + } + } + + if (pcre_rc >= 6) { + if (pcre2_substring_get_bynumber(match, 5, (PCRE2_UCHAR8 **)&relative_str, &pcre2_len) == + 0) { + if (strcmp(relative_str, "relative") == 0) { + *relative = 1; + } + else { + SCLogError("Invalid argument: \"%s\"", relative_str); + goto error; + } + } + } + + retval = 1; + + pcre2_match_data_free(match); + match = NULL; + +error: + + if (bytes_str != NULL) { + pcre2_substring_free((PCRE2_UCHAR8 *)bytes_str); + } + if (offset_str != NULL) { + pcre2_substring_free((PCRE2_UCHAR8 *)offset_str); + } + if (relative_str != NULL) { + pcre2_substring_free((PCRE2_UCHAR8 *)relative_str); + } + if (match) { + pcre2_match_data_free(match); + } + return retval; +} + +static int DetectBase64DecodeSetup(DetectEngineCtx *de_ctx, Signature *s, + const char *str) +{ + uint32_t bytes = 0; + uint32_t offset = 0; + uint8_t relative = 0; + DetectBase64Decode *data = NULL; + int sm_list; + SigMatch *sm = NULL; + SigMatch *pm = NULL; + + if (str != NULL) { + if (!DetectBase64DecodeParse(str, &bytes, &offset, &relative)) { + goto error; + } + } + data = SCCalloc(1, sizeof(DetectBase64Decode)); + if (unlikely(data == NULL)) { + goto error; + } + data->bytes = bytes; + data->offset = offset; + data->relative = relative; + + if (s->init_data->list != DETECT_SM_LIST_NOTSET) { + sm_list = s->init_data->list; + } + else { + pm = DetectGetLastSMFromLists(s, + DETECT_CONTENT, DETECT_PCRE, + DETECT_BYTETEST, DETECT_BYTEJUMP, DETECT_BYTE_EXTRACT, + DETECT_ISDATAAT, -1); + if (pm == NULL) { + sm_list = DETECT_SM_LIST_PMATCH; + } + else { + sm_list = SigMatchListSMBelongsTo(s, pm); + if (sm_list < 0) { + goto error; + } + } + } + + sm = SigMatchAlloc(); + if (sm == NULL) { + goto error; + } + sm->type = DETECT_BASE64_DECODE; + sm->ctx = (SigMatchCtx *)data; + SigMatchAppendSMToList(s, sm, sm_list); + + if (!data->bytes) { + data->bytes = BASE64_DECODE_MAX; + } + if (data->bytes > de_ctx->base64_decode_max_len) { +#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION + data->bytes = BASE64_DECODE_MAX; +#endif + de_ctx->base64_decode_max_len = data->bytes; + } + + return 0; +error: + if (data != NULL) { + SCFree(data); + } + return -1; +} + +static void DetectBase64DecodeFree(DetectEngineCtx *de_ctx, void *ptr) +{ + DetectBase64Decode *data = ptr; + SCFree(data); +} + + +#ifdef UNITTESTS +#include "detect-engine.h" +#include "util-unittest.h" +#include "util-unittest-helper.h" +#include "app-layer-parser.h" +#include "flow-util.h" +#include "stream-tcp.h" + +static int g_http_header_buffer_id = 0; + +static int DetectBase64TestDecodeParse(void) +{ + int retval = 0; + uint32_t bytes = 0; + uint32_t offset = 0; + uint8_t relative = 0; + + if (!DetectBase64DecodeParse("bytes 1", &bytes, &offset, &relative)) { + goto end; + } + if (bytes != 1 || offset != 0 || relative != 0) { + goto end; + } + + if (!DetectBase64DecodeParse("offset 9", &bytes, &offset, &relative)) { + goto end; + } + if (bytes != 0 || offset != 9 || relative != 0) { + goto end; + } + + if (!DetectBase64DecodeParse("relative", &bytes, &offset, &relative)) { + goto end; + } + if (bytes != 0 || offset != 0 || relative != 1) { + goto end; + } + + if (!DetectBase64DecodeParse("bytes 1, offset 2", &bytes, &offset, + &relative)) { + goto end; + } + if (bytes != 1 || offset != 2 || relative != 0) { + goto end; + } + + if (!DetectBase64DecodeParse("bytes 1, offset 2, relative", &bytes, &offset, + &relative)) { + goto end; + } + if (bytes != 1 || offset != 2 || relative != 1) { + goto end; + } + + if (!DetectBase64DecodeParse("offset 2, relative", &bytes, &offset, + &relative)) { + goto end; + } + if (bytes != 0 || offset != 2 || relative != 1) { + goto end; + } + + /* Misspelled relative. */ + if (DetectBase64DecodeParse("bytes 1, offset 2, relatve", &bytes, &offset, + &relative)) { + goto end; + } + + /* Misspelled bytes. */ + if (DetectBase64DecodeParse("byts 1, offset 2, relatve", &bytes, &offset, + &relative)) { + goto end; + } + + /* Misspelled offset. */ + if (DetectBase64DecodeParse("bytes 1, offst 2, relatve", &bytes, &offset, + &relative)) { + goto end; + } + + /* Misspelled empty string. */ + if (DetectBase64DecodeParse("", &bytes, &offset, &relative)) { + goto end; + } + + retval = 1; +end: + return retval; +} + +/** + * Test keyword setup on basic content. + */ +static int DetectBase64DecodeTestSetup(void) +{ + DetectEngineCtx *de_ctx = DetectEngineCtxInit(); + FAIL_IF_NULL(de_ctx); + + Signature *s = DetectEngineAppendSig(de_ctx, "alert tcp any any -> any any (" + "base64_decode; content:\"content\"; " + "sid:1; rev:1;)"); + FAIL_IF_NULL(s); + FAIL_IF_NULL(s->init_data->smlists_tail[DETECT_SM_LIST_PMATCH]); + + DetectEngineCtxFree(de_ctx); + PASS; +} + +static int DetectBase64DecodeTestDecode(void) +{ + ThreadVars tv; + DetectEngineCtx *de_ctx = NULL; + DetectEngineThreadCtx *det_ctx = NULL; + Packet *p = NULL; + int retval = 0; + + uint8_t payload[] = { + 'S', 'G', 'V', 's', 'b', 'G', '8', 'g', + 'V', '2', '9', 'y', 'b', 'G', 'Q', '=', + }; + + memset(&tv, 0, sizeof(tv)); + + if ((de_ctx = DetectEngineCtxInit()) == NULL) { + goto end; + } + + de_ctx->sig_list = SigInit(de_ctx, + "alert tcp any any -> any any (msg:\"base64 test\"; " + "base64_decode; " + "sid:1; rev:1;)"); + if (de_ctx->sig_list == NULL) { + goto end; + } + SigGroupBuild(de_ctx); + DetectEngineThreadCtxInit(&tv, (void *)de_ctx, (void *)&det_ctx); + + p = UTHBuildPacket(payload, sizeof(payload), IPPROTO_TCP); + if (p == NULL) { + goto end; + } + + SigMatchSignatures(&tv, de_ctx, det_ctx, p); + if (det_ctx->base64_decoded_len == 0) { + goto end; + } + + retval = 1; +end: + if (det_ctx != NULL) { + DetectEngineThreadCtxDeinit(&tv, det_ctx); + } + if (de_ctx != NULL) { + SigCleanSignatures(de_ctx); + SigGroupCleanup(de_ctx); + DetectEngineCtxFree(de_ctx); + } + if (p != NULL) { + UTHFreePacket(p); + } + return retval; +} + +static int DetectBase64DecodeTestDecodeWithOffset(void) +{ + ThreadVars tv; + DetectEngineCtx *de_ctx = NULL; + DetectEngineThreadCtx *det_ctx = NULL; + Packet *p = NULL; + int retval = 0; + + uint8_t payload[] = { + 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', + 'S', 'G', 'V', 's', 'b', 'G', '8', 'g', + 'V', '2', '9', 'y', 'b', 'G', 'Q', '=', + }; + char decoded[] = "Hello World"; + + memset(&tv, 0, sizeof(tv)); + + if ((de_ctx = DetectEngineCtxInit()) == NULL) { + goto end; + } + + de_ctx->sig_list = SigInit(de_ctx, + "alert tcp any any -> any any (msg:\"base64 test\"; " + "base64_decode: offset 8; " + "sid:1; rev:1;)"); + if (de_ctx->sig_list == NULL) { + goto end; + } + SigGroupBuild(de_ctx); + DetectEngineThreadCtxInit(&tv, (void *)de_ctx, (void *)&det_ctx); + + p = UTHBuildPacket(payload, sizeof(payload), IPPROTO_TCP); + if (p == NULL) { + goto end; + } + + SigMatchSignatures(&tv, de_ctx, det_ctx, p); + if (det_ctx->base64_decoded_len != (int)strlen(decoded)) { + goto end; + } + if (memcmp(det_ctx->base64_decoded, decoded, strlen(decoded))) { + goto end; + } + + retval = 1; +end: + if (det_ctx != NULL) { + DetectEngineThreadCtxDeinit(&tv, det_ctx); + } + if (de_ctx != NULL) { + SigCleanSignatures(de_ctx); + SigGroupCleanup(de_ctx); + DetectEngineCtxFree(de_ctx); + } + if (p != NULL) { + UTHFreePacket(p); + } + return retval; +} + +static int DetectBase64DecodeTestDecodeLargeOffset(void) +{ + ThreadVars tv; + DetectEngineCtx *de_ctx = NULL; + DetectEngineThreadCtx *det_ctx = NULL; + Packet *p = NULL; + int retval = 0; + + uint8_t payload[] = { + 'S', 'G', 'V', 's', 'b', 'G', '8', 'g', + 'V', '2', '9', 'y', 'b', 'G', 'Q', '=', + }; + + memset(&tv, 0, sizeof(tv)); + + if ((de_ctx = DetectEngineCtxInit()) == NULL) { + goto end; + } + + /* Offset is out of range. */ + de_ctx->sig_list = SigInit(de_ctx, + "alert tcp any any -> any any (msg:\"base64 test\"; " + "base64_decode: bytes 16, offset 32; " + "sid:1; rev:1;)"); + if (de_ctx->sig_list == NULL) { + goto end; + } + SigGroupBuild(de_ctx); + DetectEngineThreadCtxInit(&tv, (void *)de_ctx, (void *)&det_ctx); + + p = UTHBuildPacket(payload, sizeof(payload), IPPROTO_TCP); + if (p == NULL) { + goto end; + } + + SigMatchSignatures(&tv, de_ctx, det_ctx, p); + if (det_ctx->base64_decoded_len != 0) { + goto end; + } + + retval = 1; +end: + if (det_ctx != NULL) { + DetectEngineThreadCtxDeinit(&tv, det_ctx); + } + if (de_ctx != NULL) { + SigCleanSignatures(de_ctx); + SigGroupCleanup(de_ctx); + DetectEngineCtxFree(de_ctx); + } + if (p != NULL) { + UTHFreePacket(p); + } + return retval; +} + +static int DetectBase64DecodeTestDecodeRelative(void) +{ + ThreadVars tv; + DetectEngineCtx *de_ctx = NULL; + DetectEngineThreadCtx *det_ctx = NULL; + Packet *p = NULL; + int retval = 0; + + uint8_t payload[] = { + 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', + 'S', 'G', 'V', 's', 'b', 'G', '8', 'g', + 'V', '2', '9', 'y', 'b', 'G', 'Q', '=', + }; + char decoded[] = "Hello World"; + + memset(&tv, 0, sizeof(tv)); + + if ((de_ctx = DetectEngineCtxInit()) == NULL) { + goto end; + } + + de_ctx->sig_list = SigInit(de_ctx, + "alert tcp any any -> any any (msg:\"base64 test\"; " + "content:\"aaaaaaaa\"; " + "base64_decode: relative; " + "sid:1; rev:1;)"); + if (de_ctx->sig_list == NULL) { + goto end; + } + SigGroupBuild(de_ctx); + DetectEngineThreadCtxInit(&tv, (void *)de_ctx, (void *)&det_ctx); + + p = UTHBuildPacket(payload, sizeof(payload), IPPROTO_TCP); + if (p == NULL) { + goto end; + } + + SigMatchSignatures(&tv, de_ctx, det_ctx, p); + if (det_ctx->base64_decoded_len != (int)strlen(decoded)) { + goto end; + } + if (memcmp(det_ctx->base64_decoded, decoded, strlen(decoded))) { + goto end; + } + + retval = 1; +end: + if (det_ctx != NULL) { + DetectEngineThreadCtxDeinit(&tv, det_ctx); + } + if (de_ctx != NULL) { + SigCleanSignatures(de_ctx); + SigGroupCleanup(de_ctx); + DetectEngineCtxFree(de_ctx); + } + if (p != NULL) { + UTHFreePacket(p); + } + return retval; +} + +static void DetectBase64DecodeRegisterTests(void) +{ + g_http_header_buffer_id = DetectBufferTypeGetByName("http_header"); + + UtRegisterTest("DetectBase64TestDecodeParse", DetectBase64TestDecodeParse); + UtRegisterTest("DetectBase64DecodeTestSetup", DetectBase64DecodeTestSetup); + UtRegisterTest("DetectBase64DecodeTestDecode", + DetectBase64DecodeTestDecode); + UtRegisterTest("DetectBase64DecodeTestDecodeWithOffset", + DetectBase64DecodeTestDecodeWithOffset); + UtRegisterTest("DetectBase64DecodeTestDecodeLargeOffset", + DetectBase64DecodeTestDecodeLargeOffset); + UtRegisterTest("DetectBase64DecodeTestDecodeRelative", + DetectBase64DecodeTestDecodeRelative); +} +#endif /* UNITTESTS */ |