From: Jason Ish Date: Mon, 16 Sep 2019 05:43:14 +0000 (-0600) Subject: dns/detect: dns.opcode keyword X-Git-Tag: suricata-5.0.0-rc1~42 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=d79c23baa3a16c7faf5136741ef0f769dab4d1b5;p=thirdparty%2Fsuricata.git dns/detect: dns.opcode keyword Add a rule keyword, dns.opcode to match on the opcode flag found in the DNS request and response headers. Only exact matches are allowed with negation. Examples: - dns.opcode:4; - dns.opcode:!1; --- diff --git a/rust/gen-c-headers.py b/rust/gen-c-headers.py index 90caae8eca..ecf8b0fd90 100755 --- a/rust/gen-c-headers.py +++ b/rust/gen-c-headers.py @@ -61,6 +61,7 @@ type_map = { "c_void": "void", "std::os::raw::c_char": "char", + "c_char": "char", "std::os::raw::c_int": "int", "c_int": "int", "std::os::raw::int8_t": "int8_t", @@ -104,6 +105,7 @@ type_map = { "CLuaState": "lua_State", "Store": "Store", "AppProto": "AppProto", + "DetectDnsOpcode": "void *", } def convert_type(rs_type): diff --git a/rust/src/dns/detect.rs b/rust/src/dns/detect.rs new file mode 100644 index 0000000000..bbf3c71c75 --- /dev/null +++ b/rust/src/dns/detect.rs @@ -0,0 +1,209 @@ +/* Copyright (C) 2019 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. + */ + +use super::dns::DNSTransaction; +use crate::core; +use std::ffi::CStr; +use std::os::raw::{c_char, c_void}; + +#[derive(Debug, PartialEq)] +pub struct DetectDnsOpcode { + negate: bool, + opcode: u8, +} + +/// Parse a DNS opcode argument returning the code and if it is to be +/// negated or not. +/// +/// For now only an indication that an error occurred is returned, not +/// the details of the error. +fn parse_opcode(opcode: &str) -> Result { + let mut negated = false; + for (i, c) in opcode.chars().enumerate() { + match c { + ' ' | '\t' => { + continue; + } + '!' => { + negated = true; + } + _ => { + let code: u8 = (&opcode[i..]).parse().or_else(|_| Err(()))?; + return Ok(DetectDnsOpcode { + negate: negated, + opcode: code, + }); + } + } + } + Err(()) +} + +/// Perform the DNS opcode match. +/// +/// 1 will be returned on match, otherwise 0 will be returned. +#[no_mangle] +pub extern "C" fn rs_dns_opcode_match( + tx: &mut DNSTransaction, + detect: &mut DetectDnsOpcode, + flags: u8, +) -> u8 { + let header_flags = if flags & core::STREAM_TOSERVER != 0 { + if let Some(request) = &tx.request { + request.header.flags + } else { + return 0; + } + } else if flags & core::STREAM_TOCLIENT != 0 { + if let Some(response) = &tx.response { + response.header.flags + } else { + return 0; + } + } else { + // Not to server or to client?? + return 0; + }; + + if match_opcode(detect, header_flags) { + 1 + } else { + 0 + } +} + +fn match_opcode(detect: &DetectDnsOpcode, flags: u16) -> bool { + let opcode = ((flags >> 11) & 0xf) as u8; + if detect.negate { + detect.opcode != opcode + } else { + detect.opcode == opcode + } +} + +#[no_mangle] +pub unsafe extern "C" fn rs_detect_dns_opcode_parse(carg: *const c_char) -> *mut c_void { + if carg.is_null() { + return std::ptr::null_mut(); + } + let arg = match CStr::from_ptr(carg).to_str() { + Ok(arg) => arg, + _ => { + return std::ptr::null_mut(); + } + }; + + match parse_opcode(&arg) { + Ok(detect) => std::mem::transmute(Box::new(detect)), + Err(_) => std::ptr::null_mut(), + } +} + +#[no_mangle] +pub unsafe extern "C" fn rs_dns_detect_opcode_free(ptr: *mut c_void) { + if ptr != std::ptr::null_mut() { + let _: Box = std::mem::transmute(ptr); + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn parse_opcode_good() { + assert_eq!( + parse_opcode("1"), + Ok(DetectDnsOpcode { + negate: false, + opcode: 1 + }) + ); + assert_eq!( + parse_opcode("123"), + Ok(DetectDnsOpcode { + negate: false, + opcode: 123 + }) + ); + assert_eq!( + parse_opcode("!123"), + Ok(DetectDnsOpcode { + negate: true, + opcode: 123 + }) + ); + assert_eq!( + parse_opcode("!123"), + Ok(DetectDnsOpcode { + negate: true, + opcode: 123 + }) + ); + assert_eq!(parse_opcode(""), Err(())); + assert_eq!(parse_opcode("!"), Err(())); + assert_eq!(parse_opcode("! "), Err(())); + assert_eq!(parse_opcode("!asdf"), Err(())); + } + + #[test] + fn test_match_opcode() { + assert_eq!( + match_opcode( + &DetectDnsOpcode { + negate: false, + opcode: 0, + }, + 0b0000_0000_0000_0000, + ), + true + ); + + assert_eq!( + match_opcode( + &DetectDnsOpcode { + negate: true, + opcode: 0, + }, + 0b0000_0000_0000_0000, + ), + false + ); + + assert_eq!( + match_opcode( + &DetectDnsOpcode { + negate: false, + opcode: 4, + }, + 0b0010_0000_0000_0000, + ), + true + ); + + assert_eq!( + match_opcode( + &DetectDnsOpcode { + negate: true, + opcode: 4, + }, + 0b0010_0000_0000_0000, + ), + false + ); + } +} diff --git a/rust/src/dns/mod.rs b/rust/src/dns/mod.rs index 5da27124fb..685adcfa3c 100644 --- a/rust/src/dns/mod.rs +++ b/rust/src/dns/mod.rs @@ -18,6 +18,7 @@ pub mod parser; pub mod dns; pub mod log; +pub mod detect; #[cfg(feature = "lua")] pub mod lua; diff --git a/src/Makefile.am b/src/Makefile.am index 587fa492cc..38d116bbc3 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -112,6 +112,7 @@ detect-depth.c detect-depth.h \ detect-detection-filter.c detect-detection-filter.h \ detect-distance.c detect-distance.h \ detect-dnp3.c detect-dnp3.h \ +detect-dns-opcode.c detect-dns-opcode.h \ detect-dns-query.c detect-dns-query.h \ detect-tls-ja3-hash.c detect-tls-ja3-hash.h \ detect-tls-ja3-string.c detect-tls-ja3-string.h \ diff --git a/src/detect-dns-opcode.c b/src/detect-dns-opcode.c new file mode 100644 index 0000000000..1d90b0668b --- /dev/null +++ b/src/detect-dns-opcode.c @@ -0,0 +1,106 @@ +/* Copyright (C) 2019 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-opcode.h" +#include "app-layer-dns-common.h" +#include "rust-dns-detect-gen.h" + +static int dns_opcode_list_id = 0; + +static void DetectDnsOpcodeFree(void *ptr); + +static int DetectDnsOpcodeSetup(DetectEngineCtx *de_ctx, Signature *s, + const char *str) +{ + SCEnter(); + + if (DetectSignatureSetAppProto(s, ALPROTO_DNS) != 0) { + return -1; + } + + void *detect = rs_detect_dns_opcode_parse(str); + if (detect == NULL) { + SCLogNotice("Failed to parse dns.opcode: %s", str); + return -1; + } + + SigMatch *sm = SigMatchAlloc(); + if (unlikely(sm == NULL)) { + goto error; + } + + sm->type = DETECT_AL_DNS_OPCODE; + sm->ctx = (void *)detect; + SigMatchAppendSMToList(s, sm, dns_opcode_list_id); + + SCReturnInt(0); + +error: + DetectDnsOpcodeFree(detect); + SCReturnInt(-1); +} + +static void DetectDnsOpcodeFree(void *ptr) +{ + SCEnter(); + if (ptr != NULL) { + rs_dns_detect_opcode_free(ptr); + } + SCReturn; +} + +static int DetectDnsOpcodeMatch(DetectEngineThreadCtx *det_ctx, + Flow *f, uint8_t flags, void *state, void *txv, const Signature *s, + const SigMatchCtx *ctx) +{ + return rs_dns_opcode_match(txv, (void *)ctx, flags); +} + +static int DetectEngineInspectRequestGenericDnsOpcode(ThreadVars *tv, + DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx, + const Signature *s, const SigMatchData *smd, + Flow *f, uint8_t flags, void *alstate, + void *txv, uint64_t tx_id) +{ + return DetectEngineInspectGenericList(tv, de_ctx, det_ctx, s, smd, + f, flags, alstate, txv, tx_id); +} + +void DetectDnsOpcodeRegister(void) +{ + sigmatch_table[DETECT_AL_DNS_OPCODE].name = "dns.opcode"; + sigmatch_table[DETECT_AL_DNS_OPCODE].desc = "Match the DNS header opcode flag."; + sigmatch_table[DETECT_AL_DNS_OPCODE].Setup = DetectDnsOpcodeSetup; + sigmatch_table[DETECT_AL_DNS_OPCODE].Free = DetectDnsOpcodeFree; + sigmatch_table[DETECT_AL_DNS_OPCODE].Match = NULL; + sigmatch_table[DETECT_AL_DNS_OPCODE].AppLayerTxMatch = + DetectDnsOpcodeMatch; + + DetectAppLayerInspectEngineRegister("dns.opcode", + ALPROTO_DNS, SIG_FLAG_TOSERVER, 0, + DetectEngineInspectRequestGenericDnsOpcode); + + DetectAppLayerInspectEngineRegister("dns.opcode", + ALPROTO_DNS, SIG_FLAG_TOCLIENT, 0, + DetectEngineInspectRequestGenericDnsOpcode); + + dns_opcode_list_id = DetectBufferTypeGetByName("dns.opcode"); +} diff --git a/src/detect-dns-opcode.h b/src/detect-dns-opcode.h new file mode 100644 index 0000000000..0518a58b49 --- /dev/null +++ b/src/detect-dns-opcode.h @@ -0,0 +1,23 @@ +/* Copyright (C) 2019 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_OPCODE_H__ +#define __DETECT_DNS_OPCODE_H__ + +void DetectDnsOpcodeRegister(void); + +#endif /* __DETECT_DNS_OPCODE_H__ */ diff --git a/src/detect-engine-register.c b/src/detect-engine-register.c index 294a4b7d76..7a2cd382ab 100644 --- a/src/detect-engine-register.c +++ b/src/detect-engine-register.c @@ -45,6 +45,7 @@ #include "detect-engine-payload.h" #include "detect-engine-dcepayload.h" +#include "detect-dns-opcode.h" #include "detect-dns-query.h" #include "detect-tls-sni.h" #include "detect-tls-certs.h" @@ -436,6 +437,7 @@ void SigTableSetup(void) DetectHttpStatCodeRegister(); DetectDnsQueryRegister(); + DetectDnsOpcodeRegister(); DetectModbusRegister(); DetectCipServiceRegister(); DetectEnipCommandRegister(); diff --git a/src/detect-engine-register.h b/src/detect-engine-register.h index 7cc05f33c3..4c3e5ff6d5 100644 --- a/src/detect-engine-register.h +++ b/src/detect-engine-register.h @@ -195,6 +195,7 @@ enum { DETECT_IPREP, DETECT_AL_DNS_QUERY, + DETECT_AL_DNS_OPCODE, DETECT_AL_TLS_SNI, DETECT_AL_TLS_CERTS, DETECT_AL_TLS_CERT_ISSUER,