-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Lua ssh 7607 v4 #13027
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
Lua ssh 7607 v4 #13027
Changes from all commits
d054e17
5b64bbd
1269618
ec32a40
0180c87
5d49d8c
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 |
---|---|---|
|
@@ -14,3 +14,4 @@ environment without access to additional modules. | |
hashlib | ||
http | ||
packetlib | ||
ssh |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
SSH | ||
--- | ||
|
||
SSH transaction details are exposes to Lua scripts with the | ||
``suricata.ssh`` library, For example:: | ||
|
||
local ssh = require("suricata.ssh") | ||
|
||
Setup | ||
^^^^^ | ||
|
||
If your purpose is to create a logging script, initialize the buffer as: | ||
|
||
:: | ||
|
||
function init (args) | ||
local needs = {} | ||
needs["protocol"] = "ssh" | ||
return needs | ||
end | ||
|
||
If you are going to use the script for rule matching, choose one of | ||
the available SSH buffers listed in :ref:`lua-detection` and follow | ||
the pattern: | ||
|
||
:: | ||
|
||
function init (args) | ||
local needs = {} | ||
needs["ssh.server_proto"] = tostring(true) | ||
return needs | ||
end | ||
|
||
Transaction | ||
~~~~~~~~~~~ | ||
|
||
SSH is transaction based, and the current transaction must be obtained before use:: | ||
|
||
local tx, err = ssh.get_tx() | ||
if tx == err then | ||
print(err) | ||
end | ||
|
||
All other functions are methods on the transaction table. | ||
|
||
Transaction Methods | ||
~~~~~~~~~~~~~~~~~~~ | ||
|
||
``server_proto()`` | ||
^^^^^^^^^^^^^^^^^^ | ||
|
||
Get the ``server_proto`` value as a string. | ||
|
||
Example:: | ||
|
||
local tx = ssh.get_tx() | ||
local proto = tx:server_proto(); | ||
print (proto) | ||
|
||
``client_proto()`` | ||
^^^^^^^^^^^^^^^^^^ | ||
|
||
Get the ``client_proto`` value as a string. | ||
|
||
Example:: | ||
|
||
local tx = ssh.get_tx() | ||
local proto = tx:client_proto(); | ||
print (proto) | ||
|
||
``server_software()`` | ||
^^^^^^^^^^^^^^^^^^^^^ | ||
|
||
Get the ``server_software`` value as a string. | ||
|
||
Example:: | ||
|
||
local tx = ssh.get_tx() | ||
local software = tx:server_software(); | ||
print (software) | ||
|
||
``client_software()`` | ||
^^^^^^^^^^^^^^^^^^^^^ | ||
|
||
Get the ``client_software`` value as a string. | ||
|
||
Example:: | ||
|
||
local tx = ssh.get_tx() | ||
local software = tx:client_software(); | ||
print (software) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
/* 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. | ||
*/ | ||
|
||
extern crate proc_macro; | ||
use proc_macro::TokenStream; | ||
use quote::quote; | ||
use syn::{self, parse_macro_input, DeriveInput}; | ||
|
||
pub fn derive_app_layer_state(input: TokenStream) -> TokenStream { | ||
let input = parse_macro_input!(input as DeriveInput); | ||
let name = input.ident; | ||
|
||
let mut fields = Vec::new(); | ||
let mut vals = Vec::new(); | ||
let mut cstrings = Vec::new(); | ||
let mut names = Vec::new(); | ||
|
||
match input.data { | ||
syn::Data::Enum(ref data) => { | ||
for (i, v) in (&data.variants).into_iter().enumerate() { | ||
fields.push(v.ident.clone()); | ||
let name = transform_name(&v.ident.to_string()); | ||
let cname = format!("{}\0", name); | ||
names.push(name); | ||
cstrings.push(cname); | ||
vals.push(i as u8); | ||
} | ||
} | ||
_ => panic!("AppLayerState can only be derived for enums"), | ||
} | ||
|
||
let expanded = quote! { | ||
impl crate::applayer::AppLayerState for #name { | ||
fn from_u8(val: u8) -> Option<Self> { | ||
match val { | ||
#( #vals => Some(#name::#fields) ,)* | ||
_ => None, | ||
} | ||
} | ||
|
||
fn as_u8(&self) -> u8 { | ||
match *self { | ||
#( #name::#fields => #vals ,)* | ||
} | ||
} | ||
|
||
fn to_cstring(&self) -> *const std::os::raw::c_char { | ||
let s = match *self { | ||
#( #name::#fields => #cstrings ,)* | ||
}; | ||
s.as_ptr() as *const std::os::raw::c_char | ||
} | ||
|
||
fn from_str(s: &str) -> Option<#name> { | ||
match s { | ||
#( #names => Some(#name::#fields) ,)* | ||
_ => None | ||
} | ||
} | ||
} | ||
}; | ||
|
||
proc_macro::TokenStream::from(expanded) | ||
} | ||
|
||
fn transform_name(name: &str) -> String { | ||
let mut xname = String::new(); | ||
let chars: Vec<char> = name.chars().collect(); | ||
for i in 0..chars.len() { | ||
if i > 0 && i < chars.len() - 1 && chars[i].is_uppercase() && chars[i + 1].is_lowercase() { | ||
xname.push('_'); | ||
} | ||
xname.push_str(&chars[i].to_lowercase().to_string()); | ||
} | ||
xname | ||
} | ||
|
||
#[cfg(test)] | ||
mod test { | ||
use super::*; | ||
|
||
#[test] | ||
fn test_transform_name() { | ||
assert_eq!(transform_name("One"), "one"); | ||
assert_eq!(transform_name("OneTwo"), "one_two"); | ||
assert_eq!(transform_name("OneTwoThree"), "one_two_three"); | ||
assert_eq!(transform_name("NBSS"), "nbss"); | ||
assert_eq!(transform_name("NBSSHdr"), "nbss_hdr"); | ||
assert_eq!(transform_name("SMB3Data"), "smb3_data"); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -777,3 +777,67 @@ pub trait AppLayerFrameType { | |
Self::from_u8(id).map(|s| s.to_cstring()).unwrap_or_else(std::ptr::null) | ||
} | ||
} | ||
|
||
/// AppLayerState trait. | ||
/// | ||
/// This is the behavior expected from an enum of state progress. For most instances | ||
/// this behavior can be derived. | ||
/// | ||
/// Example: | ||
/// | ||
/// #[derive(AppLayerState)] | ||
/// enum SomeProtoState { | ||
/// Start, | ||
/// Complete, | ||
/// } | ||
pub trait AppLayerState { | ||
/// Create a state progress variant from a u8. | ||
/// | ||
/// None will be returned if there is no matching enum variant. | ||
fn from_u8(value: u8) -> Option<Self> | ||
where | ||
Self: Sized; | ||
|
||
/// Return the u8 value of the enum where the first entry has the value of 0. | ||
fn as_u8(&self) -> u8; | ||
|
||
/// Create a state progress variant from a &str. | ||
/// | ||
/// None will be returned if there is no matching enum variant. | ||
fn from_str(s: &str) -> Option<Self> | ||
where | ||
Self: Sized; | ||
|
||
/// Return a pointer to a C string of the enum variant suitable as-is for | ||
/// FFI. | ||
fn to_cstring(&self) -> *const c_char; | ||
|
||
/// Converts a C string formatted name to a state progress. | ||
unsafe extern "C" fn ffi_id_from_name(name: *const c_char, _dir: u8) -> c_int | ||
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. I do not know where to put this, but this is for protocols which do not need the direction (like SSH) 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. Probably as good here as any. However, perhaps the function doc should contain the additional information:
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. adding |
||
where | ||
Self: Sized, | ||
{ | ||
if name.is_null() { | ||
return -1; | ||
} | ||
if let Ok(s) = std::ffi::CStr::from_ptr(name).to_str() { | ||
Self::from_str(s).map(|t| t.as_u8() as c_int).unwrap_or(-1) | ||
} else { | ||
-1 | ||
} | ||
} | ||
|
||
/// Converts a variant ID to an FFI name. | ||
unsafe extern "C" fn ffi_name_from_id(id: c_int, _dir: u8) -> *const c_char | ||
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. Not sure |
||
where | ||
Self: Sized, | ||
{ | ||
if id < 0 || id > c_int::from(u8::MAX) { | ||
return std::ptr::null(); | ||
} | ||
if let Some(v) = Self::from_u8(id as u8) { | ||
return v.to_cstring(); | ||
} | ||
return std::ptr::null(); | ||
} | ||
} |
Uh oh!
There was an error while loading. Please reload this page.