From 3aa313d0c5fdf33833508bc9b686f807188a5b9f Mon Sep 17 00:00:00 2001 From: Hadiqa Alamdar Bukhari Date: Wed, 24 Jan 2024 17:10:49 +0500 Subject: [PATCH] dns: add dns.rcode keyword dns.rcode matches the rcode header field in DNS messages It's an unsigned integer valid ranges = [0-15] Does not support prefilter Supports matches in both flow directions Task #6621 --- doc/userguide/rules/dns-keywords.rst | 33 +++++++++ rust/src/dns/detect.rs | 106 +++++++++++++++++++++++++++ src/Makefile.am | 2 + src/detect-dns-rcode.c | 85 +++++++++++++++++++++ src/detect-dns-rcode.h | 23 ++++++ src/detect-engine-register.c | 2 + src/detect-engine-register.h | 1 + 7 files changed, 252 insertions(+) create mode 100644 src/detect-dns-rcode.c create mode 100644 src/detect-dns-rcode.h diff --git a/doc/userguide/rules/dns-keywords.rst b/doc/userguide/rules/dns-keywords.rst index bb232e39f0..f729250f70 100644 --- a/doc/userguide/rules/dns-keywords.rst +++ b/doc/userguide/rules/dns-keywords.rst @@ -57,6 +57,39 @@ Match on DNS requests where the **opcode** is not between 7 and 15: dns.opcode:!7-15; +dns.rcode +--------- + +This keyword matches on the **rcode** field found in the DNS header flags. + +dns.rcode uses an :ref:`unsigned 8-bit integer `. + +Currently, Suricata only supports rcode values in the range [0-15], while +the current DNS version supports rcode values from [0-23] as specified in +`RFC 6895 `_. + +We plan to extend the rcode values supported by Suricata according to RFC 6895 +as tracked by the ticket: https://redmine.openinfosecfoundation.org/issues/6650 + +Syntax +~~~~~~ + +:: + + dns.rcode:[!] + dns.rcode:[!]- + +Examples +~~~~~~~~ + +Match on DNS requests and responses with **rcode** 4:: + + dns.rcode:4; + +Match on DNS requests and responses where the **rcode** is NOT 0:: + + dns.rcode:!0; + dns.rrtype ---------- diff --git a/rust/src/dns/detect.rs b/rust/src/dns/detect.rs index 4e7819513f..d425853a8b 100644 --- a/rust/src/dns/detect.rs +++ b/rust/src/dns/detect.rs @@ -50,6 +50,33 @@ pub extern "C" fn rs_dns_opcode_match( return 0; } +/// Perform the DNS rcode match. +/// +/// 1 will be returned on match, otherwise 0 will be returned. +#[no_mangle] +pub extern "C" fn rs_dns_rcode_match( + tx: &mut DNSTransaction, detect: &mut DetectUintData, flags: u8, +) -> u8 { + let header_flags = if flags & Direction::ToServer as u8 != 0 { + if let Some(request) = &tx.request { + request.header.flags + } else { + return 0; + } + } else if let Some(response) = &tx.response { + response.header.flags + } else { + return 0; + }; + + let rcode = (header_flags & 0xf) as u8; + + if detect_match_uint(detect, rcode) { + return 1; + } + return 0; +} + /// Perform the DNS rrtype match. /// 1 will be returned on match, otherwise 0 will be returned. #[no_mangle] @@ -152,6 +179,85 @@ mod test { )); } + #[test] + fn parse_rcode_good() { + assert_eq!( + detect_parse_uint::("1").unwrap().1, + DetectUintData { + mode: DetectUintMode::DetectUintModeEqual, + arg1: 1, + arg2: 0, + } + ); + assert_eq!( + detect_parse_uint::("123").unwrap().1, + DetectUintData { + mode: DetectUintMode::DetectUintModeEqual, + arg1: 123, + arg2: 0, + } + ); + assert_eq!( + detect_parse_uint::("!123").unwrap().1, + DetectUintData { + mode: DetectUintMode::DetectUintModeNe, + arg1: 123, + arg2: 0, + } + ); + assert_eq!( + detect_parse_uint::("7-15").unwrap().1, + DetectUintData { + mode: DetectUintMode::DetectUintModeRange, + arg1: 7, + arg2: 15, + } + ); + assert!(detect_parse_uint::("").is_err()); + assert!(detect_parse_uint::("!").is_err()); + assert!(detect_parse_uint::("! ").is_err()); + assert!(detect_parse_uint::("!asdf").is_err()); + } + + #[test] + fn test_match_rcode() { + assert!(detect_match_uint( + &DetectUintData { + mode: DetectUintMode::DetectUintModeEqual, + arg1: 0, + arg2: 0, + }, + 0b0000_0000_0000_0000, + )); + + assert!(!detect_match_uint( + &DetectUintData { + mode: DetectUintMode::DetectUintModeNe, + arg1: 0, + arg2: 0, + }, + 0b0000_0000_0000_0000, + )); + + assert!(detect_match_uint( + &DetectUintData { + mode: DetectUintMode::DetectUintModeEqual, + arg1: 4, + arg2: 0, + }, + 4u8, + )); + + assert!(!detect_match_uint( + &DetectUintData { + mode: DetectUintMode::DetectUintModeNe, + arg1: 4, + arg2: 0, + }, + 4u8, + )); + } + #[test] fn parse_rrtype_good() { assert_eq!( diff --git a/src/Makefile.am b/src/Makefile.am index 7ced6c852d..21cb34c39b 100755 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -121,6 +121,7 @@ noinst_HEADERS = \ detect-dnp3.h \ detect-dns-answer-name.h \ detect-dns-opcode.h \ + detect-dns-rcode.h \ detect-dns-rrtype.h \ detect-dns-query.h \ detect-dns-query-name.h \ @@ -744,6 +745,7 @@ libsuricata_c_a_SOURCES = \ detect-dnp3.c \ detect-dns-answer-name.c \ detect-dns-opcode.c \ + detect-dns-rcode.c \ detect-dns-rrtype.c \ detect-dns-query.c \ detect-dns-query-name.c \ diff --git a/src/detect-dns-rcode.c b/src/detect-dns-rcode.c new file mode 100644 index 0000000000..876e68c097 --- /dev/null +++ b/src/detect-dns-rcode.c @@ -0,0 +1,85 @@ +/* Copyright (C) 2024 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-parse.h" +#include "detect-engine.h" +#include "detect-dns-rcode.h" +#include "rust.h" +#include "detect-engine-uint.h" + +static int dns_rcode_list_id = 0; + +static void DetectDnsRcodeFree(DetectEngineCtx *, void *ptr); + +static int DetectDnsRcodeSetup(DetectEngineCtx *de_ctx, Signature *s, const char *str) +{ + SCEnter(); + + if (DetectSignatureSetAppProto(s, ALPROTO_DNS) != 0) { + SCReturnInt(-1); + } + + void *detect = DetectU8Parse(str); + if (detect == NULL) { + SCLogError("failed to parse dns.rcode: %s", str); + SCReturnInt(-1); + } + + if (SigMatchAppendSMToList( + de_ctx, s, DETECT_AL_DNS_RCODE, (SigMatchCtx *)detect, dns_rcode_list_id) == NULL) { + DetectDnsRcodeFree(de_ctx, detect); + SCReturnInt(-1); + } + + SCReturnInt(0); +} + +static void DetectDnsRcodeFree(DetectEngineCtx *de_ctx, void *ptr) +{ + SCEnter(); + if (ptr != NULL) { + rs_detect_u8_free(ptr); + } + SCReturn; +} + +static int DetectDnsRcodeMatch(DetectEngineThreadCtx *det_ctx, Flow *f, uint8_t flags, void *state, + void *txv, const Signature *s, const SigMatchCtx *ctx) +{ + return rs_dns_rcode_match(txv, (void *)ctx, flags); +} + +void DetectDnsRcodeRegister(void) +{ + sigmatch_table[DETECT_AL_DNS_RCODE].name = "dns.rcode"; + sigmatch_table[DETECT_AL_DNS_RCODE].desc = "Match the DNS header rcode flag."; + sigmatch_table[DETECT_AL_DNS_RCODE].url = "/rules/dns-keywords.html#dns-rcode"; + sigmatch_table[DETECT_AL_DNS_RCODE].Setup = DetectDnsRcodeSetup; + sigmatch_table[DETECT_AL_DNS_RCODE].Free = DetectDnsRcodeFree; + sigmatch_table[DETECT_AL_DNS_RCODE].Match = NULL; + sigmatch_table[DETECT_AL_DNS_RCODE].AppLayerTxMatch = DetectDnsRcodeMatch; + + DetectAppLayerInspectEngineRegister( + "dns.rcode", ALPROTO_DNS, SIG_FLAG_TOSERVER, 0, DetectEngineInspectGenericList, NULL); + + DetectAppLayerInspectEngineRegister( + "dns.rcode", ALPROTO_DNS, SIG_FLAG_TOCLIENT, 0, DetectEngineInspectGenericList, NULL); + + dns_rcode_list_id = DetectBufferTypeGetByName("dns.rcode"); +} \ No newline at end of file diff --git a/src/detect-dns-rcode.h b/src/detect-dns-rcode.h new file mode 100644 index 0000000000..9501ceb457 --- /dev/null +++ b/src/detect-dns-rcode.h @@ -0,0 +1,23 @@ +/* Copyright (C) 2024 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. + */ + +#ifndef __DETECT_DNS_RCODE_H__ +#define __DETECT_DNS_RCODE_H__ + +void DetectDnsRcodeRegister(void); + +#endif /* __DETECT_DNS_RCODE_H__ */ \ No newline at end of file diff --git a/src/detect-engine-register.c b/src/detect-engine-register.c index 94fb303987..95f2f16412 100644 --- a/src/detect-engine-register.c +++ b/src/detect-engine-register.c @@ -48,6 +48,7 @@ #include "detect-engine-payload.h" #include "detect-engine-dcepayload.h" #include "detect-dns-opcode.h" +#include "detect-dns-rcode.h" #include "detect-dns-rrtype.h" #include "detect-dns-query.h" #include "detect-dns-answer-name.h" @@ -523,6 +524,7 @@ void SigTableSetup(void) DetectDnsQueryRegister(); DetectDnsOpcodeRegister(); + DetectDnsRcodeRegister(); DetectDnsRrtypeRegister(); DetectDnsAnswerNameRegister(); DetectDnsQueryNameRegister(); diff --git a/src/detect-engine-register.h b/src/detect-engine-register.h index dc64a4e917..f6f992d131 100644 --- a/src/detect-engine-register.h +++ b/src/detect-engine-register.h @@ -230,6 +230,7 @@ enum DetectKeywordId { DETECT_AL_DNS_QUERY, DETECT_AL_DNS_OPCODE, + DETECT_AL_DNS_RCODE, DETECT_AL_DNS_RRTYPE, DETECT_AL_DNS_ANSWER_NAME, DETECT_AL_DNS_QUERY_NAME,