"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
+ }
+ }
snmp-keywords
base64-keywords
sip-keywords
+ rfb-keywords
app-layer
xbits
thresholding
--- /dev/null
+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:
+`<https://tools.ietf.org/html/rfc6143>`_
pub mod tftp;
pub mod dhcp;
pub mod sip;
+pub mod rfb;
pub mod applayertemplate;
pub mod rdp;
--- /dev/null
+/* 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 <frank.honza@dcso.de>
+
+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
--- /dev/null
+/* 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 <frank.honza@dcso.de>
+
+use std;
+use std::fmt::Write;
+use crate::json::*;
+use super::rfb::{RFBState, RFBTransaction};
+
+fn log_rfb(tx: &RFBTransaction) -> Option<Json> {
+ 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(),
+ }
+}
--- /dev/null
+/* 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 <frank.honza@dcso.de>
+
+pub mod detect;
+pub mod logger;
+pub mod parser;
+pub mod rfb;
\ No newline at end of file
--- /dev/null
+/* 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 <frank.honza@dcso.de>
+
+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<u8>
+}
+
+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<u8>
+}
+
+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<u8>
+}
+
+named!(pub parse_protocol_version<ProtocolVersion>,
+ 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<SupportedSecurityTypes>,
+ 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<ServerSecurityType>,
+ do_parse!(
+ security_type: be_u32
+ >> (
+ ServerSecurityType{
+ security_type: security_type,
+ }
+ )
+ )
+);
+
+named!(pub parse_vnc_auth<VncAuth>,
+ do_parse!(
+ secret: take!(16)
+ >> (
+ VncAuth {
+ secret: secret.to_vec()
+ }
+ )
+ )
+);
+
+named!(pub parse_security_type_selection<SecurityTypeSelection>,
+ do_parse!(
+ security_type: be_u8
+ >> (
+ SecurityTypeSelection {
+ security_type: security_type
+ }
+ )
+ )
+);
+
+named!(pub parse_security_result<SecurityResult>,
+ do_parse!(
+ status: be_u32
+ >> (
+ SecurityResult {
+ status: status
+ }
+ )
+ )
+);
+
+named!(pub parse_failure_reason<FailureReason>,
+ do_parse!(
+ reason_length: be_u32
+ >> reason_string: take_str!(reason_length)
+ >> (
+ FailureReason {
+ reason_string: reason_string.to_string()
+ }
+ )
+ )
+);
+
+named!(pub parse_client_init<ClientInit>,
+ do_parse!(
+ shared: be_u8
+ >> (
+ ClientInit {
+ shared: shared
+ }
+ )
+ )
+);
+
+named!(pub parse_pixel_format<PixelFormat>,
+ 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<ServerInit>,
+ 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);
+ }
+ }
+ }
+
+}
--- /dev/null
+/* 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 <frank.honza@dcso.de>
+
+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<u32>,
+
+ pub tc_server_protocol_version: Option<parser::ProtocolVersion>,
+ pub ts_client_protocol_version: Option<parser::ProtocolVersion>,
+ pub tc_supported_security_types: Option<parser::SupportedSecurityTypes>,
+ pub ts_security_type_selection: Option<parser::SecurityTypeSelection>,
+ pub tc_server_security_type: Option<parser::ServerSecurityType>,
+ pub tc_vnc_challenge: Option<parser::VncAuth>,
+ pub ts_vnc_response: Option<parser::VncAuth>,
+ pub ts_client_init: Option<parser::ClientInit>,
+ pub tc_security_result: Option<parser::SecurityResult>,
+ pub tc_failure_reason: Option<parser::FailureReason>,
+ pub tc_server_init: Option<parser::ServerInit>,
+
+ 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<RFBTransaction>,
+ 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::<String>()
+ );
+
+ 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<RFBState> = 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.");
+ }
+}
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 \
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 \
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 \
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)
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)
#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"
RegisterSNMPParsers();
RegisterSIPParsers();
RegisterTemplateRustParsers();
+ RegisterRFBParsers();
RegisterTemplateParsers();
RegisterRdpParsers();
case ALPROTO_SIP:
proto_name = "sip";
break;
+ case ALPROTO_RFB:
+ proto_name = "rfb";
+ break;
case ALPROTO_TEMPLATE:
proto_name = "template";
break;
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;
ALPROTO_DHCP,
ALPROTO_SNMP,
ALPROTO_SIP,
+ ALPROTO_RFB,
ALPROTO_TEMPLATE,
ALPROTO_TEMPLATE_RUST,
ALPROTO_RDP,
--- /dev/null
+/* 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 <sascha.steinbiss@dcso.de>
+ *
+ * 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
--- /dev/null
+/* 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 <sascha.steinbiss@dcso.de>
+ */
+
+#ifndef __APP_LAYER_RFB_H__
+#define __APP_LAYER_RFB_H__
+
+void RegisterRFBParsers(void);
+
+#endif /* __APP_LAYER_RFB_H__ */
#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"
DetectSipStatMsgRegister();
DetectSipRequestLineRegister();
DetectSipResponseLineRegister();
+ DetectRfbSecresultRegister();
+ DetectRfbSectypeRegister();
+ DetectRfbNameRegister();
DetectTargetRegister();
DetectTemplateRustBufferRegister();
DetectSNMPVersionRegister();
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,
--- /dev/null
+/* 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 <sascha.steinbiss@dcso.de>
+ */
+
+#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");
+}
--- /dev/null
+/* 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__ */
--- /dev/null
+/* 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 <sascha.steinbiss@dcso.de>
+ */
+
+#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 */
+}
--- /dev/null
+/* 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 <sascha.steinbiss@dcso.de>
+ */
+
+#ifndef __DETECT_RFB_SECRESULT_H__
+#define __DETECT_RFB_SECRESULT_H__
+
+void DetectRfbSecresultRegister(void);
+
+#endif /*__DETECT_RFB_SECRESULT_H__ */
--- /dev/null
+/* 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 <sascha.steinbiss@dcso.de>
+ */
+
+#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]:[<|>|<=|>=]<type>;
+ */
+#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);
+}
--- /dev/null
+/* 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 <sascha.steinbiss@dcso.de>
+ */
+
+#ifndef __DETECT_RFB_SECTYPE_H__
+#define __DETECT_RFB_SECTYPE_H__
+
+void DetectRfbSectypeRegister(void);
+
+#endif /* __DETECT_RFB_SECTYPE_H__ */
#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"
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)
--- /dev/null
+/* 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 <frank.honza@dcso.de>
+ *
+ * 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);
+}
--- /dev/null
+/* 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 <frank.honza@dcso.de>
+ */
+
+#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__ */
#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"
JsonSNMPLogRegister();
/* SIP JSON logger. */
JsonSIPLogRegister();
+ /* RFB JSON logger. */
+ JsonRFBLogRegister();
/* Template JSON logger. */
JsonTemplateLogRegister();
/* Template Rust JSON logger. */
LOGGER_JSON_SNMP,
LOGGER_JSON_SIP,
LOGGER_JSON_TEMPLATE_RUST,
+ LOGGER_JSON_RFB,
LOGGER_JSON_TEMPLATE,
LOGGER_JSON_RDP,
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);
- ikev2
- krb5
- snmp
+ - rfb
#- sip
- dhcp:
enabled: yes
# "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: