feat: reworked command processing and storage
This commit is contained in:
@@ -7,6 +7,27 @@ version.workspace = true
|
||||
chrono = { version = "0.4.39", features = ["serde"] }
|
||||
serde = { version = "1.0.218", features = ["derive"] }
|
||||
serde_bytes = "0.11.15"
|
||||
uuid = { version = "1.14.0", features = ["serde"] }
|
||||
enum_delegate = "0.2.0"
|
||||
async-trait = "0.1.86"
|
||||
serde_json = "1.0.139"
|
||||
|
||||
leptos = { version = "0.7.7", optional = true }
|
||||
thiserror = { version = "2.0.11", optional = true }
|
||||
pcap-sys = { path = "../pcap-sys", optional = true }
|
||||
tokio = { version = "1.43.0", features = ["fs", "io-std", "io-util", "net", "process", "rt", "sync", "time", "tokio-macros"], optional = true }
|
||||
smoltcp = { version = "0.12.0", default-features = false, features = ["proto-ipv4", "socket", "socket-tcp", "medium-ethernet", "std"], optional = true }
|
||||
http = { version = "1.2.0", optional = true }
|
||||
rmp-serde = { version = "1.3.0", optional = true }
|
||||
hyper-util = { version = "0.1.10", features = ["client", "client-legacy", "http1", "http2", "service", "tokio"], optional = true }
|
||||
hyper = { version = "1.6.0", features = ["client", "http1", "http2"], optional = true }
|
||||
rustls = { version = "0.23.23", default-features = false, features = ["std"], optional = true }
|
||||
sqlx = { version = "0.8", default-features = false, features = ["chrono", "macros", "migrate", "runtime-tokio", "sqlite", "sqlx-sqlite", "uuid"], optional = true }
|
||||
|
||||
[build-dependencies]
|
||||
bindgen = "0.69"
|
||||
|
||||
[features]
|
||||
beacon = ["dep:thiserror", "dep:pcap-sys", "dep:tokio", "dep:smoltcp", "dep:http", "dep:hyper-util", "dep:rustls", "dep:hyper", "dep:rmp-serde", "uuid/v4"]
|
||||
server-ssr = ["uuid/v4", "dep:sqlx"]
|
||||
server = ["dep:leptos"]
|
||||
|
||||
@@ -1 +1,98 @@
|
||||
pub trait Action {}
|
||||
#[cfg(feature = "server")]
|
||||
use leptos::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::version::Version;
|
||||
|
||||
mod ls;
|
||||
mod update;
|
||||
mod exec;
|
||||
mod upload;
|
||||
mod install;
|
||||
mod download;
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct FileId(pub uuid::Uuid);
|
||||
|
||||
/// Macro used to enforce the invariant that struct names are used to identify
|
||||
/// the enum branch as well
|
||||
macro_rules! define_actions_enum {
|
||||
($(($mod:ident, $act:ident)),+) => {
|
||||
#[derive(::serde::Serialize, ::serde::Deserialize)]
|
||||
#[serde(tag = "cmd_type")]
|
||||
pub enum Actions {
|
||||
$($act($mod::$act)),+,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
define_actions_enum! {
|
||||
(ls, Ls),
|
||||
(update, Update),
|
||||
(exec, Exec),
|
||||
(upload, Upload),
|
||||
(install, Install),
|
||||
(download, Download)
|
||||
}
|
||||
|
||||
pub const ACTION_BUILDERS: &'static [&'static (dyn ActionBuilder + Send + Sync)] = &[
|
||||
&ActionBuilderImpl::<ls::Ls>::new(),
|
||||
&ActionBuilderImpl::<update::Update>::new(),
|
||||
&ActionBuilderImpl::<exec::Exec>::new(),
|
||||
&ActionBuilderImpl::<upload::Upload>::new(),
|
||||
&ActionBuilderImpl::<install::Install>::new(),
|
||||
&ActionBuilderImpl::<download::Download>::new(),
|
||||
];
|
||||
|
||||
#[async_trait::async_trait]
|
||||
pub trait Action: Serialize + for<'a> Deserialize<'a> {
|
||||
const REQ_VERSION: Version;
|
||||
const REQ_OS: Option<&'static str>;
|
||||
const REQ_FIELDS: &'static [(&'static str, &'static str, Option<&'static str>)];
|
||||
|
||||
type ActionData: Serialize + for<'a> Deserialize<'a>;
|
||||
|
||||
#[cfg(feature = "beacon")]
|
||||
async fn execute(&self) -> Self::ActionData;
|
||||
|
||||
#[cfg(feature = "server")]
|
||||
fn render_data(&self, data: Self::ActionData) -> impl IntoView;
|
||||
}
|
||||
|
||||
pub trait ActionBuilder {
|
||||
fn name(&self) -> &'static str;
|
||||
fn required_version(&self) -> Version;
|
||||
fn required_os(&self) -> Option<&'static str>;
|
||||
fn form_elements(&self) -> &'static [(&'static str, &'static str, Option<&'static str>)];
|
||||
fn verify_json_body(&self, body: serde_json::Value) -> Result<(), serde_json::Error>;
|
||||
}
|
||||
|
||||
pub struct ActionBuilderImpl<T>(std::marker::PhantomData<T>);
|
||||
|
||||
impl<T> ActionBuilderImpl<T> {
|
||||
pub const fn new() -> Self {
|
||||
Self(std::marker::PhantomData)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ActionBuilder for ActionBuilderImpl<T>
|
||||
where
|
||||
T: Action
|
||||
{
|
||||
fn name(&self) -> &'static str {
|
||||
let tname = std::any::type_name::<T>();
|
||||
tname.split(":").last().unwrap()
|
||||
}
|
||||
fn required_version(&self) -> Version {
|
||||
T::REQ_VERSION
|
||||
}
|
||||
fn required_os(&self) -> Option<&'static str> {
|
||||
T::REQ_OS
|
||||
}
|
||||
fn form_elements(&self) -> &'static [(&'static str, &'static str, Option<&'static str>)] {
|
||||
T::REQ_FIELDS
|
||||
}
|
||||
fn verify_json_body(&self, body: serde_json::Value) -> Result<(), serde_json::Error> {
|
||||
serde_json::from_value::<T>(body).map(|_| ())
|
||||
}
|
||||
}
|
||||
|
||||
35
sparse-actions/src/actions/download.rs
Normal file
35
sparse-actions/src/actions/download.rs
Normal file
@@ -0,0 +1,35 @@
|
||||
#[cfg(feature = "server")]
|
||||
use leptos::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::version::Version;
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct Download {
|
||||
download_src: super::FileId,
|
||||
download_path: String,
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl super::Action for Download {
|
||||
const REQ_VERSION: Version = Version::new(2, 0);
|
||||
const REQ_OS: Option<&'static str> = None;
|
||||
const REQ_FIELDS: &'static [(&'static str, &'static str, Option<&'static str>)] = &[
|
||||
("File path to download/place", "download_path", None),
|
||||
("File to download", "download_src", Some("file")),
|
||||
];
|
||||
|
||||
type ActionData = ();
|
||||
|
||||
#[cfg(feature = "beacon")]
|
||||
async fn execute(&self) -> Self::ActionData {
|
||||
"Hi".to_string();
|
||||
}
|
||||
|
||||
#[cfg(feature = "server")]
|
||||
fn render_data(&self, _data: Self::ActionData) -> impl IntoView {
|
||||
view! {
|
||||
"ls ran"
|
||||
}
|
||||
}
|
||||
}
|
||||
33
sparse-actions/src/actions/exec.rs
Normal file
33
sparse-actions/src/actions/exec.rs
Normal file
@@ -0,0 +1,33 @@
|
||||
#[cfg(feature = "server")]
|
||||
use leptos::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::version::Version;
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct Exec {
|
||||
exec_cmd: String
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl super::Action for Exec {
|
||||
const REQ_VERSION: Version = Version::new(2, 0);
|
||||
const REQ_OS: Option<&'static str> = None;
|
||||
const REQ_FIELDS: &'static [(&'static str, &'static str, Option<&'static str>)] = &[
|
||||
("Command to execute", "exec_cmd", None)
|
||||
];
|
||||
|
||||
type ActionData = String;
|
||||
|
||||
#[cfg(feature = "beacon")]
|
||||
async fn execute(&self) -> Self::ActionData {
|
||||
"Execute".to_string()
|
||||
}
|
||||
|
||||
#[cfg(feature = "server")]
|
||||
fn render_data(&self, _data: Self::ActionData) -> impl IntoView {
|
||||
view! {
|
||||
"execute command"
|
||||
}
|
||||
}
|
||||
}
|
||||
33
sparse-actions/src/actions/install.rs
Normal file
33
sparse-actions/src/actions/install.rs
Normal file
@@ -0,0 +1,33 @@
|
||||
#[cfg(feature = "server")]
|
||||
use leptos::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::version::Version;
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct Install {
|
||||
install_target: std::path::PathBuf
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl super::Action for Install {
|
||||
const REQ_VERSION: Version = Version::new(2, 0);
|
||||
const REQ_OS: Option<&'static str> = None;
|
||||
const REQ_FIELDS: &'static [(&'static str, &'static str, Option<&'static str>)] = &[
|
||||
("Binary to infect", "install_target", None)
|
||||
];
|
||||
|
||||
type ActionData = ();
|
||||
|
||||
#[cfg(feature = "beacon")]
|
||||
async fn execute(&self) -> Self::ActionData {
|
||||
"Hi".to_string();
|
||||
}
|
||||
|
||||
#[cfg(feature = "server")]
|
||||
fn render_data(&self, _data: Self::ActionData) -> impl IntoView {
|
||||
view! {
|
||||
"ls ran"
|
||||
}
|
||||
}
|
||||
}
|
||||
29
sparse-actions/src/actions/ls.rs
Normal file
29
sparse-actions/src/actions/ls.rs
Normal file
@@ -0,0 +1,29 @@
|
||||
#[cfg(feature = "server")]
|
||||
use leptos::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::version::Version;
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct Ls;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl super::Action for Ls {
|
||||
const REQ_VERSION: Version = Version::new(2, 0);
|
||||
const REQ_OS: Option<&'static str> = None;
|
||||
const REQ_FIELDS: &'static [(&'static str, &'static str, Option<&'static str>)] = &[];
|
||||
|
||||
type ActionData = ();
|
||||
|
||||
#[cfg(feature = "beacon")]
|
||||
async fn execute(&self) -> Self::ActionData {
|
||||
"Hi".to_string();
|
||||
}
|
||||
|
||||
#[cfg(feature = "server")]
|
||||
fn render_data(&self, _data: Self::ActionData) -> impl IntoView {
|
||||
view! {
|
||||
"ls ran"
|
||||
}
|
||||
}
|
||||
}
|
||||
29
sparse-actions/src/actions/update.rs
Normal file
29
sparse-actions/src/actions/update.rs
Normal file
@@ -0,0 +1,29 @@
|
||||
#[cfg(feature = "server")]
|
||||
use leptos::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::version::Version;
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct Update;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl super::Action for Update {
|
||||
const REQ_VERSION: Version = Version::new(2, 0);
|
||||
const REQ_OS: Option<&'static str> = None;
|
||||
const REQ_FIELDS: &'static [(&'static str, &'static str, Option<&'static str>)] = &[];
|
||||
|
||||
type ActionData = ();
|
||||
|
||||
#[cfg(feature = "beacon")]
|
||||
async fn execute(&self) -> Self::ActionData {
|
||||
"Hello".to_string();
|
||||
}
|
||||
|
||||
#[cfg(feature = "server")]
|
||||
fn render_data(&self, _data: Self::ActionData) -> impl IntoView {
|
||||
view! {
|
||||
"update ran"
|
||||
}
|
||||
}
|
||||
}
|
||||
33
sparse-actions/src/actions/upload.rs
Normal file
33
sparse-actions/src/actions/upload.rs
Normal file
@@ -0,0 +1,33 @@
|
||||
#[cfg(feature = "server")]
|
||||
use leptos::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::version::Version;
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct Upload {
|
||||
upload_src: String
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl super::Action for Upload {
|
||||
const REQ_VERSION: Version = Version::new(2, 0);
|
||||
const REQ_OS: Option<&'static str> = None;
|
||||
const REQ_FIELDS: &'static [(&'static str, &'static str, Option<&'static str>)] = &[
|
||||
("File path to upload/exfil", "upload_src", None)
|
||||
];
|
||||
|
||||
type ActionData = ();
|
||||
|
||||
#[cfg(feature = "beacon")]
|
||||
async fn execute(&self) -> Self::ActionData {
|
||||
"Hi".to_string();
|
||||
}
|
||||
|
||||
#[cfg(feature = "server")]
|
||||
fn render_data(&self, _data: Self::ActionData) -> impl IntoView {
|
||||
view! {
|
||||
"ls ran"
|
||||
}
|
||||
}
|
||||
}
|
||||
38
sparse-actions/src/adapter.rs
Normal file
38
sparse-actions/src/adapter.rs
Normal file
@@ -0,0 +1,38 @@
|
||||
use std::net::Ipv4Addr;
|
||||
|
||||
use crate::error;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct BeaconRoute {
|
||||
pub network: (Ipv4Addr, u8),
|
||||
pub gateway: (Ipv4Addr, u8),
|
||||
pub interface_index: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct BeaconNetworkingInfo {
|
||||
pub routes: Vec<BeaconRoute>,
|
||||
pub interfaces: Vec<BeaconInterface>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct BeaconInterface {
|
||||
pub name: Vec<u8>,
|
||||
pub mtu: u16,
|
||||
pub mac_addr: [u8; 6],
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
pub trait BeaconAdapter {
|
||||
type Error: error::AdapterError + Send + Sync;
|
||||
|
||||
const OPERATING_SYSTEM: &'static str;
|
||||
|
||||
fn interface_name_from_interface(interface: &BeaconInterface) -> Vec<u8>;
|
||||
|
||||
fn networking_info(&self) -> Result<BeaconNetworkingInfo, error::BeaconError<Self::Error>>;
|
||||
|
||||
async fn get_username(&self) -> Result<String, error::BeaconError<Self::Error>>;
|
||||
|
||||
async fn get_hostname(&self) -> Result<String, error::BeaconError<Self::Error>>;
|
||||
}
|
||||
40
sparse-actions/src/error.rs
Normal file
40
sparse-actions/src/error.rs
Normal file
@@ -0,0 +1,40 @@
|
||||
use thiserror::Error;
|
||||
|
||||
pub trait AdapterError: std::error::Error {}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum BeaconError<T>
|
||||
where
|
||||
T: AdapterError,
|
||||
{
|
||||
#[error("io error")]
|
||||
Io(#[from] std::io::Error),
|
||||
#[error("pcap error")]
|
||||
Pcap(#[from] pcap_sys::error::Error),
|
||||
#[error("utf8 decoding error")]
|
||||
Utf8(#[from] std::str::Utf8Error),
|
||||
#[error("task join error")]
|
||||
Join(#[from] tokio::task::JoinError),
|
||||
#[error("could not find default route")]
|
||||
NoDefaultRoute,
|
||||
#[error("connection error")]
|
||||
Connect(#[from] smoltcp::socket::tcp::ConnectError),
|
||||
#[error("http comms error")]
|
||||
Http(#[from] http::Error),
|
||||
#[error("uri parse error")]
|
||||
InvalidUri(#[from] http::uri::InvalidUri),
|
||||
#[error("hyper http error")]
|
||||
HyperError(#[from] hyper_util::client::legacy::Error),
|
||||
#[error("rustls")]
|
||||
Rustls(#[from] rustls::Error),
|
||||
#[error("adapter error")]
|
||||
Adapter(#[from] T),
|
||||
#[error("http error from server")]
|
||||
SparseServerHttpError(http::StatusCode),
|
||||
#[error("message pack encode error")]
|
||||
RmpSerdeEncode(#[from] rmp_serde::encode::Error),
|
||||
#[error("message pack decode error")]
|
||||
RmpSerdeDecode(#[from] rmp_serde::decode::Error),
|
||||
#[error("http error")]
|
||||
Hyper(#[from] hyper::Error),
|
||||
}
|
||||
@@ -5,4 +5,9 @@ pub mod payload_types {
|
||||
}
|
||||
|
||||
pub mod actions;
|
||||
#[cfg(feature = "beacon")]
|
||||
pub mod adapter;
|
||||
#[cfg(feature = "beacon")]
|
||||
pub mod error;
|
||||
pub mod messages;
|
||||
pub mod version;
|
||||
|
||||
7
sparse-actions/src/main.rs
Normal file
7
sparse-actions/src/main.rs
Normal file
@@ -0,0 +1,7 @@
|
||||
use sparse_actions::actions::ActionBuilder;
|
||||
|
||||
fn main() {
|
||||
for b in sparse_actions::actions::ACTION_BUILDERS {
|
||||
println!("{}", b.name());
|
||||
}
|
||||
}
|
||||
64
sparse-actions/src/version.rs
Normal file
64
sparse-actions/src/version.rs
Normal file
@@ -0,0 +1,64 @@
|
||||
use std::{
|
||||
cmp::{Ord, Ordering, PartialOrd},
|
||||
fmt::{self, Display},
|
||||
};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
|
||||
#[repr(transparent)]
|
||||
#[cfg_attr(feature = "server-ssr", derive(sqlx::Type))]
|
||||
#[cfg_attr(feature = "server-ssr", sqlx(transparent))]
|
||||
pub struct Version(u16);
|
||||
|
||||
impl Version {
|
||||
pub const fn new(maj: u8, min: u8) -> Self {
|
||||
Self(((maj as u16) << 8u16) | (min as u16))
|
||||
}
|
||||
|
||||
pub const fn major(&self) -> u8 {
|
||||
(self.0 >> 8) as u8
|
||||
}
|
||||
|
||||
pub const fn minor(&self) -> u8 {
|
||||
(self.0 & 0xFF) as u8
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for Version {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for Version {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
if self.major() == other.major() {
|
||||
self.minor().cmp(&other.minor())
|
||||
} else {
|
||||
self.major().cmp(&other.major())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Version {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "Version({}.{})", self.major(), self.minor())
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Version {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}.{}", self.major(), self.minor())
|
||||
}
|
||||
}
|
||||
|
||||
/*#[cfg(feature = "server-ssr")]
|
||||
impl<'r> sqlx::Decode<'r, sqlx::Sqlite> for Version {
|
||||
fn decode(
|
||||
value: sqlx::Sqlite::ValueRef<'r>,
|
||||
) -> Result<Self, Box<dyn Error + 'static + Send + Sync>> {
|
||||
let value = <u16 as sqlx::Decode<sqlx::Sqlite>>::decode(value)?;
|
||||
Ok(Self(value))
|
||||
}
|
||||
}*/
|
||||
Reference in New Issue
Block a user