Skip to content

Commit

Permalink
socks: initial minimal parser and logger
Browse files Browse the repository at this point in the history
Ticket: #4965.
  • Loading branch information
victorjulien committed Dec 19, 2024
1 parent fe00412 commit d9416c3
Show file tree
Hide file tree
Showing 11 changed files with 990 additions and 0 deletions.
17 changes: 17 additions & 0 deletions etc/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -4495,6 +4495,11 @@
},
"additionalProperties": false
},
"socks": {
"type": "object",
"optional": true,
"additionalProperties": true
},
"ssh": {
"type": "object",
"optional": true,
Expand Down Expand Up @@ -4732,6 +4737,10 @@
"description": "Errors encountered parsing SNMP",
"$ref": "#/$defs/stats_applayer_error"
},
"socks": {
"description": "Errors encountered parsing SOCKS",
"$ref": "#/$defs/stats_applayer_error"
},
"ssh": {
"description": "Errors encountered parsing SSH protocol",
"$ref": "#/$defs/stats_applayer_error"
Expand Down Expand Up @@ -4907,6 +4916,10 @@
"description": "Number of flows for SNMP",
"type": "integer"
},
"socks": {
"description": "Number of flows for SOCKS",
"type": "integer"
},
"ssh": {
"description": "Number of flows for SSH protocol",
"type": "integer"
Expand Down Expand Up @@ -5077,6 +5090,10 @@
"description": "Number of transactions for SNMP",
"type": "integer"
},
"socks": {
"description": "Number of transactions for SOCKS",
"type": "integer"
},
"ssh": {
"description": "Number of transactions for SSH protocol",
"type": "integer"
Expand Down
1 change: 1 addition & 0 deletions rust/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ pub mod pgsql;
pub mod telnet;
pub mod websocket;
pub mod enip;
pub mod socks;
pub mod applayertemplate;
pub mod rdp;
pub mod x509;
Expand Down
98 changes: 98 additions & 0 deletions rust/src/socks/logger.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/* Copyright (C) 2024 Open Information Security Foundation
*
* You can copy, redistribute or modify this Program under the terms of
* the GNU General Public License version 2 as published by the Free
* Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* version 2 along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/

use super::socks::SocksTransaction;
use crate::dns::log::dns_print_addr;
use crate::jsonbuilder::{JsonBuilder, JsonError};
use std;

fn auth_method_string(m: u8) -> String {
match m {
0 => "No authentication",
1 => "GSSAPI",
2 => "Username/Password",
_ => {
return m.to_string();
}
}
.to_string()
}

fn status_string(m: u8) -> String {
match m {
0 => "Success",
_ => {
return m.to_string();
}
}
.to_string()
}

fn log_socks(tx: &SocksTransaction, js: &mut JsonBuilder) -> Result<(), JsonError> {
js.open_object("socks")?;
if let Some(ref connect) = tx.connect {
js.open_object("connect")?;
if let Some(ref domain) = &connect.domain {
let domain = String::from_utf8_lossy(domain);
js.set_string("domain", &domain)?;
}
if let Some(ref ipv4) = &connect.ipv4 {
js.set_string("ipv4", &dns_print_addr(ipv4))?;
}
js.set_uint("port", connect.port as u64)?;
if let Some(status) = connect.response {
js.set_string("response", &status_string(status))?;
}
js.close()?;
}
if let Some(ref auth) = tx.auth_userpass {
js.open_object("auth_userpass")?;
js.set_uint("subnegotiation_version", auth.subver as u64)?;
let user = String::from_utf8_lossy(&auth.user);
js.set_string("user", &user)?;
// TODO needs to be optional and disabled by default
let pass = String::from_utf8_lossy(&auth.pass);
js.set_string("pass", &pass)?;
if let Some(status) = auth.response {
js.set_string("response", &status_string(status))?;
}
js.close()?;
}
if let Some(ref auth_methods) = tx.auth_methods {
js.open_object("auth_methods")?;
js.open_array("request")?;
for m in &auth_methods.request_methods {
js.append_string(&auth_method_string(*m))?;
}
js.close()?;
js.set_string(
"response",
&auth_method_string(auth_methods.response_method),
)?;
js.close()?;
}
js.close()?;
Ok(())
}

#[no_mangle]
pub unsafe extern "C" fn SCSocksLogger(
tx: *mut std::os::raw::c_void, js: &mut JsonBuilder,
) -> bool {
let tx = cast_pointer!(tx, SocksTransaction);
log_socks(tx, js).is_ok()
}
22 changes: 22 additions & 0 deletions rust/src/socks/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/* Copyright (C) 2024 Open Information Security Foundation
*
* You can copy, redistribute or modify this Program under the terms of
* the GNU General Public License version 2 as published by the Free
* Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* version 2 along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/

//! Application layer socks parser and logger module.
pub mod logger;
mod parser;
pub mod socks;
161 changes: 161 additions & 0 deletions rust/src/socks/parser.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
/* Copyright (C) 2024 Open Information Security Foundation
*
* You can copy, redistribute or modify this Program under the terms of
* the GNU General Public License version 2 as published by the Free
* Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* version 2 along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/

use nom7::{
bytes::streaming::take,
combinator::verify,
error::{make_error, ErrorKind},
multi::count,
number::streaming::{be_u16, be_u8},
Err, IResult,
};

pub struct SocksConnectRequest {
pub _ver: u8,
pub auth_methods: Vec<u8>,
}

#[derive(Debug)]
pub struct SocksAuthRequest<'a> {
pub subver: u8,
pub user: &'a [u8],
pub pass: &'a [u8],
}

pub fn parse_connect_request(i: &[u8]) -> IResult<&[u8], SocksConnectRequest> {
let (i, ver) = verify(be_u8, |&v| v == 5)(i)?;
let (i, n) = be_u8(i)?;
let (i, auth_methods_vec) = count(be_u8, n as usize)(i)?;
let record = SocksConnectRequest {
_ver: ver,
auth_methods: auth_methods_vec,
};
Ok((i, record))
}

pub fn parse_connect_response(i: &[u8]) -> IResult<&[u8], u8> {
let (i, _ver) = verify(be_u8, |&v| v == 5)(i)?;
let (i, method) = be_u8(i)?;
Ok((i, method))
}

pub fn parse_auth_request(i: &[u8]) -> IResult<&[u8], SocksAuthRequest> {
let (i, subver) = verify(be_u8, |&v| v == 1)(i)?;
let (i, len) = be_u8(i)?;
let (i, user) = take(len)(i)?;
let (i, len) = be_u8(i)?;
let (i, pass) = take(len)(i)?;
let record = SocksAuthRequest { subver, user, pass };
Ok((i, record))
}

pub fn parse_auth_response(i: &[u8]) -> IResult<&[u8], u8> {
let (i, _subver) = be_u8(i)?;
let (i, status) = be_u8(i)?;
Ok((i, status))
}

pub struct SocksConnectCommandRequest {
pub domain: Option<Vec<u8>>,
pub ipv4: Option<Vec<u8>>,
/// TODO
pub _ipv6: Option<Vec<u8>>,
pub port: u16,
}

fn parse_connect_command_request_ipv4(i: &[u8]) -> IResult<&[u8], &[u8]> {
let (i, dst) = take(4_usize)(i)?;
Ok((i, dst))
}

fn parse_connect_command_request_ipv6(i: &[u8]) -> IResult<&[u8], &[u8]> {
let (i, dst) = take(16_usize)(i)?;
Ok((i, dst))
}

fn parse_connect_command_request_domain(i: &[u8]) -> IResult<&[u8], &[u8]> {
let (i, dlen) = be_u8(i)?; // domain
let (i, domain) = take(dlen)(i)?;
Ok((i, domain))
}

pub fn parse_connect_command_request(i: &[u8]) -> IResult<&[u8], SocksConnectCommandRequest> {
let (i, _ver) = verify(be_u8, |&v| v == 5)(i)?;
let (i, _cmd) = verify(be_u8, |&v| v == 1)(i)?;
let (i, _res) = verify(be_u8, |&v| v == 0)(i)?;
// RFC 1928 defines: 1: ipv4, 3: domain, 4: ipv6. Consider all else invalid.
let (i, t) = verify(be_u8, |&v| v == 1 || v == 3 || v == 4)(i)?;
let (i, dst) = if t == 1 {
parse_connect_command_request_ipv4(i)?
} else if t == 3 {
parse_connect_command_request_domain(i)?
} else if t == 4 {
parse_connect_command_request_ipv6(i)?
} else {
return Err(Err::Error(make_error(i, ErrorKind::Verify)));
};
let (i, port) = be_u16(i)?;

let record = if t == 1 {
SocksConnectCommandRequest {
domain: None,
ipv4: Some(dst.to_vec()),
_ipv6: None,
port,
}
} else if t == 3 {
SocksConnectCommandRequest {
domain: Some(dst.to_vec()),
ipv4: None,
_ipv6: None,
port,
}
} else if t == 4 {
SocksConnectCommandRequest {
domain: None,
ipv4: None,
_ipv6: Some(dst.to_vec()),
port,
}
} else {
return Err(Err::Error(make_error(i, ErrorKind::Verify)));
};
Ok((i, record))
}

pub struct SocksConnectCommandResponse<'a> {
pub results: u8,
pub _address_type: u8,
pub _address: &'a [u8],
pub _port: u16,
}

pub fn parse_connect_command_response(i: &[u8]) -> IResult<&[u8], SocksConnectCommandResponse> {
let (i, _ver) = verify(be_u8, |&v| v == 5)(i)?;
let (i, results) = be_u8(i)?;
let (i, _res) = verify(be_u8, |&v| v == 0)(i)?;
let (i, at) = verify(be_u8, |&v| v == 1)(i)?; // domain
let (i, address) = take(4usize)(i)?;
let (i, port) = be_u16(i)?;
let record = SocksConnectCommandResponse {
results,
_address_type: at,
_address: address,
_port: port,
};
Ok((i, record))
}
Loading

0 comments on commit d9416c3

Please sign in to comment.