From c6d24297d3ee972aff22141e54cc3634e8cae8cd Mon Sep 17 00:00:00 2001 From: Philippe Antoine Date: Thu, 3 Apr 2025 16:25:19 +0200 Subject: [PATCH 1/2] lua: convert ssh function into suricata.ssh lib Ticket: 7607 --- doc/userguide/lua/libs/index.rst | 1 + doc/userguide/lua/libs/ssh.rst | 91 +++++++++++ doc/userguide/lua/lua-functions.rst | 65 -------- src/detect-lua-extensions.c | 2 - src/detect-ssh-proto.c | 7 + src/output-lua.c | 2 - src/util-lua-builtins.c | 2 + src/util-lua-ssh.c | 235 ++++++++++------------------ src/util-lua-ssh.h | 4 +- 9 files changed, 183 insertions(+), 226 deletions(-) create mode 100644 doc/userguide/lua/libs/ssh.rst diff --git a/doc/userguide/lua/libs/index.rst b/doc/userguide/lua/libs/index.rst index 281cfac9adf4..6847bcc00da5 100644 --- a/doc/userguide/lua/libs/index.rst +++ b/doc/userguide/lua/libs/index.rst @@ -14,3 +14,4 @@ environment without access to additional modules. hashlib http packetlib + ssh diff --git a/doc/userguide/lua/libs/ssh.rst b/doc/userguide/lua/libs/ssh.rst new file mode 100644 index 000000000000..555fd05a9728 --- /dev/null +++ b/doc/userguide/lua/libs/ssh.rst @@ -0,0 +1,91 @@ +SSH +--- + +SSH transaction details are exposes to Lua scripts with the +``suricata.ssh`` library, For example:: + + local ssh = require("suricata.ssh") + +Setup +^^^^^ + +If your purpose is to create a logging script, initialize the buffer as: + +:: + + function init (args) + local needs = {} + needs["protocol"] = "ssh" + return needs + end + +If you are going to use the script for rule matching, choose one of +the available SSH buffers listed in :ref:`lua-detection` and follow +the pattern: + +:: + + function init (args) + local needs = {} + needs["ssh.server_proto"] = tostring(true) + return needs + end + +Transaction +~~~~~~~~~~~ + +SSH is transaction based, and the current transaction must be obtained before use:: + + local tx, err = ssh.get_tx() + if tx == err then + print(err) + end + +All other functions are methods on the transaction table. + +Transaction Methods +~~~~~~~~~~~~~~~~~~~ + +``server_proto()`` +^^^^^^^^^^^^^^^^^^ + +Get the ``server_proto`` value as a string. + +Example:: + + local tx = ssh.get_tx() + local proto = tx:server_proto(); + print (proto) + +``client_proto()`` +^^^^^^^^^^^^^^^^^^ + +Get the ``client_proto`` value as a string. + +Example:: + + local tx = ssh.get_tx() + local proto = tx:client_proto(); + print (proto) + +``server_software()`` +^^^^^^^^^^^^^^^^^^^^^ + +Get the ``server_software`` value as a string. + +Example:: + + local tx = ssh.get_tx() + local software = tx:server_software(); + print (software) + +``client_software()`` +^^^^^^^^^^^^^^^^^^^^^ + +Get the ``client_software`` value as a string. + +Example:: + + local tx = ssh.get_tx() + local software = tx:client_software(); + print (software) diff --git a/doc/userguide/lua/lua-functions.rst b/doc/userguide/lua/lua-functions.rst index bcc0008bad92..eae15816d12c 100644 --- a/doc/userguide/lua/lua-functions.rst +++ b/doc/userguide/lua/lua-functions.rst @@ -481,71 +481,6 @@ Initialize with: return needs end -SshGetServerProtoVersion -~~~~~~~~~~~~~~~~~~~~~~~~ - -Get SSH protocol version used by the server through SshGetServerProtoVersion. - -Example: - -:: - - function log (args) - version = SshGetServerProtoVersion() - if version == nil then - return 0 - end - end - -SshGetServerSoftwareVersion -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Get SSH software used by the server through SshGetServerSoftwareVersion. - -Example: - -:: - - function log (args) - software = SshGetServerSoftwareVersion() - if software == nil then - return 0 - end - end - -SshGetClientProtoVersion -~~~~~~~~~~~~~~~~~~~~~~~~ - -Get SSH protocol version used by the client through SshGetClientProtoVersion. - -Example: - -:: - - function log (args) - version = SshGetClientProtoVersion() - if version == nil then - return 0 - end - end - -SshGetClientSoftwareVersion -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Get SSH software used by the client through SshGetClientSoftwareVersion. - -Example: - -:: - - function log (args) - software = SshGetClientSoftwareVersion() - if software == nil then - return 0 - end - end - - HasshGet ~~~~~~~~ diff --git a/src/detect-lua-extensions.c b/src/detect-lua-extensions.c index 6b6ee3731135..4de40d9dcd80 100644 --- a/src/detect-lua-extensions.c +++ b/src/detect-lua-extensions.c @@ -43,7 +43,6 @@ #include "util-lua-dns.h" #include "util-lua-ja3.h" #include "util-lua-tls.h" -#include "util-lua-ssh.h" #include "util-lua-hassh.h" #include "util-lua-smtp.h" #include "util-lua-dnp3.h" @@ -552,7 +551,6 @@ int LuaRegisterExtensions(lua_State *lua_state) LuaRegisterFunctions(lua_state); LuaRegisterJa3Functions(lua_state); LuaRegisterTlsFunctions(lua_state); - LuaRegisterSshFunctions(lua_state); LuaRegisterHasshFunctions(lua_state); LuaRegisterSmtpFunctions(lua_state); return 0; diff --git a/src/detect-ssh-proto.c b/src/detect-ssh-proto.c index b25e57db8d26..f5339055b214 100644 --- a/src/detect-ssh-proto.c +++ b/src/detect-ssh-proto.c @@ -114,4 +114,11 @@ void DetectSshProtocolRegister(void) DetectBufferTypeSetDescriptionByName(BUFFER_NAME, BUFFER_DESC); g_buffer_id = DetectBufferTypeGetByName(BUFFER_NAME); + + /* register these generic engines for lua from here for now */ + DetectAppLayerInspectEngineRegister( + "ssh_banner", ALPROTO_SSH, SIG_FLAG_TOSERVER, 1, DetectEngineInspectGenericList, NULL); + DetectAppLayerInspectEngineRegister( + "ssh_banner", ALPROTO_SSH, SIG_FLAG_TOCLIENT, 1, DetectEngineInspectGenericList, NULL); + DetectBufferTypeSetDescriptionByName("ssh_banner", "ssh banner"); } diff --git a/src/output-lua.c b/src/output-lua.c index 2a773aa77468..2fc84aa77833 100644 --- a/src/output-lua.c +++ b/src/output-lua.c @@ -39,7 +39,6 @@ #include "util-lua-dns.h" #include "util-lua-ja3.h" #include "util-lua-tls.h" -#include "util-lua-ssh.h" #include "util-lua-hassh.h" #include "util-lua-smtp.h" @@ -593,7 +592,6 @@ static lua_State *LuaScriptSetup(const char *filename, LogLuaMasterCtx *ctx) LuaRegisterFunctions(luastate); LuaRegisterJa3Functions(luastate); LuaRegisterTlsFunctions(luastate); - LuaRegisterSshFunctions(luastate); LuaRegisterHasshFunctions(luastate); LuaRegisterSmtpFunctions(luastate); diff --git a/src/util-lua-builtins.c b/src/util-lua-builtins.c index 84eeb0178867..bdc7f32e764a 100644 --- a/src/util-lua-builtins.c +++ b/src/util-lua-builtins.c @@ -22,6 +22,7 @@ #include "util-lua-dnp3.h" #include "util-lua-http.h" #include "util-lua-dns.h" +#include "util-lua-ssh.h" #include "util-lua-flowlib.h" #include "util-lua-hashlib.h" #include "util-lua-packetlib.h" @@ -37,6 +38,7 @@ static const luaL_Reg builtins[] = { { "suricata.hashlib", SCLuaLoadHashlib }, { "suricata.http", SCLuaLoadHttpLib }, { "suricata.packet", LuaLoadPacketLib }, + { "suricata.ssh", SCLuaLoadSshLib }, { NULL, NULL }, }; diff --git a/src/util-lua-ssh.c b/src/util-lua-ssh.c index a2d6d2edaf2a..3332cc8ae5ad 100644 --- a/src/util-lua-ssh.c +++ b/src/util-lua-ssh.c @@ -24,192 +24,115 @@ */ #include "suricata-common.h" -#include "detect.h" -#include "pkt-var.h" -#include "conf.h" - -#include "threads.h" -#include "threadvars.h" -#include "tm-threads.h" - -#include "util-print.h" -#include "util-unittest.h" - -#include "util-debug.h" - -#include "output.h" -#include "app-layer.h" -#include "app-layer-parser.h" -#include "app-layer-ssh.h" -#include "util-privs.h" -#include "util-buffer.h" -#include "util-proto-name.h" -#include "util-logopenfile.h" -#include "util-time.h" -#include "rust.h" - -#include "lua.h" -#include "lualib.h" -#include "lauxlib.h" - +#include "util-lua-ssh.h" #include "util-lua.h" #include "util-lua-common.h" -#include "util-lua-ssh.h" - -static int GetServerProtoVersion(lua_State *luastate, const Flow *f) -{ - void *state = FlowGetAppState(f); - if (state == NULL) - return LuaCallbackError(luastate, "error: no app layer state"); - const uint8_t *protocol = NULL; - uint32_t b_len = 0; +#include "rust.h" - void *tx = SCSshStateGetTx(state, 0); - if (SCSshTxGetProtocol(tx, &protocol, &b_len, STREAM_TOCLIENT) != 1) - return LuaCallbackError(luastate, "error: no server proto version"); - if (protocol == NULL || b_len == 0) { - return LuaCallbackError(luastate, "error: no server proto version"); - } +// #define SSH_MT "suricata:ssh:tx" +static const char ssh_tx[] = "suricata:ssh:tx"; - return LuaPushStringBuffer(luastate, protocol, b_len); -} +struct LuaTx { + void *tx; // SSHTransaction +}; -static int SshGetServerProtoVersion(lua_State *luastate) +static int LuaSshGetTx(lua_State *L) { - int r; - - if (!(LuaStateNeedProto(luastate, ALPROTO_SSH))) - return LuaCallbackError(luastate, "error: protocol not ssh"); - - Flow *f = LuaStateGetFlow(luastate); - if (f == NULL) - return LuaCallbackError(luastate, "internal error: no flow"); + if (!(LuaStateNeedProto(L, ALPROTO_SSH))) { + return LuaCallbackError(L, "error: protocol not ssh"); + } + void *tx = LuaStateGetTX(L); + if (tx == NULL) { + return LuaCallbackError(L, "error: no tx available"); + } + struct LuaTx *ltx = (struct LuaTx *)lua_newuserdata(L, sizeof(*ltx)); + if (ltx == NULL) { + return LuaCallbackError(L, "error: fail to allocate user data"); + } + ltx->tx = tx; - r = GetServerProtoVersion(luastate, f); + luaL_getmetatable(L, ssh_tx); + lua_setmetatable(L, -2); - return r; + return 1; } -static int GetServerSoftwareVersion(lua_State *luastate, const Flow *f) +static int LuaSshTxGetProto(lua_State *L, uint8_t flags) { - void *state = FlowGetAppState(f); - if (state == NULL) - return LuaCallbackError(luastate, "error: no app layer state"); - - const uint8_t *software = NULL; + const uint8_t *buf = NULL; uint32_t b_len = 0; - - void *tx = SCSshStateGetTx(state, 0); - if (SCSshTxGetSoftware(tx, &software, &b_len, STREAM_TOCLIENT) != 1) - return LuaCallbackError(luastate, "error: no server software version"); - if (software == NULL || b_len == 0) { - return LuaCallbackError(luastate, "error: no server software version"); + struct LuaTx *ltx = luaL_testudata(L, 1, ssh_tx); + if (ltx == NULL) { + lua_pushnil(L); + return 1; } - - return LuaPushStringBuffer(luastate, software, b_len); -} - -static int SshGetServerSoftwareVersion(lua_State *luastate) -{ - int r; - - if (!(LuaStateNeedProto(luastate, ALPROTO_SSH))) - return LuaCallbackError(luastate, "error: protocol not ssh"); - - Flow *f = LuaStateGetFlow(luastate); - if (f == NULL) - return LuaCallbackError(luastate, "internal error: no flow"); - - r = GetServerSoftwareVersion(luastate, f); - - return r; + if (SCSshTxGetProtocol(ltx->tx, &buf, &b_len, flags) != 1) { + lua_pushnil(L); + return 1; + } + return LuaPushStringBuffer(L, buf, b_len); } -static int GetClientProtoVersion(lua_State *luastate, const Flow *f) +static int LuaSshTxGetServerProto(lua_State *L) { - void *state = FlowGetAppState(f); - if (state == NULL) - return LuaCallbackError(luastate, "error: no app layer state"); - - const uint8_t *protocol = NULL; - uint32_t b_len = 0; - - void *tx = SCSshStateGetTx(state, 0); - if (SCSshTxGetProtocol(tx, &protocol, &b_len, STREAM_TOSERVER) != 1) - return LuaCallbackError(luastate, "error: no client proto version"); - if (protocol == NULL || b_len == 0) { - return LuaCallbackError(luastate, "error: no client proto version"); - } - - return LuaPushStringBuffer(luastate, protocol, b_len); + return LuaSshTxGetProto(L, STREAM_TOCLIENT); } -static int SshGetClientProtoVersion(lua_State *luastate) +static int LuaSshTxGetClientProto(lua_State *L) { - int r; - - if (!(LuaStateNeedProto(luastate, ALPROTO_SSH))) - return LuaCallbackError(luastate, "error: protocol not ssh"); - - Flow *f = LuaStateGetFlow(luastate); - if (f == NULL) - return LuaCallbackError(luastate, "internal error: no flow"); - - r = GetClientProtoVersion(luastate, f); - - return r; + return LuaSshTxGetProto(L, STREAM_TOSERVER); } -static int GetClientSoftwareVersion(lua_State *luastate, const Flow *f) +static int LuaSshTxGetSoftware(lua_State *L, uint8_t flags) { - void *state = FlowGetAppState(f); - if (state == NULL) - return LuaCallbackError(luastate, "error: no app layer state"); - - const uint8_t *software = NULL; + const uint8_t *buf = NULL; uint32_t b_len = 0; - - void *tx = SCSshStateGetTx(state, 0); - if (SCSshTxGetSoftware(tx, &software, &b_len, STREAM_TOSERVER) != 1) - return LuaCallbackError(luastate, "error: no client software version"); - if (software == NULL || b_len == 0) { - return LuaCallbackError(luastate, "error: no client software version"); + struct LuaTx *ltx = luaL_testudata(L, 1, ssh_tx); + if (ltx == NULL) { + lua_pushnil(L); + return 1; } - - return LuaPushStringBuffer(luastate, software, b_len); + if (SCSshTxGetSoftware(ltx->tx, &buf, &b_len, flags) != 1) { + lua_pushnil(L); + return 1; + } + return LuaPushStringBuffer(L, buf, b_len); } -static int SshGetClientSoftwareVersion(lua_State *luastate) +static int LuaSshTxGetServerSoftware(lua_State *L) { - int r; - - if (!(LuaStateNeedProto(luastate, ALPROTO_SSH))) - return LuaCallbackError(luastate, "error: protocol not ssh"); - - Flow *f = LuaStateGetFlow(luastate); - if (f == NULL) - return LuaCallbackError(luastate, "internal error: no flow"); - - r = GetClientSoftwareVersion(luastate, f); - - return r; + return LuaSshTxGetSoftware(L, STREAM_TOCLIENT); } -/** \brief register ssh lua extensions in a luastate */ -int LuaRegisterSshFunctions(lua_State *luastate) +static int LuaSshTxGetClientSoftware(lua_State *L) { - /* registration of the callbacks */ - lua_pushcfunction(luastate, SshGetServerProtoVersion); - lua_setglobal(luastate, "SshGetServerProtoVersion"); - - lua_pushcfunction(luastate, SshGetServerSoftwareVersion); - lua_setglobal(luastate, "SshGetServerSoftwareVersion"); - - lua_pushcfunction(luastate, SshGetClientProtoVersion); - lua_setglobal(luastate, "SshGetClientProtoVersion"); + return LuaSshTxGetSoftware(L, STREAM_TOSERVER); +} - lua_pushcfunction(luastate, SshGetClientSoftwareVersion); - lua_setglobal(luastate, "SshGetClientSoftwareVersion"); +static const struct luaL_Reg txlib[] = { + // clang-format off + { "server_proto", LuaSshTxGetServerProto }, + { "server_software", LuaSshTxGetServerSoftware }, + { "client_proto", LuaSshTxGetClientProto }, + { "client_software", LuaSshTxGetClientSoftware }, + { NULL, NULL, } + // clang-format on +}; + +static const struct luaL_Reg sshlib[] = { + // clang-format off + { "get_tx", LuaSshGetTx }, + { NULL, NULL,}, + // clang-format on +}; + +int SCLuaLoadSshLib(lua_State *L) +{ + luaL_newmetatable(L, ssh_tx); + lua_pushvalue(L, -1); + lua_setfield(L, -2, "__index"); + luaL_setfuncs(L, txlib, 0); - return 0; + luaL_newlib(L, sshlib); + return 1; } diff --git a/src/util-lua-ssh.h b/src/util-lua-ssh.h index c36ef105acb0..0e1fbb47907e 100644 --- a/src/util-lua-ssh.h +++ b/src/util-lua-ssh.h @@ -24,6 +24,8 @@ #ifndef SURICATA_UTIL_LUA_SSH_H #define SURICATA_UTIL_LUA_SSH_H -int LuaRegisterSshFunctions(lua_State *luastate); +#include "lua.h" + +int SCLuaLoadSshLib(lua_State *L); #endif /* SURICATA_UTIL_LUA_SSH_H */ From a87c3e7a9306d47550079d434635908919209bdf Mon Sep 17 00:00:00 2001 From: Philippe Antoine Date: Fri, 11 Apr 2025 22:53:49 +0200 Subject: [PATCH 2/2] ssh: hooks --- rust/src/ssh/ssh.rs | 54 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 52 insertions(+), 2 deletions(-) diff --git a/rust/src/ssh/ssh.rs b/rust/src/ssh/ssh.rs index a4b7b2246398..edfb1ce00a5e 100644 --- a/rust/src/ssh/ssh.rs +++ b/rust/src/ssh/ssh.rs @@ -72,6 +72,56 @@ pub enum SSHConnectionState { SshStateFinished = 3, } +impl SSHConnectionState { + fn from_str(s: &str) -> Option { + match s { + "in_progress" => Some(SSHConnectionState::SshStateInProgress), + "banner_wait_eol" => Some(SSHConnectionState::SshStateBannerWaitEol), + "banner_done" => Some(SSHConnectionState::SshStateBannerDone), + "finished" => Some(SSHConnectionState::SshStateFinished), + _ => None + } + } + + unsafe extern "C" fn ffi_id_from_name(name: *const std::os::raw::c_char, _dir: u8) -> std::ffi::c_int { + if name.is_null() { + return -1; + } + if let Ok(s) = std::ffi::CStr::from_ptr(name).to_str() { + Self::from_str(s).map(|t| t as i32).unwrap_or(-1) + } else { + -1 + } + } + + fn to_cstring(&self) -> *const std::os::raw::c_char { + let s = match *self { + SSHConnectionState::SshStateInProgress => "in_progress\0", + SSHConnectionState::SshStateBannerWaitEol => "banner_wait_eol\0", + SSHConnectionState::SshStateBannerDone => "banner_done\0", + SSHConnectionState::SshStateFinished => "finished\0", + }; + s.as_ptr() as *const std::os::raw::c_char + } + + fn try_from(v: std::ffi::c_int) -> Result { + match v { + val if val == SSHConnectionState::SshStateInProgress as std::ffi::c_int => Ok(SSHConnectionState::SshStateInProgress), + val if val == SSHConnectionState::SshStateBannerWaitEol as std::ffi::c_int => Ok(SSHConnectionState::SshStateBannerWaitEol), + val if val == SSHConnectionState::SshStateBannerDone as std::ffi::c_int => Ok(SSHConnectionState::SshStateBannerDone), + val if val == SSHConnectionState::SshStateFinished as std::ffi::c_int => Ok(SSHConnectionState::SshStateFinished), + _ => Err(()), + } + } + + unsafe extern "C" fn ffi_name_from_id(id: std::ffi::c_int, _dir: u8) -> *const std::os::raw::c_char { + match Self::try_from(id) { + Ok(v) => v.to_cstring(), + _ => std::ptr::null(), + } + } +} + pub const SSH_MAX_BANNER_LEN: usize = 256; const SSH_RECORD_HEADER_LEN: usize = 6; const SSH_MAX_REASSEMBLED_RECORD_LEN: usize = 65535; @@ -552,8 +602,8 @@ pub unsafe extern "C" fn SCRegisterSshParser() { flags: 0, get_frame_id_by_name: Some(SshFrameType::ffi_id_from_name), get_frame_name_by_id: Some(SshFrameType::ffi_name_from_id), - get_state_id_by_name: None, - get_state_name_by_id: None, + get_state_id_by_name: Some(SSHConnectionState::ffi_id_from_name), + get_state_name_by_id: Some(SSHConnectionState::ffi_name_from_id), }; let ip_proto_str = CString::new("tcp").unwrap();