suricata
detect-base64-decode.c
Go to the documentation of this file.
1/* Copyright (C) 2020-2024 Open Information Security Foundation
2 *
3 * You can copy, redistribute or modify this Program under the terms of
4 * the GNU General Public License version 2 as published by the Free
5 * Software Foundation.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
11 *
12 * You should have received a copy of the GNU General Public License
13 * version 2 along with this program; if not, write to the Free Software
14 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
15 * 02110-1301, USA.
16 */
17
18#include "suricata-common.h"
19#include "detect.h"
20#include "detect-parse.h"
22#include "util-byte.h"
23#include "util-print.h"
24#include "detect-engine-build.h"
25#include "rust.h"
26
27/* Arbitrary maximum buffer size for decoded base64 data. */
28#define BASE64_DECODE_MAX 65535
29
30typedef struct DetectBase64Decode_ {
31 uint16_t bytes;
32 uint32_t offset;
33 uint8_t relative;
35
36static const char decode_pattern[] = "\\s*(bytes\\s+(\\d+),?)?"
37 "\\s*(offset\\s+(\\d+),?)?"
38 "\\s*(\\w+)?";
39
40static DetectParseRegex decode_pcre;
41
42static int DetectBase64DecodeSetup(DetectEngineCtx *, Signature *, const char *);
43static void DetectBase64DecodeFree(DetectEngineCtx *, void *);
44#ifdef UNITTESTS
45static void DetectBase64DecodeRegisterTests(void);
46#endif
47
49{
50 sigmatch_table[DETECT_BASE64_DECODE].name = "base64_decode";
52 "Decodes base64 encoded data.";
54 "/rules/base64-keywords.html#base64-decode";
55 sigmatch_table[DETECT_BASE64_DECODE].Setup = DetectBase64DecodeSetup;
56 sigmatch_table[DETECT_BASE64_DECODE].Free = DetectBase64DecodeFree;
57#ifdef UNITTESTS
59 DetectBase64DecodeRegisterTests;
60#endif
62
63 DetectSetupParseRegexes(decode_pattern, &decode_pcre);
64}
65
67 const SigMatchData *smd, const uint8_t *payload, uint32_t payload_len)
68{
70
71#if 0
72 printf("Input data:\n");
73 PrintRawDataFp(stdout, payload, payload_len);
74#endif
75
76 if (data->relative) {
77 payload += det_ctx->buffer_offset;
79 payload_len -= det_ctx->buffer_offset;
80 }
81
82 if (data->offset) {
83 if (data->offset >= payload_len) {
84 return 0;
85 }
86 payload = payload + data->offset;
87 payload_len -= data->offset;
88 }
89
90 uint32_t decode_len = MIN(payload_len, data->bytes);
91#if 0
92 printf("Decoding:\n");
93 PrintRawDataFp(stdout, payload, decode_len);
94#endif
95
96 if (decode_len > 0) {
97 uint32_t num_decoded =
98 SCBase64Decode(payload, decode_len, SCBase64ModeRFC4648, det_ctx->base64_decoded);
99 det_ctx->base64_decoded_len = num_decoded;
100 SCLogDebug("Decoded %d bytes from base64 data.", det_ctx->base64_decoded_len);
101 }
102#if 0
103 if (det_ctx->base64_decoded_len) {
104 printf("Decoded data:\n");
105 PrintRawDataFp(stdout, det_ctx->base64_decoded,
106 det_ctx->base64_decoded_len);
107 }
108#endif
109
110 return det_ctx->base64_decoded_len > 0;
111}
112
113static int DetectBase64DecodeParse(
114 const char *str, uint16_t *bytes, uint32_t *offset, uint8_t *relative)
115{
116 const char *bytes_str = NULL;
117 const char *offset_str = NULL;
118 const char *relative_str = NULL;
119 int retval = 0;
120
121 *bytes = 0;
122 *offset = 0;
123 *relative = 0;
124 size_t pcre2_len;
125 pcre2_match_data *match = NULL;
126
127 int pcre_rc = DetectParsePcreExec(&decode_pcre, &match, str, 0, 0);
128 if (pcre_rc < 3) {
129 goto error;
130 }
131
132 if (pcre_rc >= 3) {
133 if (pcre2_substring_get_bynumber(match, 2, (PCRE2_UCHAR8 **)&bytes_str, &pcre2_len) == 0) {
134 if (StringParseUint16(bytes, 10, 0, bytes_str) <= 0) {
135 SCLogError("Bad value for bytes: \"%s\"", bytes_str);
136 goto error;
137 }
138 }
139 }
140
141 if (pcre_rc >= 5) {
142 if (pcre2_substring_get_bynumber(match, 4, (PCRE2_UCHAR8 **)&offset_str, &pcre2_len) == 0) {
143 if (StringParseUint32(offset, 10, 0, offset_str) <= 0) {
144 SCLogError("Bad value for offset: \"%s\"", offset_str);
145 goto error;
146 }
147 }
148 }
149
150 if (pcre_rc >= 6) {
151 if (pcre2_substring_get_bynumber(match, 5, (PCRE2_UCHAR8 **)&relative_str, &pcre2_len) ==
152 0) {
153 if (strcmp(relative_str, "relative") == 0) {
154 *relative = 1;
155 }
156 else {
157 SCLogError("Invalid argument: \"%s\"", relative_str);
158 goto error;
159 }
160 }
161 }
162
163 retval = 1;
164
165 pcre2_match_data_free(match);
166 match = NULL;
167
168error:
169
170 if (bytes_str != NULL) {
171 pcre2_substring_free((PCRE2_UCHAR8 *)bytes_str);
172 }
173 if (offset_str != NULL) {
174 pcre2_substring_free((PCRE2_UCHAR8 *)offset_str);
175 }
176 if (relative_str != NULL) {
177 pcre2_substring_free((PCRE2_UCHAR8 *)relative_str);
178 }
179 if (match) {
180 pcre2_match_data_free(match);
181 }
182 return retval;
183}
184
185static int DetectBase64DecodeSetup(DetectEngineCtx *de_ctx, Signature *s,
186 const char *str)
187{
188 uint16_t bytes = 0;
189 uint32_t offset = 0;
190 uint8_t relative = 0;
191 DetectBase64Decode *data = NULL;
192 int sm_list;
193 SigMatch *pm = NULL;
194
195 if (str != NULL) {
196 if (!DetectBase64DecodeParse(str, &bytes, &offset, &relative)) {
197 goto error;
198 }
199 }
200 data = SCCalloc(1, sizeof(DetectBase64Decode));
201 if (unlikely(data == NULL)) {
202 goto error;
203 }
204 data->bytes = bytes;
205 data->offset = offset;
206 data->relative = relative;
207
209 sm_list = s->init_data->list;
210 }
211 else {
215 DETECT_ISDATAAT, -1);
216 if (pm == NULL) {
217 sm_list = DETECT_SM_LIST_PMATCH;
218 }
219 else {
220 sm_list = SigMatchListSMBelongsTo(s, pm);
221 if (sm_list < 0) {
222 goto error;
223 }
224 }
225 }
226
228 NULL) {
229 goto error;
230 }
231
232 if (!data->bytes) {
233 data->bytes = BASE64_DECODE_MAX;
234 }
235 if (data->bytes > de_ctx->base64_decode_max_len) {
236 de_ctx->base64_decode_max_len = data->bytes;
237 }
238
239 return 0;
240error:
241 if (data != NULL) {
242 SCFree(data);
243 }
244 return -1;
245}
246
247static void DetectBase64DecodeFree(DetectEngineCtx *de_ctx, void *ptr)
248{
249 DetectBase64Decode *data = ptr;
250 SCFree(data);
251}
252
253
254#ifdef UNITTESTS
255#include "detect-engine.h"
256#include "util-unittest.h"
257#include "util-unittest-helper.h"
258#include "app-layer-parser.h"
259#include "flow-util.h"
260#include "stream-tcp.h"
261
262static int g_http_header_buffer_id = 0;
263
264static int DetectBase64TestDecodeParse(void)
265{
266 int retval = 0;
267 uint16_t bytes = 0;
268 uint32_t offset = 0;
269 uint8_t relative = 0;
270
271 if (!DetectBase64DecodeParse("bytes 1", &bytes, &offset, &relative)) {
272 goto end;
273 }
274 if (bytes != 1 || offset != 0 || relative != 0) {
275 goto end;
276 }
277
278 if (!DetectBase64DecodeParse("offset 9", &bytes, &offset, &relative)) {
279 goto end;
280 }
281 if (bytes != 0 || offset != 9 || relative != 0) {
282 goto end;
283 }
284
285 if (!DetectBase64DecodeParse("relative", &bytes, &offset, &relative)) {
286 goto end;
287 }
288 if (bytes != 0 || offset != 0 || relative != 1) {
289 goto end;
290 }
291
292 if (!DetectBase64DecodeParse("bytes 1, offset 2", &bytes, &offset,
293 &relative)) {
294 goto end;
295 }
296 if (bytes != 1 || offset != 2 || relative != 0) {
297 goto end;
298 }
299
300 if (!DetectBase64DecodeParse("bytes 1, offset 2, relative", &bytes, &offset,
301 &relative)) {
302 goto end;
303 }
304 if (bytes != 1 || offset != 2 || relative != 1) {
305 goto end;
306 }
307
308 if (!DetectBase64DecodeParse("offset 2, relative", &bytes, &offset,
309 &relative)) {
310 goto end;
311 }
312 if (bytes != 0 || offset != 2 || relative != 1) {
313 goto end;
314 }
315
316 /* Misspelled relative. */
317 if (DetectBase64DecodeParse("bytes 1, offset 2, relatve", &bytes, &offset,
318 &relative)) {
319 goto end;
320 }
321
322 /* Misspelled bytes. */
323 if (DetectBase64DecodeParse("byts 1, offset 2, relatve", &bytes, &offset,
324 &relative)) {
325 goto end;
326 }
327
328 /* Misspelled offset. */
329 if (DetectBase64DecodeParse("bytes 1, offst 2, relatve", &bytes, &offset,
330 &relative)) {
331 goto end;
332 }
333
334 /* Misspelled empty string. */
335 if (DetectBase64DecodeParse("", &bytes, &offset, &relative)) {
336 goto end;
337 }
338
339 retval = 1;
340end:
341 return retval;
342}
343
344/**
345 * Test keyword setup on basic content.
346 */
347static int DetectBase64DecodeTestSetup(void)
348{
351
352 Signature *s = DetectEngineAppendSig(de_ctx, "alert tcp any any -> any any ("
353 "base64_decode; content:\"content\"; "
354 "sid:1; rev:1;)");
355 FAIL_IF_NULL(s);
357
359 PASS;
360}
361
362static int DetectBase64DecodeTestDecode(void)
363{
365 DetectEngineCtx *de_ctx = NULL;
366 DetectEngineThreadCtx *det_ctx = NULL;
367 Packet *p = NULL;
368 int retval = 0;
369
370 uint8_t payload[] = {
371 'S', 'G', 'V', 's', 'b', 'G', '8', 'g',
372 'V', '2', '9', 'y', 'b', 'G', 'Q', '=',
373 };
374
375 memset(&tv, 0, sizeof(tv));
376
377 if ((de_ctx = DetectEngineCtxInit()) == NULL) {
378 goto end;
379 }
380
382 "alert tcp any any -> any any (msg:\"base64 test\"; "
383 "base64_decode; "
384 "sid:1; rev:1;)");
385 if (de_ctx->sig_list == NULL) {
386 goto end;
387 }
389 DetectEngineThreadCtxInit(&tv, (void *)de_ctx, (void *)&det_ctx);
390
391 p = UTHBuildPacket(payload, sizeof(payload), IPPROTO_TCP);
392 if (p == NULL) {
393 goto end;
394 }
395
396 SigMatchSignatures(&tv, de_ctx, det_ctx, p);
397 if (det_ctx->base64_decoded_len == 0) {
398 goto end;
399 }
400
401 retval = 1;
402end:
403 if (det_ctx != NULL) {
405 }
406 if (de_ctx != NULL) {
410 }
411 if (p != NULL) {
412 UTHFreePacket(p);
413 }
414 return retval;
415}
416
417static int DetectBase64DecodeTestDecodeWithOffset(void)
418{
420 DetectEngineCtx *de_ctx = NULL;
421 DetectEngineThreadCtx *det_ctx = NULL;
422 Packet *p = NULL;
423 int retval = 0;
424
425 uint8_t payload[] = {
426 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a',
427 'S', 'G', 'V', 's', 'b', 'G', '8', 'g',
428 'V', '2', '9', 'y', 'b', 'G', 'Q', '=',
429 };
430 char decoded[] = "Hello World";
431
432 memset(&tv, 0, sizeof(tv));
433
434 if ((de_ctx = DetectEngineCtxInit()) == NULL) {
435 goto end;
436 }
437
439 "alert tcp any any -> any any (msg:\"base64 test\"; "
440 "base64_decode: offset 8; "
441 "sid:1; rev:1;)");
442 if (de_ctx->sig_list == NULL) {
443 goto end;
444 }
446 DetectEngineThreadCtxInit(&tv, (void *)de_ctx, (void *)&det_ctx);
447
448 p = UTHBuildPacket(payload, sizeof(payload), IPPROTO_TCP);
449 if (p == NULL) {
450 goto end;
451 }
452
453 SigMatchSignatures(&tv, de_ctx, det_ctx, p);
454 if (det_ctx->base64_decoded_len != (int)strlen(decoded)) {
455 goto end;
456 }
457 if (memcmp(det_ctx->base64_decoded, decoded, strlen(decoded))) {
458 goto end;
459 }
460
461 retval = 1;
462end:
463 if (det_ctx != NULL) {
465 }
466 if (de_ctx != NULL) {
470 }
471 if (p != NULL) {
472 UTHFreePacket(p);
473 }
474 return retval;
475}
476
477static int DetectBase64DecodeTestDecodeLargeOffset(void)
478{
480 DetectEngineCtx *de_ctx = NULL;
481 DetectEngineThreadCtx *det_ctx = NULL;
482 Packet *p = NULL;
483 int retval = 0;
484
485 uint8_t payload[] = {
486 'S', 'G', 'V', 's', 'b', 'G', '8', 'g',
487 'V', '2', '9', 'y', 'b', 'G', 'Q', '=',
488 };
489
490 memset(&tv, 0, sizeof(tv));
491
492 if ((de_ctx = DetectEngineCtxInit()) == NULL) {
493 goto end;
494 }
495
496 /* Offset is out of range. */
498 "alert tcp any any -> any any (msg:\"base64 test\"; "
499 "base64_decode: bytes 16, offset 32; "
500 "sid:1; rev:1;)");
501 if (de_ctx->sig_list == NULL) {
502 goto end;
503 }
505 DetectEngineThreadCtxInit(&tv, (void *)de_ctx, (void *)&det_ctx);
506
507 p = UTHBuildPacket(payload, sizeof(payload), IPPROTO_TCP);
508 if (p == NULL) {
509 goto end;
510 }
511
512 SigMatchSignatures(&tv, de_ctx, det_ctx, p);
513 if (det_ctx->base64_decoded_len != 0) {
514 goto end;
515 }
516
517 retval = 1;
518end:
519 if (det_ctx != NULL) {
521 }
522 if (de_ctx != NULL) {
526 }
527 if (p != NULL) {
528 UTHFreePacket(p);
529 }
530 return retval;
531}
532
533static int DetectBase64DecodeTestDecodeRelative(void)
534{
536 DetectEngineCtx *de_ctx = NULL;
537 DetectEngineThreadCtx *det_ctx = NULL;
538 Packet *p = NULL;
539 int retval = 0;
540
541 uint8_t payload[] = {
542 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a',
543 'S', 'G', 'V', 's', 'b', 'G', '8', 'g',
544 'V', '2', '9', 'y', 'b', 'G', 'Q', '=',
545 };
546 char decoded[] = "Hello World";
547
548 memset(&tv, 0, sizeof(tv));
549
550 if ((de_ctx = DetectEngineCtxInit()) == NULL) {
551 goto end;
552 }
553
555 "alert tcp any any -> any any (msg:\"base64 test\"; "
556 "content:\"aaaaaaaa\"; "
557 "base64_decode: relative; "
558 "sid:1; rev:1;)");
559 if (de_ctx->sig_list == NULL) {
560 goto end;
561 }
563 DetectEngineThreadCtxInit(&tv, (void *)de_ctx, (void *)&det_ctx);
564
565 p = UTHBuildPacket(payload, sizeof(payload), IPPROTO_TCP);
566 if (p == NULL) {
567 goto end;
568 }
569
570 SigMatchSignatures(&tv, de_ctx, det_ctx, p);
571 if (det_ctx->base64_decoded_len != (int)strlen(decoded)) {
572 goto end;
573 }
574 if (memcmp(det_ctx->base64_decoded, decoded, strlen(decoded))) {
575 goto end;
576 }
577
578 retval = 1;
579end:
580 if (det_ctx != NULL) {
582 }
583 if (de_ctx != NULL) {
587 }
588 if (p != NULL) {
589 UTHFreePacket(p);
590 }
591 return retval;
592}
593
594static void DetectBase64DecodeRegisterTests(void)
595{
596 g_http_header_buffer_id = DetectBufferTypeGetByName("http_header");
597
598 UtRegisterTest("DetectBase64TestDecodeParse", DetectBase64TestDecodeParse);
599 UtRegisterTest("DetectBase64DecodeTestSetup", DetectBase64DecodeTestSetup);
600 UtRegisterTest("DetectBase64DecodeTestDecode",
601 DetectBase64DecodeTestDecode);
602 UtRegisterTest("DetectBase64DecodeTestDecodeWithOffset",
603 DetectBase64DecodeTestDecodeWithOffset);
604 UtRegisterTest("DetectBase64DecodeTestDecodeLargeOffset",
605 DetectBase64DecodeTestDecodeLargeOffset);
606 UtRegisterTest("DetectBase64DecodeTestDecodeRelative",
607 DetectBase64DecodeTestDecodeRelative);
608}
609#endif /* UNITTESTS */
int DetectBase64DecodeDoMatch(DetectEngineThreadCtx *det_ctx, const Signature *s, const SigMatchData *smd, const uint8_t *payload, uint32_t payload_len)
struct DetectBase64Decode_ DetectBase64Decode
#define BASE64_DECODE_MAX
void DetectBase64DecodeRegister(void)
int SigGroupBuild(DetectEngineCtx *de_ctx)
Convert the signature list into the runtime match structure.
void SigCleanSignatures(DetectEngineCtx *de_ctx)
int SigGroupCleanup(DetectEngineCtx *de_ctx)
@ DETECT_BYTE_EXTRACT
@ DETECT_BYTETEST
@ DETECT_BASE64_DECODE
@ DETECT_BYTEJUMP
@ DETECT_ISDATAAT
DetectEngineCtx * DetectEngineCtxInit(void)
void DetectEngineCtxFree(DetectEngineCtx *)
Free a DetectEngineCtx::
Signature * DetectEngineAppendSig(DetectEngineCtx *, const char *)
Parse and append a Signature into the Detection Engine Context signature list.
TmEcode DetectEngineThreadCtxInit(ThreadVars *tv, void *initdata, void **data)
initialize thread specific detection engine context
int DetectBufferTypeGetByName(const char *name)
TmEcode DetectEngineThreadCtxDeinit(ThreadVars *tv, void *data)
void DetectSetupParseRegexes(const char *parse_str, DetectParseRegex *detect_parse)
int DetectParsePcreExec(DetectParseRegex *parse_regex, pcre2_match_data **match, const char *str, int start_offset, int options)
Signature * SigInit(DetectEngineCtx *de_ctx, const char *sigstr)
Parses a signature and adds it to the Detection Engine Context.
SigMatch * SCSigMatchAppendSMToList(DetectEngineCtx *de_ctx, Signature *s, uint16_t type, SigMatchCtx *ctx, const int list)
Append a SigMatch to the list type.
SigMatch * DetectGetLastSMFromLists(const Signature *s,...)
Returns the sm with the largest index (added latest) from the lists passed to us.
int SigMatchListSMBelongsTo(const Signature *s, const SigMatch *key_sm)
SigTableElmt * sigmatch_table
void SigMatchSignatures(ThreadVars *tv, DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx, Packet *p)
wrapper for old tests
Definition detect.c:2420
#define DETECT_SM_LIST_NOTSET
Definition detect.h:144
#define SIGMATCH_OPTIONAL_OPT
Definition detect.h:1661
@ DETECT_SM_LIST_PMATCH
Definition detect.h:119
ThreadVars * tv
DetectEngineCtx * de_ctx
#define FAIL_IF_NULL(expr)
Fail a test if expression evaluates to NULL.
void UtRegisterTest(const char *name, int(*TestFn)(void))
Register unit test.
#define PASS
Pass the test.
uint16_t payload_len
main detection engine ctx
Definition detect.h:932
Signature * sig_list
Definition detect.h:941
uint16_t base64_decode_max_len
Definition detect.h:1020
uint8_t * base64_decoded
Definition detect.h:1326
Used to start a pointer to SigMatch context Should never be dereferenced without casting to something...
Definition detect.h:351
Data needed for Match()
Definition detect.h:365
SigMatchCtx * ctx
Definition detect.h:368
a single match condition for a signature
Definition detect.h:356
const char * url
Definition detect.h:1462
int(* Setup)(DetectEngineCtx *, Signature *, const char *)
Definition detect.h:1441
void(* Free)(DetectEngineCtx *, void *)
Definition detect.h:1446
uint16_t flags
Definition detect.h:1450
const char * desc
Definition detect.h:1461
void(* RegisterTests)(void)
Definition detect.h:1448
const char * name
Definition detect.h:1459
struct SigMatch_ * smlists_tail[DETECT_SM_LIST_MAX]
Definition detect.h:644
Signature container.
Definition detect.h:668
SignatureInitData * init_data
Definition detect.h:747
Per thread variable structure.
Definition threadvars.h:58
#define MIN(x, y)
#define str(s)
int StringParseUint16(uint16_t *res, int base, size_t len, const char *str)
Definition util-byte.c:337
int StringParseUint32(uint32_t *res, int base, size_t len, const char *str)
Definition util-byte.c:313
#define SCLogDebug(...)
Definition util-debug.h:275
#define SCLogError(...)
Macro used to log ERROR messages.
Definition util-debug.h:267
#define SCFree(p)
Definition util-mem.h:61
#define SCCalloc(nm, sz)
Definition util-mem.h:53
#define unlikely(expr)
void PrintRawDataFp(FILE *fp, const uint8_t *buf, uint32_t buflen)
Definition util-print.c:112
uint64_t offset
Packet * UTHBuildPacket(uint8_t *payload, uint16_t payload_len, uint8_t ipproto)
UTHBuildPacket is a wrapper that build packets with default ip and port fields.
void UTHFreePacket(Packet *p)
UTHFreePacket: function to release the allocated data from UTHBuildPacket and the packet itself.
#define DEBUG_VALIDATE_BUG_ON(exp)