parse: move SSH parser from C to Rust

pull/4971/head
Philippe Antoine 6 years ago committed by Victor Julien
parent cb3c478525
commit 69b4fffdae

@ -71,3 +71,4 @@ pub mod rfb;
pub mod applayertemplate;
pub mod rdp;
pub mod x509;
pub mod ssh;

@ -0,0 +1,92 @@
/* Copyright (C) 2020 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::ssh::SSHTransaction;
use crate::core::{STREAM_TOCLIENT, STREAM_TOSERVER};
use std::ptr;
#[no_mangle]
pub extern "C" fn rs_ssh_tx_get_protocol(
tx: *mut std::os::raw::c_void, buffer: *mut *const u8, buffer_len: *mut u32, direction: u8,
) -> u8 {
let tx = cast_pointer!(tx, SSHTransaction);
match direction {
STREAM_TOSERVER => {
let m = &tx.cli_hdr.protover;
if m.len() > 0 {
unsafe {
*buffer = m.as_ptr();
*buffer_len = m.len() as u32;
}
return 1;
}
}
STREAM_TOCLIENT => {
let m = &tx.srv_hdr.protover;
if m.len() > 0 {
unsafe {
*buffer = m.as_ptr();
*buffer_len = m.len() as u32;
}
return 1;
}
}
_ => {}
}
unsafe {
*buffer = ptr::null();
*buffer_len = 0;
}
return 0;
}
#[no_mangle]
pub extern "C" fn rs_ssh_tx_get_software(
tx: *mut std::os::raw::c_void, buffer: *mut *const u8, buffer_len: *mut u32, direction: u8,
) -> u8 {
let tx = cast_pointer!(tx, SSHTransaction);
match direction {
STREAM_TOSERVER => {
let m = &tx.cli_hdr.swver;
if m.len() > 0 {
unsafe {
*buffer = m.as_ptr();
*buffer_len = m.len() as u32;
}
return 1;
}
}
STREAM_TOCLIENT => {
let m = &tx.srv_hdr.swver;
if m.len() > 0 {
unsafe {
*buffer = m.as_ptr();
*buffer_len = m.len() as u32;
}
return 1;
}
}
_ => {}
}
unsafe {
*buffer = ptr::null();
*buffer_len = 0;
}
return 0;
}

@ -0,0 +1,64 @@
/* Copyright (C) 2020 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::ssh::SSHTransaction;
use crate::json::*;
fn log_ssh(tx: &SSHTransaction) -> Option<Json> {
if tx.cli_hdr.protover.len() == 0 && tx.srv_hdr.protover.len() == 0 {
return None;
}
let js = Json::object();
if tx.cli_hdr.protover.len() > 0 {
let cjs = Json::object();
cjs.set_string_from_bytes(
"proto_version",
&tx.cli_hdr.protover,
);
if tx.cli_hdr.swver.len() > 0 {
cjs.set_string_from_bytes(
"software_version",
&tx.cli_hdr.swver,
);
}
js.set("client", cjs);
}
if tx.srv_hdr.protover.len() > 0 {
let sjs = Json::object();
sjs.set_string_from_bytes(
"proto_version",
&tx.srv_hdr.protover,
);
if tx.srv_hdr.swver.len() > 0 {
sjs.set_string_from_bytes(
"software_version",
&tx.srv_hdr.swver,
);
}
js.set("server", sjs);
}
return Some(js);
}
#[no_mangle]
pub extern "C" fn rs_ssh_log_json(tx: *mut std::os::raw::c_void) -> *mut JsonT {
let tx = cast_pointer!(tx, SSHTransaction);
match log_ssh(tx) {
Some(js) => js.unwrap(),
None => std::ptr::null_mut(),
}
}

@ -0,0 +1,21 @@
/* Copyright (C) 2020 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 logger;
mod parser;
pub mod ssh;

@ -0,0 +1,196 @@
/* Copyright (C) 2020 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 nom::combinator::rest;
use nom::number::streaming::{be_u32, be_u8};
use std::fmt;
#[inline]
fn is_not_lineend(b: u8) -> bool {
if b == 10 || b == 13 {
return false;
}
return true;
}
//may leave \r at the end to be removed
named!(pub ssh_parse_line<&[u8], &[u8]>,
terminated!(
take_while!(is_not_lineend),
alt!( tag!("\n") | tag!("\r\n") |
do_parse!(
bytes: tag!("\r") >>
not!(eof!()) >> (bytes)
)
)
)
);
#[derive(PartialEq)]
pub struct SshBanner<'a> {
pub protover: &'a [u8],
pub swver: &'a [u8],
}
// Could be simplified adding dummy \n at the end
// or use nom5 nom::bytes::complete::is_not
named!(pub ssh_parse_banner<SshBanner>,
do_parse!(
tag!("SSH-") >>
protover: is_not!("-") >>
char!('-') >>
swver: alt!( complete!( is_not!(" \r\n") ) | rest ) >>
//remaining after space is comments
(SshBanner{protover, swver})
)
);
#[derive(PartialEq)]
pub struct SshRecordHeader {
pub pkt_len: u32,
padding_len: u8,
pub msg_code: u8,
}
impl fmt::Display for SshRecordHeader {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"(pkt_len:{}, padding_len:{}, msg_code:{})",
self.pkt_len, self.padding_len, self.msg_code
)
}
}
named!(pub ssh_parse_record_header<SshRecordHeader>,
do_parse!(
pkt_len: verify!(be_u32, |&val| val > 1) >>
padding_len: be_u8 >>
msg_code: be_u8 >>
(SshRecordHeader{pkt_len, padding_len, msg_code})
)
);
//test for evasion against pkt_len=0or1...
named!(pub ssh_parse_record<SshRecordHeader>,
do_parse!(
pkt_len: verify!(be_u32, |&val| val > 1) >>
padding_len: be_u8 >>
msg_code: be_u8 >>
take!((pkt_len-2) as usize) >>
(SshRecordHeader{pkt_len, padding_len, msg_code})
)
);
#[cfg(test)]
mod tests {
use super::*;
/// Simple test of some valid data.
#[test]
fn test_ssh_parse_banner() {
let buf = b"SSH-Single-";
let result = ssh_parse_banner(buf);
match result {
Ok((_, message)) => {
// Check the first message.
assert_eq!(message.protover, b"Single");
assert_eq!(message.swver, b"");
}
Err(err) => {
panic!("Result should not be an error: {:?}.", err);
}
}
let buf2 = b"SSH-2.0-Soft";
let result2 = ssh_parse_banner(buf2);
match result2 {
Ok((_, message)) => {
// Check the first message.
assert_eq!(message.protover, b"2.0");
assert_eq!(message.swver, b"Soft");
}
Err(err) => {
panic!("Result should not be an error: {:?}.", err);
}
}
}
#[test]
fn test_parse_line() {
let buf = b"SSH-Single\n";
let result = ssh_parse_line(buf);
match result {
Ok((_, message)) => {
// Check the first message.
assert_eq!(message, b"SSH-Single");
}
Err(err) => {
panic!("Result should not be an error: {:?}.", err);
}
}
let buf2 = b"SSH-Double\r\n";
let result2 = ssh_parse_line(buf2);
match result2 {
Ok((_, message)) => {
// Check the first message.
assert_eq!(message, b"SSH-Double");
}
Err(err) => {
panic!("Result should not be an error: {:?}.", err);
}
}
let buf3 = b"SSH-Oops\rMore\r\n";
let result3 = ssh_parse_line(buf3);
match result3 {
Ok((rem, message)) => {
// Check the first message.
assert_eq!(message, b"SSH-Oops");
assert_eq!(rem, b"More\r\n");
}
Err(err) => {
panic!("Result should not be an error: {:?}.", err);
}
}
let buf4 = b"SSH-Miss\r";
let result4 = ssh_parse_line(buf4);
match result4 {
Ok((_, _)) => {
panic!("Expected incomplete result");
}
Err(nom::Err::Incomplete(_)) => {
//OK
assert_eq!(1, 1);
}
Err(err) => {
panic!("Result should not be an error: {:?}.", err);
}
}
let buf5 = b"\n";
let result5 = ssh_parse_line(buf5);
match result5 {
Ok((_, message)) => {
// Check empty line
assert_eq!(message, b"");
}
Err(err) => {
panic!("Result should not be an error: {:?}.", err);
}
}
}
}

@ -0,0 +1,531 @@
/* Copyright (C) 2020 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::parser;
use crate::applayer::*;
use crate::core::STREAM_TOSERVER;
use crate::core::{self, AppProto, Flow, ALPROTO_UNKNOWN, IPPROTO_TCP};
use crate::log::*;
use std::ffi::{CStr, CString};
use std::mem::transmute;
static mut ALPROTO_SSH: AppProto = ALPROTO_UNKNOWN;
#[repr(u32)]
pub enum SSHEvent {
InvalidBanner = 0,
LongBanner,
InvalidRecord,
}
impl SSHEvent {
fn from_i32(value: i32) -> Option<SSHEvent> {
match value {
0 => Some(SSHEvent::InvalidBanner),
1 => Some(SSHEvent::LongBanner),
2 => Some(SSHEvent::InvalidRecord),
_ => None,
}
}
}
#[repr(u8)]
#[derive(Copy, Clone, PartialOrd, PartialEq)]
pub enum SSHConnectionState {
SshStateInProgress = 0,
SshStateBannerWaitEol = 1,
SshStateBannerDone = 2,
SshStateFinished = 3,
}
const SSH_MAX_BANNER_LEN: usize = 256;
const SSH_RECORD_HEADER_LEN: usize = 6;
//TODO complete enum and parse messages contents
const SSH_MSG_NEWKEYS: u8 = 21;
pub struct SshHeader {
record_left: u32,
flags: SSHConnectionState,
pub protover: Vec<u8>,
pub swver: Vec<u8>,
}
impl SshHeader {
pub fn new() -> SshHeader {
SshHeader {
record_left: 0,
flags: SSHConnectionState::SshStateInProgress,
protover: Vec::new(),
swver: Vec::new(),
}
}
}
pub struct SSHTransaction {
pub srv_hdr: SshHeader,
pub cli_hdr: SshHeader,
logged: LoggerFlags,
de_state: Option<*mut core::DetectEngineState>,
detect_flags: TxDetectFlags,
events: *mut core::AppLayerDecoderEvents,
}
impl SSHTransaction {
pub fn new() -> SSHTransaction {
SSHTransaction {
srv_hdr: SshHeader::new(),
cli_hdr: SshHeader::new(),
logged: LoggerFlags::new(),
de_state: None,
detect_flags: TxDetectFlags::default(),
events: std::ptr::null_mut(),
}
}
pub fn free(&mut self) {
if self.events != std::ptr::null_mut() {
core::sc_app_layer_decoder_events_free_events(&mut self.events);
}
if let Some(state) = self.de_state {
core::sc_detect_engine_state_free(state);
}
}
}
impl Drop for SSHTransaction {
fn drop(&mut self) {
self.free();
}
}
pub struct SSHState {
transaction: SSHTransaction,
}
impl SSHState {
pub fn new() -> Self {
Self {
transaction: SSHTransaction::new(),
}
}
fn set_event(&mut self, event: SSHEvent) {
let ev = event as u8;
core::sc_app_layer_decoder_events_set_event_raw(&mut self.transaction.events, ev);
}
fn parse_record(
&mut self, mut input: &[u8], resp: bool, pstate: *mut std::os::raw::c_void,
) -> AppLayerResult {
let (mut hdr, ohdr) = if !resp {
(&mut self.transaction.cli_hdr, &self.transaction.srv_hdr)
} else {
(&mut self.transaction.srv_hdr, &self.transaction.cli_hdr)
};
let il = input.len();
//first skip record left bytes
if hdr.record_left > 0 {
//should we check for overflow ?
let ilen = input.len() as u32;
if hdr.record_left >= ilen {
hdr.record_left -= ilen;
return AppLayerResult::ok();
} else {
let start = hdr.record_left as usize;
input = &input[start..];
hdr.record_left = 0;
}
}
//parse records out of input
while input.len() > 0 {
match parser::ssh_parse_record(input) {
Ok((rem, head)) => {
SCLogDebug!("SSH valid record {}", head);
input = rem;
if head.msg_code == SSH_MSG_NEWKEYS {
hdr.flags = SSHConnectionState::SshStateFinished;
if ohdr.flags >= SSHConnectionState::SshStateFinished {
unsafe {
AppLayerParserStateSetFlag(
pstate,
APP_LAYER_PARSER_NO_INSPECTION
| APP_LAYER_PARSER_NO_REASSEMBLY
| APP_LAYER_PARSER_BYPASS_READY,
);
}
}
}
//header and complete data (not returned)
}
Err(nom::Err::Incomplete(_)) => {
match parser::ssh_parse_record_header(input) {
Ok((rem, head)) => {
SCLogDebug!("SSH valid record header {}", head);
let remlen = rem.len() as u32;
hdr.record_left = head.pkt_len - 2 - remlen;
//header with rem as incomplete data
if head.msg_code == SSH_MSG_NEWKEYS {
hdr.flags = SSHConnectionState::SshStateFinished;
}
return AppLayerResult::ok();
}
Err(nom::Err::Incomplete(_)) => {
//we may have consumed data from previous records
if input.len() < SSH_RECORD_HEADER_LEN {
//do not trust nom incomplete value
return AppLayerResult::incomplete(
(il - input.len()) as u32,
SSH_RECORD_HEADER_LEN as u32,
);
} else {
panic!("SSH invalid length record header");
}
}
Err(e) => {
SCLogDebug!("SSH invalid record header {}", e);
self.set_event(SSHEvent::InvalidRecord);
return AppLayerResult::err();
}
}
}
Err(e) => {
SCLogDebug!("SSH invalid record {}", e);
self.set_event(SSHEvent::InvalidRecord);
return AppLayerResult::err();
}
}
}
return AppLayerResult::ok();
}
fn parse_banner(
&mut self, input: &[u8], resp: bool, pstate: *mut std::os::raw::c_void,
) -> AppLayerResult {
let mut hdr = if !resp {
&mut self.transaction.cli_hdr
} else {
&mut self.transaction.srv_hdr
};
if hdr.flags == SSHConnectionState::SshStateBannerWaitEol {
match parser::ssh_parse_line(input) {
Ok((rem, _)) => {
return self.parse_record(rem, resp, pstate);
}
Err(nom::Err::Incomplete(_)) => {
return AppLayerResult::incomplete(0 as u32, (input.len() + 1) as u32);
}
Err(e) => {
SCLogDebug!("SSH invalid banner {}", e);
self.set_event(SSHEvent::InvalidBanner);
return AppLayerResult::err();
}
}
}
match parser::ssh_parse_line(input) {
Ok((rem, line)) => {
if let Ok((_, banner)) = parser::ssh_parse_banner(line) {
hdr.protover.extend(banner.protover);
if banner.swver.len() > 0 {
hdr.swver.extend(banner.swver);
}
hdr.flags = SSHConnectionState::SshStateBannerDone;
} else {
SCLogDebug!("SSH invalid banner");
self.set_event(SSHEvent::InvalidBanner);
return AppLayerResult::err();
}
if line.len() >= SSH_MAX_BANNER_LEN {
SCLogDebug!(
"SSH banner too long {} vs {}",
line.len(),
SSH_MAX_BANNER_LEN
);
self.set_event(SSHEvent::LongBanner);
}
return self.parse_record(rem, resp, pstate);
}
Err(nom::Err::Incomplete(_)) => {
if input.len() < SSH_MAX_BANNER_LEN {
//0 consumed, needs at least one more byte
return AppLayerResult::incomplete(0 as u32, (input.len() + 1) as u32);
} else {
SCLogDebug!(
"SSH banner too long {} vs {} and waiting for eol",
input.len(),
SSH_MAX_BANNER_LEN
);
if let Ok((_, banner)) = parser::ssh_parse_banner(input) {
hdr.protover.extend(banner.protover);
if banner.swver.len() > 0 {
hdr.swver.extend(banner.swver);
}
hdr.flags = SSHConnectionState::SshStateBannerWaitEol;
self.set_event(SSHEvent::LongBanner);
return AppLayerResult::ok();
} else {
self.set_event(SSHEvent::InvalidBanner);
return AppLayerResult::err();
}
}
}
Err(e) => {
SCLogDebug!("SSH invalid banner {}", e);
self.set_event(SSHEvent::InvalidBanner);
return AppLayerResult::err();
}
}
}
}
// C exports.
export_tx_get_detect_state!(rs_ssh_tx_get_detect_state, SSHTransaction);
export_tx_set_detect_state!(rs_ssh_tx_set_detect_state, SSHTransaction);
export_tx_detect_flags_set!(rs_ssh_set_tx_detect_flags, SSHTransaction);
export_tx_detect_flags_get!(rs_ssh_get_tx_detect_flags, SSHTransaction);
#[no_mangle]
pub extern "C" fn rs_ssh_state_get_events(
tx: *mut std::os::raw::c_void,
) -> *mut core::AppLayerDecoderEvents {
let tx = cast_pointer!(tx, SSHTransaction);
return tx.events;
}
#[no_mangle]
pub extern "C" fn rs_ssh_state_get_event_info(
event_name: *const std::os::raw::c_char, event_id: *mut std::os::raw::c_int,
event_type: *mut core::AppLayerEventType,
) -> std::os::raw::c_int {
if event_name == std::ptr::null() {
return -1;
}
let c_event_name: &CStr = unsafe { CStr::from_ptr(event_name) };
let event = match c_event_name.to_str() {
Ok(s) => {
match s {
"invalid_banner" => SSHEvent::InvalidBanner as i32,
"long_banner" => SSHEvent::LongBanner as i32,
"invalid_record" => SSHEvent::InvalidRecord as i32,
_ => -1, // unknown event
}
}
Err(_) => -1, // UTF-8 conversion failed
};
unsafe {
*event_type = core::APP_LAYER_EVENT_TYPE_TRANSACTION;
*event_id = event as std::os::raw::c_int;
};
0
}
#[no_mangle]
pub extern "C" fn rs_ssh_state_get_event_info_by_id(
event_id: std::os::raw::c_int, event_name: *mut *const std::os::raw::c_char,
event_type: *mut core::AppLayerEventType,
) -> i8 {
if let Some(e) = SSHEvent::from_i32(event_id as i32) {
let estr = match e {
SSHEvent::InvalidBanner => "invalid_banner\0",
SSHEvent::LongBanner => "long_banner\0",
SSHEvent::InvalidRecord => "invalid_record\0",
};
unsafe {
*event_name = estr.as_ptr() as *const std::os::raw::c_char;
*event_type = core::APP_LAYER_EVENT_TYPE_TRANSACTION;
};
0
} else {
-1
}
}
#[no_mangle]
pub extern "C" fn rs_ssh_state_new() -> *mut std::os::raw::c_void {
let state = SSHState::new();
let boxed = Box::new(state);
return unsafe { transmute(boxed) };
}
#[no_mangle]
pub extern "C" fn rs_ssh_state_free(state: *mut std::os::raw::c_void) {
// Just unbox...
let _drop: Box<SSHState> = unsafe { transmute(state) };
}
#[no_mangle]
pub extern "C" fn rs_ssh_state_tx_free(_state: *mut std::os::raw::c_void, _tx_id: u64) {
//do nothing
}
#[no_mangle]
pub extern "C" fn rs_ssh_parse_request(
_flow: *const Flow, state: *mut std::os::raw::c_void, pstate: *mut std::os::raw::c_void,
input: *const u8, input_len: u32, _data: *const std::os::raw::c_void, _flags: u8,
) -> AppLayerResult {
let state = &mut cast_pointer!(state, SSHState);
let buf = build_slice!(input, input_len as usize);
let hdr = &mut state.transaction.cli_hdr;
if hdr.flags < SSHConnectionState::SshStateBannerDone {
return state.parse_banner(buf, false, pstate);
} else {
return state.parse_record(buf, false, pstate);
}
}
#[no_mangle]
pub extern "C" fn rs_ssh_parse_response(
_flow: *const Flow, state: *mut std::os::raw::c_void, pstate: *mut std::os::raw::c_void,
input: *const u8, input_len: u32, _data: *const std::os::raw::c_void, _flags: u8,
) -> AppLayerResult {
let state = &mut cast_pointer!(state, SSHState);
let buf = build_slice!(input, input_len as usize);
let hdr = &mut state.transaction.srv_hdr;
if hdr.flags < SSHConnectionState::SshStateBannerDone {
return state.parse_banner(buf, true, pstate);
} else {
return state.parse_record(buf, true, pstate);
}
}
#[no_mangle]
pub extern "C" fn rs_ssh_state_get_tx(
state: *mut std::os::raw::c_void, _tx_id: u64,
) -> *mut std::os::raw::c_void {
let state = cast_pointer!(state, SSHState);
return unsafe { transmute(&state.transaction) };
}
#[no_mangle]
pub extern "C" fn rs_ssh_state_get_tx_count(_state: *mut std::os::raw::c_void) -> u64 {
return 1;
}
#[no_mangle]
pub extern "C" fn rs_ssh_state_progress_completion_status(_direction: u8) -> std::os::raw::c_int {
return SSHConnectionState::SshStateFinished as i32;
}
#[no_mangle]
pub extern "C" fn rs_ssh_tx_get_flags(
tx: *mut std::os::raw::c_void, direction: u8,
) -> SSHConnectionState {
let tx = cast_pointer!(tx, SSHTransaction);
if direction == STREAM_TOSERVER {
return tx.cli_hdr.flags;
} else {
return tx.srv_hdr.flags;
}
}
#[no_mangle]
pub extern "C" fn rs_ssh_tx_get_alstate_progress(
tx: *mut std::os::raw::c_void, direction: u8,
) -> std::os::raw::c_int {
let tx = cast_pointer!(tx, SSHTransaction);
if tx.cli_hdr.flags >= SSHConnectionState::SshStateFinished
&& tx.srv_hdr.flags >= SSHConnectionState::SshStateFinished
{
return SSHConnectionState::SshStateFinished as i32;
}
if direction == STREAM_TOSERVER {
if tx.cli_hdr.flags >= SSHConnectionState::SshStateBannerDone {
return SSHConnectionState::SshStateBannerDone as i32;
}
} else {
if tx.srv_hdr.flags >= SSHConnectionState::SshStateBannerDone {
return SSHConnectionState::SshStateBannerDone as i32;
}
}
return SSHConnectionState::SshStateInProgress as i32;
}
#[no_mangle]
pub extern "C" fn rs_ssh_tx_get_logged(
_state: *mut std::os::raw::c_void, tx: *mut std::os::raw::c_void,
) -> u32 {
let tx = cast_pointer!(tx, SSHTransaction);
return tx.logged.get();
}
#[no_mangle]
pub extern "C" fn rs_ssh_tx_set_logged(
_state: *mut std::os::raw::c_void, tx: *mut std::os::raw::c_void, logged: u32,
) {
let tx = cast_pointer!(tx, SSHTransaction);
tx.logged.set(logged);
}
// Parser name as a C style string.
const PARSER_NAME: &'static [u8] = b"ssh\0";
#[no_mangle]
pub unsafe extern "C" fn rs_ssh_register_parser() {
let default_port = CString::new("[22]").unwrap();
let parser = RustParser {
name: PARSER_NAME.as_ptr() as *const std::os::raw::c_char,
default_port: default_port.as_ptr(),
ipproto: IPPROTO_TCP,
//simple patterns, no probing
probe_ts: None,
probe_tc: None,
min_depth: 0,
max_depth: 0,
state_new: rs_ssh_state_new,
state_free: rs_ssh_state_free,
tx_free: rs_ssh_state_tx_free,
parse_ts: rs_ssh_parse_request,
parse_tc: rs_ssh_parse_response,
get_tx_count: rs_ssh_state_get_tx_count,
get_tx: rs_ssh_state_get_tx,
tx_get_comp_st: rs_ssh_state_progress_completion_status,
tx_get_progress: rs_ssh_tx_get_alstate_progress,
get_tx_logged: Some(rs_ssh_tx_get_logged),
set_tx_logged: Some(rs_ssh_tx_set_logged),
get_de_state: rs_ssh_tx_get_detect_state,
set_de_state: rs_ssh_tx_set_detect_state,
get_events: Some(rs_ssh_state_get_events),
get_eventinfo: Some(rs_ssh_state_get_event_info),
get_eventinfo_byid: Some(rs_ssh_state_get_event_info_by_id),
localstorage_new: None,
localstorage_free: None,
get_tx_mpm_id: None,
set_tx_mpm_id: None,
get_files: None,
get_tx_iterator: None,
get_tx_detect_flags: Some(rs_ssh_get_tx_detect_flags),
set_tx_detect_flags: Some(rs_ssh_set_tx_detect_flags),
};
let ip_proto_str = CString::new("tcp").unwrap();
if AppLayerProtoDetectConfProtoDetectionEnabled(ip_proto_str.as_ptr(), parser.name) != 0 {
let alproto = AppLayerRegisterProtocolDetection(&parser, 1);
ALPROTO_SSH = alproto;
if AppLayerParserConfParserEnabled(ip_proto_str.as_ptr(), parser.name) != 0 {
let _ = AppLayerRegisterParser(&parser, alproto);
}
SCLogDebug!("Rust ssh parser registered.");
} else {
SCLogNotice!("Protocol detector and parser disabled for SSH.");
}
}

@ -724,17 +724,17 @@ static void PacketToDataProtoTLS(const Packet *p, const PacketAlert *pa, idmef_a
static void PacketToDataProtoSSH(const Packet *p, const PacketAlert *pa, idmef_alert_t *alert)
{
json_t *js, *s_js;
SshState *ssh_state = (SshState *)FlowGetAppState(p->flow);
void *ssh_state = FlowGetAppState(p->flow);
if (ssh_state == NULL)
return;
js = json_object();
if (js == NULL)
void *tx_ptr = rs_ssh_state_get_tx(ssh_state, 0);
BUG_ON(tx_ptr == NULL);
js = rs_ssh_log_json(tx_ptr);
if (unlikely(js == NULL))
return;
JsonSshLogJSON(js, ssh_state);
s_js = json_object_get(js, "server");
if (s_js != NULL) {
JsonToAdditionalData(NULL, s_js, alert);

File diff suppressed because it is too large Load Diff

@ -25,63 +25,6 @@
#ifndef __APP_LAYER_SSH_H__
#define __APP_LAYER_SSH_H__
/* header flag */
#define SSH_FLAG_VERSION_PARSED 0x01
/* This flags indicate that the rest of the communication
* must be ciphered, so the parsing finish here */
#define SSH_FLAG_PARSER_DONE 0x02
/* MSG_CODE */
#define SSH_MSG_NEWKEYS 21
/** From SSH-TRANSP rfc
SSH Bunary packet structure:
uint32 packet_length
byte padding_length
byte[n1] payload; n1 = packet_length - padding_length - 1
byte[n2] random padding; n2 = padding_length
byte[m] mac (Message Authentication Code - MAC); m = mac_length
So we are going to do a header struct to store
the lenghts and msg_code (inside payload, if any)
*/
typedef struct SshHeader_ {
uint32_t pkt_len;
uint8_t padding_len;
uint8_t msg_code;
uint16_t banner_len;
uint8_t buf[6];
uint8_t buf_offset;
uint8_t flags;
uint32_t record_left;
uint8_t *proto_version;
uint8_t *software_version;
uint8_t *banner_buffer;
} SshHeader;
enum {
SSH_STATE_IN_PROGRESS,
SSH_STATE_BANNER_DONE,
SSH_STATE_FINISHED,
};
/** structure to store the SSH state values */
typedef struct SshState_ {
SshHeader srv_hdr;
SshHeader cli_hdr;
/* specifies which loggers are done logging */
uint32_t logged;
uint64_t detect_flags_ts;
uint64_t detect_flags_tc;
DetectEngineState *de_state;
} SshState;
void RegisterSSHParsers(void);
void SSHParserRegisterTests(void);

@ -51,6 +51,7 @@
#include "app-layer-parser.h"
#include "app-layer-ssh.h"
#include "detect-ssh-proto-version.h"
#include "rust.h"
#include "stream-tcp.h"
@ -109,34 +110,35 @@ static int DetectSshVersionMatch (DetectEngineThreadCtx *det_ctx,
SCLogDebug("lets see");
DetectSshVersionData *ssh = (DetectSshVersionData *)m;
SshState *ssh_state = (SshState *)state;
if (ssh_state == NULL) {
if (state == NULL) {
SCLogDebug("no ssh state, no match");
SCReturnInt(0);
}
int ret = 0;
if ((flags & STREAM_TOCLIENT) && (ssh_state->srv_hdr.flags & SSH_FLAG_VERSION_PARSED)) {
if (ssh->flags & SSH_FLAG_PROTOVERSION_2_COMPAT) {
SCLogDebug("looking for ssh server protoversion 2 compat");
if (strncmp((char *) ssh_state->srv_hdr.proto_version, "2", 1) == 0 ||
strncmp((char *) ssh_state->srv_hdr.proto_version, "2.", 2) == 0 ||
strncmp((char *) ssh_state->srv_hdr.proto_version, "1.99", 4) == 0)
const uint8_t *protocol = NULL;
uint32_t b_len = 0;
if (rs_ssh_tx_get_protocol(txv, &protocol, &b_len, flags) != 1)
SCReturnInt(0);
if (protocol == NULL || b_len == 0)
SCReturnInt(0);
if (ssh->flags & SSH_FLAG_PROTOVERSION_2_COMPAT) {
SCLogDebug("looking for ssh protoversion 2 compat");
if (protocol[0] == '2') {
ret = 1;
} else if (b_len >= 4) {
if (memcmp(protocol, "1.99", 4) == 0) {
ret = 1;
} else {
SCLogDebug("looking for ssh server protoversion %s length %"PRIu16"", ssh->ver, ssh->len);
ret = (strncmp((char *) ssh_state->srv_hdr.proto_version, (char *) ssh->ver, ssh->len) == 0)? 1 : 0;
}
}
} else if ((flags & STREAM_TOSERVER) && (ssh_state->cli_hdr.flags & SSH_FLAG_VERSION_PARSED)) {
if (ssh->flags & SSH_FLAG_PROTOVERSION_2_COMPAT) {
SCLogDebug("looking for client ssh client protoversion 2 compat");
if (strncmp((char *) ssh_state->cli_hdr.proto_version, "2", 1) == 0 ||
strncmp((char *) ssh_state->cli_hdr.proto_version, "2.", 2) == 0 ||
strncmp((char *) ssh_state->cli_hdr.proto_version, "1.99", 4) == 0)
} else {
SCLogDebug("looking for ssh protoversion %s length %"PRIu16"", ssh->ver, ssh->len);
if (b_len == ssh->len) {
if (memcmp(protocol, ssh->ver, ssh->len) == 0) {
ret = 1;
} else {
SCLogDebug("looking for ssh client protoversion %s length %"PRIu16"", ssh->ver, ssh->len);
ret = (strncmp((char *) ssh_state->cli_hdr.proto_version, (char *) ssh->ver, ssh->len) == 0)? 1 : 0;
}
}
}
SCReturnInt(ret);
@ -331,46 +333,52 @@ static int DetectSshVersionTestParse03 (void)
#include "stream-tcp-reassemble.h"
#include "stream-tcp-util.h"
/** \test Send a get request in three chunks + more data. */
static int DetectSshVersionTestDetect01(void)
{
Flow f;
TcpReassemblyThreadCtx *ra_ctx = NULL;
ThreadVars tv;
TcpSession ssn;
Flow *f = NULL;
Packet *p = NULL;
uint8_t sshbuf1[] = "SSH-1.";
uint32_t sshlen1 = sizeof(sshbuf1) - 1;
uint8_t sshbuf2[] = "10-PuTTY_2.123" ;
uint32_t sshlen2 = sizeof(sshbuf2) - 1;
uint8_t sshbuf3[] = "\n";
uint32_t sshlen3 = sizeof(sshbuf3) - 1;
uint8_t sshbuf4[] = "whatever...";
uint32_t sshlen4 = sizeof(sshbuf4) - 1;
TcpSession ssn;
Packet *p = NULL;
Signature *s = NULL;
ThreadVars th_v;
DetectEngineThreadCtx *det_ctx = NULL;
AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc();
memset(&th_v, 0, sizeof(th_v));
memset(&f, 0, sizeof(f));
memset(&ssn, 0, sizeof(ssn));
uint8_t* sshbufs[4] = {sshbuf1, sshbuf2, sshbuf3, sshbuf4};
uint32_t sshlens[4] = {sizeof(sshbuf1) - 1, sizeof(sshbuf2) - 1, sizeof(sshbuf3) - 1, sizeof(sshbuf4) - 1};
memset(&tv, 0x00, sizeof(tv));
StreamTcpUTInit(&ra_ctx);
StreamTcpUTInitInline();
StreamTcpUTSetupSession(&ssn);
StreamTcpUTSetupStream(&ssn.server, 1);
StreamTcpUTSetupStream(&ssn.client, 1);
f = UTHBuildFlow(AF_INET, "1.1.1.1", "2.2.2.2", 1234, 2222);
FAIL_IF_NULL(f);
f->protoctx = &ssn;
f->proto = IPPROTO_TCP;
f->alproto = ALPROTO_SSH;
p = UTHBuildPacket(NULL, 0, IPPROTO_TCP);
FAIL_IF_NULL(p);
FAIL_IF(unlikely(p == NULL));
p->flow = f;
FLOW_INITIALIZE(&f);
f.protoctx = (void *)&ssn;
p->flow = &f;
p->flowflags |= FLOW_PKT_TOSERVER;
p->flowflags |= FLOW_PKT_ESTABLISHED;
p->flags |= PKT_HAS_FLOW|PKT_STREAM_EST;
f.alproto = ALPROTO_SSH;
f.proto = IPPROTO_TCP;
Signature *s = NULL;
ThreadVars th_v;
DetectEngineThreadCtx *det_ctx = NULL;
StreamTcpInitConfig(TRUE);
memset(&th_v, 0, sizeof(th_v));
DetectEngineCtx *de_ctx = DetectEngineCtxInit();
FAIL_IF_NULL (de_ctx);
FAIL_IF_NULL(de_ctx);
de_ctx->flags |= DE_QUIET;
s = de_ctx->sig_list = SigInit(de_ctx,"alert ssh any any -> any any (msg:\"SSH\"; ssh.protoversion:1.10; sid:1;)");
@ -379,276 +387,183 @@ static int DetectSshVersionTestDetect01(void)
SigGroupBuild(de_ctx);
DetectEngineThreadCtxInit(&th_v, (void *)de_ctx, (void *)&det_ctx);
SCLogDebug("==> 1");
int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_SSH,
STREAM_TOSERVER, sshbuf1, sshlen1);
FAIL_IF(r != 0);
SCLogDebug("==> 2");
r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_SSH, STREAM_TOSERVER,
sshbuf2, sshlen2);
FAIL_IF(r != 0);
SCLogDebug("==> 3");
r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_SSH, STREAM_TOSERVER,
sshbuf3, sshlen3);
FAIL_IF(r != 0);
SCLogDebug("==> 4");
r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_SSH, STREAM_TOSERVER,
sshbuf4, sshlen4);
FAIL_IF(r != 0);
uint32_t seq = 2;
for (int i=0; i<4; i++) {
FAIL_IF(StreamTcpUTAddSegmentWithPayload(&tv, ra_ctx, &ssn.server, seq, sshbufs[i], sshlens[i]) == -1);
seq += sshlens[i];
FAIL_IF(StreamTcpReassembleAppLayer(&tv, ra_ctx, &ssn, &ssn.server, p, UPDATE_DIR_PACKET) < 0);
}
SshState *ssh_state = f.alstate;
void *ssh_state = f->alstate;
FAIL_IF_NULL(ssh_state);
/* do detect */
SigMatchSignatures(&th_v, de_ctx, det_ctx, p);
FAIL_IF(!(PacketAlertCheck(p, 1)));
FAIL_IF(PacketAlertCheck(p, 1));
DetectEngineThreadCtxDeinit(&th_v, (void *)det_ctx);
DetectEngineCtxFree(de_ctx);
StreamTcpFreeConfig(TRUE);
FLOW_DESTROY(&f);
UTHFreePackets(&p, 1);
AppLayerParserThreadCtxFree(alp_tctx);
UTHFreePacket(p);
StreamTcpUTClearSession(&ssn);
StreamTcpUTDeinit(ra_ctx);
UTHFreeFlow(f);
PASS;
}
/** \test Send a get request in three chunks + more data. */
static int DetectSshVersionTestDetect02(void)
{
int result = 0;
Flow f;
uint8_t sshbuf1[] = "SSH-1.";
uint32_t sshlen1 = sizeof(sshbuf1) - 1;
uint8_t sshbuf2[] = "99-PuTTY_2.123" ;
uint32_t sshlen2 = sizeof(sshbuf2) - 1;
uint8_t sshbuf3[] = "\n";
uint32_t sshlen3 = sizeof(sshbuf3) - 1;
uint8_t sshbuf4[] = "whatever...";
uint32_t sshlen4 = sizeof(sshbuf4) - 1;
TcpReassemblyThreadCtx *ra_ctx = NULL;
ThreadVars tv;
TcpSession ssn;
Flow *f = NULL;
Packet *p = NULL;
Signature *s = NULL;
ThreadVars th_v;
DetectEngineThreadCtx *det_ctx = NULL;
AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc();
memset(&th_v, 0, sizeof(th_v));
memset(&f, 0, sizeof(f));
memset(&ssn, 0, sizeof(ssn));
uint8_t sshbuf1[] = "SSH-1.99-Pu";
uint8_t sshbuf2[] = "TTY_2.123" ;
uint8_t sshbuf3[] = "\n";
uint8_t sshbuf4[] = "whatever...";
uint8_t* sshbufs[4] = {sshbuf1, sshbuf2, sshbuf3, sshbuf4};
uint32_t sshlens[4] = {sizeof(sshbuf1) - 1, sizeof(sshbuf2) - 1, sizeof(sshbuf3) - 1, sizeof(sshbuf4) - 1};
memset(&tv, 0x00, sizeof(tv));
StreamTcpUTInit(&ra_ctx);
StreamTcpUTInitInline();
StreamTcpUTSetupSession(&ssn);
StreamTcpUTSetupStream(&ssn.server, 1);
StreamTcpUTSetupStream(&ssn.client, 1);
f = UTHBuildFlow(AF_INET, "1.1.1.1", "2.2.2.2", 1234, 2222);
FAIL_IF_NULL(f);
f->protoctx = &ssn;
f->proto = IPPROTO_TCP;
f->alproto = ALPROTO_SSH;
p = UTHBuildPacket(NULL, 0, IPPROTO_TCP);
FAIL_IF(unlikely(p == NULL));
p->flow = f;
FLOW_INITIALIZE(&f);
f.protoctx = (void *)&ssn;
p->flow = &f;
p->flowflags |= FLOW_PKT_TOSERVER;
p->flowflags |= FLOW_PKT_ESTABLISHED;
p->flags |= PKT_HAS_FLOW|PKT_STREAM_EST;
f.alproto = ALPROTO_SSH;
f.proto = IPPROTO_TCP;
Signature *s = NULL;
ThreadVars th_v;
DetectEngineThreadCtx *det_ctx = NULL;
StreamTcpInitConfig(TRUE);
memset(&th_v, 0, sizeof(th_v));
DetectEngineCtx *de_ctx = DetectEngineCtxInit();
if (de_ctx == NULL) {
goto end;
}
FAIL_IF_NULL(de_ctx);
de_ctx->flags |= DE_QUIET;
s = de_ctx->sig_list = SigInit(de_ctx,"alert ssh any any -> any any (msg:\"SSH\"; ssh.protoversion:2_compat; sid:1;)");
if (s == NULL) {
goto end;
}
FAIL_IF_NULL(s);
SigGroupBuild(de_ctx);
DetectEngineThreadCtxInit(&th_v, (void *)de_ctx, (void *)&det_ctx);
FLOWLOCK_WRLOCK(&f);
int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_SSH,
STREAM_TOSERVER, sshbuf1, sshlen1);
if (r != 0) {
printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r);
goto end;
}
r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_SSH, STREAM_TOSERVER,
sshbuf2, sshlen2);
if (r != 0) {
printf("toserver chunk 2 returned %" PRId32 ", expected 0: ", r);
FLOWLOCK_UNLOCK(&f);
goto end;
}
r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_SSH, STREAM_TOSERVER,
sshbuf3, sshlen3);
if (r != 0) {
printf("toserver chunk 3 returned %" PRId32 ", expected 0: ", r);
FLOWLOCK_UNLOCK(&f);
goto end;
uint32_t seq = 2;
for (int i=0; i<4; i++) {
FAIL_IF(StreamTcpUTAddSegmentWithPayload(&tv, ra_ctx, &ssn.server, seq, sshbufs[i], sshlens[i]) == -1);
seq += sshlens[i];
FAIL_IF(StreamTcpReassembleAppLayer(&tv, ra_ctx, &ssn, &ssn.server, p, UPDATE_DIR_PACKET) < 0);
}
r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_SSH, STREAM_TOSERVER,
sshbuf4, sshlen4);
if (r != 0) {
printf("toserver chunk 4 returned %" PRId32 ", expected 0: ", r);
FLOWLOCK_UNLOCK(&f);
goto end;
}
FLOWLOCK_UNLOCK(&f);
SshState *ssh_state = f.alstate;
if (ssh_state == NULL) {
printf("no ssh state: ");
goto end;
}
void *ssh_state = f->alstate;
FAIL_IF_NULL(ssh_state);
/* do detect */
SigMatchSignatures(&th_v, de_ctx, det_ctx, p);
if ( !(PacketAlertCheck(p, 1))) {
printf("Error, the sig should match: ");
goto end;
}
result = 1;
end:
SigGroupCleanup(de_ctx);
SigCleanSignatures(de_ctx);
FAIL_IF(PacketAlertCheck(p, 1));
DetectEngineThreadCtxDeinit(&th_v, (void *)det_ctx);
DetectEngineCtxFree(de_ctx);
StreamTcpFreeConfig(TRUE);
FLOW_DESTROY(&f);
UTHFreePackets(&p, 1);
if (alp_tctx != NULL)
AppLayerParserThreadCtxFree(alp_tctx);
return result;
UTHFreePacket(p);
StreamTcpUTClearSession(&ssn);
StreamTcpUTDeinit(ra_ctx);
UTHFreeFlow(f);
PASS;
}
/** \test Send a get request in three chunks + more data. */
static int DetectSshVersionTestDetect03(void)
{
int result = 0;
Flow f;
TcpReassemblyThreadCtx *ra_ctx = NULL;
ThreadVars tv;
TcpSession ssn;
Flow *f = NULL;
Packet *p = NULL;
uint8_t sshbuf1[] = "SSH-1.";
uint32_t sshlen1 = sizeof(sshbuf1) - 1;
uint8_t sshbuf2[] = "7-PuTTY_2.123" ;
uint32_t sshlen2 = sizeof(sshbuf2) - 1;
uint8_t sshbuf3[] = "\n";
uint32_t sshlen3 = sizeof(sshbuf3) - 1;
uint8_t sshbuf4[] = "whatever...";
uint32_t sshlen4 = sizeof(sshbuf4) - 1;
TcpSession ssn;
Packet *p = NULL;
Signature *s = NULL;
ThreadVars th_v;
DetectEngineThreadCtx *det_ctx = NULL;
AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc();
memset(&th_v, 0, sizeof(th_v));
memset(&f, 0, sizeof(f));
memset(&ssn, 0, sizeof(ssn));
uint8_t* sshbufs[4] = {sshbuf1, sshbuf2, sshbuf3, sshbuf4};
uint32_t sshlens[4] = {sizeof(sshbuf1) - 1, sizeof(sshbuf2) - 1, sizeof(sshbuf3) - 1, sizeof(sshbuf4) - 1};
memset(&tv, 0x00, sizeof(tv));
StreamTcpUTInit(&ra_ctx);
StreamTcpUTInitInline();
StreamTcpUTSetupSession(&ssn);
StreamTcpUTSetupStream(&ssn.server, 1);
StreamTcpUTSetupStream(&ssn.client, 1);
f = UTHBuildFlow(AF_INET, "1.1.1.1", "2.2.2.2", 1234, 2222);
FAIL_IF_NULL(f);
f->protoctx = &ssn;
f->proto = IPPROTO_TCP;
f->alproto = ALPROTO_SSH;
p = UTHBuildPacket(NULL, 0, IPPROTO_TCP);
FAIL_IF(unlikely(p == NULL));
p->flow = f;
FLOW_INITIALIZE(&f);
f.protoctx = (void *)&ssn;
p->flow = &f;
p->flowflags |= FLOW_PKT_TOSERVER;
p->flowflags |= FLOW_PKT_ESTABLISHED;
p->flags |= PKT_HAS_FLOW|PKT_STREAM_EST;
f.alproto = ALPROTO_SSH;
f.proto = IPPROTO_TCP;
Signature *s = NULL;
ThreadVars th_v;
DetectEngineThreadCtx *det_ctx = NULL;
StreamTcpInitConfig(TRUE);
memset(&th_v, 0, sizeof(th_v));
DetectEngineCtx *de_ctx = DetectEngineCtxInit();
if (de_ctx == NULL) {
goto end;
}
FAIL_IF_NULL(de_ctx);
de_ctx->flags |= DE_QUIET;
s = de_ctx->sig_list = SigInit(de_ctx,"alert ssh any any -> any any (msg:\"SSH\"; ssh.protoversion:2_compat; sid:1;)");
if (s == NULL) {
goto end;
}
FAIL_IF_NULL(s);
SigGroupBuild(de_ctx);
DetectEngineThreadCtxInit(&th_v, (void *)de_ctx, (void *)&det_ctx);
FLOWLOCK_WRLOCK(&f);
int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_SSH,
STREAM_TOSERVER, sshbuf1, sshlen1);
if (r != 0) {
printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r);
FLOWLOCK_UNLOCK(&f);
goto end;
}
r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_SSH, STREAM_TOSERVER,
sshbuf2, sshlen2);
if (r != 0) {
printf("toserver chunk 2 returned %" PRId32 ", expected 0: ", r);
FLOWLOCK_UNLOCK(&f);
goto end;
}
r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_SSH, STREAM_TOSERVER,
sshbuf3, sshlen3);
if (r != 0) {
printf("toserver chunk 3 returned %" PRId32 ", expected 0: ", r);
FLOWLOCK_UNLOCK(&f);
goto end;
uint32_t seq = 2;
for (int i=0; i<4; i++) {
FAIL_IF(StreamTcpUTAddSegmentWithPayload(&tv, ra_ctx, &ssn.server, seq, sshbufs[i], sshlens[i]) == -1);
seq += sshlens[i];
FAIL_IF(StreamTcpReassembleAppLayer(&tv, ra_ctx, &ssn, &ssn.server, p, UPDATE_DIR_PACKET) < 0);
}
r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_SSH, STREAM_TOSERVER,
sshbuf4, sshlen4);
if (r != 0) {
printf("toserver chunk 4 returned %" PRId32 ", expected 0: ", r);
FLOWLOCK_UNLOCK(&f);
goto end;
}
FLOWLOCK_UNLOCK(&f);
SshState *ssh_state = f.alstate;
if (ssh_state == NULL) {
printf("no ssh state: ");
goto end;
}
void *ssh_state = f->alstate;
FAIL_IF_NULL(ssh_state);
/* do detect */
SigMatchSignatures(&th_v, de_ctx, det_ctx, p);
if (PacketAlertCheck(p, 1)) {
printf("Error, 1.7 version is not 2 compat, so the sig should not match: ");
goto end;
}
result = 1;
end:
SigGroupCleanup(de_ctx);
SigCleanSignatures(de_ctx);
FAIL_IF(PacketAlertCheck(p, 1));
DetectEngineThreadCtxDeinit(&th_v, (void *)det_ctx);
DetectEngineCtxFree(de_ctx);
StreamTcpFreeConfig(TRUE);
FLOW_DESTROY(&f);
UTHFreePackets(&p, 1);
if (alp_tctx != NULL)
AppLayerParserThreadCtxFree(alp_tctx);
return result;
UTHFreePacket(p);
StreamTcpUTClearSession(&ssn);
StreamTcpUTDeinit(ra_ctx);
UTHFreeFlow(f);
PASS;
}
#endif /* UNITTESTS */

@ -46,6 +46,7 @@
#include "app-layer-parser.h"
#include "app-layer-ssh.h"
#include "detect-ssh-proto.h"
#include "rust.h"
#define KEYWORD_NAME "ssh.proto"
#define KEYWORD_NAME_LEGACY "ssh_proto"
@ -63,27 +64,17 @@ static InspectionBuffer *GetSshData(DetectEngineThreadCtx *det_ctx,
InspectionBuffer *buffer = InspectionBufferGet(det_ctx, list_id);
if (buffer->inspect == NULL) {
uint8_t *protocol = NULL;
SshState *ssh_state = (SshState *) txv;
const uint8_t *protocol = NULL;
uint32_t b_len = 0;
if (flow_flags & STREAM_TOSERVER)
protocol = ssh_state->cli_hdr.proto_version;
else if (flow_flags & STREAM_TOCLIENT)
protocol = ssh_state->srv_hdr.proto_version;
if (protocol == NULL) {
SCLogDebug("SSL protocol not set");
if (rs_ssh_tx_get_protocol(txv, &protocol, &b_len, flow_flags) != 1)
return NULL;
}
uint32_t data_len = strlen((char *)protocol);
uint8_t *data = protocol;
if (data == NULL || data_len == 0) {
SCLogDebug("SSL protocol not present");
if (protocol == NULL || b_len == 0) {
SCLogDebug("SSH protocol not set");
return NULL;
}
InspectionBufferSetup(buffer, data, data_len);
InspectionBufferSetup(buffer, protocol, b_len);
InspectionBufferApplyTransforms(buffer, transforms);
}
@ -113,16 +104,16 @@ void DetectSshProtocolRegister(void)
DetectAppLayerMpmRegister2(BUFFER_NAME, SIG_FLAG_TOSERVER, 2,
PrefilterGenericMpmRegister, GetSshData,
ALPROTO_SSH, SSH_STATE_BANNER_DONE),
ALPROTO_SSH, SshStateBannerDone),
DetectAppLayerMpmRegister2(BUFFER_NAME, SIG_FLAG_TOCLIENT, 2,
PrefilterGenericMpmRegister, GetSshData,
ALPROTO_SSH, SSH_STATE_BANNER_DONE),
ALPROTO_SSH, SshStateBannerDone),
DetectAppLayerInspectEngineRegister2(BUFFER_NAME,
ALPROTO_SSH, SIG_FLAG_TOSERVER, SSH_STATE_BANNER_DONE,
ALPROTO_SSH, SIG_FLAG_TOSERVER, SshStateBannerDone,
DetectEngineInspectBufferGeneric, GetSshData);
DetectAppLayerInspectEngineRegister2(BUFFER_NAME,
ALPROTO_SSH, SIG_FLAG_TOCLIENT, SSH_STATE_BANNER_DONE,
ALPROTO_SSH, SIG_FLAG_TOCLIENT, SshStateBannerDone,
DetectEngineInspectBufferGeneric, GetSshData);
DetectBufferTypeSetDescriptionByName(BUFFER_NAME, BUFFER_DESC);

@ -55,6 +55,7 @@
#include "app-layer-parser.h"
#include "app-layer-ssh.h"
#include "detect-ssh-software-version.h"
#include "rust.h"
#include "stream-tcp.h"
@ -103,10 +104,10 @@ void DetectSshSoftwareVersionRegister(void)
g_ssh_banner_list_id = DetectBufferTypeRegister("ssh_banner");
DetectAppLayerInspectEngineRegister("ssh_banner",
ALPROTO_SSH, SIG_FLAG_TOSERVER, SSH_STATE_BANNER_DONE,
ALPROTO_SSH, SIG_FLAG_TOSERVER, SshStateBannerDone,
InspectSshBanner);
DetectAppLayerInspectEngineRegister("ssh_banner",
ALPROTO_SSH, SIG_FLAG_TOCLIENT, SSH_STATE_BANNER_DONE,
ALPROTO_SSH, SIG_FLAG_TOCLIENT, SshStateBannerDone,
InspectSshBanner);
}
@ -128,20 +129,25 @@ static int DetectSshSoftwareVersionMatch (DetectEngineThreadCtx *det_ctx,
SCEnter();
DetectSshSoftwareVersionData *ssh = (DetectSshSoftwareVersionData *)m;
SshState *ssh_state = (SshState *)state;
if (ssh_state == NULL) {
if (state == NULL) {
SCLogDebug("no ssh state, no match");
SCReturnInt(0);
}
int ret = 0;
if ((flags & STREAM_TOCLIENT) && (ssh_state->srv_hdr.flags & SSH_FLAG_VERSION_PARSED)) {
SCLogDebug("looking for ssh server softwareversion %s length %"PRIu16" on %s", ssh->software_ver, ssh->len, ssh_state->srv_hdr.software_version);
ret = (strncmp((char *) ssh_state->srv_hdr.software_version, (char *) ssh->software_ver, ssh->len) == 0)? 1 : 0;
} else if ((flags & STREAM_TOSERVER) && (ssh_state->cli_hdr.flags & SSH_FLAG_VERSION_PARSED)) {
SCLogDebug("looking for ssh client softwareversion %s length %"PRIu16" on %s", ssh->software_ver, ssh->len, ssh_state->cli_hdr.software_version);
ret = (strncmp((char *) ssh_state->cli_hdr.software_version, (char *) ssh->software_ver, ssh->len) == 0)? 1 : 0;
const uint8_t *software = NULL;
uint32_t b_len = 0;
if (rs_ssh_tx_get_software(txv, &software, &b_len, flags) != 1)
SCReturnInt(0);
if (software == NULL || b_len == 0)
SCReturnInt(0);
if (b_len == ssh->len) {
if (memcmp(software, ssh->software_ver, ssh->len) == 0) {
ret = 1;
}
}
SCReturnInt(ret);
}
@ -312,357 +318,237 @@ static int DetectSshSoftwareVersionTestParse03 (void)
#include "stream-tcp-reassemble.h"
#include "stream-tcp-util.h"
/** \test Send a get request in three chunks + more data. */
static int DetectSshSoftwareVersionTestDetect01(void)
{
int result = 0;
Flow f;
TcpReassemblyThreadCtx *ra_ctx = NULL;
ThreadVars tv;
TcpSession ssn;
Flow *f = NULL;
Packet *p = NULL;
uint8_t sshbuf1[] = "SSH-1.";
uint32_t sshlen1 = sizeof(sshbuf1) - 1;
uint8_t sshbuf2[] = "10-PuTTY_2.123" ;
uint32_t sshlen2 = sizeof(sshbuf2) - 1;
uint8_t sshbuf3[] = "\n";
uint32_t sshlen3 = sizeof(sshbuf3) - 1;
uint8_t sshbuf4[] = "whatever...";
uint32_t sshlen4 = sizeof(sshbuf4) - 1;
TcpSession ssn;
Packet *p = NULL;
Signature *s = NULL;
ThreadVars th_v;
DetectEngineThreadCtx *det_ctx = NULL;
AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc();
memset(&th_v, 0, sizeof(th_v));
memset(&f, 0, sizeof(f));
memset(&ssn, 0, sizeof(ssn));
uint8_t* sshbufs[4] = {sshbuf1, sshbuf2, sshbuf3, sshbuf4};
uint32_t sshlens[4] = {sizeof(sshbuf1) - 1, sizeof(sshbuf2) - 1, sizeof(sshbuf3) - 1, sizeof(sshbuf4) - 1};
memset(&tv, 0x00, sizeof(tv));
StreamTcpUTInit(&ra_ctx);
StreamTcpUTInitInline();
StreamTcpUTSetupSession(&ssn);
StreamTcpUTSetupStream(&ssn.server, 1);
StreamTcpUTSetupStream(&ssn.client, 1);
f = UTHBuildFlow(AF_INET, "1.1.1.1", "2.2.2.2", 1234, 2222);
FAIL_IF_NULL(f);
f->protoctx = &ssn;
f->proto = IPPROTO_TCP;
f->alproto = ALPROTO_SSH;
p = UTHBuildPacket(NULL, 0, IPPROTO_TCP);
FAIL_IF(unlikely(p == NULL));
p->flow = f;
FLOW_INITIALIZE(&f);
f.protoctx = (void *)&ssn;
p->flow = &f;
p->flowflags |= FLOW_PKT_TOSERVER;
p->flowflags |= FLOW_PKT_ESTABLISHED;
p->flags |= PKT_HAS_FLOW|PKT_STREAM_EST;
f.alproto = ALPROTO_SSH;
f.proto = IPPROTO_TCP;
Signature *s = NULL;
ThreadVars th_v;
DetectEngineThreadCtx *det_ctx = NULL;
StreamTcpInitConfig(TRUE);
memset(&th_v, 0, sizeof(th_v));
DetectEngineCtx *de_ctx = DetectEngineCtxInit();
if (de_ctx == NULL) {
goto end;
}
FAIL_IF_NULL(de_ctx);
de_ctx->flags |= DE_QUIET;
s = de_ctx->sig_list = SigInit(de_ctx,"alert ssh any any -> any any (msg:\"SSH\"; ssh.softwareversion:PuTTY_2.123; sid:1;)");
if (s == NULL) {
goto end;
}
FAIL_IF_NULL(s);
SigGroupBuild(de_ctx);
DetectEngineThreadCtxInit(&th_v, (void *)de_ctx, (void *)&det_ctx);
FLOWLOCK_WRLOCK(&f);
int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_SSH,
STREAM_TOSERVER, sshbuf1, sshlen1);
if (r != 0) {
printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r);
FLOWLOCK_UNLOCK(&f);
goto end;
uint32_t seq = 2;
for (int i=0; i<4; i++) {
FAIL_IF(StreamTcpUTAddSegmentWithPayload(&tv, ra_ctx, &ssn.server, seq, sshbufs[i], sshlens[i]) == -1);
seq += sshlens[i];
FAIL_IF(StreamTcpReassembleAppLayer(&tv, ra_ctx, &ssn, &ssn.server, p, UPDATE_DIR_PACKET) < 0);
}
r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_SSH, STREAM_TOSERVER,
sshbuf2, sshlen2);
if (r != 0) {
printf("toserver chunk 2 returned %" PRId32 ", expected 0: ", r);
FLOWLOCK_UNLOCK(&f);
goto end;
}
r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_SSH, STREAM_TOSERVER,
sshbuf3, sshlen3);
if (r != 0) {
printf("toserver chunk 3 returned %" PRId32 ", expected 0: ", r);
FLOWLOCK_UNLOCK(&f);
goto end;
}
r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_SSH, STREAM_TOSERVER,
sshbuf4, sshlen4);
if (r != 0) {
printf("toserver chunk 4 returned %" PRId32 ", expected 0: ", r);
FLOWLOCK_UNLOCK(&f);
goto end;
}
FLOWLOCK_UNLOCK(&f);
SshState *ssh_state = f.alstate;
if (ssh_state == NULL) {
printf("no ssh state: ");
goto end;
}
void *ssh_state = f->alstate;
FAIL_IF_NULL(ssh_state);
/* do detect */
SigMatchSignatures(&th_v, de_ctx, det_ctx, p);
if ( !(PacketAlertCheck(p, 1))) {
printf("Error, the sig should match: ");
goto end;
}
result = 1;
end:
SigGroupCleanup(de_ctx);
SigCleanSignatures(de_ctx);
FAIL_IF(PacketAlertCheck(p, 1));
DetectEngineThreadCtxDeinit(&th_v, (void *)det_ctx);
DetectEngineCtxFree(de_ctx);
StreamTcpFreeConfig(TRUE);
FLOW_DESTROY(&f);
UTHFreePackets(&p, 1);
if (alp_tctx != NULL)
AppLayerParserThreadCtxFree(alp_tctx);
return result;
UTHFreePacket(p);
StreamTcpUTClearSession(&ssn);
StreamTcpUTDeinit(ra_ctx);
UTHFreeFlow(f);
PASS;
}
/** \test Send a get request in three chunks + more data. */
static int DetectSshSoftwareVersionTestDetect02(void)
{
int result = 0;
Flow f;
TcpReassemblyThreadCtx *ra_ctx = NULL;
ThreadVars tv;
TcpSession ssn;
Flow *f = NULL;
Packet *p = NULL;
uint8_t sshbuf1[] = "SSH-1.99-Pu";
uint32_t sshlen1 = sizeof(sshbuf1) - 1;
uint8_t sshbuf2[] = "TTY_2.123" ;
uint32_t sshlen2 = sizeof(sshbuf2) - 1;
uint8_t sshbuf3[] = "\n";
uint32_t sshlen3 = sizeof(sshbuf3) - 1;
uint8_t sshbuf4[] = "whatever...";
uint32_t sshlen4 = sizeof(sshbuf4) - 1;
TcpSession ssn;
Packet *p = NULL;
Signature *s = NULL;
ThreadVars th_v;
DetectEngineThreadCtx *det_ctx = NULL;
AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc();
memset(&th_v, 0, sizeof(th_v));
memset(&f, 0, sizeof(f));
memset(&ssn, 0, sizeof(ssn));
uint8_t* sshbufs[4] = {sshbuf1, sshbuf2, sshbuf3, sshbuf4};
uint32_t sshlens[4] = {sizeof(sshbuf1) - 1, sizeof(sshbuf2) - 1, sizeof(sshbuf3) - 1, sizeof(sshbuf4) - 1};
memset(&tv, 0x00, sizeof(tv));
StreamTcpUTInit(&ra_ctx);
StreamTcpUTInitInline();
StreamTcpUTSetupSession(&ssn);
StreamTcpUTSetupStream(&ssn.server, 1);
StreamTcpUTSetupStream(&ssn.client, 1);
f = UTHBuildFlow(AF_INET, "1.1.1.1", "2.2.2.2", 1234, 2222);
FAIL_IF_NULL(f);
f->protoctx = &ssn;
f->proto = IPPROTO_TCP;
f->alproto = ALPROTO_SSH;
p = UTHBuildPacket(NULL, 0, IPPROTO_TCP);
FAIL_IF(unlikely(p == NULL));
p->flow = f;
FLOW_INITIALIZE(&f);
f.protoctx = (void *)&ssn;
p->flow = &f;
p->flowflags |= FLOW_PKT_TOSERVER;
p->flowflags |= FLOW_PKT_ESTABLISHED;
p->flags |= PKT_HAS_FLOW|PKT_STREAM_EST;
f.alproto = ALPROTO_SSH;
f.proto = IPPROTO_TCP;
Signature *s = NULL;
ThreadVars th_v;
DetectEngineThreadCtx *det_ctx = NULL;
StreamTcpInitConfig(TRUE);
memset(&th_v, 0, sizeof(th_v));
DetectEngineCtx *de_ctx = DetectEngineCtxInit();
if (de_ctx == NULL) {
goto end;
}
FAIL_IF_NULL(de_ctx);
de_ctx->flags |= DE_QUIET;
s = de_ctx->sig_list = SigInit(de_ctx,"alert ssh any any -> any any (msg:\"SSH\"; ssh.softwareversion:PuTTY_2.123; sid:1;)");
if (s == NULL) {
goto end;
}
FAIL_IF_NULL(s);
SigGroupBuild(de_ctx);
DetectEngineThreadCtxInit(&th_v, (void *)de_ctx, (void *)&det_ctx);
FLOWLOCK_WRLOCK(&f);
int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_SSH,
STREAM_TOSERVER, sshbuf1, sshlen1);
if (r != 0) {
printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r);
FLOWLOCK_UNLOCK(&f);
goto end;
}
r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_SSH, STREAM_TOSERVER,
sshbuf2, sshlen2);
if (r != 0) {
printf("toserver chunk 2 returned %" PRId32 ", expected 0: ", r);
FLOWLOCK_UNLOCK(&f);
goto end;
uint32_t seq = 2;
for (int i=0; i<4; i++) {
FAIL_IF(StreamTcpUTAddSegmentWithPayload(&tv, ra_ctx, &ssn.server, seq, sshbufs[i], sshlens[i]) == -1);
seq += sshlens[i];
FAIL_IF(StreamTcpReassembleAppLayer(&tv, ra_ctx, &ssn, &ssn.server, p, UPDATE_DIR_PACKET) < 0);
}
r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_SSH, STREAM_TOSERVER,
sshbuf3, sshlen3);
if (r != 0) {
printf("toserver chunk 3 returned %" PRId32 ", expected 0: ", r);
FLOWLOCK_UNLOCK(&f);
goto end;
}
r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_SSH, STREAM_TOSERVER,
sshbuf4, sshlen4);
if (r != 0) {
printf("toserver chunk 4 returned %" PRId32 ", expected 0: ", r);
FLOWLOCK_UNLOCK(&f);
goto end;
}
FLOWLOCK_UNLOCK(&f);
SshState *ssh_state = f.alstate;
if (ssh_state == NULL) {
printf("no ssh state: ");
goto end;
}
void *ssh_state = f->alstate;
FAIL_IF_NULL(ssh_state);
/* do detect */
SigMatchSignatures(&th_v, de_ctx, det_ctx, p);
if ( !(PacketAlertCheck(p, 1))) {
printf("Error, the sig should match: ");
goto end;
}
result = 1;
end:
SigGroupCleanup(de_ctx);
SigCleanSignatures(de_ctx);
FAIL_IF(PacketAlertCheck(p, 1));
DetectEngineThreadCtxDeinit(&th_v, (void *)det_ctx);
DetectEngineCtxFree(de_ctx);
StreamTcpFreeConfig(TRUE);
FLOW_DESTROY(&f);
UTHFreePackets(&p, 1);
if (alp_tctx != NULL)
AppLayerParserThreadCtxFree(alp_tctx);
return result;
UTHFreePacket(p);
StreamTcpUTClearSession(&ssn);
StreamTcpUTDeinit(ra_ctx);
UTHFreeFlow(f);
PASS;
}
/** \test Send a get request in three chunks + more data. */
static int DetectSshSoftwareVersionTestDetect03(void)
{
int result = 0;
Flow f;
TcpReassemblyThreadCtx *ra_ctx = NULL;
ThreadVars tv;
TcpSession ssn;
Flow *f = NULL;
Packet *p = NULL;
uint8_t sshbuf1[] = "SSH-1.";
uint32_t sshlen1 = sizeof(sshbuf1) - 1;
uint8_t sshbuf2[] = "7-PuTTY_2.123" ;
uint32_t sshlen2 = sizeof(sshbuf2) - 1;
uint8_t sshbuf3[] = "\n";
uint32_t sshlen3 = sizeof(sshbuf3) - 1;
uint8_t sshbuf4[] = "whatever...";
uint32_t sshlen4 = sizeof(sshbuf4) - 1;
TcpSession ssn;
Packet *p = NULL;
Signature *s = NULL;
ThreadVars th_v;
DetectEngineThreadCtx *det_ctx = NULL;
AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc();
memset(&th_v, 0, sizeof(th_v));
memset(&f, 0, sizeof(f));
memset(&ssn, 0, sizeof(ssn));
uint8_t* sshbufs[4] = {sshbuf1, sshbuf2, sshbuf3, sshbuf4};
uint32_t sshlens[4] = {sizeof(sshbuf1) - 1, sizeof(sshbuf2) - 1, sizeof(sshbuf3) - 1, sizeof(sshbuf4) - 1};
memset(&tv, 0x00, sizeof(tv));
StreamTcpUTInit(&ra_ctx);
StreamTcpUTInitInline();
StreamTcpUTSetupSession(&ssn);
StreamTcpUTSetupStream(&ssn.server, 1);
StreamTcpUTSetupStream(&ssn.client, 1);
f = UTHBuildFlow(AF_INET, "1.1.1.1", "2.2.2.2", 1234, 2222);
FAIL_IF_NULL(f);
f->protoctx = &ssn;
f->proto = IPPROTO_TCP;
f->alproto = ALPROTO_SSH;
p = UTHBuildPacket(NULL, 0, IPPROTO_TCP);
FAIL_IF(unlikely(p == NULL));
p->flow = f;
FLOW_INITIALIZE(&f);
f.protoctx = (void *)&ssn;
p->flow = &f;
p->flowflags |= FLOW_PKT_TOSERVER;
p->flowflags |= FLOW_PKT_ESTABLISHED;
p->flags |= PKT_HAS_FLOW|PKT_STREAM_EST;
f.alproto = ALPROTO_SSH;
f.proto = IPPROTO_TCP;
Signature *s = NULL;
ThreadVars th_v;
DetectEngineThreadCtx *det_ctx = NULL;
StreamTcpInitConfig(TRUE);
memset(&th_v, 0, sizeof(th_v));
DetectEngineCtx *de_ctx = DetectEngineCtxInit();
if (de_ctx == NULL) {
goto end;
}
FAIL_IF_NULL(de_ctx);
de_ctx->flags |= DE_QUIET;
s = de_ctx->sig_list = SigInit(de_ctx,"alert ssh any any -> any any (msg:\"SSH\"; ssh.softwareversion:lalala-3.1.4; sid:1;)");
if (s == NULL) {
goto end;
}
FAIL_IF_NULL(s);
SigGroupBuild(de_ctx);
DetectEngineThreadCtxInit(&th_v, (void *)de_ctx, (void *)&det_ctx);
FLOWLOCK_WRLOCK(&f);
int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_SSH,
STREAM_TOSERVER, sshbuf1, sshlen1);
if (r != 0) {
printf("toserver chunk 1 returned %" PRId32 ", expected 0: ", r);
FLOWLOCK_UNLOCK(&f);
goto end;
}
r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_SSH, STREAM_TOSERVER,
sshbuf2, sshlen2);
if (r != 0) {
printf("toserver chunk 2 returned %" PRId32 ", expected 0: ", r);
FLOWLOCK_UNLOCK(&f);
goto end;
uint32_t seq = 2;
for (int i=0; i<4; i++) {
FAIL_IF(StreamTcpUTAddSegmentWithPayload(&tv, ra_ctx, &ssn.server, seq, sshbufs[i], sshlens[i]) == -1);
seq += sshlens[i];
FAIL_IF(StreamTcpReassembleAppLayer(&tv, ra_ctx, &ssn, &ssn.server, p, UPDATE_DIR_PACKET) < 0);
}
r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_SSH, STREAM_TOSERVER,
sshbuf3, sshlen3);
if (r != 0) {
printf("toserver chunk 3 returned %" PRId32 ", expected 0: ", r);
FLOWLOCK_UNLOCK(&f);
goto end;
}
r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_SSH, STREAM_TOSERVER,
sshbuf4, sshlen4);
if (r != 0) {
printf("toserver chunk 4 returned %" PRId32 ", expected 0: ", r);
FLOWLOCK_UNLOCK(&f);
goto end;
}
FLOWLOCK_UNLOCK(&f);
SshState *ssh_state = f.alstate;
if (ssh_state == NULL) {
printf("no ssh state: ");
goto end;
}
void *ssh_state = f->alstate;
FAIL_IF_NULL(ssh_state);
/* do detect */
SigMatchSignatures(&th_v, de_ctx, det_ctx, p);
if (PacketAlertCheck(p, 1)) {
printf("Error, 1.7 version is not 2 compat, so the sig should not match: ");
goto end;
}
result = 1;
end:
SigGroupCleanup(de_ctx);
SigCleanSignatures(de_ctx);
FAIL_IF(PacketAlertCheck(p, 1));
DetectEngineThreadCtxDeinit(&th_v, (void *)det_ctx);
DetectEngineCtxFree(de_ctx);
StreamTcpFreeConfig(TRUE);
FLOW_DESTROY(&f);
UTHFreePackets(&p, 1);
if (alp_tctx != NULL)
AppLayerParserThreadCtxFree(alp_tctx);
return result;
UTHFreePacket(p);
StreamTcpUTClearSession(&ssn);
StreamTcpUTDeinit(ra_ctx);
UTHFreeFlow(f);
PASS;
}
#endif /* UNITTESTS */

@ -46,6 +46,7 @@
#include "app-layer-parser.h"
#include "app-layer-ssh.h"
#include "detect-ssh-software.h"
#include "rust.h"
#define KEYWORD_NAME "ssh.software"
#define KEYWORD_NAME_LEGACY "ssh_software"
@ -63,27 +64,17 @@ static InspectionBuffer *GetSshData(DetectEngineThreadCtx *det_ctx,
InspectionBuffer *buffer = InspectionBufferGet(det_ctx, list_id);
if (buffer->inspect == NULL) {
uint8_t *software = NULL;
SshState *ssh_state = (SshState *) txv;
const uint8_t *software = NULL;
uint32_t b_len = 0;
if (flow_flags & STREAM_TOSERVER)
software = ssh_state->cli_hdr.software_version;
else if (flow_flags & STREAM_TOCLIENT)
software = ssh_state->srv_hdr.software_version;
if (software == NULL) {
SCLogDebug("SSL software version not set");
if (rs_ssh_tx_get_software(txv, &software, &b_len, flow_flags) != 1)
return NULL;
}
uint32_t data_len = strlen((char *)software);
uint8_t *data = software;
if (data == NULL || data_len == 0) {
SCLogDebug("SSL software version not present");
if (software == NULL || b_len == 0) {
SCLogDebug("SSH software version not set");
return NULL;
}
InspectionBufferSetup(buffer, data, data_len);
InspectionBufferSetup(buffer, software, b_len);
InspectionBufferApplyTransforms(buffer, transforms);
}
@ -113,16 +104,16 @@ void DetectSshSoftwareRegister(void)
DetectAppLayerMpmRegister2(BUFFER_NAME, SIG_FLAG_TOSERVER, 2,
PrefilterGenericMpmRegister, GetSshData,
ALPROTO_SSH, SSH_STATE_BANNER_DONE),
ALPROTO_SSH, SshStateBannerDone),
DetectAppLayerMpmRegister2(BUFFER_NAME, SIG_FLAG_TOCLIENT, 2,
PrefilterGenericMpmRegister, GetSshData,
ALPROTO_SSH, SSH_STATE_BANNER_DONE),
ALPROTO_SSH, SshStateBannerDone),
DetectAppLayerInspectEngineRegister2(BUFFER_NAME,
ALPROTO_SSH, SIG_FLAG_TOSERVER, SSH_STATE_BANNER_DONE,
ALPROTO_SSH, SIG_FLAG_TOSERVER, SshStateBannerDone,
DetectEngineInspectBufferGeneric, GetSshData);
DetectAppLayerInspectEngineRegister2(BUFFER_NAME,
ALPROTO_SSH, SIG_FLAG_TOCLIENT, SSH_STATE_BANNER_DONE,
ALPROTO_SSH, SIG_FLAG_TOCLIENT, SshStateBannerDone,
DetectEngineInspectBufferGeneric, GetSshData);
DetectBufferTypeSetDescriptionByName(BUFFER_NAME, BUFFER_DESC);

@ -61,6 +61,7 @@
#include "output-json-http.h"
#include "output-json-tls.h"
#include "output-json-ssh.h"
#include "rust.h"
#include "output-json-smtp.h"
#include "output-json-email-common.h"
#include "output-json-nfs.h"
@ -145,14 +146,13 @@ static void AlertJsonTls(const Flow *f, json_t *js)
static void AlertJsonSsh(const Flow *f, json_t *js)
{
SshState *ssh_state = (SshState *)FlowGetAppState(f);
void *ssh_state = FlowGetAppState(f);
if (ssh_state) {
json_t *tjs = json_object();
void *tx_ptr = rs_ssh_state_get_tx(ssh_state, 0);
json_t *tjs = rs_ssh_log_json(tx_ptr);
if (unlikely(tjs == NULL))
return;
JsonSshLogJSON(tjs, ssh_state);
json_object_set_new(js, "ssh", tjs);
}

@ -49,6 +49,7 @@
#include "output-json.h"
#include "output-json-ssh.h"
#include "rust.h"
#define MODULE_NAME "LogSshLog"
@ -64,62 +65,30 @@ typedef struct JsonSshLogThread_ {
} JsonSshLogThread;
void JsonSshLogJSON(json_t *tjs, SshState *ssh_state)
{
json_t *cjs = json_object();
if (cjs != NULL) {
json_object_set_new(cjs, "proto_version",
SCJsonString((char *)ssh_state->cli_hdr.proto_version));
json_object_set_new(cjs, "software_version",
SCJsonString((char *)ssh_state->cli_hdr.software_version));
}
json_object_set_new(tjs, "client", cjs);
json_t *sjs = json_object();
if (sjs != NULL) {
json_object_set_new(sjs, "proto_version",
SCJsonString((char *)ssh_state->srv_hdr.proto_version));
json_object_set_new(sjs, "software_version",
SCJsonString((char *)ssh_state->srv_hdr.software_version));
}
json_object_set_new(tjs, "server", sjs);
}
static int JsonSshLogger(ThreadVars *tv, void *thread_data, const Packet *p,
Flow *f, void *state, void *txptr, uint64_t tx_id)
{
JsonSshLogThread *aft = (JsonSshLogThread *)thread_data;
OutputSshCtx *ssh_ctx = aft->sshlog_ctx;
SshState *ssh_state = (SshState *)state;
if (unlikely(ssh_state == NULL)) {
if (unlikely(state == NULL)) {
return 0;
}
if (ssh_state->cli_hdr.software_version == NULL ||
ssh_state->srv_hdr.software_version == NULL)
return 0;
json_t *js = CreateJSONHeader(p, LOG_DIR_FLOW, "ssh");
if (unlikely(js == NULL))
return 0;
JsonAddCommonOptions(&ssh_ctx->cfg, p, f, js);
json_t *tjs = json_object();
if (tjs == NULL) {
free(js);
return 0;
}
/* reset */
MemBufferReset(aft->buffer);
JsonSshLogJSON(tjs, ssh_state);
json_t *tjs = rs_ssh_log_json(txptr);
if (unlikely(tjs == NULL)) {
free(js);
return 0;
}
json_object_set_new(js, "ssh", tjs);
OutputJSONBuffer(js, ssh_ctx->file_ctx, &aft->buffer);
@ -261,13 +230,13 @@ void JsonSshLogRegister (void)
OutputRegisterTxModuleWithProgress(LOGGER_JSON_SSH,
"JsonSshLog", "ssh-json-log",
OutputSshLogInit, ALPROTO_SSH, JsonSshLogger,
SSH_STATE_BANNER_DONE, SSH_STATE_BANNER_DONE,
SshStateBannerDone, SshStateBannerDone,
JsonSshLogThreadInit, JsonSshLogThreadDeinit, NULL);
/* also register as child of eve-log */
OutputRegisterTxSubModuleWithProgress(LOGGER_JSON_SSH,
"eve-log", "JsonSshLog", "eve-log.ssh",
OutputSshLogInitSub, ALPROTO_SSH, JsonSshLogger,
SSH_STATE_BANNER_DONE, SSH_STATE_BANNER_DONE,
SshStateBannerDone, SshStateBannerDone,
JsonSshLogThreadInit, JsonSshLogThreadDeinit, NULL);
}

@ -26,8 +26,4 @@
void JsonSshLogRegister(void);
#include "app-layer-ssh.h"
void JsonSshLogJSON(json_t *js, SshState *tx);
#endif /* __OUTPUT_JSON_SSH_H__ */

@ -823,8 +823,8 @@ static OutputInitResult OutputLuaLogInit(ConfNode *conf)
} else if (opts.alproto == ALPROTO_SSH) {
om->TxLogFunc = LuaTxLogger;
om->alproto = ALPROTO_SSH;
om->tc_log_progress = SSH_STATE_BANNER_DONE;
om->ts_log_progress = SSH_STATE_BANNER_DONE;
om->tc_log_progress = SshStateBannerDone;
om->ts_log_progress = SshStateBannerDone;
AppLayerParserRegisterLogger(IPPROTO_TCP, ALPROTO_SSH);
} else if (opts.alproto == ALPROTO_SMTP) {
om->TxLogFunc = LuaTxLogger;

@ -47,6 +47,7 @@
#include "util-proto-name.h"
#include "util-logopenfile.h"
#include "util-time.h"
#include "rust.h"
#ifdef HAVE_LUA
@ -63,14 +64,17 @@ static int GetServerProtoVersion(lua_State *luastate, const Flow *f)
void *state = FlowGetAppState(f);
if (state == NULL)
return LuaCallbackError(luastate, "error: no app layer state");
const uint8_t *protocol = NULL;
uint32_t b_len = 0;
SshState *ssh_state = (SshState *)state;
if (ssh_state->srv_hdr.proto_version == NULL)
void *tx = rs_ssh_state_get_tx(state, 0);
if (rs_ssh_tx_get_protocol(tx, &protocol, &b_len, STREAM_TOCLIENT) != 1)
return LuaCallbackError(luastate, "error: no server proto version");
if (protocol == NULL || b_len == 0) {
return LuaCallbackError(luastate, "error: no server proto version");
}
return LuaPushStringBuffer(luastate, ssh_state->srv_hdr.proto_version,
strlen((char *)ssh_state->srv_hdr.proto_version));
return LuaPushStringBuffer(luastate, protocol, b_len);
}
static int SshGetServerProtoVersion(lua_State *luastate)
@ -95,13 +99,17 @@ static int GetServerSoftwareVersion(lua_State *luastate, const Flow *f)
if (state == NULL)
return LuaCallbackError(luastate, "error: no app layer state");
SshState *ssh_state = (SshState *)state;
const uint8_t *software = NULL;
uint32_t b_len = 0;
if (ssh_state->srv_hdr.software_version == NULL)
void *tx = rs_ssh_state_get_tx(state, 0);
if (rs_ssh_tx_get_software(tx, &software, &b_len, STREAM_TOCLIENT) != 1)
return LuaCallbackError(luastate, "error: no server software version");
if (software == NULL || b_len == 0) {
return LuaCallbackError(luastate, "error: no server software version");
}
return LuaPushStringBuffer(luastate, ssh_state->srv_hdr.software_version,
strlen((char *)ssh_state->srv_hdr.software_version));
return LuaPushStringBuffer(luastate, software, b_len);
}
static int SshGetServerSoftwareVersion(lua_State *luastate)
@ -126,13 +134,17 @@ static int GetClientProtoVersion(lua_State *luastate, const Flow *f)
if (state == NULL)
return LuaCallbackError(luastate, "error: no app layer state");
SshState *ssh_state = (SshState *)state;
const uint8_t *protocol = NULL;
uint32_t b_len = 0;
if (ssh_state->cli_hdr.proto_version == NULL)
void *tx = rs_ssh_state_get_tx(state, 0);
if (rs_ssh_tx_get_protocol(tx, &protocol, &b_len, STREAM_TOSERVER) != 1)
return LuaCallbackError(luastate, "error: no client proto version");
if (protocol == NULL || b_len == 0) {
return LuaCallbackError(luastate, "error: no client proto version");
}
return LuaPushStringBuffer(luastate, ssh_state->cli_hdr.proto_version,
strlen((char *)ssh_state->cli_hdr.proto_version));
return LuaPushStringBuffer(luastate, protocol, b_len);
}
static int SshGetClientProtoVersion(lua_State *luastate)
@ -157,13 +169,17 @@ static int GetClientSoftwareVersion(lua_State *luastate, const Flow *f)
if (state == NULL)
return LuaCallbackError(luastate, "error: no app layer state");
SshState *ssh_state = (SshState *)state;
const uint8_t *software = NULL;
uint32_t b_len = 0;
if (ssh_state->cli_hdr.software_version == NULL)
void *tx = rs_ssh_state_get_tx(state, 0);
if (rs_ssh_tx_get_software(tx, &software, &b_len, STREAM_TOSERVER) != 1)
return LuaCallbackError(luastate, "error: no client software version");
if (software == NULL || b_len == 0) {
return LuaCallbackError(luastate, "error: no client software version");
}
return LuaPushStringBuffer(luastate, ssh_state->cli_hdr.software_version,
strlen((char *)ssh_state->cli_hdr.software_version));
return LuaPushStringBuffer(luastate, software, b_len);
}
static int SshGetClientSoftwareVersion(lua_State *luastate)

Loading…
Cancel
Save