-
Notifications
You must be signed in to change notification settings - Fork 1.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Applayer plugin 5053 v11 #12471
Applayer plugin 5053 v11 #12471
Changes from all commits
84f4085
bd42f70
a863ac9
2836674
8582f99
6ec9920
7d8e0e5
55484af
b35cd6d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -1,7 +1,7 @@ | ||||||||||||||||||||||
.. _libsuricata: | ||||||||||||||||||||||
|
||||||||||||||||||||||
LibSuricata | ||||||||||||||||||||||
=========== | ||||||||||||||||||||||
LibSuricata and Plugins | ||||||||||||||||||||||
======================= | ||||||||||||||||||||||
|
||||||||||||||||||||||
Using Suricata as a Library | ||||||||||||||||||||||
--------------------------- | ||||||||||||||||||||||
|
@@ -10,5 +10,61 @@ The ability to turn Suricata into a library that can be utilized in other tools | |||||||||||||||||||||
is currently a work in progress, tracked by Redmine Ticket #2693: | ||||||||||||||||||||||
https://redmine.openinfosecfoundation.org/issues/2693. | ||||||||||||||||||||||
|
||||||||||||||||||||||
Plugins | ||||||||||||||||||||||
------- | ||||||||||||||||||||||
|
||||||||||||||||||||||
A related work are Suricata plugins, also in progress and tracked by Redmine | ||||||||||||||||||||||
Ticket #4101: https://redmine.openinfosecfoundation.org/issues/4101. | ||||||||||||||||||||||
|
||||||||||||||||||||||
Plugins can be used by modifying suricata.yaml ``plugins`` section to include | ||||||||||||||||||||||
the path of the dynamic library to load. | ||||||||||||||||||||||
|
||||||||||||||||||||||
Plugins should export a ``SCPluginRegister`` function that will be the entry point | ||||||||||||||||||||||
used by Suricata. | ||||||||||||||||||||||
|
||||||||||||||||||||||
Application-layer plugins | ||||||||||||||||||||||
~~~~~~~~~~~~~~~~~~~~~~~~~ | ||||||||||||||||||||||
|
||||||||||||||||||||||
Application layer plugins can be added as demonstrated by example | ||||||||||||||||||||||
https://github.com/OISF/suricata/blob/master/examples/plugins/altemplate/ | ||||||||||||||||||||||
|
||||||||||||||||||||||
The plugin code contains the same files as an application layer in the source tree: | ||||||||||||||||||||||
- alname.rs | ||||||||||||||||||||||
- detect.rs | ||||||||||||||||||||||
- lib.rs | ||||||||||||||||||||||
- log.rs | ||||||||||||||||||||||
- parser.rs | ||||||||||||||||||||||
catenacyber marked this conversation as resolved.
Show resolved
Hide resolved
Comment on lines
+32
to
+36
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
this indentation renders the proper list format in the docs :) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. thanks |
||||||||||||||||||||||
|
||||||||||||||||||||||
These files will have different ``use`` statements, targetting the suricata crate. | ||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ok |
||||||||||||||||||||||
|
||||||||||||||||||||||
.. attention:: A plugin should not use rust structures from suricata crate if they are not repr(C), especially JsonBuilder. | ||||||||||||||||||||||
|
||||||||||||||||||||||
This is because the rust compiler does not guarantee the structure layout unless you specify this representation. | ||||||||||||||||||||||
Thus, the plugin may expect the ``JsonBuilder`` fields at different offsets that they are supplied by Suricata at runtime. | ||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
I think? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ok |
||||||||||||||||||||||
The solution is to go through the ``JsonBuilder`` C API which uses an opaque pointer. | ||||||||||||||||||||||
|
||||||||||||||||||||||
And the plugin contains also additional files: | ||||||||||||||||||||||
- plugin.rs : defines the entry point of the plugin ``SCPluginRegister`` | ||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same here
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ok |
||||||||||||||||||||||
|
||||||||||||||||||||||
``SCPluginRegister`` should register callback that should then call ``SCPluginRegisterAppLayer`` | ||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. better |
||||||||||||||||||||||
passing a ``SCAppLayerPlugin`` structure to suricata. | ||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ok |
||||||||||||||||||||||
It should also call ``suricata::plugin::init();`` to ensure the plugin has initialized | ||||||||||||||||||||||
its value of the Suricata Context, a structure needed by rust, to call some C functions, | ||||||||||||||||||||||
that cannot be found at compile time because of circular dependencies, and are therefore | ||||||||||||||||||||||
resolved at runtime. | ||||||||||||||||||||||
Comment on lines
+51
to
+54
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Suggest a minor changes (if I understand this portion correctly)
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ok |
||||||||||||||||||||||
|
||||||||||||||||||||||
This ``SCAppLayerPlugin`` begins by a version number ``SC_PLUGIN_API_VERSION`` for runtime compatibility | ||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit:
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ok |
||||||||||||||||||||||
between Suricata and the plugin. | ||||||||||||||||||||||
|
||||||||||||||||||||||
Known limitations are: | ||||||||||||||||||||||
|
||||||||||||||||||||||
- Plugins can only use simple logging as defined by ``EveJsonSimpleTxLogFunc`` | ||||||||||||||||||||||
without suricata.yaml configuration, see https://github.com/OISF/suricata/pull/11160 | ||||||||||||||||||||||
- Keywords cannot use validate callbacks, see https://redmine.openinfosecfoundation.org/issues/5634 | ||||||||||||||||||||||
- Plugins cannot have keywords matching on mulitple protocols (like ja4), | ||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. thanks |
||||||||||||||||||||||
see https://redmine.openinfosecfoundation.org/issues/7304 | ||||||||||||||||||||||
|
||||||||||||||||||||||
.. attention:: A pure rust plugin needs to be compiled with ``RUSTFLAGS=-Clink-args=-Wl,-undefined,dynamic_lookup`` | ||||||||||||||||||||||
|
||||||||||||||||||||||
This is because the plugin will link dynamically at runtime the functions defined in Suricata runtime. | ||||||||||||||||||||||
You can define this rust flags in a ``.cargo/config.toml`` file. | ||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. one flag only |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
[build] | ||
# custom flags to pass to all compiler invocations | ||
rustflags = ["-Clink-args=-Wl,-undefined,dynamic_lookup"] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
[package] | ||
name = "suricata-altemplate" | ||
version = "0.1.0" | ||
edition = "2021" | ||
|
||
[lib] | ||
crate-type = ["cdylib"] | ||
|
||
[dependencies] | ||
nom7 = { version="7.0", package="nom" } | ||
libc = "~0.2.82" | ||
suricata = { path = "../../../rust/" } | ||
|
||
[features] | ||
default = ["suricata8"] | ||
suricata8 = [] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
alert altemplate any any -> any any (msg:"TEST"; altemplate.buffer; content:"Hello"; flow:established,to_server; sid:1; rev:1;) | ||
alert altemplate any any -> any any (msg:"TEST"; altemplate.buffer; content:"Bye"; flow:established,to_client; sid:2; rev:1;) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
%YAML 1.1 | ||
--- | ||
|
||
outputs: | ||
- eve-log: | ||
enabled: yes | ||
types: | ||
- altemplate | ||
- alert | ||
- flow | ||
|
||
app-layer: | ||
protocols: | ||
altemplate: | ||
enabled: yes | ||
detection-ports: | ||
dp: 7000 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
/* 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. | ||
*/ | ||
|
||
// same file as rust/src/applayertemplate/detect.rs except | ||
// TEMPLATE_START_REMOVE removed | ||
// different paths for use statements | ||
// keywords prefixed with altemplate instead of just template | ||
|
||
use super::template::{TemplateTransaction, ALPROTO_TEMPLATE}; | ||
use std::os::raw::{c_int, c_void}; | ||
use suricata::cast_pointer; | ||
use suricata::detect::{ | ||
DetectBufferSetActiveList, DetectHelperBufferMpmRegister, DetectHelperGetData, | ||
DetectHelperKeywordRegister, DetectSignatureSetAppProto, SCSigTableElmt, | ||
SIGMATCH_INFO_STICKY_BUFFER, SIGMATCH_NOOPT, | ||
}; | ||
use suricata::direction::Direction; | ||
|
||
static mut G_TEMPLATE_BUFFER_BUFFER_ID: c_int = 0; | ||
|
||
unsafe extern "C" fn template_buffer_setup( | ||
de: *mut c_void, s: *mut c_void, _raw: *const std::os::raw::c_char, | ||
) -> c_int { | ||
if DetectSignatureSetAppProto(s, ALPROTO_TEMPLATE) != 0 { | ||
return -1; | ||
} | ||
if DetectBufferSetActiveList(de, s, G_TEMPLATE_BUFFER_BUFFER_ID) < 0 { | ||
return -1; | ||
} | ||
return 0; | ||
} | ||
|
||
/// Get the request/response buffer for a transaction from C. | ||
unsafe extern "C" fn template_buffer_get_data( | ||
tx: *const c_void, flags: u8, buf: *mut *const u8, len: *mut u32, | ||
) -> bool { | ||
let tx = cast_pointer!(tx, TemplateTransaction); | ||
if flags & Direction::ToClient as u8 != 0 { | ||
if let Some(ref response) = tx.response { | ||
*len = response.len() as u32; | ||
*buf = response.as_ptr(); | ||
return true; | ||
} | ||
} else if let Some(ref request) = tx.request { | ||
*len = request.len() as u32; | ||
*buf = request.as_ptr(); | ||
return true; | ||
} | ||
return false; | ||
} | ||
|
||
unsafe extern "C" fn template_buffer_get( | ||
de: *mut c_void, transforms: *const c_void, flow: *const c_void, flow_flags: u8, | ||
tx: *const c_void, list_id: c_int, | ||
) -> *mut c_void { | ||
return DetectHelperGetData( | ||
de, | ||
transforms, | ||
flow, | ||
flow_flags, | ||
tx, | ||
list_id, | ||
template_buffer_get_data, | ||
); | ||
} | ||
|
||
pub(super) unsafe extern "C" fn detect_template_register() { | ||
// TODO create a suricata-verify test | ||
// Setup a keyword structure and register it | ||
let kw = SCSigTableElmt { | ||
name: b"altemplate.buffer\0".as_ptr() as *const libc::c_char, | ||
desc: b"Template content modifier to match on the template buffer\0".as_ptr() | ||
as *const libc::c_char, | ||
// TODO use the right anchor for url and write doc | ||
url: b"/rules/template-keywords.html#buffer\0".as_ptr() as *const libc::c_char, | ||
Setup: template_buffer_setup, | ||
flags: SIGMATCH_NOOPT | SIGMATCH_INFO_STICKY_BUFFER, | ||
AppLayerTxMatch: None, | ||
Free: None, | ||
}; | ||
let _g_template_buffer_kw_id = DetectHelperKeywordRegister(&kw); | ||
G_TEMPLATE_BUFFER_BUFFER_ID = DetectHelperBufferMpmRegister( | ||
b"altemplate.buffer\0".as_ptr() as *const libc::c_char, | ||
b"template.buffer intern description\0".as_ptr() as *const libc::c_char, | ||
ALPROTO_TEMPLATE, | ||
true, //toclient | ||
true, //toserver | ||
template_buffer_get, | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
mod detect; | ||
mod log; | ||
mod parser; | ||
pub mod plugin; | ||
mod template; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
/* Copyright (C) 2018 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. | ||
*/ | ||
|
||
// same file as rust/src/applayertemplate/logger.rs except | ||
// different paths for use statements | ||
// open_object using altemplate instead of just template | ||
// Jsonbuilder using C API due to opaque implementation | ||
|
||
use super::template::TemplateTransaction; | ||
use std::ffi::{c_char, CString}; | ||
use suricata::cast_pointer; | ||
use suricata::jsonbuilder::JsonError; | ||
|
||
use std; | ||
|
||
// Jsonbuilder opaque with implementation using C API to feel like usual | ||
#[repr(C)] | ||
pub struct JsonBuilder { | ||
_data: [u8; 0], | ||
} | ||
|
||
extern "C" { | ||
pub fn jb_set_string(jb: &mut JsonBuilder, key: *const c_char, val: *const c_char) -> bool; | ||
pub fn jb_close(jb: &mut JsonBuilder) -> bool; | ||
pub fn jb_open_object(jb: &mut JsonBuilder, key: *const c_char) -> bool; | ||
} | ||
|
||
impl JsonBuilder { | ||
fn close(&mut self) -> Result<(), JsonError> { | ||
if unsafe { !jb_close(self) } { | ||
return Err(JsonError::Memory); | ||
} | ||
Ok(()) | ||
} | ||
fn open_object(&mut self, key: &str) -> Result<(), JsonError> { | ||
let keyc = CString::new(key).unwrap(); | ||
if unsafe { !jb_open_object(self, keyc.as_ptr()) } { | ||
return Err(JsonError::Memory); | ||
} | ||
Ok(()) | ||
} | ||
fn set_string(&mut self, key: &str, val: &str) -> Result<(), JsonError> { | ||
let keyc = CString::new(key).unwrap(); | ||
let valc = CString::new(val.escape_default().to_string()).unwrap(); | ||
if unsafe { !jb_set_string(self, keyc.as_ptr(), valc.as_ptr()) } { | ||
return Err(JsonError::Memory); | ||
} | ||
Ok(()) | ||
} | ||
} | ||
|
||
fn log_template(tx: &TemplateTransaction, js: &mut JsonBuilder) -> Result<(), JsonError> { | ||
js.open_object("altemplate")?; | ||
if let Some(ref request) = tx.request { | ||
js.set_string("request", request)?; | ||
} | ||
if let Some(ref response) = tx.response { | ||
js.set_string("response", response)?; | ||
} | ||
js.close()?; | ||
Ok(()) | ||
} | ||
|
||
pub(super) unsafe extern "C" fn template_logger_log( | ||
tx: *const std::os::raw::c_void, js: *mut std::os::raw::c_void, | ||
) -> bool { | ||
let tx = cast_pointer!(tx, TemplateTransaction); | ||
let js = cast_pointer!(js, JsonBuilder); | ||
log_template(tx, js).is_ok() | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok