diff --git a/doc/userguide/output/eve/eve-json-format.rst b/doc/userguide/output/eve/eve-json-format.rst index 615662f974..5f950fc883 100644 --- a/doc/userguide/output/eve/eve-json-format.rst +++ b/doc/userguide/output/eve/eve-json-format.rst @@ -1226,3 +1226,64 @@ RDP logging, with transition to TLS: "16ed2aa0495f259d4f5d99edada570d1" ] } + +Event type: RFB +--------------- + +Fields +~~~~~~ + +* "server_protocol_version.major", "server_protocol_version.minor": The RFB protocol version offered by the server. +* "client_protocol_version.major", "client_protocol_version.minor": The RFB protocol version agreed by the client. +* "authentication.security_type": Security type agreed upon in the logged transaction, e.g. ``2`` is VNC auth. +* "authentication.vnc.challenge", "authentication.vnc.response": Only available when security type 2 is used. Contains the challenge and response byte buffers exchanged by the server and client as hex strings. +* "authentication.security-result": Result of the authentication process (``OK``, ``FAIL`` or ``TOOMANY``). +* "screen_shared": Boolean value describing whether the client requested screen sharing. +* "framebuffer": Contains metadata about the initial screen setup process. Only available when the handshake completed this far. +* "framebuffer.width", "framebuffer.height": Screen size as offered by the server. +* "framebuffer.name": Desktop name as advertised by the server. +* "framebuffer.pixel_format": Pixel representation information, such as color depth. See RFC6143 (https://tools.ietf.org/html/rfc6143) for details. + + +Examples +~~~~~~~~ + +Example of RFB logging, with full VNC style authentication parameters: + +:: + + "rfb": { + "server_protocol_version": { + "major": "003", + "minor": "007" + }, + "client_protocol_version": { + "major": "003", + "minor": "007" + }, + "authentication": { + "security_type": 2, + "vnc": { + "challenge": "0805b790b58e967f2b350a0c99de3881", + "response": "aecb26faeaaa62179636a5934bac1078" + }, + "security-result": "OK" + }, + "screen_shared": false, + "framebuffer": { + "width": 1280, + "height": 800, + "name": "foobar@localhost.localdomain", + "pixel_format": { + "bits_per_pixel": 32, + "depth": 24, + "big_endian": false, + "true_color": true, + "red_max": 255, + "green_max": 255, + "blue_max": 255, + "red_shift": 16, + "green_shift": 8, + "blue_shift": 0 + } + } diff --git a/doc/userguide/rules/index.rst b/doc/userguide/rules/index.rst index 637606440e..6525dda341 100644 --- a/doc/userguide/rules/index.rst +++ b/doc/userguide/rules/index.rst @@ -25,6 +25,7 @@ Suricata Rules snmp-keywords base64-keywords sip-keywords + rfb-keywords app-layer xbits thresholding diff --git a/doc/userguide/rules/rfb-keywords.rst b/doc/userguide/rules/rfb-keywords.rst new file mode 100644 index 0000000000..628b3d85c5 --- /dev/null +++ b/doc/userguide/rules/rfb-keywords.rst @@ -0,0 +1,56 @@ +RFB Keywords +============ + +The ``rfb.name`` and ``rfb.sectype`` keywords can be used for matching on various properties of +RFB (Remote Framebuffer, i.e. VNC) handshakes. + + +rfb.name +-------- + +Match on the value of the RFB desktop name field. + +Examples:: + + rfb.name; content:"Alice's desktop"; + rfb.name; pcre:"/.* \(screen [0-9]\)$/"; + +``rfb.name`` is a 'sticky buffer'. + +``rfb.name`` can be used as ``fast_pattern``. + + +rfb.secresult +------------- + +Match on the value of the RFB security result, e.g. ``ok``, ``fail``, ``toomany`` or ``unknown``. + +Examples:: + + rfb.secresult: ok; + rfb.secresult: unknown; + + +rfb.sectype +----------- + +Match on the value of the RFB security type field, e.g. ``2`` for VNC challenge-response authentication, ``0`` for no authentication, and ``30`` for Apple's custom Remote Desktop authentication. + +This keyword takes a numeric argument after a colon and supports additional qualifiers, such as: + +* ``>`` (greater than) +* ``<`` (less than) +* ``>=`` (greater than or equal) +* ``<=`` (less than or equal) + +Examples:: + + rfb.sectype:2; + rfb.sectype:>=3; + + +Additional information +---------------------- + +More information on the protocol can be found here: +``_ diff --git a/rust/src/lib.rs b/rust/src/lib.rs index a9b6c756ce..dddb9ec143 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -66,5 +66,6 @@ pub mod ntp; pub mod tftp; pub mod dhcp; pub mod sip; +pub mod rfb; pub mod applayertemplate; pub mod rdp; diff --git a/rust/src/rfb/detect.rs b/rust/src/rfb/detect.rs new file mode 100644 index 0000000000..d30014a139 --- /dev/null +++ b/rust/src/rfb/detect.rs @@ -0,0 +1,70 @@ +/* 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. + */ + +// Author: Frank Honza + +use crate::rfb::rfb::*; +use std::ptr; + +#[no_mangle] +pub unsafe extern "C" fn rs_rfb_tx_get_name( + tx: &mut RFBTransaction, + buffer: *mut *const u8, + buffer_len: *mut u32, +) -> u8 { + if let Some(ref r) = tx.tc_server_init { + let p = &r.name; + if p.len() > 0 { + *buffer = p.as_ptr(); + *buffer_len = p.len() as u32; + return 1; + } + } + + *buffer = ptr::null(); + *buffer_len = 0; + + return 0; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_rfb_tx_get_sectype( + tx: &mut RFBTransaction, + sectype: *mut u32, +) -> u8 { + if let Some(ref r) = tx.chosen_security_type { + *sectype = *r; + return 1; + } + + *sectype = 0; + + return 0; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_rfb_tx_get_secresult( + tx: &mut RFBTransaction, + secresult: *mut u32, +) -> u8 { + if let Some(ref r) = tx.tc_security_result { + *secresult = r.status; + return 1; + } + + return 0; +} \ No newline at end of file diff --git a/rust/src/rfb/logger.rs b/rust/src/rfb/logger.rs new file mode 100644 index 0000000000..ef9cef26c9 --- /dev/null +++ b/rust/src/rfb/logger.rs @@ -0,0 +1,120 @@ +/* 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. + */ + +// Author: Frank Honza + +use std; +use std::fmt::Write; +use crate::json::*; +use super::rfb::{RFBState, RFBTransaction}; + +fn log_rfb(tx: &RFBTransaction) -> Option { + let js = Json::object(); + + // Protocol version + if let Some(tx_spv) = &tx.tc_server_protocol_version { + let spv = Json::object(); + spv.set_string("major", &tx_spv.major); + spv.set_string("minor", &tx_spv.minor); + js.set("server_protocol_version", spv); + } + if let Some(tx_cpv) = &tx.ts_client_protocol_version { + let cpv = Json::object(); + cpv.set_string("major", &tx_cpv.major); + cpv.set_string("minor", &tx_cpv.minor); + js.set("client_protocol_version", cpv); + } + + // Authentication + let auth = Json::object(); + if let Some(chosen_security_type) = tx.chosen_security_type { + auth.set_integer("security_type", chosen_security_type as u64); + } + match tx.chosen_security_type { + Some(2) => { + let vncauth = Json::object(); + if let Some(ref sc) = tx.tc_vnc_challenge { + let mut s = String::new(); + for &byte in &sc.secret[..] { + write!(&mut s, "{:02x}", byte).expect("Unable to write"); + } + vncauth.set_string("challenge", &s); + } + if let Some(ref sr) = tx.ts_vnc_response { + let mut s = String::new(); + for &byte in &sr.secret[..] { + write!(&mut s, "{:02x}", byte).expect("Unable to write"); + } + vncauth.set_string("response", &s); + } + auth.set("vnc", vncauth); + } + _ => () + } + if let Some(security_result) = &tx.tc_security_result { + match security_result.status { + 0 => auth.set_string("security_result", "OK"), + 1 => auth.set_string("security-result", "FAIL"), + 2 => auth.set_string("security_result", "TOOMANY"), + _ => auth.set_string("security_result", + &format!("UNKNOWN ({})", security_result.status)), + } + } + js.set("authentication", auth); + + if let Some(ref reason) = tx.tc_failure_reason { + js.set_string("server_security_failure_reason", &reason.reason_string); + } + + // Client/Server init + if let Some(s) = &tx.ts_client_init { + js.set_boolean("screen_shared", s.shared != 0); + } + if let Some(tc_server_init) = &tx.tc_server_init { + let fb = Json::object(); + fb.set_integer("width", tc_server_init.width as u64); + fb.set_integer("height", tc_server_init.height as u64); + fb.set_string_from_bytes("name", &tc_server_init.name); + + let pfj = Json::object(); + pfj.set_integer("bits_per_pixel", tc_server_init.pixel_format.bits_per_pixel as u64); + pfj.set_integer("depth", tc_server_init.pixel_format.depth as u64); + pfj.set_boolean("big_endian", tc_server_init.pixel_format.big_endian_flag != 0); + pfj.set_boolean("true_color", tc_server_init.pixel_format.true_colour_flag != 0); + pfj.set_integer("red_max", tc_server_init.pixel_format.red_max as u64); + pfj.set_integer("green_max", tc_server_init.pixel_format.green_max as u64); + pfj.set_integer("blue_max", tc_server_init.pixel_format.blue_max as u64); + pfj.set_integer("red_shift", tc_server_init.pixel_format.red_shift as u64); + pfj.set_integer("green_shift", tc_server_init.pixel_format.green_shift as u64); + pfj.set_integer("blue_shift", tc_server_init.pixel_format.blue_shift as u64); + pfj.set_integer("depth", tc_server_init.pixel_format.depth as u64); + fb.set("pixel_format", pfj); + + js.set("framebuffer", fb); + } + + return Some(js); +} + +#[no_mangle] +pub extern "C" fn rs_rfb_logger_log(_state: &mut RFBState, tx: *mut std::os::raw::c_void) -> *mut JsonT { + let tx = cast_pointer!(tx, RFBTransaction); + match log_rfb(tx) { + Some(js) => js.unwrap(), + None => std::ptr::null_mut(), + } +} diff --git a/rust/src/rfb/mod.rs b/rust/src/rfb/mod.rs new file mode 100644 index 0000000000..4bde7935cb --- /dev/null +++ b/rust/src/rfb/mod.rs @@ -0,0 +1,23 @@ +/* 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. + */ + +// Author: Frank Honza + +pub mod detect; +pub mod logger; +pub mod parser; +pub mod rfb; \ No newline at end of file diff --git a/rust/src/rfb/parser.rs b/rust/src/rfb/parser.rs new file mode 100644 index 0000000000..8aed9c7f81 --- /dev/null +++ b/rust/src/rfb/parser.rs @@ -0,0 +1,321 @@ +/* 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. + */ + +// Author: Frank Honza + +use std::fmt; +use nom::*; +use nom::number::streaming::*; + +pub enum RFBGlobalState { + TCServerProtocolVersion, + TCSupportedSecurityTypes, + TCVncChallenge, + TCServerInit, + TCFailureReason, + TSClientProtocolVersion, + TCServerSecurityType, + TSSecurityTypeSelection, + TSVncResponse, + TCSecurityResult, + TSClientInit, + Message +} + +impl fmt::Display for RFBGlobalState { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + RFBGlobalState::TCServerProtocolVersion => write!(f, "TCServerProtocolVersion"), + RFBGlobalState::TCSupportedSecurityTypes => write!(f, "TCSupportedSecurityTypes"), + RFBGlobalState::TCVncChallenge => write!(f, "TCVncChallenge"), + RFBGlobalState::TCServerInit => write!(f, "TCServerInit"), + RFBGlobalState::TCFailureReason => write!(f, "TCFailureReason"), + RFBGlobalState::TSClientProtocolVersion => write!(f, "TSClientProtocolVersion"), + RFBGlobalState::TSSecurityTypeSelection => write!(f, "TSSecurityTypeSelection"), + RFBGlobalState::TSVncResponse => write!(f, "TSVncResponse"), + RFBGlobalState::TCSecurityResult => write!(f, "TCSecurityResult"), + RFBGlobalState::TCServerSecurityType => write!(f, "TCServerSecurityType"), + RFBGlobalState::TSClientInit => write!(f, "TSClientInit"), + RFBGlobalState::Message => write!(f, "Message") + } + } +} + +pub struct ProtocolVersion { + pub major: String, + pub minor: String +} + +pub struct SupportedSecurityTypes { + pub number_of_types: u8, + pub types: Vec +} + +pub struct SecurityTypeSelection { + pub security_type: u8 +} + +pub struct ServerSecurityType { + pub security_type: u32 +} + +pub struct SecurityResult { + pub status: u32 +} + +pub struct FailureReason { + pub reason_string: String +} + +pub struct VncAuth { + pub secret: Vec +} + +pub struct ClientInit { + pub shared: u8 +} + +pub struct PixelFormat { + pub bits_per_pixel: u8, + pub depth: u8, + pub big_endian_flag: u8, + pub true_colour_flag: u8, + pub red_max: u16, + pub green_max: u16, + pub blue_max: u16, + pub red_shift: u8, + pub green_shift: u8, + pub blue_shift: u8, +} + +pub struct ServerInit { + pub width: u16, + pub height: u16, + pub pixel_format: PixelFormat, + pub name_length: u32, + pub name: Vec +} + +named!(pub parse_protocol_version, + do_parse!( + _rfb_string: take_str!(3) + >> be_u8 + >> major: take_str!(3) + >> be_u8 + >> minor: take_str!(3) + >> be_u8 + >> ( + ProtocolVersion{ + major: major.to_string(), + minor: minor.to_string(), + } + ) + ) +); + +named!(pub parse_supported_security_types, + do_parse!( + number_of_types: be_u8 + >> types: take!(number_of_types) + >> ( + SupportedSecurityTypes{ + number_of_types: number_of_types, + types: types.to_vec() + } + ) + ) +); + +named!(pub parse_server_security_type, + do_parse!( + security_type: be_u32 + >> ( + ServerSecurityType{ + security_type: security_type, + } + ) + ) +); + +named!(pub parse_vnc_auth, + do_parse!( + secret: take!(16) + >> ( + VncAuth { + secret: secret.to_vec() + } + ) + ) +); + +named!(pub parse_security_type_selection, + do_parse!( + security_type: be_u8 + >> ( + SecurityTypeSelection { + security_type: security_type + } + ) + ) +); + +named!(pub parse_security_result, + do_parse!( + status: be_u32 + >> ( + SecurityResult { + status: status + } + ) + ) +); + +named!(pub parse_failure_reason, + do_parse!( + reason_length: be_u32 + >> reason_string: take_str!(reason_length) + >> ( + FailureReason { + reason_string: reason_string.to_string() + } + ) + ) +); + +named!(pub parse_client_init, + do_parse!( + shared: be_u8 + >> ( + ClientInit { + shared: shared + } + ) + ) +); + +named!(pub parse_pixel_format, + do_parse!( + bits_per_pixel: be_u8 + >> depth: be_u8 + >> big_endian_flag: be_u8 + >> true_colour_flag: be_u8 + >> red_max: be_u16 + >> green_max: be_u16 + >> blue_max: be_u16 + >> red_shift: be_u8 + >> green_shift: be_u8 + >> blue_shift: be_u8 + >> take!(3) + >> ( + PixelFormat { + bits_per_pixel: bits_per_pixel, + depth: depth, + big_endian_flag: big_endian_flag, + true_colour_flag: true_colour_flag, + red_max: red_max, + green_max: green_max, + blue_max: blue_max, + red_shift: red_shift, + green_shift: green_shift, + blue_shift: blue_shift, + } + ) + ) +); + +named!(pub parse_server_init, + do_parse!( + width: be_u16 + >> height: be_u16 + >> pixel_format: parse_pixel_format + >> name_length: be_u32 + >> name: take!(name_length) + >> ( + ServerInit { + width: width, + height: height, + pixel_format: pixel_format, + name_length: name_length, + name: name.to_vec() + } + ) + ) +); + +#[cfg(test)] +mod tests { + use nom::*; + use super::*; + + /// Simple test of some valid data. + #[test] + fn test_parse_version() { + let buf = b"RFB 003.008\n"; + + let result = parse_protocol_version(buf); + match result { + Ok((remainder, message)) => { + // Check the first message. + assert_eq!(message.major, "003"); + + // And we should have 6 bytes left. + assert_eq!(remainder.len(), 0); + } + Err(Err::Incomplete(_)) => { + panic!("Result should not have been incomplete."); + } + Err(Err::Error(err)) | + Err(Err::Failure(err)) => { + panic!("Result should not be an error: {:?}.", err); + } + } + } + + #[test] + fn test_parse_server_init() { + let buf = [ + 0x05, 0x00, 0x03, 0x20, 0x20, 0x18, 0x00, 0x01, + 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x10, 0x08, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, + 0x61, 0x6e, 0x65, 0x61, 0x67, 0x6c, 0x65, 0x73, + 0x40, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x68, 0x6f, + 0x73, 0x74, 0x2e, 0x6c, 0x6f, 0x63, 0x61, 0x6c, + 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e + ]; + + let result = parse_server_init(&buf); + match result { + Ok((remainder, message)) => { + // Check the first message. + assert_eq!(message.width, 1280); + assert_eq!(message.height, 800); + assert_eq!(message.pixel_format.bits_per_pixel, 32); + + // And we should have 6 bytes left. + assert_eq!(remainder.len(), 0); + } + Err(Err::Incomplete(_)) => { + panic!("Result should not have been incomplete."); + } + Err(Err::Error(err)) | + Err(Err::Failure(err)) => { + panic!("Result should not be an error: {:?}.", err); + } + } + } + +} diff --git a/rust/src/rfb/rfb.rs b/rust/src/rfb/rfb.rs new file mode 100644 index 0000000000..c0e2f210b2 --- /dev/null +++ b/rust/src/rfb/rfb.rs @@ -0,0 +1,810 @@ +/* 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. + */ + +// Author: Frank Honza + +use std; +use std::ffi::CString; +use std::mem::transmute; +use crate::core::{self, ALPROTO_UNKNOWN, AppProto, Flow, IPPROTO_TCP}; +use crate::log::*; +use crate::applayer; +use crate::applayer::*; +use nom; +use super::parser; + +static mut ALPROTO_RFB: AppProto = ALPROTO_UNKNOWN; + +pub struct RFBTransaction { + tx_id: u64, + pub complete: bool, + pub chosen_security_type: Option, + + pub tc_server_protocol_version: Option, + pub ts_client_protocol_version: Option, + pub tc_supported_security_types: Option, + pub ts_security_type_selection: Option, + pub tc_server_security_type: Option, + pub tc_vnc_challenge: Option, + pub ts_vnc_response: Option, + pub ts_client_init: Option, + pub tc_security_result: Option, + pub tc_failure_reason: Option, + pub tc_server_init: Option, + + logged: LoggerFlags, + de_state: Option<*mut core::DetectEngineState>, + events: *mut core::AppLayerDecoderEvents, + detect_flags: applayer::TxDetectFlags, +} + +impl RFBTransaction { + pub fn new() -> RFBTransaction { + RFBTransaction { + tx_id: 0, + complete: false, + chosen_security_type: None, + + tc_server_protocol_version: None, + ts_client_protocol_version: None, + tc_supported_security_types: None, + ts_security_type_selection: None, + tc_server_security_type: None, + tc_vnc_challenge: None, + ts_vnc_response: None, + ts_client_init: None, + tc_security_result: None, + tc_failure_reason: None, + tc_server_init: None, + + logged: LoggerFlags::new(), + de_state: None, + events: std::ptr::null_mut(), + detect_flags: applayer::TxDetectFlags::default(), + } + } + + 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 RFBTransaction { + fn drop(&mut self) { + self.free(); + } +} + +pub struct RFBState { + tx_id: u64, + transactions: Vec, + state: parser::RFBGlobalState +} + +impl RFBState { + pub fn new() -> Self { + Self { + tx_id: 0, + transactions: Vec::new(), + state: parser::RFBGlobalState::TCServerProtocolVersion + } + } + + // Free a transaction by ID. + fn free_tx(&mut self, tx_id: u64) { + let len = self.transactions.len(); + let mut found = false; + let mut index = 0; + for i in 0..len { + let tx = &self.transactions[i]; + if tx.tx_id == tx_id + 1 { + found = true; + index = i; + break; + } + } + if found { + self.transactions.remove(index); + } + } + + pub fn get_tx(&mut self, tx_id: u64) -> Option<&RFBTransaction> { + for tx in &mut self.transactions { + if tx.tx_id == tx_id + 1 { + return Some(tx); + } + } + return None; + } + + fn new_tx(&mut self) -> RFBTransaction { + let mut tx = RFBTransaction::new(); + self.tx_id += 1; + tx.tx_id = self.tx_id; + return tx; + } + + fn get_current_tx(&mut self) -> Option<&mut RFBTransaction> { + for tx in &mut self.transactions { + if tx.tx_id == self.tx_id { + return Some(tx); + } + } + return None; + } + + fn parse_request(&mut self, input: &[u8]) -> AppLayerResult { + // We're not interested in empty requests. + if input.len() == 0 { + return AppLayerResult::ok(); + } + + let mut current = input; + SCLogDebug!("request_state {}, input_len {}", self.state, input.len()); + loop { + if current.len() == 0 { + return AppLayerResult::ok(); + } + match self.state { + parser::RFBGlobalState::TSClientProtocolVersion => { + match parser::parse_protocol_version(current) { + Ok((rem, request)) => { + current = rem; + if request.major == "003" && request.minor == "003" { + // in version 3.3 the server decided security type + self.state = parser::RFBGlobalState::TCServerSecurityType; + } else { + self.state = parser::RFBGlobalState::TCSupportedSecurityTypes; + } + + match self.get_current_tx() { + Some(current_transaction) => { + current_transaction.ts_client_protocol_version = Some(request); + } + _ => { + return AppLayerResult::err(); + } + } + } + Err(nom::Err::Incomplete(v)) => { + if let nom::Needed::Size(n) = v { + return AppLayerResult::incomplete((input.len() - current.len()) as u32, + (current.len() + n) as u32); + } + return AppLayerResult::err(); + } + Err(_) => { + return AppLayerResult::err(); + } + } + } + parser::RFBGlobalState::TSSecurityTypeSelection => { + match parser::parse_security_type_selection(current) { + Ok((rem, request)) => { + current = rem; + + let chosen_security_type = request.security_type; + match chosen_security_type { + 2 => self.state = parser::RFBGlobalState::TCVncChallenge, + 1 => self.state = parser::RFBGlobalState::TSClientInit, + _ => return AppLayerResult::err(), + } + + match self.get_current_tx() { + Some(current_transaction) => { + current_transaction.ts_security_type_selection = Some(request); + current_transaction.chosen_security_type = Some(chosen_security_type as u32); + } + _ => { + return AppLayerResult::err(); + } + } + } + Err(nom::Err::Incomplete(v)) => { + if let nom::Needed::Size(n) = v { + return AppLayerResult::incomplete((input.len() - current.len()) as u32, + (current.len() + n) as u32); + } + return AppLayerResult::err(); + } + Err(_) => { + return AppLayerResult::err(); + } + } + } + parser::RFBGlobalState::TSVncResponse => { + match parser::parse_vnc_auth(current) { + Ok((rem, request)) => { + current = rem; + + self.state = parser::RFBGlobalState::TCSecurityResult; + + match self.get_current_tx() { + Some(current_transaction) => { + current_transaction.ts_vnc_response = Some(request); + } + _ => { + return AppLayerResult::err(); + } + } + } + Err(nom::Err::Incomplete(v)) => { + if let nom::Needed::Size(n) = v { + return AppLayerResult::incomplete((input.len() - current.len()) as u32, + (current.len() + n) as u32); + } + return AppLayerResult::err(); + } + Err(_) => { + return AppLayerResult::err(); + } + } + } + parser::RFBGlobalState::TSClientInit => { + match parser::parse_client_init(current) { + Ok((rem, request)) => { + current = rem; + self.state = parser::RFBGlobalState::TCServerInit; + + match self.get_current_tx() { + Some(current_transaction) => { + current_transaction.ts_client_init = Some(request); + } + _ => { + return AppLayerResult::err(); + } + } + } + Err(nom::Err::Incomplete(v)) => { + if let nom::Needed::Size(n) = v { + return AppLayerResult::incomplete((input.len() - current.len()) as u32, + (current.len() + n) as u32); + } + return AppLayerResult::err(); + } + Err(_) => { + return AppLayerResult::err(); + } + } + } + parser::RFBGlobalState::Message => { + //todo implement RFB messages, for now we stop here + return AppLayerResult::err(); + } + parser::RFBGlobalState::TCServerProtocolVersion => { + SCLogDebug!("Reversed traffic, expected response."); + return AppLayerResult::err(); + } + _ => { + SCLogDebug!("Invalid state for request {}", self.state); + current = b""; + } + } + } + } + + fn parse_response(&mut self, input: &[u8]) -> AppLayerResult { + // We're not interested in empty responses. + if input.len() == 0 { + return AppLayerResult::ok(); + } + + let mut current = input; + SCLogDebug!("response_state {}, response_len {}", self.state, input.len()); + loop { + if current.len() == 0 { + return AppLayerResult::ok(); + } + match self.state { + parser::RFBGlobalState::TCServerProtocolVersion => { + match parser::parse_protocol_version(current) { + Ok((rem, request)) => { + current = rem; + self.state = parser::RFBGlobalState::TSClientProtocolVersion; + let tx = self.new_tx(); + self.transactions.push(tx); + + match self.get_current_tx() { + Some(current_transaction) => { + current_transaction.tc_server_protocol_version = Some(request); + } + _ => { + return AppLayerResult::err(); + } + } + } + Err(nom::Err::Incomplete(v)) => { + if let nom::Needed::Size(n) = v { + return AppLayerResult::incomplete((input.len() - current.len()) as u32, + (current.len() + n) as u32); + } + return AppLayerResult::err(); + } + Err(_) => { + return AppLayerResult::err(); + } + } + } + parser::RFBGlobalState::TCSupportedSecurityTypes => { + match parser::parse_supported_security_types(current) { + Ok((rem, request)) => { + current = rem; + SCLogDebug!( + "supported_security_types: {}, types: {}", request.number_of_types, + request.types.iter().map(ToString::to_string).map(|v| v + " ").collect::() + ); + + self.state = parser::RFBGlobalState::TSSecurityTypeSelection; + if request.number_of_types == 0 { + self.state = parser::RFBGlobalState::TCFailureReason; + } + + match self.get_current_tx() { + Some(current_transaction) => { + current_transaction.tc_supported_security_types = Some(request); + } + _ => { + return AppLayerResult::err(); + } + } + } + Err(nom::Err::Incomplete(v)) => { + if let nom::Needed::Size(n) = v { + return AppLayerResult::incomplete((input.len() - current.len()) as u32, + (current.len() + n) as u32); + } + return AppLayerResult::err(); + } + Err(_) => { + return AppLayerResult::err(); + } + } + } + parser::RFBGlobalState::TCServerSecurityType => { + // In RFB 3.3, the server decides the authentication type + match parser::parse_server_security_type(current) { + Ok((rem, request)) => { + current = rem; + let chosen_security_type = request.security_type; + SCLogDebug!("chosen_security_type: {}", chosen_security_type); + match chosen_security_type { + 0 => self.state = parser::RFBGlobalState::TCFailureReason, + 1 => self.state = parser::RFBGlobalState::TSClientInit, + 2 => self.state = parser::RFBGlobalState::TCVncChallenge, + _ => { + // TODO Event unknown security type + return AppLayerResult::err(); + } + } + + match self.get_current_tx() { + Some(current_transaction) => { + current_transaction.tc_server_security_type = Some(request); + current_transaction.chosen_security_type = Some(chosen_security_type); + } + _ => { + return AppLayerResult::err(); + } + } + } + Err(nom::Err::Incomplete(v)) => { + if let nom::Needed::Size(n) = v { + return AppLayerResult::incomplete((input.len() - current.len()) as u32, + (current.len() + n) as u32); + } + return AppLayerResult::err(); + } + Err(_) => { + return AppLayerResult::err(); + } + } + } + parser::RFBGlobalState::TCVncChallenge => { + match parser::parse_vnc_auth(current) { + Ok((rem, request)) => { + current = rem; + + self.state = parser::RFBGlobalState::TSVncResponse; + + match self.get_current_tx() { + Some(current_transaction) => { + current_transaction.tc_vnc_challenge = Some(request); + } + _ => { + return AppLayerResult::err(); + } + } + } + Err(nom::Err::Incomplete(v)) => { + if let nom::Needed::Size(n) = v { + return AppLayerResult::incomplete((input.len() - current.len()) as u32, + (current.len() + n) as u32); + } + return AppLayerResult::err(); + } + Err(_) => { + return AppLayerResult::err(); + } + } + } + parser::RFBGlobalState::TCSecurityResult => { + match parser::parse_security_result(current) { + Ok((rem, request)) => { + current = rem; + + if request.status == 0 { + self.state = parser::RFBGlobalState::TSClientInit; + + match self.get_current_tx() { + Some(current_transaction) => { + current_transaction.tc_security_result = Some(request); + } + _ => { + return AppLayerResult::err(); + } + } + } else if request.status == 1 { + self.state = parser::RFBGlobalState::TCFailureReason; + } else { + // TODO: Event: unknown security result value + } + } + Err(nom::Err::Incomplete(v)) => { + if let nom::Needed::Size(n) = v { + return AppLayerResult::incomplete((input.len() - current.len()) as u32, + (current.len() + n) as u32); + } + return AppLayerResult::err(); + } + Err(_) => { + return AppLayerResult::err(); + } + } + } + parser::RFBGlobalState::TCFailureReason => { + match parser::parse_failure_reason(current) { + Ok((_rem, request)) => { + match self.get_current_tx() { + Some(current_transaction) => { + current_transaction.tc_failure_reason = Some(request); + } + _ => { + return AppLayerResult::err(); + } + } + return AppLayerResult::err(); + } + Err(nom::Err::Incomplete(v)) => { + if let nom::Needed::Size(n) = v { + return AppLayerResult::incomplete((input.len() - current.len()) as u32, + (current.len() + n) as u32); + } + return AppLayerResult::err(); + } + Err(_) => { + return AppLayerResult::err(); + } + } + } + parser::RFBGlobalState::TCServerInit => { + match parser::parse_server_init(current) { + Ok((rem, request)) => { + current = rem; + self.state = parser::RFBGlobalState::Message; + + match self.get_current_tx() { + Some(current_transaction) => { + current_transaction.tc_server_init = Some(request); + // connection initialization is complete and parsed + current_transaction.complete = true; + } + _ => { + return AppLayerResult::err(); + } + } + } + Err(nom::Err::Incomplete(v)) => { + if let nom::Needed::Size(n) = v { + return AppLayerResult::incomplete((input.len() - current.len()) as u32, + (current.len() + n) as u32); + } + return AppLayerResult::err(); + } + Err(_) => { + return AppLayerResult::err(); + } + } + } + parser::RFBGlobalState::Message => { + //todo implement RFB messages, for now we stop here + return AppLayerResult::err(); + } + _ => { + SCLogDebug!("Invalid state for response"); + return AppLayerResult::err(); + } + } + } + } + + fn tx_iterator( + &mut self, + min_tx_id: u64, + state: &mut u64, + ) -> Option<(&RFBTransaction, u64, bool)> { + let mut index = *state as usize; + let len = self.transactions.len(); + + while index < len { + let tx = &self.transactions[index]; + if tx.tx_id < min_tx_id + 1 { + index += 1; + continue; + } + *state = index as u64; + return Some((tx, tx.tx_id - 1, (len - index) > 1)); + } + + return None; + } +} + +// C exports. + +export_tx_get_detect_state!( + rs_rfb_tx_get_detect_state, + RFBTransaction +); +export_tx_set_detect_state!( + rs_rfb_tx_set_detect_state, + RFBTransaction +); + +#[no_mangle] +pub extern "C" fn rs_rfb_state_new() -> *mut std::os::raw::c_void { + let state = RFBState::new(); + let boxed = Box::new(state); + return unsafe { transmute(boxed) }; +} + +#[no_mangle] +pub extern "C" fn rs_rfb_state_free(state: *mut std::os::raw::c_void) { + // Just unbox... + let _drop: Box = unsafe { transmute(state) }; +} + +#[no_mangle] +pub extern "C" fn rs_rfb_state_tx_free( + state: *mut std::os::raw::c_void, + tx_id: u64, +) { + let state = cast_pointer!(state, RFBState); + state.free_tx(tx_id); +} + +#[no_mangle] +pub extern "C" fn rs_rfb_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 = cast_pointer!(state, RFBState); + let buf = build_slice!(input, input_len as usize); + return state.parse_request(buf); +} + +#[no_mangle] +pub extern "C" fn rs_rfb_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 = cast_pointer!(state, RFBState); + let buf = build_slice!(input, input_len as usize); + return state.parse_response(buf); +} + +#[no_mangle] +pub extern "C" fn rs_rfb_state_get_tx( + state: *mut std::os::raw::c_void, + tx_id: u64, +) -> *mut std::os::raw::c_void { + let state = cast_pointer!(state, RFBState); + match state.get_tx(tx_id) { + Some(tx) => { + return unsafe { transmute(tx) }; + } + None => { + return std::ptr::null_mut(); + } + } +} + +#[no_mangle] +pub extern "C" fn rs_rfb_state_get_tx_count( + state: *mut std::os::raw::c_void, +) -> u64 { + let state = cast_pointer!(state, RFBState); + return state.tx_id; +} + +#[no_mangle] +pub extern "C" fn rs_rfb_state_progress_completion_status( + _direction: u8, +) -> std::os::raw::c_int { + // This parser uses 1 to signal transaction completion status. + return 1; +} + +#[no_mangle] +pub extern "C" fn rs_rfb_tx_get_alstate_progress( + tx: *mut std::os::raw::c_void, + _direction: u8, +) -> std::os::raw::c_int { + let tx = cast_pointer!(tx, RFBTransaction); + if tx.complete { + return 1; + } + return 0; +} + +#[no_mangle] +pub extern "C" fn rs_rfb_tx_get_logged( + _state: *mut std::os::raw::c_void, + tx: *mut std::os::raw::c_void, +) -> u32 { + let tx = cast_pointer!(tx, RFBTransaction); + return tx.logged.get(); +} + +#[no_mangle] +pub extern "C" fn rs_rfb_tx_set_logged( + _state: *mut std::os::raw::c_void, + tx: *mut std::os::raw::c_void, + logged: u32, +) { + let tx = cast_pointer!(tx, RFBTransaction); + tx.logged.set(logged); +} + +#[no_mangle] +pub extern "C" fn rs_rfb_state_get_events( + tx: *mut std::os::raw::c_void +) -> *mut core::AppLayerDecoderEvents { + let tx = cast_pointer!(tx, RFBTransaction); + return tx.events; +} + +#[no_mangle] +pub extern "C" fn rs_rfb_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 { + return -1; +} + +#[no_mangle] +pub extern "C" fn rs_rfb_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 { + return -1; +} +#[no_mangle] +pub extern "C" fn rs_rfb_state_get_tx_iterator( + _ipproto: u8, + _alproto: AppProto, + state: *mut std::os::raw::c_void, + min_tx_id: u64, + _max_tx_id: u64, + istate: &mut u64, +) -> applayer::AppLayerGetTxIterTuple { + let state = cast_pointer!(state, RFBState); + match state.tx_iterator(min_tx_id, istate) { + Some((tx, out_tx_id, has_next)) => { + let c_tx = unsafe { transmute(tx) }; + let ires = applayer::AppLayerGetTxIterTuple::with_values( + c_tx, + out_tx_id, + has_next, + ); + return ires; + } + None => { + return applayer::AppLayerGetTxIterTuple::not_found(); + } + } +} + +// Parser name as a C style string. +const PARSER_NAME: &'static [u8] = b"rfb\0"; + +export_tx_detect_flags_set!(rs_rfb_set_tx_detect_flags, RFBTransaction); +export_tx_detect_flags_get!(rs_rfb_get_tx_detect_flags, RFBTransaction); + +#[no_mangle] +pub unsafe extern "C" fn rs_rfb_register_parser() { + let default_port = CString::new("[5900]").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, + probe_ts: None, + probe_tc: None, + min_depth: 0, + max_depth: 16, + state_new: rs_rfb_state_new, + state_free: rs_rfb_state_free, + tx_free: rs_rfb_state_tx_free, + parse_ts: rs_rfb_parse_request, + parse_tc: rs_rfb_parse_response, + get_tx_count: rs_rfb_state_get_tx_count, + get_tx: rs_rfb_state_get_tx, + tx_get_comp_st: rs_rfb_state_progress_completion_status, + tx_get_progress: rs_rfb_tx_get_alstate_progress, + get_tx_logged: Some(rs_rfb_tx_get_logged), + set_tx_logged: Some(rs_rfb_tx_set_logged), + get_de_state: rs_rfb_tx_get_detect_state, + set_de_state: rs_rfb_tx_set_detect_state, + get_events: Some(rs_rfb_state_get_events), + get_eventinfo: Some(rs_rfb_state_get_event_info), + get_eventinfo_byid : Some(rs_rfb_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: Some(rs_rfb_state_get_tx_iterator), + get_tx_detect_flags: Some(rs_rfb_get_tx_detect_flags), + set_tx_detect_flags: Some(rs_rfb_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_RFB = alproto; + if AppLayerParserConfParserEnabled( + ip_proto_str.as_ptr(), + parser.name, + ) != 0 + { + let _ = AppLayerRegisterParser(&parser, alproto); + } + SCLogDebug!("Rust rfb parser registered."); + } else { + SCLogDebug!("Protocol detector and parser disabled for RFB."); + } +} diff --git a/src/Makefile.am b/src/Makefile.am index 282861d36d..783ec2bc78 100755 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -42,6 +42,7 @@ app-layer-register.c app-layer-register.h \ app-layer-tftp.c app-layer-tftp.h \ app-layer-ikev2.c app-layer-ikev2.h \ app-layer-krb5.c app-layer-krb5.h \ +app-layer-rfb.c app-layer-rfb.h \ app-layer-template.c app-layer-template.h \ app-layer-template-rust.c app-layer-template-rust.h \ app-layer-rdp.c app-layer-rdp.h \ @@ -248,6 +249,9 @@ detect-sip-stat-code.c detect-sip-stat-code.h \ detect-sip-stat-msg.c detect-sip-stat-msg.h \ detect-sip-request-line.c detect-sip-request-line.h \ detect-sip-response-line.c detect-sip-response-line.h \ +detect-rfb-secresult.c detect-rfb-secresult.h \ +detect-rfb-sectype.c detect-rfb-sectype.h \ +detect-rfb-name.c detect-rfb-name.h \ detect-ssh-proto.c detect-ssh-proto.h \ detect-ssh-proto-version.c detect-ssh-proto-version.h \ detect-ssh-software.c detect-ssh-software.h \ @@ -350,6 +354,7 @@ output-json-ikev2.c output-json-ikev2.h \ output-json-krb5.c output-json-krb5.h \ output-json-dhcp.c output-json-dhcp.h \ output-json-snmp.c output-json-snmp.h \ +output-json-rfb.c output-json-rfb.h \ output-json-template.c output-json-template.h \ output-json-template-rust.c output-json-template-rust.h \ output-json-rdp.c output-json-rdp.h \ diff --git a/src/app-layer-detect-proto.c b/src/app-layer-detect-proto.c index 7d6f9854b1..ae954fdc1a 100644 --- a/src/app-layer-detect-proto.c +++ b/src/app-layer-detect-proto.c @@ -869,6 +869,8 @@ static void AppLayerProtoDetectPrintProbingParsers(AppLayerProtoDetectProbingPar printf(" alproto: ALPROTO_SIP\n"); else if (pp_pe->alproto == ALPROTO_TEMPLATE_RUST) printf(" alproto: ALPROTO_TEMPLATE_RUST\n"); + else if (pp_pe->alproto == ALPROTO_RFB) + printf(" alproto: ALPROTO_RFB\n"); else if (pp_pe->alproto == ALPROTO_TEMPLATE) printf(" alproto: ALPROTO_TEMPLATE\n"); else if (pp_pe->alproto == ALPROTO_DNP3) @@ -942,6 +944,8 @@ static void AppLayerProtoDetectPrintProbingParsers(AppLayerProtoDetectProbingPar printf(" alproto: ALPROTO_SIP\n"); else if (pp_pe->alproto == ALPROTO_TEMPLATE_RUST) printf(" alproto: ALPROTO_TEMPLATE_RUST\n"); + else if (pp_pe->alproto == ALPROTO_RFB) + printf(" alproto: ALPROTO_RFB\n"); else if (pp_pe->alproto == ALPROTO_TEMPLATE) printf(" alproto: ALPROTO_TEMPLATE\n"); else if (pp_pe->alproto == ALPROTO_DNP3) diff --git a/src/app-layer-parser.c b/src/app-layer-parser.c index e4a3ec2727..bd60a545bf 100644 --- a/src/app-layer-parser.c +++ b/src/app-layer-parser.c @@ -65,6 +65,7 @@ #include "app-layer-krb5.h" #include "app-layer-snmp.h" #include "app-layer-sip.h" +#include "app-layer-rfb.h" #include "app-layer-template.h" #include "app-layer-template-rust.h" #include "app-layer-rdp.h" @@ -1587,6 +1588,7 @@ void AppLayerParserRegisterProtocolParsers(void) RegisterSNMPParsers(); RegisterSIPParsers(); RegisterTemplateRustParsers(); + RegisterRFBParsers(); RegisterTemplateParsers(); RegisterRdpParsers(); diff --git a/src/app-layer-protos.c b/src/app-layer-protos.c index 2a72051529..9db24bac99 100644 --- a/src/app-layer-protos.c +++ b/src/app-layer-protos.c @@ -102,6 +102,9 @@ const char *AppProtoToString(AppProto alproto) case ALPROTO_SIP: proto_name = "sip"; break; + case ALPROTO_RFB: + proto_name = "rfb"; + break; case ALPROTO_TEMPLATE: proto_name = "template"; break; @@ -150,6 +153,7 @@ AppProto StringToAppProto(const char *proto_name) if (strcmp(proto_name,"dhcp")==0) return ALPROTO_DHCP; if (strcmp(proto_name,"snmp")==0) return ALPROTO_SNMP; if (strcmp(proto_name,"sip")==0) return ALPROTO_SIP; + if (strcmp(proto_name,"rfb")==0) return ALPROTO_RFB; if (strcmp(proto_name,"template")==0) return ALPROTO_TEMPLATE; if (strcmp(proto_name,"template-rust")==0) return ALPROTO_TEMPLATE_RUST; if (strcmp(proto_name,"rdp")==0) return ALPROTO_RDP; diff --git a/src/app-layer-protos.h b/src/app-layer-protos.h index 9ee8631269..589d68d853 100644 --- a/src/app-layer-protos.h +++ b/src/app-layer-protos.h @@ -51,6 +51,7 @@ enum AppProtoEnum { ALPROTO_DHCP, ALPROTO_SNMP, ALPROTO_SIP, + ALPROTO_RFB, ALPROTO_TEMPLATE, ALPROTO_TEMPLATE_RUST, ALPROTO_RDP, diff --git a/src/app-layer-rfb.c b/src/app-layer-rfb.c new file mode 100644 index 0000000000..7abedb22e6 --- /dev/null +++ b/src/app-layer-rfb.c @@ -0,0 +1,155 @@ +/* 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. + */ + +/** + * \file + * + * \author Sascha Steinbiss + * + * RFB (VNC) application layer detector and parser. + * + */ + +#include "suricata-common.h" + +#include "util-unittest.h" + +#include "app-layer-detect-proto.h" +#include "app-layer-parser.h" +#include "app-layer-rfb.h" + +#include "rust-bindings.h" + +static int RFBRegisterPatternsForProtocolDetection(void) +{ + if (AppLayerProtoDetectPMRegisterPatternCI(IPPROTO_TCP, ALPROTO_RFB, + "RFB ", 4, 0, STREAM_TOCLIENT) < 0) + { + return -1; + } + if (AppLayerProtoDetectPMRegisterPatternCI(IPPROTO_TCP, ALPROTO_RFB, + "RFB ", 4, 0, STREAM_TOSERVER) < 0) + { + return -1; + } + return 0; +} + +void RFBParserRegisterTests(void); + +void RegisterRFBParsers(void) +{ + rs_rfb_register_parser(); + if (RFBRegisterPatternsForProtocolDetection() < 0 ) + return; +#ifdef UNITTESTS + AppLayerParserRegisterProtocolUnittests(IPPROTO_TCP, ALPROTO_RFB, + RFBParserRegisterTests); +#endif +} + + +#ifdef UNITTESTS + +#include "stream-tcp.h" +#include "util-unittest-helper.h" + +static int RFBParserTest(void) +{ + uint64_t ret[4]; + AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc(); + FAIL_IF_NULL(alp_tctx); + + StreamTcpInitConfig(TRUE); + TcpSession ssn; + memset(&ssn, 0, sizeof(ssn)); + + Flow *f = UTHBuildFlow(AF_INET, "1.2.3.4", "1.2.3.5", 59001, 5900); + FAIL_IF_NULL(f); + f->protoctx = &ssn; + f->proto = IPPROTO_TCP; + f->alproto = ALPROTO_RFB; + + static const unsigned char rfb_version_str[12] = { + 0x52, 0x46, 0x42, 0x20, 0x30, 0x30, 0x33, 0x2e, 0x30, 0x30, 0x37, 0x0a + }; + + // the RFB server sending the first handshake message + int r = AppLayerParserParse(NULL, alp_tctx, f, ALPROTO_RFB, STREAM_TOCLIENT | STREAM_START, + (uint8_t *)rfb_version_str, sizeof(rfb_version_str)); + FAIL_IF_NOT(r == 0); + + r = AppLayerParserParse( + NULL, alp_tctx, f, ALPROTO_RFB, STREAM_TOSERVER, (uint8_t *)rfb_version_str, sizeof(rfb_version_str)); + FAIL_IF_NOT(r == 0); + + static const unsigned char security_types[3] = { + 0x02, 0x01, 0x02 + }; + r = AppLayerParserParse( + NULL, alp_tctx, f, ALPROTO_RFB, STREAM_TOCLIENT, (uint8_t *)security_types, sizeof(security_types)); + FAIL_IF_NOT(r == 0); + + static const unsigned char type_selection[1] = { + 0x01 + }; + r = AppLayerParserParse( + NULL, alp_tctx, f, ALPROTO_RFB, STREAM_TOSERVER, (uint8_t *)type_selection, sizeof(type_selection)); + FAIL_IF_NOT(r == 0); + + static const unsigned char client_init[1] = { + 0x01 + }; + r = AppLayerParserParse( + NULL, alp_tctx, f, ALPROTO_RFB, STREAM_TOSERVER, (uint8_t *)client_init, sizeof(client_init)); + FAIL_IF_NOT(r == 0); + + static const unsigned char server_init[] = { + 0x05, 0x00, 0x03, 0x20, 0x20, 0x18, 0x00, 0x01, + 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x10, 0x08, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, + 0x61, 0x6e, 0x65, 0x61, 0x67, 0x6c, 0x65, 0x73, + 0x40, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x68, 0x6f, + 0x73, 0x74, 0x2e, 0x6c, 0x6f, 0x63, 0x61, 0x6c, + 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e + }; + + r = AppLayerParserParse( + NULL, alp_tctx, f, ALPROTO_RFB, STREAM_TOCLIENT, (uint8_t *)server_init, sizeof(server_init)); + FAIL_IF_NOT(r == 0); + + AppLayerParserTransactionsCleanup(f); + UTHAppLayerParserStateGetIds(f->alparser, &ret[0], &ret[1], &ret[2], &ret[3]); + FAIL_IF_NOT(ret[0] == 1); // inspect_id[0] + FAIL_IF_NOT(ret[1] == 1); // inspect_id[1] + FAIL_IF_NOT(ret[2] == 1); // log_id + FAIL_IF_NOT(ret[3] == 1); // min_id + + AppLayerParserTransactionsCleanup(f); + AppLayerParserThreadCtxFree(alp_tctx); + StreamTcpFreeConfig(TRUE); + UTHFreeFlow(f); + + PASS; +} + +void RFBParserRegisterTests(void) +{ + UtRegisterTest("RFBParserTest", RFBParserTest); +} + +#endif diff --git a/src/app-layer-rfb.h b/src/app-layer-rfb.h new file mode 100644 index 0000000000..79c2f49b81 --- /dev/null +++ b/src/app-layer-rfb.h @@ -0,0 +1,29 @@ +/* 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. + */ + +/** + * \file + * + * \author Sascha Steinbiss + */ + +#ifndef __APP_LAYER_RFB_H__ +#define __APP_LAYER_RFB_H__ + +void RegisterRFBParsers(void); + +#endif /* __APP_LAYER_RFB_H__ */ diff --git a/src/detect-engine-register.c b/src/detect-engine-register.c index 35e5b21009..5830a8566c 100644 --- a/src/detect-engine-register.c +++ b/src/detect-engine-register.c @@ -188,6 +188,9 @@ #include "detect-sip-stat-msg.h" #include "detect-sip-request-line.h" #include "detect-sip-response-line.h" +#include "detect-rfb-secresult.h" +#include "detect-rfb-sectype.h" +#include "detect-rfb-name.h" #include "detect-target.h" #include "detect-template-rust-buffer.h" #include "detect-snmp-version.h" @@ -562,6 +565,9 @@ void SigTableSetup(void) DetectSipStatMsgRegister(); DetectSipRequestLineRegister(); DetectSipResponseLineRegister(); + DetectRfbSecresultRegister(); + DetectRfbSectypeRegister(); + DetectRfbNameRegister(); DetectTargetRegister(); DetectTemplateRustBufferRegister(); DetectSNMPVersionRegister(); diff --git a/src/detect-engine-register.h b/src/detect-engine-register.h index f6ca2e1e4e..f488ecac21 100644 --- a/src/detect-engine-register.h +++ b/src/detect-engine-register.h @@ -233,7 +233,9 @@ enum DetectKeywordId { DETECT_AL_SIP_STAT_MSG, DETECT_AL_SIP_REQUEST_LINE, DETECT_AL_SIP_RESPONSE_LINE, - + DETECT_AL_RFB_SECRESULT, + DETECT_AL_RFB_SECTYPE, + DETECT_AL_RFB_NAME, DETECT_TEMPLATE, DETECT_TEMPLATE2, DETECT_IPV4HDR, diff --git a/src/detect-rfb-name.c b/src/detect-rfb-name.c new file mode 100644 index 0000000000..440cb12336 --- /dev/null +++ b/src/detect-rfb-name.c @@ -0,0 +1,113 @@ +/* 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. + */ + +/** + * + * \author Sascha Steinbiss + */ + +#include "suricata-common.h" +#include "threads.h" +#include "debug.h" +#include "decode.h" +#include "detect.h" + +#include "detect-parse.h" +#include "detect-engine.h" +#include "detect-engine-mpm.h" +#include "detect-engine-prefilter.h" +#include "detect-urilen.h" + +#include "flow.h" +#include "flow-var.h" +#include "flow-util.h" + +#include "util-debug.h" +#include "util-unittest.h" +#include "util-unittest-helper.h" +#include "util-spm.h" + +#include "app-layer.h" +#include "app-layer-parser.h" + +#include "detect-rfb-name.h" +#include "stream-tcp.h" + +#include "rust.h" +#include "app-layer-rfb.h" +#include "rust-bindings.h" + +#define KEYWORD_NAME "rfb.name" +#define KEYWORD_DOC "rfb-keywords.html#rfb-name"; +#define BUFFER_NAME "rfb.name" +#define BUFFER_DESC "rfb name" +static int g_buffer_id = 0; + +static int DetectRfbNameSetup(DetectEngineCtx *de_ctx, Signature *s, const char *str) +{ + if (DetectBufferSetActiveList(s, g_buffer_id) < 0) + return -1; + + if (DetectSignatureSetAppProto(s, ALPROTO_RFB) < 0) + return -1; + + return 0; +} + +static InspectionBuffer *GetData(DetectEngineThreadCtx *det_ctx, + const DetectEngineTransforms *transforms, Flow *_f, + const uint8_t _flow_flags, void *txv, const int list_id) +{ + InspectionBuffer *buffer = InspectionBufferGet(det_ctx, list_id); + if (buffer->inspect == NULL) { + const uint8_t *b = NULL; + uint32_t b_len = 0; + + if (rs_rfb_tx_get_name(txv, &b, &b_len) != 1) + return NULL; + if (b == NULL || b_len == 0) + return NULL; + + InspectionBufferSetup(buffer, b, b_len); + InspectionBufferApplyTransforms(buffer, transforms); + } + + return buffer; +} + +void DetectRfbNameRegister(void) +{ + sigmatch_table[DETECT_AL_RFB_NAME].name = KEYWORD_NAME; + sigmatch_table[DETECT_AL_RFB_NAME].url = DOC_URL DOC_VERSION "/rules/" KEYWORD_DOC + sigmatch_table[DETECT_AL_RFB_NAME].desc = "sticky buffer to match on the RFB desktop name"; + sigmatch_table[DETECT_AL_RFB_NAME].Setup = DetectRfbNameSetup; + sigmatch_table[DETECT_AL_RFB_NAME].flags |= SIGMATCH_NOOPT|SIGMATCH_INFO_STICKY_BUFFER; + + DetectAppLayerInspectEngineRegister2(BUFFER_NAME, ALPROTO_RFB, + SIG_FLAG_TOCLIENT, 1, + DetectEngineInspectBufferGeneric, GetData); + + DetectAppLayerMpmRegister2(BUFFER_NAME, SIG_FLAG_TOCLIENT, 1, + PrefilterGenericMpmRegister, GetData, ALPROTO_RFB, + 1); + + DetectBufferTypeSetDescriptionByName(BUFFER_NAME, BUFFER_DESC); + + g_buffer_id = DetectBufferTypeGetByName(BUFFER_NAME); + + SCLogDebug("registering " BUFFER_NAME " rule option"); +} diff --git a/src/detect-rfb-name.h b/src/detect-rfb-name.h new file mode 100644 index 0000000000..0627eeda22 --- /dev/null +++ b/src/detect-rfb-name.h @@ -0,0 +1,29 @@ +/* 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. + */ + +/** + * \file + * + * \author Frank Honza + */ + +#ifndef __DETECT_RFB_NAME_H__ +#define __DETECT_RFB_NAME_H__ + +void DetectRfbNameRegister(void); + +#endif /* __DETECT_RFB_NAME_H__ */ diff --git a/src/detect-rfb-secresult.c b/src/detect-rfb-secresult.c new file mode 100644 index 0000000000..12425264b2 --- /dev/null +++ b/src/detect-rfb-secresult.c @@ -0,0 +1,311 @@ +/* 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. + */ + +/** + * \file + * + * \author Sascha Steinbiss + */ + +#include "suricata-common.h" +#include "conf.h" +#include "detect.h" +#include "detect-parse.h" +#include "detect-engine.h" +#include "detect-engine-content-inspection.h" +#include "detect-rfb-secresult.h" +#include "util-unittest.h" + +#include "rust-bindings.h" + +#define PARSE_REGEX "\\S[A-z]" +static DetectParseRegex parse_regex; + +static int rfb_secresult_id = 0; + +static int DetectRfbSecresultMatch(DetectEngineThreadCtx *det_ctx, + Flow *f, uint8_t flags, void *state, + void *txv, const Signature *s, + const SigMatchCtx *ctx); +static int DetectRfbSecresultSetup (DetectEngineCtx *, Signature *, const char *); +void RfbSecresultRegisterTests(void); +void DetectRfbSecresultFree(void *); + +static int DetectEngineInspectRfbSecresultGeneric(ThreadVars *tv, + DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx, + const Signature *s, const SigMatchData *smd, + Flow *f, uint8_t flags, void *alstate, + void *txv, uint64_t tx_id); + +typedef struct DetectRfbSecresultData_ { + uint32_t result; /** result code */ +} DetectRfbSecresultData; + +/** + * \brief Registration function for rfb.secresult: keyword + */ +void DetectRfbSecresultRegister (void) +{ + sigmatch_table[DETECT_AL_RFB_SECRESULT].name = "rfb.secresult"; + sigmatch_table[DETECT_AL_RFB_SECRESULT].desc = "match RFB security result"; + sigmatch_table[DETECT_AL_RFB_SECRESULT].url = DOC_URL DOC_VERSION "/rules/rfb-keywords.html#rfb-secresult"; + sigmatch_table[DETECT_AL_RFB_SECRESULT].AppLayerTxMatch = DetectRfbSecresultMatch; + sigmatch_table[DETECT_AL_RFB_SECRESULT].Setup = DetectRfbSecresultSetup; + sigmatch_table[DETECT_AL_RFB_SECRESULT].Free = DetectRfbSecresultFree; + sigmatch_table[DETECT_AL_RFB_SECRESULT].RegisterTests = RfbSecresultRegisterTests; + + DetectSetupParseRegexes(PARSE_REGEX, &parse_regex); + + DetectAppLayerInspectEngineRegister("rfb.secresult", + ALPROTO_RFB, SIG_FLAG_TOCLIENT, 1, + DetectEngineInspectRfbSecresultGeneric); + + rfb_secresult_id = DetectBufferTypeGetByName("rfb.secresult"); +} + +static int DetectEngineInspectRfbSecresultGeneric(ThreadVars *tv, + DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx, + const Signature *s, const SigMatchData *smd, + Flow *f, uint8_t flags, void *alstate, + void *txv, uint64_t tx_id) +{ + return DetectEngineInspectGenericList(tv, de_ctx, det_ctx, s, smd, + f, flags, alstate, txv, tx_id); +} + +enum { + RFB_SECRESULT_OK = 0, + RFB_SECRESULT_FAIL, + RFB_SECRESULT_TOOMANY, + RFB_SECRESULT_UNKNOWN +}; + +/** + * \struct DetectRfbSecresult_ + * DetectRfbSecresult_ is used to store values + */ + +struct DetectRfbSecresult_ { + const char *result; + uint16_t code; +} results[] = { + { "ok", RFB_SECRESULT_OK, }, + { "fail", RFB_SECRESULT_FAIL, }, + { "toomany", RFB_SECRESULT_TOOMANY, }, + { "unknown", RFB_SECRESULT_UNKNOWN, }, + { NULL, 0 }, +}; + +/** + * \internal + * \brief Function to match security result of a RFB TX + * + * \param det_ctx Pointer to the pattern matcher thread. + * \param f Pointer to the current flow. + * \param flags Flags. + * \param state App layer state. + * \param txv Pointer to the RFBTransaction. + * \param s Pointer to the Signature. + * \param ctx Pointer to the sigmatch that we will cast into DetectRfbSecresultData. + * + * \retval 0 no match. + * \retval 1 match. + */ +static int DetectRfbSecresultMatch(DetectEngineThreadCtx *det_ctx, + Flow *f, uint8_t flags, void *state, + void *txv, const Signature *s, + const SigMatchCtx *ctx) +{ + const DetectRfbSecresultData *de = (const DetectRfbSecresultData *)ctx; + uint32_t resultcode; + int ret = 0; + + if (!de) + return 0; + + ret = rs_rfb_tx_get_secresult(txv, &resultcode); + if (ret == 0) { + return 0; + } + + if (de->result < 3) { + /* we are asking for a defined code... */ + if (resultcode == de->result) { + /* ... which needs to match */ + return 1; + } + } else { + /* we are asking for an unknown code */ + if (resultcode > 2) { + /* match any unknown code */ + return 1; + } + } + + return 0; +} + +/** + * \internal + * \brief This function is used to parse options passed via rfb.secresults: keyword + * + * \param rawstr Pointer to the user provided secresult options + * + * \retval de pointer to DetectRfbSecresultData on success + * \retval NULL on failure + */ +static DetectRfbSecresultData *DetectRfbSecresultParse (const char *rawstr) +{ + int i; + DetectRfbSecresultData *de = NULL; +#define MAX_SUBSTRINGS 30 + int ret = 0, found = 0; + int ov[MAX_SUBSTRINGS]; + + ret = DetectParsePcreExec(&parse_regex, rawstr, 0, 0, ov, MAX_SUBSTRINGS); + if (ret < 1) { + SCLogError(SC_ERR_PCRE_MATCH, "pcre_exec parse error, ret %" PRId32 ", string %s", ret, rawstr); + goto error; + } + + for(i = 0; results[i].result != NULL; i++) { + if((strcasecmp(results[i].result,rawstr)) == 0) { + found = 1; + break; + } + } + + if(found == 0) { + SCLogError(SC_ERR_UNKNOWN_VALUE, "unknown secresult value %s", rawstr); + goto error; + } + + de = SCMalloc(sizeof(DetectRfbSecresultData)); + if (unlikely(de == NULL)) + goto error; + + de->result = results[i].code; + + return de; + +error: + if (de) SCFree(de); + return NULL; +} + +/** + * \internal + * \brief this function is used to add the parsed secresult into the current signature + * + * \param de_ctx pointer to the Detection Engine Context + * \param s pointer to the Current Signature + * \param rawstr pointer to the user provided secresult options + * + * \retval 0 on Success + * \retval -1 on Failure + */ +static int DetectRfbSecresultSetup (DetectEngineCtx *de_ctx, Signature *s, const char *rawstr) +{ + DetectRfbSecresultData *de = NULL; + SigMatch *sm = NULL; + + de = DetectRfbSecresultParse(rawstr); + if (de == NULL) + goto error; + + sm = SigMatchAlloc(); + if (sm == NULL) + goto error; + + sm->type = DETECT_AL_RFB_SECRESULT; + sm->ctx = (SigMatchCtx *)de; + + SigMatchAppendSMToList(s, sm, rfb_secresult_id); + + return 0; + +error: + if (de) SCFree(de); + if (sm) SCFree(sm); + return -1; +} + +/** + * \internal + * \brief this function will free memory associated with DetectRfbSecresultData + * + * \param de pointer to DetectRfbSecresultData + */ +void DetectRfbSecresultFree(void *de_ptr) +{ + DetectRfbSecresultData *de = (DetectRfbSecresultData *)de_ptr; + if(de) SCFree(de); +} + +/* + * ONLY TESTS BELOW THIS COMMENT + */ + +#ifdef UNITTESTS +/** + * \test RfbSecresultTestParse01 is a test for a valid secresult value + * + * \retval 1 on succces + * \retval 0 on failure + */ +static int RfbSecresultTestParse01 (void) +{ + DetectRfbSecresultData *de = NULL; + de = DetectRfbSecresultParse("fail"); + if (de) { + DetectRfbSecresultFree(de); + return 1; + } + + return 0; +} + +/** + * \test RfbSecresultTestParse02 is a test for an invalid secresult value + * + * \retval 1 on succces + * \retval 0 on failure + */ +static int RfbSecresultTestParse02 (void) +{ + DetectRfbSecresultData *de = NULL; + de = DetectRfbSecresultParse("invalidopt"); + if (de) { + DetectRfbSecresultFree(de); + return 0; + } + + return 1; +} + +#endif /* UNITTESTS */ + +/** + * \brief this function registers unit tests for RfbSecresult + */ +void RfbSecresultRegisterTests(void) +{ +#ifdef UNITTESTS + UtRegisterTest("RfbSecresultTestParse01", RfbSecresultTestParse01); + UtRegisterTest("RfbSecresultTestParse02", RfbSecresultTestParse02); +#endif /* UNITTESTS */ +} diff --git a/src/detect-rfb-secresult.h b/src/detect-rfb-secresult.h new file mode 100644 index 0000000000..d3e296d8e4 --- /dev/null +++ b/src/detect-rfb-secresult.h @@ -0,0 +1,29 @@ +/* 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. + */ + +/** + * \file + * + * \author Sascha Steinbiss + */ + +#ifndef __DETECT_RFB_SECRESULT_H__ +#define __DETECT_RFB_SECRESULT_H__ + +void DetectRfbSecresultRegister(void); + +#endif /*__DETECT_RFB_SECRESULT_H__ */ diff --git a/src/detect-rfb-sectype.c b/src/detect-rfb-sectype.c new file mode 100644 index 0000000000..17edd73d57 --- /dev/null +++ b/src/detect-rfb-sectype.c @@ -0,0 +1,283 @@ +/* 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. + */ + +/** + * + * \author Sascha Steinbiss + */ + +#include "suricata-common.h" +#include "conf.h" +#include "detect.h" +#include "detect-parse.h" +#include "detect-engine.h" +#include "detect-engine-content-inspection.h" +#include "detect-rfb-sectype.h" +#include "app-layer-parser.h" +#include "util-byte.h" + +#include "rust-bindings.h" + +/** + * [rfb.sectype]:[<|>|<=|>=]; + */ +#define PARSE_REGEX "^\\s*(<=|>=|<|>)?\\s*([0-9]+)\\s*$" +static DetectParseRegex parse_regex; + +enum DetectRfbSectypeCompareMode { + PROCEDURE_EQ = 1, /* equal */ + PROCEDURE_LT, /* less than */ + PROCEDURE_LE, /* less than or equal */ + PROCEDURE_GT, /* greater than */ + PROCEDURE_GE, /* greater than or equal */ +}; + +typedef struct { + uint32_t version; + enum DetectRfbSectypeCompareMode mode; +} DetectRfbSectypeData; + +static DetectRfbSectypeData *DetectRfbSectypeParse (const char *); +static int DetectRfbSectypeSetup (DetectEngineCtx *, Signature *s, const char *str); +static void DetectRfbSectypeFree(void *); +static int g_rfb_sectype_buffer_id = 0; + +static int DetectEngineInspectRfbSectypeGeneric(ThreadVars *tv, + DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx, + const Signature *s, const SigMatchData *smd, + Flow *f, uint8_t flags, void *alstate, + void *txv, uint64_t tx_id); + +static int DetectRfbSectypeMatch (DetectEngineThreadCtx *, Flow *, + uint8_t, void *, void *, const Signature *, + const SigMatchCtx *); + +/** + * \brief Registration function for rfb.sectype keyword. + */ +void DetectRfbSectypeRegister (void) +{ + sigmatch_table[DETECT_AL_RFB_SECTYPE].name = "rfb.sectype"; + sigmatch_table[DETECT_AL_RFB_SECTYPE].desc = "match RFB security type"; + sigmatch_table[DETECT_AL_RFB_SECTYPE].url = DOC_URL DOC_VERSION "/rules/rfb-keywords.html#rfb-sectype"; + sigmatch_table[DETECT_AL_RFB_SECTYPE].AppLayerTxMatch = DetectRfbSectypeMatch; + sigmatch_table[DETECT_AL_RFB_SECTYPE].Setup = DetectRfbSectypeSetup; + sigmatch_table[DETECT_AL_RFB_SECTYPE].Free = DetectRfbSectypeFree; + + DetectSetupParseRegexes(PARSE_REGEX, &parse_regex); + + DetectAppLayerInspectEngineRegister("rfb.sectype", + ALPROTO_RFB, SIG_FLAG_TOSERVER, 1, + DetectEngineInspectRfbSectypeGeneric); + + g_rfb_sectype_buffer_id = DetectBufferTypeGetByName("rfb.sectype"); +} + +static int DetectEngineInspectRfbSectypeGeneric(ThreadVars *tv, + DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx, + const Signature *s, const SigMatchData *smd, + Flow *f, uint8_t flags, void *alstate, + void *txv, uint64_t tx_id) +{ + return DetectEngineInspectGenericList(tv, de_ctx, det_ctx, s, smd, + f, flags, alstate, txv, tx_id); +} + +static inline int SectypeMatch(const uint32_t version, + enum DetectRfbSectypeCompareMode mode, uint32_t ref_version) +{ + switch (mode) { + case PROCEDURE_EQ: + if (version == ref_version) + SCReturnInt(1); + break; + case PROCEDURE_LT: + if (version < ref_version) + SCReturnInt(1); + break; + case PROCEDURE_LE: + if (version <= ref_version) + SCReturnInt(1); + break; + case PROCEDURE_GT: + if (version > ref_version) + SCReturnInt(1); + break; + case PROCEDURE_GE: + if (version >= ref_version) + SCReturnInt(1); + break; + } + SCReturnInt(0); +} + +/** + * \internal + * \brief Function to match security type of a RFB TX + * + * \param det_ctx Pointer to the pattern matcher thread. + * \param f Pointer to the current flow. + * \param flags Flags. + * \param state App layer state. + * \param txv Pointer to the RFBTransaction. + * \param s Pointer to the Signature. + * \param ctx Pointer to the sigmatch that we will cast into DetectRfbSectypeData. + * + * \retval 0 no match. + * \retval 1 match. + */ +static int DetectRfbSectypeMatch (DetectEngineThreadCtx *det_ctx, + Flow *f, uint8_t flags, void *state, + void *txv, const Signature *s, + const SigMatchCtx *ctx) +{ + SCEnter(); + + const DetectRfbSectypeData *dd = (const DetectRfbSectypeData *)ctx; + uint32_t version; + rs_rfb_tx_get_sectype(txv, &version); + if (SectypeMatch(version, dd->mode, dd->version)) + SCReturnInt(1); + SCReturnInt(0); +} + +/** + * \internal + * \brief Function to parse options passed via rfb.sectype keywords. + * + * \param rawstr Pointer to the user provided options. + * + * \retval dd pointer to DetectRfbSectypeData on success. + * \retval NULL on failure. + */ +static DetectRfbSectypeData *DetectRfbSectypeParse (const char *rawstr) +{ + DetectRfbSectypeData *dd = NULL; +#define MAX_SUBSTRINGS 30 + int ret = 0, res = 0; + int ov[MAX_SUBSTRINGS]; + char mode[2] = ""; + char value1[20] = ""; + + ret = DetectParsePcreExec(&parse_regex, rawstr, 0, 0, ov, MAX_SUBSTRINGS); + if (ret < 3 || ret > 5) { + SCLogError(SC_ERR_PCRE_MATCH, "Parse error %s", rawstr); + goto error; + } + + res = pcre_copy_substring((char *)rawstr, ov, MAX_SUBSTRINGS, 1, mode, + sizeof(mode)); + if (res < 0) { + SCLogError(SC_ERR_PCRE_GET_SUBSTRING, "pcre_copy_substring failed"); + goto error; + } + + res = pcre_copy_substring((char *)rawstr, ov, MAX_SUBSTRINGS, 2, value1, + sizeof(value1)); + if (res < 0) { + SCLogError(SC_ERR_PCRE_GET_SUBSTRING, "pcre_copy_substring failed"); + goto error; + } + + dd = SCCalloc(1, sizeof(DetectRfbSectypeData)); + if (unlikely(dd == NULL)) + goto error; + + if (strlen(mode) == 0) { + dd->mode = PROCEDURE_EQ; + } else if (strlen(mode) == 1) { + if (mode[0] == '<') + dd->mode = PROCEDURE_LT; + else if (mode[0] == '>') + dd->mode = PROCEDURE_GT; + } else if (strlen(mode) == 2) { + if (strcmp(mode, "<=") == 0) + dd->mode = PROCEDURE_LE; + if (strcmp(mode, ">=") == 0) + dd->mode = PROCEDURE_GE; + } else { + SCLogError(SC_ERR_INVALID_SIGNATURE, "invalid mode for rfb.sectype keyword"); + goto error; + } + + if (dd->mode == 0) { + dd->mode = PROCEDURE_EQ; + } + + /* set the first value */ + if (ByteExtractStringUint32(&dd->version, 10, strlen(value1), value1) <= 0) { + SCLogError(SC_ERR_INVALID_SIGNATURE, "invalid character as arg " + "to rfb.sectype keyword"); + goto error; + } + + return dd; + +error: + if (dd) + SCFree(dd); + return NULL; +} + +/** + * \brief Function to add the parsed RFB security type field into the current signature. + * + * \param de_ctx Pointer to the Detection Engine Context. + * \param s Pointer to the Current Signature. + * \param rawstr Pointer to the user provided flags options. + * + * \retval 0 on Success. + * \retval -1 on Failure. + */ +static int DetectRfbSectypeSetup (DetectEngineCtx *de_ctx, Signature *s, const char *rawstr) +{ + if (DetectSignatureSetAppProto(s, ALPROTO_RFB) != 0) + return -1; + + DetectRfbSectypeData *dd = DetectRfbSectypeParse(rawstr); + if (dd == NULL) { + SCLogError(SC_ERR_INVALID_ARGUMENT,"Parsing \'%s\' failed", rawstr); + goto error; + } + + /* okay so far so good, lets get this into a SigMatch + * and put it in the Signature. */ + SigMatch *sm = SigMatchAlloc(); + if (sm == NULL) + goto error; + + sm->type = DETECT_AL_RFB_SECTYPE; + sm->ctx = (void *)dd; + + SigMatchAppendSMToList(s, sm, g_rfb_sectype_buffer_id); + return 0; + +error: + DetectRfbSectypeFree(dd); + return -1; +} + +/** + * \internal + * \brief Function to free memory associated with DetectRfbSectypeData. + * + * \param de_ptr Pointer to DetectRfbSectypeData. + */ +static void DetectRfbSectypeFree(void *ptr) +{ + SCFree(ptr); +} diff --git a/src/detect-rfb-sectype.h b/src/detect-rfb-sectype.h new file mode 100644 index 0000000000..15a1f801de --- /dev/null +++ b/src/detect-rfb-sectype.h @@ -0,0 +1,29 @@ +/* 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. + */ + +/** + * \file + * + * \author Sascha Steinbiss + */ + +#ifndef __DETECT_RFB_SECTYPE_H__ +#define __DETECT_RFB_SECTYPE_H__ + +void DetectRfbSectypeRegister(void); + +#endif /* __DETECT_RFB_SECTYPE_H__ */ diff --git a/src/output-json-alert.c b/src/output-json-alert.c index 24cd50d681..e72c1701c3 100644 --- a/src/output-json-alert.c +++ b/src/output-json-alert.c @@ -67,6 +67,7 @@ #include "output-json-smb.h" #include "output-json-flow.h" #include "output-json-sip.h" +#include "output-json-rfb.h" #include "util-byte.h" #include "util-privs.h" @@ -471,6 +472,11 @@ static int AlertJson(ThreadVars *tv, JsonAlertLogThread *aft, const Packet *p) if (hjs) json_object_set_new(js, "sip", hjs); break; + case ALPROTO_RFB: + hjs = JsonRFBAddMetadata(p->flow, pa->tx_id); + if (hjs) + json_object_set_new(js, "rfb", hjs); + break; case ALPROTO_FTPDATA: hjs = JsonFTPDataAddMetadata(p->flow); if (hjs) diff --git a/src/output-json-rfb.c b/src/output-json-rfb.c new file mode 100644 index 0000000000..1067b0a653 --- /dev/null +++ b/src/output-json-rfb.c @@ -0,0 +1,178 @@ +/* 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. + */ + +/** + * \file + * + * \author Frank Honza + * + * Implement JSON/eve logging app-layer RFB. + */ + +#include "suricata-common.h" +#include "conf.h" + +#include "threads.h" +#include "threadvars.h" +#include "tm-threads.h" + +#include "util-unittest.h" +#include "util-buffer.h" +#include "util-debug.h" +#include "util-byte.h" + +#include "output.h" +#include "output-json.h" + +#include "app-layer.h" +#include "app-layer-parser.h" + +#include "app-layer-rfb.h" +#include "output-json-rfb.h" + +#include "rust-bindings.h" + +typedef struct LogRFBFileCtx_ { + LogFileCtx *file_ctx; + uint32_t flags; +} LogRFBFileCtx; + +typedef struct LogRFBLogThread_ { + LogRFBFileCtx *rfblog_ctx; + uint32_t count; + MemBuffer *buffer; +} LogRFBLogThread; + +json_t *JsonRFBAddMetadata(const Flow *f, uint64_t tx_id) +{ + RFBState *state = FlowGetAppState(f); + if (state) { + RFBTransaction *tx = AppLayerParserGetTx(f->proto, ALPROTO_RFB, state, tx_id); + if (tx) { + return rs_rfb_logger_log(state, tx); + } + } + + return NULL; +} + +static int JsonRFBLogger(ThreadVars *tv, void *thread_data, + const Packet *p, Flow *f, void *state, void *tx, uint64_t tx_id) +{ + LogRFBLogThread *thread = thread_data; + + json_t *js = CreateJSONHeader(p, LOG_DIR_FLOW, "rfb"); + if (unlikely(js == NULL)) { + return TM_ECODE_FAILED; + } + + json_t *rfb_js = rs_rfb_logger_log(NULL, tx); + if (unlikely(rfb_js == NULL)) { + goto error; + } + json_object_set_new(js, "rfb", rfb_js); + + MemBufferReset(thread->buffer); + OutputJSONBuffer(js, thread->rfblog_ctx->file_ctx, &thread->buffer); + json_decref(js); + + return TM_ECODE_OK; + +error: + json_decref(js); + return TM_ECODE_FAILED; +} + +static void OutputRFBLogDeInitCtxSub(OutputCtx *output_ctx) +{ + LogRFBFileCtx *rfblog_ctx = (LogRFBFileCtx *)output_ctx->data; + SCFree(rfblog_ctx); + SCFree(output_ctx); +} + +static OutputInitResult OutputRFBLogInitSub(ConfNode *conf, + OutputCtx *parent_ctx) +{ + OutputInitResult result = { NULL, false }; + OutputJsonCtx *ajt = parent_ctx->data; + + LogRFBFileCtx *rfblog_ctx = SCCalloc(1, sizeof(*rfblog_ctx)); + if (unlikely(rfblog_ctx == NULL)) { + return result; + } + rfblog_ctx->file_ctx = ajt->file_ctx; + + OutputCtx *output_ctx = SCCalloc(1, sizeof(*output_ctx)); + if (unlikely(output_ctx == NULL)) { + SCFree(rfblog_ctx); + return result; + } + output_ctx->data = rfblog_ctx; + output_ctx->DeInit = OutputRFBLogDeInitCtxSub; + + AppLayerParserRegisterLogger(IPPROTO_TCP, ALPROTO_RFB); + + result.ctx = output_ctx; + result.ok = true; + return result; +} + +static TmEcode JsonRFBLogThreadInit(ThreadVars *t, const void *initdata, void **data) +{ + if (initdata == NULL) { + SCLogDebug("Error getting context for EveLogRFB. \"initdata\" is NULL."); + return TM_ECODE_FAILED; + } + + LogRFBLogThread *thread = SCCalloc(1, sizeof(*thread)); + if (unlikely(thread == NULL)) { + return TM_ECODE_FAILED; + } + + thread->buffer = MemBufferCreateNew(JSON_OUTPUT_BUFFER_SIZE); + if (unlikely(thread->buffer == NULL)) { + SCFree(thread); + return TM_ECODE_FAILED; + } + + thread->rfblog_ctx = ((OutputCtx *)initdata)->data; + *data = (void *)thread; + + return TM_ECODE_OK; +} + +static TmEcode JsonRFBLogThreadDeinit(ThreadVars *t, void *data) +{ + LogRFBLogThread *thread = (LogRFBLogThread *)data; + if (thread == NULL) { + return TM_ECODE_OK; + } + if (thread->buffer != NULL) { + MemBufferFree(thread->buffer); + } + SCFree(thread); + return TM_ECODE_OK; +} + +void JsonRFBLogRegister(void) +{ + /* Register as an eve sub-module. */ + OutputRegisterTxSubModule(LOGGER_JSON_RFB, "eve-log", + "JsonRFBLog", "eve-log.rfb", + OutputRFBLogInitSub, ALPROTO_RFB, JsonRFBLogger, + JsonRFBLogThreadInit, JsonRFBLogThreadDeinit, NULL); +} diff --git a/src/output-json-rfb.h b/src/output-json-rfb.h new file mode 100644 index 0000000000..bdb8c5b114 --- /dev/null +++ b/src/output-json-rfb.h @@ -0,0 +1,31 @@ +/* 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. + */ + +/** + * \file + * + * \author Frank Honza + */ + +#ifndef __OUTPUT_JSON_RFB_H__ +#define __OUTPUT_JSON_RFB_H__ + +void JsonRFBLogRegister(void); + +json_t *JsonRFBAddMetadata(const Flow *f, uint64_t tx_id); + +#endif /* __OUTPUT_JSON_RFB_H__ */ diff --git a/src/output.c b/src/output.c index 82c927f6ea..9dd643fc26 100644 --- a/src/output.c +++ b/src/output.c @@ -75,6 +75,7 @@ #include "output-json-dhcp.h" #include "output-json-snmp.h" #include "output-json-sip.h" +#include "output-json-rfb.h" #include "output-json-template.h" #include "output-json-template-rust.h" #include "output-json-rdp.h" @@ -1157,6 +1158,8 @@ void OutputRegisterLoggers(void) JsonSNMPLogRegister(); /* SIP JSON logger. */ JsonSIPLogRegister(); + /* RFB JSON logger. */ + JsonRFBLogRegister(); /* Template JSON logger. */ JsonTemplateLogRegister(); /* Template Rust JSON logger. */ diff --git a/src/suricata-common.h b/src/suricata-common.h index f9655eaccc..6bbd224add 100644 --- a/src/suricata-common.h +++ b/src/suricata-common.h @@ -440,6 +440,7 @@ typedef enum { LOGGER_JSON_SNMP, LOGGER_JSON_SIP, LOGGER_JSON_TEMPLATE_RUST, + LOGGER_JSON_RFB, LOGGER_JSON_TEMPLATE, LOGGER_JSON_RDP, diff --git a/src/util-profiling.c b/src/util-profiling.c index 3fbcd6b6a4..7a590a5573 100644 --- a/src/util-profiling.c +++ b/src/util-profiling.c @@ -1320,6 +1320,7 @@ const char * PacketProfileLoggertIdToString(LoggerId id) CASE_CODE (LOGGER_JSON_TLS); CASE_CODE (LOGGER_JSON_SIP); CASE_CODE (LOGGER_JSON_TEMPLATE_RUST); + CASE_CODE (LOGGER_JSON_RFB); CASE_CODE (LOGGER_JSON_TEMPLATE); CASE_CODE (LOGGER_JSON_RDP); CASE_CODE (LOGGER_TLS_STORE); diff --git a/suricata.yaml.in b/suricata.yaml.in index eb94f6c7ce..4890729498 100644 --- a/suricata.yaml.in +++ b/suricata.yaml.in @@ -263,6 +263,7 @@ outputs: - ikev2 - krb5 - snmp + - rfb #- sip - dhcp: enabled: yes @@ -711,6 +712,10 @@ pcap-file: # "detection-only" enables protocol detection only (parser disabled). app-layer: protocols: + rfb: + enabled: yes + detection-ports: + dp: 5900, 5901, 5902, 5903, 5904, 5905, 5906, 5907, 5908, 5909 krb5: enabled: yes snmp: