modbus: move from C to rust

Adds a new rust modbus app layer parser and detection module.

Moves the C module to rust but leaves the test cases in place to
regression test the new rust module.
pull/6101/head
Simon Dugas 5 years ago committed by Victor Julien
parent 7c99fe3689
commit a458a94dca

@ -33,6 +33,8 @@ widestring = "~0.4.3"
flate2 = "~1.0.19"
brotli = "~3.3.0"
sawp-modbus = "~0.4.0"
sawp = "~0.4.0"
der-parser = "~4.0.2"
kerberos-parser = "~0.5.0"
ntp-parser = "~0.4.0"
@ -45,6 +47,8 @@ sha2 = "~0.9.2"
digest = "~0.9.0"
sha-1 = "~0.9.2"
md-5 = "~0.9.1"
regex = "~1.4.2"
lazy_static = "~1.4.0"
[dev-dependencies]
test-case = "~1.1.0"

@ -75,6 +75,7 @@ include = [
"AppLayerGetTxIterTuple",
"RdpState",
"SIPState",
"ModbusState",
"CMark",
]

@ -66,6 +66,7 @@ pub mod ftp;
pub mod smb;
pub mod krb;
pub mod dcerpc;
pub mod modbus;
pub mod ike;
pub mod snmp;

@ -0,0 +1,539 @@
/* Copyright (C) 2021 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::modbus::ModbusTransaction;
use lazy_static::lazy_static;
use regex::Regex;
use sawp_modbus::{AccessType, CodeCategory, Data, Flags, FunctionCode, Message};
use std::ffi::CStr;
use std::ops::{Range, RangeInclusive};
use std::os::raw::{c_char, c_void};
use std::str::FromStr;
lazy_static! {
static ref ACCESS_RE: Regex = Regex::new(
"^\\s*\"?\\s*access\\s*(read|write)\
\\s*(discretes|coils|input|holding)?\
(?:,\\s*address\\s+([<>]?\\d+)(?:<>(\\d+))?\
(?:,\\s*value\\s+([<>]?\\d+)(?:<>(\\d+))?)?)?\
\\s*\"?\\s*$"
)
.unwrap();
static ref FUNC_RE: Regex = Regex::new(
"^\\s*\"?\\s*function\\s*(!?[A-z0-9]+)\
(?:,\\s*subfunction\\s+(\\d+))?\\s*\"?\\s*$"
)
.unwrap();
static ref UNIT_RE: Regex = Regex::new(
"^\\s*\"?\\s*unit\\s+([<>]?\\d+)\
(?:<>(\\d+))?(?:,\\s*(.*))?\\s*\"?\\s*$"
)
.unwrap();
}
#[derive(Debug, PartialEq)]
pub struct DetectModbusRust {
category: Option<Flags<CodeCategory>>,
function: Option<FunctionCode>,
subfunction: Option<u16>,
access_type: Option<Flags<AccessType>>,
unit_id: Option<Range<u16>>,
address: Option<Range<u16>>,
value: Option<Range<u16>>,
}
/// TODO: remove these after regression testing commit
#[no_mangle]
pub extern "C" fn rs_modbus_get_category(modbus: *const DetectModbusRust) -> u8 {
let modbus = unsafe { modbus.as_ref() }.unwrap();
modbus.category.map(|val| val.bits()).unwrap_or(0)
}
#[no_mangle]
pub extern "C" fn rs_modbus_get_function(modbus: *const DetectModbusRust) -> u8 {
let modbus = unsafe { modbus.as_ref() }.unwrap();
modbus.function.map(|val| val as u8).unwrap_or(0)
}
#[no_mangle]
pub extern "C" fn rs_modbus_get_subfunction(modbus: *const DetectModbusRust) -> u16 {
let modbus = unsafe { modbus.as_ref() }.unwrap();
modbus.subfunction.unwrap_or(0)
}
#[no_mangle]
pub extern "C" fn rs_modbus_get_has_subfunction(modbus: *const DetectModbusRust) -> bool {
let modbus = unsafe { modbus.as_ref() }.unwrap();
modbus.subfunction.is_some()
}
#[no_mangle]
pub extern "C" fn rs_modbus_get_access_type(modbus: *const DetectModbusRust) -> u8 {
let modbus = unsafe { modbus.as_ref() }.unwrap();
modbus.access_type.map(|val| val.bits()).unwrap_or(0)
}
#[no_mangle]
pub extern "C" fn rs_modbus_get_unit_id_min(modbus: *const DetectModbusRust) -> u16 {
let modbus = unsafe { modbus.as_ref() }.unwrap();
modbus.unit_id.as_ref().map(|val| val.start).unwrap_or(0)
}
#[no_mangle]
pub extern "C" fn rs_modbus_get_unit_id_max(modbus: *const DetectModbusRust) -> u16 {
let modbus = unsafe { modbus.as_ref() }.unwrap();
modbus.unit_id.as_ref().map(|val| val.end).unwrap_or(0)
}
#[no_mangle]
pub extern "C" fn rs_modbus_get_address_min(modbus: *const DetectModbusRust) -> u16 {
let modbus = unsafe { modbus.as_ref() }.unwrap();
modbus.address.as_ref().map(|val| val.start).unwrap_or(0)
}
#[no_mangle]
pub extern "C" fn rs_modbus_get_address_max(modbus: *const DetectModbusRust) -> u16 {
let modbus = unsafe { modbus.as_ref() }.unwrap();
modbus.address.as_ref().map(|val| val.end).unwrap_or(0)
}
#[no_mangle]
pub extern "C" fn rs_modbus_get_data_min(modbus: *const DetectModbusRust) -> u16 {
let modbus = unsafe { modbus.as_ref() }.unwrap();
modbus.value.as_ref().map(|val| val.start).unwrap_or(0)
}
#[no_mangle]
pub extern "C" fn rs_modbus_get_data_max(modbus: *const DetectModbusRust) -> u16 {
let modbus = unsafe { modbus.as_ref() }.unwrap();
modbus.value.as_ref().map(|val| val.end).unwrap_or(0)
}
impl Default for DetectModbusRust {
fn default() -> Self {
DetectModbusRust {
category: None,
function: None,
subfunction: None,
access_type: None,
unit_id: None,
address: None,
value: None,
}
}
}
/// Compares a range from the alert signature to the transaction's unit_id/address/value
/// range. If the signature's range intersects with the transaction, it is a match and true is
/// returned.
fn check_match_range(sig_range: &Range<u16>, trans_range: RangeInclusive<u16>) -> bool {
if sig_range.start == sig_range.end {
sig_range.start >= *trans_range.start() && sig_range.start <= *trans_range.end()
} else if sig_range.start == std::u16::MIN {
sig_range.end > *trans_range.start()
} else if sig_range.end == std::u16::MAX {
sig_range.start < *trans_range.end()
} else {
sig_range.start < *trans_range.end() && *trans_range.start() < sig_range.end
}
}
/// Compares a range from the alert signature to the transaction's unit_id/address/value.
/// If the signature's range intersects with the transaction, it is a match and true is
/// returned.
fn check_match(sig_range: &Range<u16>, value: u16) -> bool {
if sig_range.start == sig_range.end {
sig_range.start == value
} else if sig_range.start == std::u16::MIN {
sig_range.end > value
} else if sig_range.end == std::u16::MAX {
sig_range.start < value
} else {
sig_range.start < value && value < sig_range.end
}
}
/// Gets the min/max range of an alert signature from the respective capture groups.
/// In the case where the max is not given, it is set based on the first char of the min str
/// which indicates what range we are looking for:
/// '<' = std::u16::MIN..min
/// '>' = min..std::u16::MAX
/// _ = min..min
/// If the max is given, the range returned is min..max
fn parse_range(min_str: &str, max_str: &str) -> Result<Range<u16>, ()> {
if max_str.is_empty() {
if let Some(sign) = min_str.chars().next() {
match min_str[!sign.is_ascii_digit() as usize..].parse::<u16>() {
Ok(num) => match sign {
'>' => Ok(num..std::u16::MAX),
'<' => Ok(std::u16::MIN..num),
_ => Ok(num..num),
},
Err(_) => {
SCLogError!("Invalid min number: {}", min_str);
Err(())
}
}
} else {
Err(())
}
} else {
let min = match min_str.parse::<u16>() {
Ok(num) => num,
Err(_) => {
SCLogError!("Invalid min number: {}", min_str);
return Err(());
}
};
let max = match max_str.parse::<u16>() {
Ok(num) => num,
Err(_) => {
SCLogError!("Invalid max number: {}", max_str);
return Err(());
}
};
Ok(min..max)
}
}
/// Intermediary function between the C code and the parsing functions.
#[no_mangle]
pub unsafe extern "C" fn rs_modbus_parse(c_arg: *const c_char) -> *mut c_void {
if c_arg.is_null() {
return std::ptr::null_mut();
}
if let Ok(arg) = CStr::from_ptr(c_arg).to_str() {
match parse_unit_id(&arg)
.or_else(|_| parse_function(&arg))
.or_else(|_| parse_access(&arg))
{
Ok(detect) => return Box::into_raw(Box::new(detect)) as *mut c_void,
Err(()) => return std::ptr::null_mut(),
}
}
std::ptr::null_mut()
}
#[no_mangle]
pub unsafe extern "C" fn rs_modbus_free(ptr: *mut c_void) {
if !ptr.is_null() {
let _ = Box::from_raw(ptr as *mut DetectModbusRust);
}
}
/// Compares a transaction to a signature to determine whether the transaction
/// matches the signature. If it does, 1 is returned; otherwise 0 is returned.
#[no_mangle]
pub extern "C" fn rs_modbus_inspect(tx: &ModbusTransaction, modbus: &DetectModbusRust) -> u8 {
// All necessary information can be found in the request (value inspection currently
// only supports write functions, which hold the value in the request).
// Only inspect the response in the case where there is no request.
let msg = match &tx.request {
Some(r) => r,
None => match &tx.response {
Some(r) => r,
None => return 0,
},
};
if let Some(unit_id) = &modbus.unit_id {
if !check_match(unit_id, msg.unit_id.into()) {
return 0;
}
}
if let Some(access_type) = &modbus.access_type {
let rd_wr_access = *access_type & (AccessType::READ | AccessType::WRITE);
let access_func = *access_type & AccessType::FUNC_MASK;
if rd_wr_access.is_empty()
|| !msg.access_type.intersects(rd_wr_access)
|| (!access_func.is_empty() && !msg.access_type.intersects(access_func))
{
return 0;
}
return inspect_data(msg, modbus) as u8;
}
if let Some(category) = modbus.category {
return u8::from(msg.category.intersects(category));
}
match &modbus.function {
Some(func) if func == &msg.function.code => match modbus.subfunction {
Some(subfunc) => {
if let Data::Diagnostic { func, data: _ } = &msg.data {
u8::from(subfunc == func.raw)
} else {
0
}
}
None => 1,
},
None => 1,
_ => 0,
}
}
/// Compares the transaction's data with the signature to determine whether or
/// not it is a match
fn inspect_data(msg: &Message, modbus: &DetectModbusRust) -> bool {
let sig_address = if let Some(sig_addr) = &modbus.address {
// Compare the transaction's address with the signature to determine whether or
// not it is a match
if let Some(req_addr) = msg.get_address_range() {
if !check_match_range(sig_addr, req_addr) {
return false;
}
} else {
return false;
}
sig_addr.start
} else {
return true;
};
let sig_value = if let Some(value) = &modbus.value {
value
} else {
return true;
};
if let Some(value) = msg.get_write_value_at_address(&sig_address) {
check_match(sig_value, value)
} else {
false
}
}
/// Parses the access type for the signature
fn parse_access(access_str: &str) -> Result<DetectModbusRust, ()> {
let re = if let Some(re) = ACCESS_RE.captures(access_str) {
re
} else {
return Err(());
};
// 1: Read | Write
let mut access_type: Flags<AccessType> = match re.get(1) {
Some(access) => match AccessType::from_str(access.as_str()) {
Ok(access_type) => access_type.into(),
Err(_) => {
SCLogError!("Unknown access keyword {}", access.as_str());
return Err(());
}
},
None => {
SCLogError!("No access keyword found");
return Err(());
}
};
// 2: Discretes | Coils | Input | Holding
access_type = match re.get(2) {
Some(x) if x.as_str() == "coils" => access_type | AccessType::COILS,
Some(x) if x.as_str() == "holding" => access_type | AccessType::HOLDING,
Some(x) if x.as_str() == "discretes" => {
if access_type == AccessType::WRITE {
SCLogError!("Discrete access is only read access");
return Err(());
}
access_type | AccessType::DISCRETES
}
Some(x) if x.as_str() == "input" => {
if access_type == AccessType::WRITE {
SCLogError!("Input access is only read access");
return Err(());
}
access_type | AccessType::INPUT
}
Some(unknown) => {
SCLogError!("Unknown access keyword {}", unknown.as_str());
return Err(());
}
None => access_type,
};
// 3: Address min
let address = if let Some(min) = re.get(3) {
// 4: Address max
let max_str = if let Some(max) = re.get(4) {
max.as_str()
} else {
""
};
parse_range(min.as_str(), max_str)?
} else {
return Ok(DetectModbusRust {
access_type: Some(access_type),
..Default::default()
});
};
// 5: Value min
let value = if let Some(min) = re.get(5) {
if address.start != address.end {
SCLogError!("rule contains conflicting keywords (address range and value).");
return Err(());
}
if access_type == AccessType::READ {
SCLogError!("Value keyword only works in write access");
return Err(());
}
// 6: Value max
let max_str = if let Some(max) = re.get(6) {
max.as_str()
} else {
""
};
parse_range(min.as_str(), max_str)?
} else {
return Ok(DetectModbusRust {
access_type: Some(access_type),
address: Some(address),
..Default::default()
});
};
Ok(DetectModbusRust {
access_type: Some(access_type),
address: Some(address),
value: Some(value),
..Default::default()
})
}
fn parse_function(func_str: &str) -> Result<DetectModbusRust, ()> {
let re = if let Some(re) = FUNC_RE.captures(func_str) {
re
} else {
return Err(());
};
let mut modbus: DetectModbusRust = Default::default();
// 1: Function
if let Some(x) = re.get(1) {
let word = x.as_str();
// Digit
if let Ok(num) = word.parse::<u8>() {
if num == 0 {
SCLogError!("Invalid modbus function value");
return Err(());
}
modbus.function = Some(FunctionCode::from_raw(num));
// 2: Subfunction (optional)
match re.get(2) {
Some(x) => {
let subfunc = x.as_str();
match subfunc.parse::<u16>() {
Ok(num) => {
modbus.subfunction = Some(num);
}
Err(_) => {
SCLogError!("Invalid subfunction value: {}", subfunc);
return Err(());
}
}
}
None => return Ok(modbus),
}
}
// Non-digit
else {
let neg = word.starts_with('!');
let category = match &word[neg as usize..] {
"assigned" => CodeCategory::PUBLIC_ASSIGNED.into(),
"unassigned" => CodeCategory::PUBLIC_UNASSIGNED.into(),
"public" => CodeCategory::PUBLIC_ASSIGNED | CodeCategory::PUBLIC_UNASSIGNED,
"user" => CodeCategory::USER_DEFINED.into(),
"reserved" => CodeCategory::RESERVED.into(),
"all" => {
CodeCategory::PUBLIC_ASSIGNED
| CodeCategory::PUBLIC_UNASSIGNED
| CodeCategory::USER_DEFINED
| CodeCategory::RESERVED
}
_ => {
SCLogError!("Keyword unknown: {}", word);
return Err(());
}
};
if neg {
modbus.category = Some(!category);
} else {
modbus.category = Some(category);
}
}
} else {
return Err(());
}
Ok(modbus)
}
fn parse_unit_id(unit_str: &str) -> Result<DetectModbusRust, ()> {
let re = if let Some(re) = UNIT_RE.captures(unit_str) {
re
} else {
return Err(());
};
// 3: Either function or access string
let mut modbus = if let Some(x) = re.get(3) {
let extra = x.as_str();
if let Ok(mbus) = parse_function(extra) {
mbus
} else if let Ok(mbus) = parse_access(extra) {
mbus
} else {
SCLogError!("Invalid modbus option: {}", extra);
return Err(());
}
} else {
Default::default()
};
// 1: Unit ID min
if let Some(min) = re.get(1) {
// 2: Unit ID max
let max_str = if let Some(max) = re.get(2) {
max.as_str()
} else {
""
};
modbus.unit_id = Some(parse_range(min.as_str(), max_str)?);
} else {
SCLogError!("Min modbus unit ID not found");
return Err(());
}
Ok(modbus)
}

@ -0,0 +1,19 @@
/* Copyright (C) 2021 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.
*/
pub mod detect;
pub mod modbus;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -34,25 +34,6 @@
#ifndef __APP_LAYER_MODBUS_H__
#define __APP_LAYER_MODBUS_H__
#include "decode.h"
#include "detect-engine-state.h"
#include "queue.h"
#include "rust.h"
/* Modbus Application Data Unit (ADU)
* and Protocol Data Unit (PDU) messages */
enum {
MODBUS_DECODER_EVENT_INVALID_PROTOCOL_ID,
MODBUS_DECODER_EVENT_UNSOLICITED_RESPONSE,
MODBUS_DECODER_EVENT_INVALID_LENGTH,
MODBUS_DECODER_EVENT_INVALID_UNIT_IDENTIFIER,
MODBUS_DECODER_EVENT_INVALID_FUNCTION_CODE,
MODBUS_DECODER_EVENT_INVALID_VALUE,
MODBUS_DECODER_EVENT_INVALID_EXCEPTION_CODE,
MODBUS_DECODER_EVENT_VALUE_MISMATCH,
MODBUS_DECODER_EVENT_FLOODED,
};
/* Modbus Function Code Categories. */
#define MODBUS_CAT_NONE 0x0
#define MODBUS_CAT_PUBLIC_ASSIGNED (1<<0)
@ -79,57 +60,6 @@ enum {
#define MODBUS_TYP_WRITE_MULTIPLE (MODBUS_TYP_WRITE | MODBUS_TYP_MULTIPLE)
#define MODBUS_TYP_READ_WRITE_MULTIPLE (MODBUS_TYP_READ | MODBUS_TYP_WRITE | MODBUS_TYP_MULTIPLE)
/* Modbus Function Code. */
#define MODBUS_FUNC_NONE 0x00
/* Modbus Transaction Structure, request/response. */
typedef struct ModbusTransaction_ {
struct ModbusState_ *modbus;
uint64_t tx_num; /**< internal: id */
uint16_t transactionId;
uint16_t length;
uint8_t unit_id;
uint8_t function;
uint8_t category;
uint8_t type;
uint8_t replied; /**< bool indicating request is replied to. */
union {
uint16_t subFunction;
uint8_t mei;
struct {
struct {
uint16_t address;
uint16_t quantity;
} read;
struct {
uint16_t address;
uint16_t quantity;
uint8_t count;
} write;
};
};
uint16_t *data; /**< to store data to write, bit is converted in 16bits. */
AppLayerDecoderEvents *decoder_events; /**< per tx events */
DetectEngineState *de_state;
AppLayerTxData tx_data;
TAILQ_ENTRY(ModbusTransaction_) next;
} ModbusTransaction;
/* Modbus State Structure. */
typedef struct ModbusState_ {
TAILQ_HEAD(, ModbusTransaction_) tx_list; /**< transaction list */
ModbusTransaction *curr; /**< ptr to current tx */
uint64_t transaction_max;
uint32_t unreplied_cnt; /**< number of unreplied requests */
uint16_t events;
uint8_t givenup; /**< bool indicating flood. */
} ModbusState;
void RegisterModbusParsers(void);
void ModbusParserRegisterTests(void);
#endif /* __APP_LAYER_MODBUS_H__ */

@ -35,7 +35,6 @@
#include "suricata-common.h"
#include "app-layer.h"
#include "app-layer-modbus.h"
#include "detect.h"
#include "detect-modbus.h"
@ -46,224 +45,6 @@
#include "util-debug.h"
/** \internal
*
* \brief Value match detection code
*
* \param value Modbus value context (min, max and mode)
* \param min Minimum value to compare
* \param inter Interval or maximum (min + inter) value to compare
*
* \retval 1 match or 0 no match
*/
static int DetectEngineInspectModbusValueMatch(DetectModbusValue *value,
uint16_t min,
uint16_t inter)
{
SCEnter();
uint16_t max = min + inter;
int ret = 0;
switch (value->mode) {
case DETECT_MODBUS_EQ:
if ((value->min >= min) && (value->min <= max))
ret = 1;
break;
case DETECT_MODBUS_LT:
if (value->min > min)
ret = 1;
break;
case DETECT_MODBUS_GT:
if (value->min < max)
ret = 1;
break;
case DETECT_MODBUS_RA:
if ((value->max > min) && (value->min < max))
ret = 1;
break;
}
SCReturnInt(ret);
}
/** \internal
*
* \brief Do data (and address) inspection & validation for a signature
*
* \param tx Pointer to Modbus Transaction
* \param address Address inspection
* \param data Pointer to data signature structure to match
*
* \retval 0 no match or 1 match
*/
static int DetectEngineInspectModbusData(ModbusTransaction *tx,
uint16_t address,
DetectModbusValue *data)
{
SCEnter();
uint16_t offset, value = 0, type = tx->type;
if (type & MODBUS_TYP_SINGLE) {
/* Output/Register(s) Value */
if (type & MODBUS_TYP_COILS)
value = (tx->data[0])? 1 : 0;
else
value = tx->data[0];
} else if (type & MODBUS_TYP_MULTIPLE) {
int i, size = (int) sizeof(tx->data);
offset = address - (tx->write.address + 1);
/* In case of Coils, offset is in bit (convert in byte) */
if (type & MODBUS_TYP_COILS)
offset >>= 3;
for (i=0; i< size; i++) {
/* Select the correct register/coils amongst the output value */
if (!(offset--)) {
value = tx->data[i];
break;
}
}
/* In case of Coils, offset is now in the bit is the rest of previous convert */
if (type & MODBUS_TYP_COILS) {
offset = (address - (tx->write.address + 1)) & 0x7;
value = (value >> offset) & 0x1;
}
} else {
/* It is not possible to define the value that is writing for Mask */
/* Write Register function because the current content is not available.*/
SCReturnInt(0);
}
SCReturnInt(DetectEngineInspectModbusValueMatch(data, value, 0));
}
/** \internal
*
* \brief Do address inspection & validation for a signature
*
* \param tx Pointer to Modbus Transaction
* \param address Pointer to address signature structure to match
* \param access Access mode (READ or WRITE)
*
* \retval 0 no match or 1 match
*/
static int DetectEngineInspectModbusAddress(ModbusTransaction *tx,
DetectModbusValue *address,
uint8_t access)
{
SCEnter();
int ret = 0;
/* Check if read/write address of request is at/in the address range of signature */
if (access == MODBUS_TYP_READ) {
/* In the PDU Coils are addresses starting at zero */
/* therefore Coils numbered 1-16 are addressed as 0-15 */
ret = DetectEngineInspectModbusValueMatch(address,
tx->read.address + 1,
tx->read.quantity - 1);
} else {
/* In the PDU Registers are addresses starting at zero */
/* therefore Registers numbered 1-16 are addressed as 0-15 */
if (tx->type & MODBUS_TYP_SINGLE)
ret = DetectEngineInspectModbusValueMatch(address,
tx->write.address + 1,
0);
else
ret = DetectEngineInspectModbusValueMatch(address,
tx->write.address + 1,
tx->write.quantity - 1);
}
SCReturnInt(ret);
}
/** \brief Do the content inspection & validation for a signature
*
* \param de_ctx Detection engine context
* \param det_ctx Detection engine thread context
* \param s Signature to inspect ( and sm: SigMatch to inspect)
* \param f Flow
* \param flags App layer flags
* \param alstate App layer state
* \param txv Pointer to Modbus Transaction structure
*
* \retval 0 no match or 1 match
*/
int DetectEngineInspectModbus(DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx,
const struct DetectEngineAppInspectionEngine_ *engine, const Signature *s, Flow *f,
uint8_t flags, void *alstate, void *txv, uint64_t tx_id)
{
SCEnter();
ModbusTransaction *tx = (ModbusTransaction *)txv;
DetectModbus *modbus = (DetectModbus *)engine->smd->ctx;
int ret = 0;
if (modbus == NULL) {
SCLogDebug("no modbus state, no match");
SCReturnInt(0);
}
if (modbus->unit_id != NULL) {
if (DetectEngineInspectModbusValueMatch(modbus->unit_id, tx->unit_id, 0) == 0) {
SCReturnInt(0);
} else {
ret = 1;
}
}
if (modbus->type == MODBUS_TYP_NONE) {
if (modbus->category == MODBUS_CAT_NONE) {
if (modbus->function != MODBUS_FUNC_NONE) {
if (modbus->function == tx->function) {
if (modbus->has_subfunction) {
SCLogDebug("looking for Modbus server function %d and subfunction %d",
modbus->function, modbus->subfunction);
ret = (modbus->subfunction == (tx->subFunction))? 1 : 0;
} else {
SCLogDebug("looking for Modbus server function %d", modbus->function);
ret = 1;
}
} else {
ret = 0;
}
}
} else {
SCLogDebug("looking for Modbus category function %d", modbus->category);
ret = (tx->category & modbus->category)? 1 : 0;
}
} else {
uint8_t access = modbus->type & MODBUS_TYP_ACCESS_MASK;
uint8_t function = modbus->type & MODBUS_TYP_ACCESS_FUNCTION_MASK;
if (access != MODBUS_TYP_NONE) {
if ((access & tx->type) && ((function == MODBUS_TYP_NONE) || (function & tx->type))) {
if (modbus->address != NULL) {
ret = DetectEngineInspectModbusAddress(tx, modbus->address, access);
if (ret && (modbus->data != NULL)) {
ret = DetectEngineInspectModbusData(tx, modbus->address->min, modbus->data);
}
} else {
SCLogDebug("looking for Modbus access type %d and function type %d", access, function);
ret = 1;
}
} else {
ret = 0;
}
}
}
SCReturnInt(ret);
}
#ifdef UNITTESTS /* UNITTESTS */
#include "app-layer-parser.h"
@ -396,8 +177,7 @@ static int DetectEngineInspectModbusTest01(void)
FAIL_IF_NOT(r == 0);
FLOWLOCK_UNLOCK(&f);
ModbusState *modbus_state = f.alstate;
FAIL_IF_NULL(modbus_state);
FAIL_IF_NULL(f.alstate);
/* do detect */
SigMatchSignatures(&tv, de_ctx, det_ctx, p);
@ -467,8 +247,7 @@ static int DetectEngineInspectModbusTest02(void)
FAIL_IF_NOT(r == 0);
FLOWLOCK_UNLOCK(&f);
ModbusState *modbus_state = f.alstate;
FAIL_IF_NULL(modbus_state);
FAIL_IF_NULL(f.alstate);
/* do detect */
SigMatchSignatures(&tv, de_ctx, det_ctx, p);
@ -539,8 +318,7 @@ static int DetectEngineInspectModbusTest03(void)
FAIL_IF_NOT(r == 0);
FLOWLOCK_UNLOCK(&f);
ModbusState *modbus_state = f.alstate;
FAIL_IF_NULL(modbus_state);
FAIL_IF_NULL(f.alstate);
/* do detect */
SigMatchSignatures(&tv, de_ctx, det_ctx, p);
@ -610,8 +388,7 @@ static int DetectEngineInspectModbusTest04(void)
FAIL_IF_NOT(r == 0);
FLOWLOCK_UNLOCK(&f);
ModbusState *modbus_state = f.alstate;
FAIL_IF_NULL(modbus_state);
FAIL_IF_NULL(f.alstate);
/* do detect */
SigMatchSignatures(&tv, de_ctx, det_ctx, p);
@ -681,8 +458,7 @@ static int DetectEngineInspectModbusTest05(void)
FAIL_IF_NOT(r == 0);
FLOWLOCK_UNLOCK(&f);
ModbusState *modbus_state = f.alstate;
FAIL_IF_NULL(modbus_state);
FAIL_IF_NULL(f.alstate);
/* do detect */
SigMatchSignatures(&tv, de_ctx, det_ctx, p);
@ -752,8 +528,7 @@ static int DetectEngineInspectModbusTest06(void)
FAIL_IF_NOT(r == 0);
FLOWLOCK_UNLOCK(&f);
ModbusState *modbus_state = f.alstate;
FAIL_IF_NULL(modbus_state);
FAIL_IF_NULL(f.alstate);
/* do detect */
SigMatchSignatures(&tv, de_ctx, det_ctx, p);
@ -823,8 +598,7 @@ static int DetectEngineInspectModbusTest07(void)
FAIL_IF_NOT(r == 0);
FLOWLOCK_UNLOCK(&f);
ModbusState *modbus_state = f.alstate;
FAIL_IF_NULL(modbus_state);
FAIL_IF_NULL(f.alstate);
/* do detect */
SigMatchSignatures(&tv, de_ctx, det_ctx, p);
@ -942,8 +716,7 @@ static int DetectEngineInspectModbusTest08(void)
FAIL_IF_NOT(r == 0);
FLOWLOCK_UNLOCK(&f);
ModbusState *modbus_state = f.alstate;
FAIL_IF_NULL(modbus_state);
FAIL_IF_NULL(f.alstate);
/* do detect */
SigMatchSignatures(&tv, de_ctx, det_ctx, p);
@ -1074,8 +847,7 @@ static int DetectEngineInspectModbusTest09(void)
FAIL_IF_NOT(r == 0);
FLOWLOCK_UNLOCK(&f);
ModbusState *modbus_state = f.alstate;
FAIL_IF_NULL(modbus_state);
FAIL_IF_NULL(f.alstate);
/* do detect */
SigMatchSignatures(&tv, de_ctx, det_ctx, p);
@ -1196,8 +968,7 @@ static int DetectEngineInspectModbusTest10(void)
FAIL_IF_NOT(r == 0);
FLOWLOCK_UNLOCK(&f);
ModbusState *modbus_state = f.alstate;
FAIL_IF_NULL(modbus_state);
FAIL_IF_NULL(f.alstate);
/* do detect */
SigMatchSignatures(&tv, de_ctx, det_ctx, p);
@ -1313,8 +1084,7 @@ static int DetectEngineInspectModbusTest11(void)
FAIL_IF_NOT(r == 0);
FLOWLOCK_UNLOCK(&f);
ModbusState *modbus_state = f.alstate;
FAIL_IF_NULL(modbus_state);
FAIL_IF_NULL(f.alstate);
/* do detect */
SigMatchSignatures(&tv, de_ctx, det_ctx, p);
@ -1411,8 +1181,7 @@ static int DetectEngineInspectModbusTest12(void)
FAIL_IF_NOT(r == 0);
FLOWLOCK_UNLOCK(&f);
ModbusState *modbus_state = f.alstate;
FAIL_IF_NULL(modbus_state);
FAIL_IF_NULL(f.alstate);
/* do detect */
SigMatchSignatures(&tv, de_ctx, det_ctx, p);

@ -48,7 +48,6 @@
#include "detect-engine.h"
#include "detect-modbus.h"
#include "detect-engine-modbus.h"
#include "util-debug.h"
#include "util-byte.h"
@ -56,29 +55,12 @@
#include "app-layer-modbus.h"
#include "stream-tcp.h"
/**
* \brief Regex for parsing the Modbus unit id string
*/
#define PARSE_REGEX_UNIT_ID "^\\s*\"?\\s*unit\\s+([<>]?\\d+)(<>\\d+)?(,\\s*(.*))?\\s*\"?\\s*$"
static DetectParseRegex unit_id_parse_regex;
/**
* \brief Regex for parsing the Modbus function string
*/
#define PARSE_REGEX_FUNCTION "^\\s*\"?\\s*function\\s*(!?[A-z0-9]+)(,\\s*subfunction\\s+(\\d+))?\\s*\"?\\s*$"
static DetectParseRegex function_parse_regex;
/**
* \brief Regex for parsing the Modbus access string
*/
#define PARSE_REGEX_ACCESS "^\\s*\"?\\s*access\\s*(read|write)\\s*(discretes|coils|input|holding)?(,\\s*address\\s+([<>]?\\d+)(<>\\d+)?(,\\s*value\\s+([<>]?\\d+)(<>\\d+)?)?)?\\s*\"?\\s*$"
static DetectParseRegex access_parse_regex;
#include "rust.h"
static int g_modbus_buffer_id = 0;
#ifdef UNITTESTS
void DetectModbusRegisterTests(void);
static void DetectModbusRegisterTests(void);
#endif
/** \internal
@ -89,413 +71,12 @@ void DetectModbusRegisterTests(void);
*/
static void DetectModbusFree(DetectEngineCtx *de_ctx, void *ptr) {
SCEnter();
DetectModbus *modbus = (DetectModbus *) ptr;
if(modbus) {
if (modbus->unit_id)
SCFree(modbus->unit_id);
if (modbus->address)
SCFree(modbus->address);
if (modbus->data)
SCFree(modbus->data);
SCFree(modbus);
}
}
/** \internal
*
* \brief This function is used to parse Modbus parameters in access mode
*
* \param de_ctx Pointer to the detection engine context
* \param str Pointer to the user provided id option
*
* \retval Pointer to DetectModbusData on success or NULL on failure
*/
static DetectModbus *DetectModbusAccessParse(DetectEngineCtx *de_ctx, const char *str)
{
SCEnter();
DetectModbus *modbus = NULL;
char arg[MAX_SUBSTRINGS];
int ov[MAX_SUBSTRINGS], ret, res;
ret = DetectParsePcreExec(&access_parse_regex, str, 0, 0, ov, MAX_SUBSTRINGS);
if (ret < 1)
goto error;
res = pcre_copy_substring(str, ov, MAX_SUBSTRINGS, 1, arg, MAX_SUBSTRINGS);
if (res < 0) {
SCLogError(SC_ERR_PCRE_GET_SUBSTRING, "pcre_get_substring failed");
goto error;
}
/* We have a correct Modbus option */
modbus = (DetectModbus *) SCCalloc(1, sizeof(DetectModbus));
if (unlikely(modbus == NULL))
goto error;
if (strcmp(arg, "read") == 0)
modbus->type = MODBUS_TYP_READ;
else if (strcmp(arg, "write") == 0)
modbus->type = MODBUS_TYP_WRITE;
else
goto error;
if (ret > 2) {
res = pcre_copy_substring(str, ov, MAX_SUBSTRINGS, 2, arg, MAX_SUBSTRINGS);
if (res < 0) {
SCLogError(SC_ERR_PCRE_GET_SUBSTRING, "pcre_get_substring failed");
goto error;
}
if (*arg != '\0') {
if (strcmp(arg, "discretes") == 0) {
if (modbus->type == MODBUS_TYP_WRITE)
/* Discrete access is only read access. */
goto error;
modbus->type |= MODBUS_TYP_DISCRETES;
}
else if (strcmp(arg, "coils") == 0) {
modbus->type |= MODBUS_TYP_COILS;
}
else if (strcmp(arg, "input") == 0) {
if (modbus->type == MODBUS_TYP_WRITE) {
/* Input access is only read access. */
goto error;
}
modbus->type |= MODBUS_TYP_INPUT;
}
else if (strcmp(arg, "holding") == 0) {
modbus->type |= MODBUS_TYP_HOLDING;
}
else
goto error;
}
if (ret > 4) {
res = pcre_copy_substring(str, ov, MAX_SUBSTRINGS, 4, arg, MAX_SUBSTRINGS);
if (res < 0) {
SCLogError(SC_ERR_PCRE_GET_SUBSTRING, "pcre_get_substring failed");
goto error;
}
/* We have a correct address option */
modbus->address = (DetectModbusValue *) SCCalloc(1, sizeof(DetectModbusValue));
if (unlikely(modbus->address == NULL))
goto error;
uint8_t idx;
if (arg[0] == '>') {
idx = 1;
modbus->address->mode = DETECT_MODBUS_GT;
} else if (arg[0] == '<') {
idx = 1;
modbus->address->mode = DETECT_MODBUS_LT;
} else {
idx = 0;
}
if (StringParseUint16(&modbus->address->min, 10, 0,
(const char *) (arg + idx)) < 0) {
SCLogError(SC_ERR_INVALID_VALUE, "Invalid value for min "
"address: %s", (const char *)(arg + idx));
goto error;
}
SCLogDebug("and min/equal address %d", modbus->address->min);
if (ret > 5) {
res = pcre_copy_substring(str, ov, MAX_SUBSTRINGS, 5, arg, MAX_SUBSTRINGS);
if (res < 0) {
SCLogError(SC_ERR_PCRE_GET_SUBSTRING, "pcre_get_substring failed");
goto error;
}
if (*arg != '\0') {
if (StringParseUint16(&modbus->address->max, 10, 0,
(const char*) (arg + 2)) < 0) {
SCLogError(SC_ERR_INVALID_VALUE, "Invalid value for max "
"address: %s", (const char*)(arg + 2));
goto error;
}
modbus->address->mode = DETECT_MODBUS_RA;
SCLogDebug("and max address %d", modbus->address->max);
}
if (ret > 7) {
res = pcre_copy_substring(str, ov, MAX_SUBSTRINGS, 7, arg, MAX_SUBSTRINGS);
if (res < 0) {
SCLogError(SC_ERR_PCRE_GET_SUBSTRING, "pcre_get_substring failed");
goto error;
}
if (modbus->address->mode != DETECT_MODBUS_EQ) {
SCLogError(SC_ERR_CONFLICTING_RULE_KEYWORDS, "rule contains conflicting keywords (address range and value).");
goto error;
}
/* We have a correct address option */
if (modbus->type == MODBUS_TYP_READ)
/* Value access is only possible in write access. */
goto error;
modbus->data = (DetectModbusValue *) SCCalloc(1, sizeof(DetectModbusValue));
if (unlikely(modbus->data == NULL))
goto error;
uint8_t idx_mode;
if (arg[0] == '>') {
idx_mode = 1;
modbus->data->mode = DETECT_MODBUS_GT;
} else if (arg[0] == '<') {
idx_mode = 1;
modbus->data->mode = DETECT_MODBUS_LT;
} else {
idx_mode = 0;
}
if (StringParseUint16(&modbus->data->min, 10, 0,
(const char*) (arg + idx_mode)) < 0) {
SCLogError(SC_ERR_INVALID_VALUE, "Invalid value for "
"min data: %s", (const char*)(arg + idx_mode));
goto error;
}
SCLogDebug("and min/equal value %d", modbus->data->min);
if (ret > 8) {
res = pcre_copy_substring(str, ov, MAX_SUBSTRINGS, 8, arg, MAX_SUBSTRINGS);
if (res < 0) {
SCLogError(SC_ERR_PCRE_GET_SUBSTRING, "pcre_get_substring failed");
goto error;
}
if (*arg != '\0') {
if (StringParseUint16(&modbus->data->max,
10, 0, (const char*) (arg + 2)) < 0) {
SCLogError(SC_ERR_INVALID_VALUE,
"Invalid value for max data: %s",
(const char*)(arg + 2));
goto error;
}
modbus->data->mode = DETECT_MODBUS_RA;
SCLogDebug("and max value %d", modbus->data->max);
}
}
}
}
}
}
SCReturnPtr(modbus, "DetectModbusAccess");
error:
if (modbus != NULL)
DetectModbusFree(de_ctx, modbus);
SCReturnPtr(NULL, "DetectModbus");
}
/** \internal
*
* \brief This function is used to parse Modbus parameters in function mode
*
* \param str Pointer to the user provided id option
*
* \retval id_d pointer to DetectModbusData on success
* \retval NULL on failure
*/
static DetectModbus *DetectModbusFunctionParse(DetectEngineCtx *de_ctx, const char *str)
{
SCEnter();
DetectModbus *modbus = NULL;
char arg[MAX_SUBSTRINGS], *ptr = arg;
int ov[MAX_SUBSTRINGS], res, ret;
ret = DetectParsePcreExec(&function_parse_regex, str, 0, 0, ov, MAX_SUBSTRINGS);
if (ret < 1)
goto error;
res = pcre_copy_substring(str, ov, MAX_SUBSTRINGS, 1, arg, MAX_SUBSTRINGS);
if (res < 0) {
SCLogError(SC_ERR_PCRE_GET_SUBSTRING, "pcre_get_substring failed");
goto error;
}
/* We have a correct Modbus function option */
modbus = (DetectModbus *) SCCalloc(1, sizeof(DetectModbus));
if (unlikely(modbus == NULL))
goto error;
if (isdigit((unsigned char)ptr[0])) {
if (StringParseUint8(&modbus->function, 10, 0, (const char *)ptr) < 0) {
SCLogError(SC_ERR_INVALID_VALUE, "Invalid value for "
"modbus function: %s", (const char *)ptr);
goto error;
}
/* Function code 0 is managed by decoder_event INVALID_FUNCTION_CODE */
if (modbus->function == MODBUS_FUNC_NONE) {
SCLogError(SC_ERR_INVALID_SIGNATURE,
"Invalid argument \"%d\" supplied to modbus function keyword.",
modbus->function);
goto error;
}
SCLogDebug("will look for modbus function %d", modbus->function);
if (ret > 2) {
res = pcre_copy_substring(str, ov, MAX_SUBSTRINGS, 3, arg, MAX_SUBSTRINGS);
if (res < 0) {
SCLogError(SC_ERR_PCRE_GET_SUBSTRING, "pcre_get_substring failed");
goto error;
}
if (StringParseUint16(&modbus->subfunction, 10, 0, (const char *)arg) < 0) {
SCLogError(SC_ERR_INVALID_VALUE, "Invalid value for "
"modbus subfunction: %s", (const char*)arg);
goto error;
}
modbus->has_subfunction = true;
SCLogDebug("and subfunction %d", modbus->subfunction);
}
} else {
uint8_t neg = 0;
if (ptr[0] == '!') {
neg = 1;
ptr++;
}
if (strcmp("assigned", ptr) == 0)
modbus->category = MODBUS_CAT_PUBLIC_ASSIGNED;
else if (strcmp("unassigned", ptr) == 0)
modbus->category = MODBUS_CAT_PUBLIC_UNASSIGNED;
else if (strcmp("public", ptr) == 0)
modbus->category = MODBUS_CAT_PUBLIC_ASSIGNED | MODBUS_CAT_PUBLIC_UNASSIGNED;
else if (strcmp("user", ptr) == 0)
modbus->category = MODBUS_CAT_USER_DEFINED;
else if (strcmp("reserved", ptr) == 0)
modbus->category = MODBUS_CAT_RESERVED;
else if (strcmp("all", ptr) == 0)
modbus->category = MODBUS_CAT_ALL;
if (neg)
modbus->category = ~modbus->category;
SCLogDebug("will look for modbus category function %d", modbus->category);
}
SCReturnPtr(modbus, "DetectModbusFunction");
error:
if (modbus != NULL)
DetectModbusFree(de_ctx, modbus);
SCReturnPtr(NULL, "DetectModbus");
}
/** \internal
*
* \brief This function is used to parse Modbus parameters in unit id mode
*
* \param str Pointer to the user provided id option
*
* \retval Pointer to DetectModbusUnit on success or NULL on failure
*/
static DetectModbus *DetectModbusUnitIdParse(DetectEngineCtx *de_ctx, const char *str)
{
SCEnter();
DetectModbus *modbus = NULL;
char arg[MAX_SUBSTRINGS];
int ov[MAX_SUBSTRINGS], ret, res;
ret = DetectParsePcreExec(&unit_id_parse_regex, str, 0, 0, ov, MAX_SUBSTRINGS);
if (ret < 1)
goto error;
res = pcre_copy_substring(str, ov, MAX_SUBSTRINGS, 1, arg, MAX_SUBSTRINGS);
if (res < 0) {
SCLogError(SC_ERR_PCRE_GET_SUBSTRING, "pcre_get_substring failed");
goto error;
}
if (ret > 3) {
/* We have more Modbus option */
const char *str_ptr;
res = pcre_get_substring((char *)str, ov, MAX_SUBSTRINGS, 4, &str_ptr);
if (res < 0) {
SCLogError(SC_ERR_PCRE_GET_SUBSTRING, "pcre_get_substring failed");
goto error;
}
if ((modbus = DetectModbusFunctionParse(de_ctx, str_ptr)) == NULL) {
if ((modbus = DetectModbusAccessParse(de_ctx, str_ptr)) == NULL) {
SCLogError(SC_ERR_PCRE_MATCH, "invalid modbus option");
goto error;
}
}
} else {
/* We have only unit id Modbus option */
modbus = (DetectModbus *) SCCalloc(1, sizeof(DetectModbus));
if (unlikely(modbus == NULL))
goto error;
}
/* We have a correct unit id option */
modbus->unit_id = (DetectModbusValue *) SCCalloc(1, sizeof(DetectModbusValue));
if (unlikely(modbus->unit_id == NULL))
goto error;
uint8_t idx;
if (arg[0] == '>') {
idx = 1;
modbus->unit_id->mode = DETECT_MODBUS_GT;
} else if (arg[0] == '<') {
idx = 1;
modbus->unit_id->mode = DETECT_MODBUS_LT;
} else {
idx = 0;
}
if (StringParseUint16(&modbus->unit_id->min, 10, 0, (const char *) (arg + idx)) < 0) {
SCLogError(SC_ERR_INVALID_VALUE, "Invalid value for "
"modbus min unit id: %s", (const char*)(arg + idx));
goto error;
}
SCLogDebug("and min/equal unit id %d", modbus->unit_id->min);
if (ret > 2) {
res = pcre_copy_substring(str, ov, MAX_SUBSTRINGS, 2, arg, MAX_SUBSTRINGS);
if (res < 0) {
SCLogError(SC_ERR_PCRE_GET_SUBSTRING, "pcre_get_substring failed");
goto error;
}
if (*arg != '\0') {
if (StringParseUint16(&modbus->unit_id->max, 10, 0, (const char *) (arg + 2)) < 0) {
SCLogError(SC_ERR_INVALID_VALUE, "Invalid value for "
"modbus max unit id: %s", (const char*)(arg + 2));
goto error;
}
modbus->unit_id->mode = DETECT_MODBUS_RA;
SCLogDebug("and max unit id %d", modbus->unit_id->max);
}
if (ptr != NULL) {
rs_modbus_free(ptr);
}
SCReturnPtr(modbus, "DetectModbusUnitId");
error:
if (modbus != NULL)
DetectModbusFree(de_ctx, modbus);
SCReturnPtr(NULL, "DetectModbus");
SCReturn;
}
/** \internal
*
* \brief this function is used to add the parsed "id" option into the current signature
@ -509,19 +90,15 @@ error:
static int DetectModbusSetup(DetectEngineCtx *de_ctx, Signature *s, const char *str)
{
SCEnter();
DetectModbus *modbus = NULL;
DetectModbusRust *modbus = NULL;
SigMatch *sm = NULL;
if (DetectSignatureSetAppProto(s, ALPROTO_MODBUS) != 0)
return -1;
if ((modbus = DetectModbusUnitIdParse(de_ctx, str)) == NULL) {
if ((modbus = DetectModbusFunctionParse(de_ctx, str)) == NULL) {
if ((modbus = DetectModbusAccessParse(de_ctx, str)) == NULL) {
SCLogError(SC_ERR_PCRE_MATCH, "invalid modbus option");
goto error;
}
}
if ((modbus = rs_modbus_parse(str)) == NULL) {
SCLogError(SC_ERR_PCRE_MATCH, "invalid modbus option");
goto error;
}
/* Okay so far so good, lets get this into a SigMatch and put it in the Signature. */
@ -544,24 +121,47 @@ error:
SCReturnInt(-1);
}
static int DetectModbusMatch(DetectEngineThreadCtx *det_ctx, Flow *f, uint8_t flags, void *state,
void *txv, const Signature *s, const SigMatchCtx *ctx)
{
return rs_modbus_inspect(txv, (void *)ctx);
}
/** \brief Do the content inspection & validation for a signature
*
* \param de_ctx Detection engine context
* \param det_ctx Detection engine thread context
* \param s Signature to inspect ( and sm: SigMatch to inspect)
* \param f Flow
* \param flags App layer flags
* \param alstate App layer state
* \param txv Pointer to Modbus Transaction structure
*
* \retval 0 no match or 1 match
*/
static int DetectEngineInspectModbus(DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx,
const struct DetectEngineAppInspectionEngine_ *engine, const Signature *s, Flow *f,
uint8_t flags, void *alstate, void *txv, uint64_t tx_id)
{
return DetectEngineInspectGenericList(
de_ctx, det_ctx, s, engine->smd, f, flags, alstate, txv, tx_id);
}
/**
* \brief Registration function for Modbus keyword
*/
void DetectModbusRegister(void)
{
SCEnter();
sigmatch_table[DETECT_AL_MODBUS].name = "modbus";
sigmatch_table[DETECT_AL_MODBUS].desc = "match on various properties of Modbus requests";
sigmatch_table[DETECT_AL_MODBUS].url = "/rules/modbus-keyword.html#modbus-keyword";
sigmatch_table[DETECT_AL_MODBUS].Match = NULL;
sigmatch_table[DETECT_AL_MODBUS].Setup = DetectModbusSetup;
sigmatch_table[DETECT_AL_MODBUS].Free = DetectModbusFree;
sigmatch_table[DETECT_AL_MODBUS].name = "modbus";
sigmatch_table[DETECT_AL_MODBUS].desc = "match on various properties of Modbus requests";
sigmatch_table[DETECT_AL_MODBUS].url = "/rules/modbus-keyword.html#modbus-keyword";
sigmatch_table[DETECT_AL_MODBUS].Match = NULL;
sigmatch_table[DETECT_AL_MODBUS].Setup = DetectModbusSetup;
sigmatch_table[DETECT_AL_MODBUS].Free = DetectModbusFree;
sigmatch_table[DETECT_AL_MODBUS].AppLayerTxMatch = DetectModbusMatch;
#ifdef UNITTESTS
sigmatch_table[DETECT_AL_MODBUS].RegisterTests = DetectModbusRegisterTests;
#endif
DetectSetupParseRegexes(PARSE_REGEX_UNIT_ID, &unit_id_parse_regex);
DetectSetupParseRegexes(PARSE_REGEX_FUNCTION, &function_parse_regex);
DetectSetupParseRegexes(PARSE_REGEX_ACCESS, &access_parse_regex);
DetectAppLayerInspectEngineRegister2(
"modbus", ALPROTO_MODBUS, SIG_FLAG_TOSERVER, 0, DetectEngineInspectModbus, NULL);
@ -572,6 +172,72 @@ void DetectModbusRegister(void)
#ifdef UNITTESTS /* UNITTESTS */
#include "util-unittest.h"
/** Convert rust structure to C for regression tests.
*
* Note: Newly allocated `DetectModbus` structure must be freed.
*
* TODO: remove this after regression testing commit.
*/
static DetectModbusValue *DetectModbusValueRustToC(uint16_t min, uint16_t max)
{
DetectModbusValue *value = SCMalloc(sizeof(*value));
FAIL_IF_NULL(value);
value->min = min;
value->max = max;
if (min == max) {
value->mode = DETECT_MODBUS_EQ;
} else if (min == 0) {
value->mode = DETECT_MODBUS_LT;
} else if (max == UINT16_MAX) {
value->mode = DETECT_MODBUS_GT;
} else {
value->mode = DETECT_MODBUS_RA;
}
return value;
}
static DetectModbus *DetectModbusRustToC(DetectModbusRust *ctx)
{
DetectModbus *modbus = SCMalloc(sizeof(*modbus));
FAIL_IF_NULL(modbus);
modbus->category = rs_modbus_get_category(ctx);
modbus->function = rs_modbus_get_function(ctx);
modbus->subfunction = rs_modbus_get_subfunction(ctx);
modbus->has_subfunction = rs_modbus_get_has_subfunction(ctx);
modbus->type = rs_modbus_get_access_type(ctx);
modbus->unit_id = DetectModbusValueRustToC(
rs_modbus_get_unit_id_min(ctx), rs_modbus_get_unit_id_max(ctx));
modbus->address = DetectModbusValueRustToC(
rs_modbus_get_address_min(ctx), rs_modbus_get_address_max(ctx));
modbus->data =
DetectModbusValueRustToC(rs_modbus_get_data_min(ctx), rs_modbus_get_data_max(ctx));
return modbus;
}
static void DetectModbusCFree(DetectModbus *modbus)
{
if (modbus) {
if (modbus->unit_id)
SCFree(modbus->unit_id);
if (modbus->address)
SCFree(modbus->address);
if (modbus->data)
SCFree(modbus->data);
SCFree(modbus);
}
}
/** \test Signature containing a function. */
static int DetectModbusTest01(void)
{
@ -590,10 +256,12 @@ static int DetectModbusTest01(void)
FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]);
FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
modbus = (DetectModbus *) de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx;
modbus = DetectModbusRustToC(
(DetectModbusRust *)de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
FAIL_IF_NOT(modbus->function == 1);
DetectModbusCFree(modbus);
SigGroupCleanup(de_ctx);
SigCleanSignatures(de_ctx);
DetectEngineCtxFree(de_ctx);
@ -618,11 +286,13 @@ static int DetectModbusTest02(void)
FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]);
FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
modbus = (DetectModbus *) de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx;
modbus = DetectModbusRustToC(
(DetectModbusRust *)de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
FAIL_IF_NOT(modbus->function == 8);
FAIL_IF_NOT(modbus->subfunction == 4);
DetectModbusCFree(modbus);
SigGroupCleanup(de_ctx);
SigCleanSignatures(de_ctx);
DetectEngineCtxFree(de_ctx);
@ -647,10 +317,12 @@ static int DetectModbusTest03(void)
FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]);
FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
modbus = (DetectModbus *) de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx;
modbus = DetectModbusRustToC(
(DetectModbusRust *)de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
FAIL_IF_NOT(modbus->category == MODBUS_CAT_RESERVED);
DetectModbusCFree(modbus);
SigGroupCleanup(de_ctx);
SigCleanSignatures(de_ctx);
DetectEngineCtxFree(de_ctx);
@ -663,8 +335,6 @@ static int DetectModbusTest04(void)
DetectEngineCtx *de_ctx = NULL;
DetectModbus *modbus = NULL;
uint8_t category = ~MODBUS_CAT_PUBLIC_ASSIGNED;
de_ctx = DetectEngineCtxInit();
FAIL_IF_NULL(de_ctx);
@ -677,10 +347,15 @@ static int DetectModbusTest04(void)
FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]);
FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
modbus = (DetectModbus *) de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx;
modbus = DetectModbusRustToC(
(DetectModbusRust *)de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
FAIL_IF_NOT(modbus->category == category);
FAIL_IF(modbus->category & MODBUS_CAT_PUBLIC_ASSIGNED);
FAIL_IF_NOT(modbus->category & MODBUS_CAT_PUBLIC_UNASSIGNED);
FAIL_IF_NOT(modbus->category & MODBUS_CAT_USER_DEFINED);
FAIL_IF_NOT(modbus->category & MODBUS_CAT_RESERVED);
DetectModbusCFree(modbus);
SigGroupCleanup(de_ctx);
SigCleanSignatures(de_ctx);
DetectEngineCtxFree(de_ctx);
@ -705,10 +380,12 @@ static int DetectModbusTest05(void)
FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]);
FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
modbus = (DetectModbus *) de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx;
modbus = DetectModbusRustToC(
(DetectModbusRust *)de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
FAIL_IF_NOT(modbus->type == MODBUS_TYP_READ);
DetectModbusCFree(modbus);
SigGroupCleanup(de_ctx);
SigCleanSignatures(de_ctx);
DetectEngineCtxFree(de_ctx);
@ -735,10 +412,12 @@ static int DetectModbusTest06(void)
FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]);
FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
modbus = (DetectModbus *) de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx;
modbus = DetectModbusRustToC(
(DetectModbusRust *)de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
FAIL_IF_NOT(modbus->type == type);
DetectModbusCFree(modbus);
SigGroupCleanup(de_ctx);
SigCleanSignatures(de_ctx);
DetectEngineCtxFree(de_ctx);
@ -766,12 +445,14 @@ static int DetectModbusTest07(void)
FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]);
FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
modbus = (DetectModbus *) de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx;
modbus = DetectModbusRustToC(
(DetectModbusRust *)de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
FAIL_IF_NOT(modbus->type == type);
FAIL_IF_NOT((*modbus->address).mode == mode);
FAIL_IF_NOT((*modbus->address).min == 1000);
DetectModbusCFree(modbus);
SigGroupCleanup(de_ctx);
SigCleanSignatures(de_ctx);
DetectEngineCtxFree(de_ctx);
@ -799,12 +480,14 @@ static int DetectModbusTest08(void)
FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]);
FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
modbus = (DetectModbus *) de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx;
modbus = DetectModbusRustToC(
(DetectModbusRust *)de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
FAIL_IF_NOT(modbus->type == type);
FAIL_IF_NOT((*modbus->address).mode == mode);
FAIL_IF_NOT((*modbus->address).min == 500);
DetectModbusCFree(modbus);
SigGroupCleanup(de_ctx);
SigCleanSignatures(de_ctx);
DetectEngineCtxFree(de_ctx);
@ -833,7 +516,8 @@ static int DetectModbusTest09(void)
FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]);
FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
modbus = (DetectModbus *) de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx;
modbus = DetectModbusRustToC(
(DetectModbusRust *)de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
FAIL_IF_NOT(modbus->type == type);
FAIL_IF_NOT((*modbus->address).mode == addressMode);
@ -842,6 +526,7 @@ static int DetectModbusTest09(void)
FAIL_IF_NOT((*modbus->data).min == 500);
FAIL_IF_NOT((*modbus->data).max == 1000);
DetectModbusCFree(modbus);
SigGroupCleanup(de_ctx);
SigCleanSignatures(de_ctx);
DetectEngineCtxFree(de_ctx);
@ -867,11 +552,13 @@ static int DetectModbusTest10(void)
FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]);
FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
modbus = (DetectModbus *) de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx;
modbus = DetectModbusRustToC(
(DetectModbusRust *)de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
FAIL_IF_NOT((*modbus->unit_id).min == 10);
FAIL_IF_NOT((*modbus->unit_id).mode == mode);
DetectModbusCFree(modbus);
SigGroupCleanup(de_ctx);
SigCleanSignatures(de_ctx);
DetectEngineCtxFree(de_ctx);
@ -897,13 +584,15 @@ static int DetectModbusTest11(void)
FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]);
FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
modbus = (DetectModbus *) de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx;
modbus = DetectModbusRustToC(
(DetectModbusRust *)de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
FAIL_IF_NOT((*modbus->unit_id).min == 10);
FAIL_IF_NOT((*modbus->unit_id).mode == mode);
FAIL_IF_NOT(modbus->function == 8);
FAIL_IF_NOT(modbus->subfunction == 4);
DetectModbusCFree(modbus);
SigGroupCleanup(de_ctx);
SigCleanSignatures(de_ctx);
DetectEngineCtxFree(de_ctx);
@ -931,7 +620,8 @@ static int DetectModbusTest12(void)
FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]);
FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
modbus = (DetectModbus *) de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx;
modbus = DetectModbusRustToC(
(DetectModbusRust *)de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
FAIL_IF_NOT((*modbus->unit_id).min == 10);
FAIL_IF_NOT((*modbus->unit_id).mode == mode);
@ -939,6 +629,7 @@ static int DetectModbusTest12(void)
FAIL_IF_NOT((*modbus->address).mode == mode);
FAIL_IF_NOT((*modbus->address).min == 1000);
DetectModbusCFree(modbus);
SigGroupCleanup(de_ctx);
SigCleanSignatures(de_ctx);
DetectEngineCtxFree(de_ctx);
@ -964,12 +655,14 @@ static int DetectModbusTest13(void)
FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]);
FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
modbus = (DetectModbus *) de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx;
modbus = DetectModbusRustToC(
(DetectModbusRust *)de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
FAIL_IF_NOT((*modbus->unit_id).min == 10);
FAIL_IF_NOT((*modbus->unit_id).max == 500);
FAIL_IF_NOT((*modbus->unit_id).mode == mode);
DetectModbusCFree(modbus);
SigGroupCleanup(de_ctx);
SigCleanSignatures(de_ctx);
DetectEngineCtxFree(de_ctx);

@ -119,7 +119,6 @@
#include "app-layer-ssh.h"
#include "app-layer-ftp.h"
#include "app-layer-smtp.h"
#include "app-layer-modbus.h"
#include "app-layer-enip.h"
#include "app-layer-dnp3.h"
#include "app-layer-smb.h"

Loading…
Cancel
Save