Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion doc/userguide/rules/dnp3-keywords.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ This keyword will match on the application function code found in DNP3
request and responses. It can be specified as the integer value or
the symbolic name of the function code.

dnp3_func uses an :ref:`unsigned 8-bits integer <rules-integer-keywords>`.

Syntax
~~~~~~

Expand Down Expand Up @@ -68,7 +70,7 @@ dnp3_ind
This keyword matches on the DNP3 internal indicator flags in the
response application header.

dnp3_ind uses :ref:`unsigned 16-bit integer <rules-integer-keywords>` with bitmasks.
dnp3_ind uses an :ref:`unsigned 16-bits integer <rules-integer-keywords>` with bitmasks.

Syntax
~~~~~~
Expand Down
14 changes: 14 additions & 0 deletions doc/userguide/rules/header-keywords.rst
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,8 @@ this way, the receiver of the packet knows which fragments belong to
the same packet. (IP ID does not take care of the order, in that case
offset is used. It clarifies the order of the fragments.)

id uses an :ref:`unsigned 16-bits integer <rules-integer-keywords>`.

Format of id::

id:<number>;
Expand Down Expand Up @@ -246,6 +248,8 @@ reassembly. The id is used to determine which fragments belong to
which packet and the fragmentation offset field clarifies the order of
the fragments.

fragoffset uses an :ref:`unsigned 16-bits integer <rules-integer-keywords>`.

You can use the following modifiers::

< match if the value is smaller than the specified value
Expand Down Expand Up @@ -374,6 +378,8 @@ keeping track of to what place in a data-stream a byte belongs. If the
SYN flag is set at 1, then the sequence number of the first byte of
the data is this number plus 1 (so, 2).

seq uses an :ref:`unsigned 32-bits integer <rules-integer-keywords>`.

Example::

seq:0;
Expand Down Expand Up @@ -401,6 +407,8 @@ occasions every packet of a TCP connection has an ACK flag after the
first SYN and a ack-number which increases with the receipt of every
new data-byte.

ack uses an :ref:`unsigned 32-bits integer <rules-integer-keywords>`.

Format of ``ack``::

ack:1;
Expand Down Expand Up @@ -430,6 +438,8 @@ This mechanism is used to prevent the receiver from being overflowed by
data. The value of the window size is limited and can be 2 to 65.535 bytes.
To make more use of your bandwidth you can use a bigger TCP-window.

window uses an :ref:`unsigned 16-bits integer <rules-integer-keywords>`.

The format of the window keyword is::

window:[!]<number>;
Expand Down Expand Up @@ -706,6 +716,8 @@ receiver has received the packet, it will send a reply using the same
id so the sender will recognize it and connects it with the correct
ICMP-request.

icmp_id uses an :ref:`unsigned 16-bits integer <rules-integer-keywords>`.

Format of the icmp_id keyword::

icmp_id:<number>;
Expand All @@ -729,6 +741,8 @@ ICMP messages all have sequence numbers. This can be useful (together
with the id) for checking which reply message belongs to which request
message.

icmp_seq uses an :ref:`unsigned 16-bits integer <rules-integer-keywords>`.

Format of the icmp_seq keyword::

icmp_seq:<number>;
Expand Down
21 changes: 18 additions & 3 deletions etc/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -600,7 +600,12 @@
}
},
"function_code": {
"type": "integer"
"type": "integer",
"suricata": {
"keywords": [
"dnp3.func"
]
}
},
"objects": {
"type": "array",
Expand Down Expand Up @@ -721,7 +726,12 @@
}
},
"function_code": {
"type": "integer"
"type": "integer",
"suricata": {
"keywords": [
"dnp3.func"
]
}
},
"objects": {
"type": "array",
Expand Down Expand Up @@ -832,7 +842,12 @@
}
},
"function_code": {
"type": "integer"
"type": "integer",
"suricata": {
"keywords": [
"dnp3.func"
]
}
},
"objects": {
"type": "array",
Expand Down
31 changes: 30 additions & 1 deletion rust/src/detect/uint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
*/

use nom7::branch::alt;
use nom7::bytes::complete::{is_a, tag, tag_no_case, take, take_while};
use nom7::bytes::complete::{is_a, tag, tag_no_case, take, take_till, take_while};
use nom7::character::complete::{anychar, char, digit1, hex_digit1, i32 as nom_i32};
use nom7::combinator::{all_consuming, map_opt, opt, value, verify};
use nom7::error::{make_error, Error, ErrorKind};
Expand Down Expand Up @@ -935,6 +935,35 @@ pub unsafe extern "C" fn SCDetectU16Parse(
return std::ptr::null_mut();
}

pub fn detect_parse_unquote_uint<T: DetectIntType>(i: &str) -> IResult<&str, DetectUintData<T>> {
let (i, _) = take_while(|c| c == ' ')(i)?;
let (i, quote) = opt(tag("\""))(i)?;
if quote.is_some() {
let (i, unquote) = take_till(|c| c == '"')(i)?;
if i.is_empty() {
return Err(Err::Error(make_error(i, ErrorKind::Tag)));
}
let (_i, uint) = detect_parse_uint(unquote)?;
return Ok((i, uint));
}
let (i, uint) = detect_parse_uint(i)?;
Ok((i, uint))
}

#[no_mangle]
pub unsafe extern "C" fn SCDetectU16UnquoteParse(
ustr: *const std::os::raw::c_char,
) -> *mut DetectUintData<u16> {
let ft_name: &CStr = CStr::from_ptr(ustr); //unsafe
if let Ok(s) = ft_name.to_str() {
if let Ok((_, ctx)) = detect_parse_unquote_uint::<u16>(s) {
let boxed = Box::new(ctx);
return Box::into_raw(boxed) as *mut _;
}
}
return std::ptr::null_mut();
}

#[no_mangle]
pub unsafe extern "C" fn SCDetectU16Match(
arg: u16, ctx: &DetectUintData<u16>,
Expand Down
81 changes: 80 additions & 1 deletion rust/src/dnp3/detect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
* 02110-1301, USA.
*/

use crate::detect::uint::{detect_parse_uint_bitflags, DetectBitflagModifier, DetectUintData};
use crate::detect::uint::{
detect_parse_uint_bitflags, detect_parse_uint_enum, DetectBitflagModifier, DetectUintData,
};

use std::ffi::CStr;

Expand Down Expand Up @@ -45,6 +47,48 @@ fn dnp3_detect_ind_parse(s: &str) -> Option<DetectUintData<u16>> {
detect_parse_uint_bitflags::<u16, Dnp3IndFlag>(s, DetectBitflagModifier::Any, false)
}

#[repr(u8)]
#[derive(EnumStringU8)]
pub enum Dnp3Func {
Confirm = 0,
Read = 1,
Write = 2,
Select = 3,
Operate = 4,
DirectOperate = 5,
DirectOperateNr = 6,
ImmedFreeze = 7,
ImmedFreezeNr = 8,
FreezeClear = 9,
FreezeClearNr = 10,
FreezeAtTime = 11,
FreezeAtTimeNr = 12,
ColdRestart = 13,
WarmRestart = 14,
InitializeData = 15,
InitializeAppl = 16,
StartAppl = 17,
StopAppl = 18,
SaveConfig = 19,
EnableUnsolicited = 20,
DisableUnsolicited = 21,
AssignClass = 22,
DelayMeasure = 23,
RecordCurrentTime = 24,
OpenFile = 25,
CloseFile = 26,
DeleteFile = 27,
GetFileInfo = 28,
AuthenticateFile = 29,
AbortFile = 30,
ActivateConfig = 31,
AutenthicateReq = 32,
AutenthicateErr = 33,
Response = 129,
UnsolicitedResponse = 130,
AuthenticateResp = 131,
}

#[no_mangle]
pub unsafe extern "C" fn SCDnp3DetectIndParse(
ustr: *const std::os::raw::c_char,
Expand All @@ -59,6 +103,20 @@ pub unsafe extern "C" fn SCDnp3DetectIndParse(
return std::ptr::null_mut();
}

#[no_mangle]
pub unsafe extern "C" fn SCDnp3DetectFuncParse(
ustr: *const std::os::raw::c_char,
) -> *mut DetectUintData<u8> {
let ft_name: &CStr = CStr::from_ptr(ustr); //unsafe
if let Ok(s) = ft_name.to_str() {
if let Some(ctx) = detect_parse_uint_enum::<u8, Dnp3Func>(s) {
let boxed = Box::new(ctx);
return Box::into_raw(boxed) as *mut _;
}
}
return std::ptr::null_mut();
}

#[cfg(test)]
mod test {
use super::*;
Expand All @@ -84,4 +142,25 @@ mod test {
assert_eq!(ctx.arg1, 0x600);
assert!(dnp3_detect_ind_parse("something",).is_none());
}

#[test]
fn dnp3_func_parse() {
let ctx = detect_parse_uint_enum::<u8, Dnp3Func>("0").unwrap();
assert_eq!(ctx.arg1, 0);
let ctx = detect_parse_uint_enum::<u8, Dnp3Func>("1").unwrap();
assert_eq!(ctx.arg1, 1);
let ctx = detect_parse_uint_enum::<u8, Dnp3Func>("254").unwrap();
assert_eq!(ctx.arg1, 254);
let ctx = detect_parse_uint_enum::<u8, Dnp3Func>("255").unwrap();
assert_eq!(ctx.arg1, 255);
let ctx = detect_parse_uint_enum::<u8, Dnp3Func>("confirm").unwrap();
assert_eq!(ctx.arg1, 0);
let ctx = detect_parse_uint_enum::<u8, Dnp3Func>("CONFIRM").unwrap();
assert_eq!(ctx.arg1, 0);
assert!(detect_parse_uint_enum::<u8, Dnp3Func>("").is_none());
assert!(detect_parse_uint_enum::<u8, Dnp3Func>("-1").is_none());
assert!(detect_parse_uint_enum::<u8, Dnp3Func>("-2").is_none());
assert!(detect_parse_uint_enum::<u8, Dnp3Func>("256").is_none());
assert!(detect_parse_uint_enum::<u8, Dnp3Func>("unknown_function_code").is_none());
}
}
Loading
Loading