feat: reworked command processing and storage
This commit is contained in:
parent
ceb4aa808e
commit
5ed8efca94
44
Cargo.lock
generated
44
Cargo.lock
generated
@ -990,6 +990,30 @@ dependencies = [
|
|||||||
"cfg-if",
|
"cfg-if",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "enum_delegate"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a8ea75f31022cba043afe037940d73684327e915f88f62478e778c3de914cd0a"
|
||||||
|
dependencies = [
|
||||||
|
"enum_delegate_lib",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 1.0.109",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "enum_delegate_lib"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2e1f6c3800b304a6be0012039e2a45a322a093539c45ab818d9e6895a39c90fe"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"rand 0.8.5",
|
||||||
|
"syn 1.0.109",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "equivalent"
|
name = "equivalent"
|
||||||
version = "1.0.2"
|
version = "1.0.2"
|
||||||
@ -3453,15 +3477,30 @@ dependencies = [
|
|||||||
name = "sparse-actions"
|
name = "sparse-actions"
|
||||||
version = "2.0.0"
|
version = "2.0.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"async-trait",
|
||||||
"bindgen",
|
"bindgen",
|
||||||
"chrono",
|
"chrono",
|
||||||
|
"enum_delegate",
|
||||||
|
"http",
|
||||||
|
"hyper",
|
||||||
|
"hyper-util",
|
||||||
|
"leptos",
|
||||||
|
"pcap-sys",
|
||||||
|
"rmp-serde",
|
||||||
|
"rustls",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_bytes",
|
"serde_bytes",
|
||||||
|
"serde_json",
|
||||||
|
"smoltcp",
|
||||||
|
"sqlx",
|
||||||
|
"thiserror 2.0.11",
|
||||||
|
"tokio",
|
||||||
|
"uuid",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sparse-beacon"
|
name = "sparse-beacon"
|
||||||
version = "0.7.0"
|
version = "2.0.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"bytes",
|
"bytes",
|
||||||
@ -3515,7 +3554,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sparse-server"
|
name = "sparse-server"
|
||||||
version = "0.1.0"
|
version = "2.0.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"axum",
|
"axum",
|
||||||
@ -4565,6 +4604,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "93d59ca99a559661b96bf898d8fce28ed87935fd2bea9f05983c1464dd6c71b1"
|
checksum = "93d59ca99a559661b96bf898d8fce28ed87935fd2bea9f05983c1464dd6c71b1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"getrandom 0.3.1",
|
"getrandom 0.3.1",
|
||||||
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
@ -7,6 +7,27 @@ version.workspace = true
|
|||||||
chrono = { version = "0.4.39", features = ["serde"] }
|
chrono = { version = "0.4.39", features = ["serde"] }
|
||||||
serde = { version = "1.0.218", features = ["derive"] }
|
serde = { version = "1.0.218", features = ["derive"] }
|
||||||
serde_bytes = "0.11.15"
|
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]
|
[build-dependencies]
|
||||||
bindgen = "0.69"
|
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;
|
pub mod actions;
|
||||||
|
#[cfg(feature = "beacon")]
|
||||||
|
pub mod adapter;
|
||||||
|
#[cfg(feature = "beacon")]
|
||||||
|
pub mod error;
|
||||||
pub mod messages;
|
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))
|
||||||
|
}
|
||||||
|
}*/
|
||||||
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "sparse-beacon"
|
name = "sparse-beacon"
|
||||||
version = "0.7.0"
|
version.workspace = true
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
publish = false
|
publish = false
|
||||||
|
|
||||||
@ -29,9 +29,9 @@ http-body = "1.0.1"
|
|||||||
rmp-serde = "1.3.0"
|
rmp-serde = "1.3.0"
|
||||||
cron = "0.13.0"
|
cron = "0.13.0"
|
||||||
|
|
||||||
pcap-sys = { version = "0.1.0", path = "../pcap-sys" }
|
pcap-sys = { path = "../pcap-sys" }
|
||||||
sparse-actions = { version = "2.0.0", path = "../sparse-actions" }
|
sparse-actions = { path = "../sparse-actions", features = ["beacon"] }
|
||||||
packets = { version = "0.1.0", path = "../packets" }
|
packets = { path = "../packets" }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
openssl = ["dep:rustls-openssl"]
|
openssl = ["dep:rustls-openssl"]
|
||||||
|
|||||||
@ -12,13 +12,13 @@ use hyper_util::{
|
|||||||
use rustls::RootCertStore;
|
use rustls::RootCertStore;
|
||||||
use tower_service::Service;
|
use tower_service::Service;
|
||||||
|
|
||||||
use sparse_actions::payload_types::Parameters;
|
use sparse_actions::{
|
||||||
|
|
||||||
use crate::{
|
|
||||||
adapter, error,
|
adapter, error,
|
||||||
tcp::{self, setup_network},
|
payload_types::Parameters
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::tcp::{self, setup_network};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct ServerConnector<T>
|
pub struct ServerConnector<T>
|
||||||
where
|
where
|
||||||
|
|||||||
@ -3,17 +3,13 @@ use sparse_actions::payload_types::Parameters;
|
|||||||
use http_body_util::{BodyExt, Full};
|
use http_body_util::{BodyExt, Full};
|
||||||
use hyper::{Request, Method};
|
use hyper::{Request, Method};
|
||||||
|
|
||||||
use sparse_actions::messages;
|
use sparse_actions::{adapter, error::BeaconError, messages};
|
||||||
|
|
||||||
mod callback;
|
mod callback;
|
||||||
mod socket;
|
mod socket;
|
||||||
mod tcp;
|
mod tcp;
|
||||||
mod params;
|
mod params;
|
||||||
|
|
||||||
pub mod adapter;
|
|
||||||
pub mod error;
|
|
||||||
pub use error::BeaconError;
|
|
||||||
|
|
||||||
pub fn install_rustls() {
|
pub fn install_rustls() {
|
||||||
#[cfg(feature = "openssl")]
|
#[cfg(feature = "openssl")]
|
||||||
let _ = rustls_openssl::default_provider().install_default();
|
let _ = rustls_openssl::default_provider().install_default();
|
||||||
@ -63,7 +59,7 @@ where
|
|||||||
let hostname = host_adapter.get_hostname().await.unwrap_or("(unknown)".to_string());
|
let hostname = host_adapter.get_hostname().await.unwrap_or("(unknown)".to_string());
|
||||||
let userent = host_adapter.get_username().await.unwrap_or("(unknown)".to_string());
|
let userent = host_adapter.get_username().await.unwrap_or("(unknown)".to_string());
|
||||||
|
|
||||||
let mut config: messages::BeaconConfig = {
|
let _config: messages::BeaconConfig = {
|
||||||
let client = callback::obtain_https_client(&host_adapter, ¶ms).await?;
|
let client = callback::obtain_https_client(&host_adapter, ¶ms).await?;
|
||||||
|
|
||||||
make_request(
|
make_request(
|
||||||
@ -83,15 +79,15 @@ where
|
|||||||
loop {
|
loop {
|
||||||
// let client = callback::obtain_https_client(&host_adapter, ¶ms).await?;
|
// let client = callback::obtain_https_client(&host_adapter, ¶ms).await?;
|
||||||
|
|
||||||
use messages::RuntimeConfig as RC;
|
//use messages::RuntimeConfig as RC;
|
||||||
let target_wake_time = match &config.runtime_config {
|
//let target_wake_time = match &config.runtime_config {
|
||||||
RC::Oneshot => { break; },
|
// RC::Oneshot => { break; },
|
||||||
RC::Random { interval_min, interval_max } => {},
|
// RC::Random { interval_min, interval_max } => {},
|
||||||
RC::Regular { interval } => {},
|
// RC::Regular { interval } => {},
|
||||||
RC::Cron { schedule, timezone } => {
|
// RC::Cron { schedule, timezone } => {
|
||||||
|
|
||||||
}
|
// }
|
||||||
};
|
//};
|
||||||
}
|
}
|
||||||
|
|
||||||
// for _ in 1..5 {
|
// for _ in 1..5 {
|
||||||
@ -106,6 +102,4 @@ where
|
|||||||
// let body = body.collect().await;
|
// let body = body.collect().await;
|
||||||
// println!("{:?}", body);
|
// println!("{:?}", body);
|
||||||
// }
|
// }
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,4 @@
|
|||||||
use sparse_actions::payload_types::Parameters;
|
use sparse_actions::{adapter::BeaconAdapter, error::BeaconError, payload_types::Parameters};
|
||||||
|
|
||||||
use crate::adapter::BeaconAdapter;
|
|
||||||
use crate::error::BeaconError;
|
|
||||||
|
|
||||||
pub fn domain_name<'a, T>(params: &'a Parameters) -> Result<&'a str, BeaconError<T::Error>>
|
pub fn domain_name<'a, T>(params: &'a Parameters) -> Result<&'a str, BeaconError<T::Error>>
|
||||||
where
|
where
|
||||||
|
|||||||
@ -2,7 +2,7 @@ use smoltcp::phy::{self, Device, DeviceCapabilities, Medium};
|
|||||||
|
|
||||||
use pcap_sys::Interface;
|
use pcap_sys::Interface;
|
||||||
|
|
||||||
use crate::{adapter, error};
|
use sparse_actions::{adapter, error};
|
||||||
|
|
||||||
struct SocketInner {
|
struct SocketInner {
|
||||||
lower: Interface,
|
lower: Interface,
|
||||||
|
|||||||
@ -18,9 +18,10 @@ use tokio::{
|
|||||||
task::{spawn_blocking, JoinHandle},
|
task::{spawn_blocking, JoinHandle},
|
||||||
};
|
};
|
||||||
|
|
||||||
use sparse_actions::payload_types::Parameters;
|
use sparse_actions::{
|
||||||
|
adapter, error,
|
||||||
use crate::{adapter, error};
|
payload_types::Parameters
|
||||||
|
};
|
||||||
|
|
||||||
pub struct NetInterfaceHandle {
|
pub struct NetInterfaceHandle {
|
||||||
net: Arc<Mutex<(SocketSet<'static>, crate::socket::RawSocket, Interface)>>,
|
net: Arc<Mutex<(SocketSet<'static>, crate::socket::RawSocket, Interface)>>,
|
||||||
|
|||||||
@ -18,5 +18,6 @@ rustls = { version = "0.23", default-features = false, features = ["ring", "std"
|
|||||||
rcgen = { version = "0.13.2", features = ["pem", "x509-parser", "crypto"] }
|
rcgen = { version = "0.13.2", features = ["pem", "x509-parser", "crypto"] }
|
||||||
rustls-pki-types = "1.11.0"
|
rustls-pki-types = "1.11.0"
|
||||||
axum-msgpack = "0.4.0"
|
axum-msgpack = "0.4.0"
|
||||||
sparse-actions = { version = "2.0.0", path = "../sparse-actions" }
|
|
||||||
chrono = { version = "0.4.39", features = ["serde"] }
|
chrono = { version = "0.4.39", features = ["serde"] }
|
||||||
|
|
||||||
|
sparse-actions = { path = "../sparse-actions" }
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "sparse-server"
|
name = "sparse-server"
|
||||||
version = "0.1.0"
|
version.workspace = true
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
@ -51,7 +51,7 @@ server_fn = { version = "0.7.7", features = ["multipart"] }
|
|||||||
multer = { version = "3.1.0", optional = true }
|
multer = { version = "3.1.0", optional = true }
|
||||||
uuid = { version = "1.14.0", features = ["v4"], optional = true }
|
uuid = { version = "1.14.0", features = ["v4"], optional = true }
|
||||||
|
|
||||||
sparse-actions = { path = "../sparse-actions", optional = true }
|
sparse-actions = { path = "../sparse-actions", features = ["server"] }
|
||||||
sparse-handler = { path = "../sparse-handler", optional = true }
|
sparse-handler = { path = "../sparse-handler", optional = true }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
@ -79,14 +79,14 @@ ssr = [
|
|||||||
"dep:cron",
|
"dep:cron",
|
||||||
"dep:sparse-handler",
|
"dep:sparse-handler",
|
||||||
"dep:rustls-pki-types",
|
"dep:rustls-pki-types",
|
||||||
"dep:sparse-actions",
|
|
||||||
"dep:rand",
|
"dep:rand",
|
||||||
"dep:multer",
|
"dep:multer",
|
||||||
"dep:uuid",
|
"dep:uuid",
|
||||||
"leptos/ssr",
|
"leptos/ssr",
|
||||||
"leptos_meta/ssr",
|
"leptos_meta/ssr",
|
||||||
"leptos_router/ssr",
|
"leptos_router/ssr",
|
||||||
"leptos-use/ssr"
|
"leptos-use/ssr",
|
||||||
|
"sparse-actions/server-ssr"
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.metadata.leptos]
|
[package.metadata.leptos]
|
||||||
|
|||||||
@ -0,0 +1,3 @@
|
|||||||
|
-- Add migration script here
|
||||||
|
|
||||||
|
ALTER TABLE beacon_instance ADD COLUMN version int NOT NULL DEFAULT 512;
|
||||||
27
sparse-server/migrations/20250223215600_commands_v2.sql
Normal file
27
sparse-server/migrations/20250223215600_commands_v2.sql
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
-- Add migration script here
|
||||||
|
DROP TABLE beacon_command_invocation;
|
||||||
|
DROP TABLE beacon_command;
|
||||||
|
|
||||||
|
CREATE TABLE beacon_command (
|
||||||
|
command_id integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||||
|
|
||||||
|
cmd_parameters varchar NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE beacon_command_invocation (
|
||||||
|
beacon_id varchar NOT NULL,
|
||||||
|
command_id int NOT NULL,
|
||||||
|
|
||||||
|
issue_date int NOT NULL,
|
||||||
|
|
||||||
|
invoker_id int NOT NULL,
|
||||||
|
|
||||||
|
invocation_date int,
|
||||||
|
invocation_result varchar,
|
||||||
|
|
||||||
|
PRIMARY KEY (beacon_id, command_id),
|
||||||
|
|
||||||
|
FOREIGN KEY (command_id) REFERENCES beacon_command,
|
||||||
|
FOREIGN KEY (beacon_id) REFERENCES beacon_instance,
|
||||||
|
FOREIGN KEY (invoker_id) REFERENCES users
|
||||||
|
);
|
||||||
@ -1,66 +1,17 @@
|
|||||||
use leptos::{either::Either, prelude::*};
|
use leptos::{either::Either, prelude::*};
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use leptos::server_fn::codec::{MultipartData, MultipartFormData};
|
use leptos::server_fn::codec::{MultipartData, MultipartFormData};
|
||||||
use web_sys::FormData;
|
use web_sys::FormData;
|
||||||
use send_wrapper::SendWrapper;
|
use send_wrapper::SendWrapper;
|
||||||
|
|
||||||
#[cfg(feature = "ssr")]
|
#[cfg(feature = "ssr")]
|
||||||
use {
|
use {
|
||||||
|
sparse_actions::version::Version,
|
||||||
leptos::server_fn::error::NoCustomError,
|
leptos::server_fn::error::NoCustomError,
|
||||||
sqlx::{sqlite::SqliteRow, FromRow, Row},
|
|
||||||
tokio::io::AsyncWriteExt,
|
tokio::io::AsyncWriteExt,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::beacons::{BeaconResources, categories::Category};
|
use crate::beacons::{BeaconResources, categories::Category};
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize)]
|
|
||||||
pub enum CommandBody {
|
|
||||||
Update,
|
|
||||||
Exec { command: String },
|
|
||||||
Install { target_binary: String },
|
|
||||||
Upload { target_file: String },
|
|
||||||
Download { src_file: String, target_path: String },
|
|
||||||
Chdir { target_dir: String },
|
|
||||||
Ls
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "ssr")]
|
|
||||||
impl FromRow<'_, SqliteRow> for CommandBody {
|
|
||||||
fn from_row(row: &SqliteRow) -> sqlx::Result<Self> {
|
|
||||||
match row.try_get("cmd_type")? {
|
|
||||||
"update" => Ok(Self::Update),
|
|
||||||
"exec" => Ok(Self::Exec {
|
|
||||||
command: row.try_get("exec_command")?
|
|
||||||
}),
|
|
||||||
"install" => Ok(Self::Install {
|
|
||||||
target_binary: row.try_get("install_target")?
|
|
||||||
}),
|
|
||||||
"upload" => Ok(Self::Upload {
|
|
||||||
target_file: row.try_get("upload_src")?
|
|
||||||
}),
|
|
||||||
"download" => Ok(Self::Download {
|
|
||||||
src_file: row.try_get("download_src")?,
|
|
||||||
target_path: row.try_get("download_path")?
|
|
||||||
}),
|
|
||||||
"chdir" => Ok(Self::Chdir {
|
|
||||||
target_dir: row.try_get("chdir_target")?
|
|
||||||
}),
|
|
||||||
"ls" => Ok(Self::Ls),
|
|
||||||
type_name => Err(sqlx::Error::TypeNotFound {
|
|
||||||
type_name: type_name.to_string()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg_attr(feature = "ssr", derive(FromRow))]
|
|
||||||
#[derive(Clone, Serialize, Deserialize)]
|
|
||||||
pub struct BeaconCommand {
|
|
||||||
pub command_id: i64,
|
|
||||||
#[cfg_attr(feature = "ssr", sqlx(flatten))]
|
|
||||||
pub body: CommandBody
|
|
||||||
}
|
|
||||||
|
|
||||||
#[server(
|
#[server(
|
||||||
prefix = "/api/commands",
|
prefix = "/api/commands",
|
||||||
endpoint = "issue_command",
|
endpoint = "issue_command",
|
||||||
@ -75,138 +26,19 @@ pub async fn issue_command(
|
|||||||
));
|
));
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut fields = std::collections::HashMap::<String, String>::new();
|
let mut fields = serde_json::Map::new();
|
||||||
let mut download_src = None::<multer::Field>;
|
let db = crate::db::get_db()?;
|
||||||
|
|
||||||
let mut data = data.into_inner().ok_or(ServerFnError::<NoCustomError>::ServerError(
|
let mut data = data.into_inner().ok_or(ServerFnError::<NoCustomError>::ServerError(
|
||||||
"No form data was provided".to_owned(),
|
"No form data was provided".to_owned(),
|
||||||
))?;
|
))?;
|
||||||
while let Ok(Some(field)) = data.next_field().await {
|
while let Ok(Some(mut field)) = data.next_field().await {
|
||||||
let name = field.name().unwrap_or_default().to_string();
|
tracing::debug!("Processing field {:?}", field.name());
|
||||||
tracing::trace!("Found field {}", &name);
|
let Some(name) = field.name().map(|f| f.to_string()) else { continue; };
|
||||||
if name != "download_src" {
|
|
||||||
fields.insert(name.clone(), field.text().await.unwrap_or_default());
|
|
||||||
} else {
|
|
||||||
download_src = Some(field);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum Target {
|
let file_name = field.file_name().map(str::to_string);
|
||||||
Beacon(String),
|
|
||||||
Category(i64)
|
|
||||||
}
|
|
||||||
|
|
||||||
let Some(target_beacons) = fields
|
if let Some(file_name) = file_name {
|
||||||
.get("beacon_id")
|
|
||||||
.map(Clone::clone)
|
|
||||||
.map(Target::Beacon)
|
|
||||||
.or(fields
|
|
||||||
.get("category_id")
|
|
||||||
.map(|id| id.parse::<i64>().ok())
|
|
||||||
.flatten()
|
|
||||||
.map(Target::Category)) else {
|
|
||||||
return Err(ServerFnError::<NoCustomError>::ServerError(
|
|
||||||
"A beacon command cannot be made without a target".to_owned(),
|
|
||||||
));
|
|
||||||
};
|
|
||||||
|
|
||||||
let db = crate::db::get_db()?;
|
|
||||||
|
|
||||||
let command_id = match fields.get("cmd_type").map(Clone::clone).as_deref() {
|
|
||||||
Some("update") => {
|
|
||||||
Ok(sqlx::query!(
|
|
||||||
"INSERT INTO beacon_command (cmd_type) VALUES ('update')"
|
|
||||||
)
|
|
||||||
.execute(&db)
|
|
||||||
.await?
|
|
||||||
.last_insert_rowid())
|
|
||||||
}
|
|
||||||
Some("ls") => {
|
|
||||||
Ok(sqlx::query!(
|
|
||||||
"INSERT INTO beacon_command (cmd_type) VALUES ('ls')"
|
|
||||||
)
|
|
||||||
.execute(&db)
|
|
||||||
.await?
|
|
||||||
.last_insert_rowid())
|
|
||||||
}
|
|
||||||
Some("chdir") => {
|
|
||||||
let chdir_target = fields
|
|
||||||
.get("chdir_target")
|
|
||||||
.filter(|s| !s.is_empty())
|
|
||||||
.ok_or(ServerFnError::<NoCustomError>::ServerError(
|
|
||||||
"Directory target cannot be empty".to_owned(),
|
|
||||||
))?;
|
|
||||||
|
|
||||||
Ok(sqlx::query!(
|
|
||||||
"INSERT INTO beacon_command (cmd_type, chdir_target) VALUES ('chdir', ?)",
|
|
||||||
chdir_target
|
|
||||||
)
|
|
||||||
.execute(&db)
|
|
||||||
.await?
|
|
||||||
.last_insert_rowid())
|
|
||||||
}
|
|
||||||
Some("exec") => {
|
|
||||||
let exec_command = fields
|
|
||||||
.get("exec_command")
|
|
||||||
.filter(|s| !s.is_empty())
|
|
||||||
.ok_or(ServerFnError::<NoCustomError>::ServerError(
|
|
||||||
"Command cannot be empty".to_owned(),
|
|
||||||
))?;
|
|
||||||
|
|
||||||
Ok(sqlx::query!(
|
|
||||||
"INSERT INTO beacon_command (cmd_type, exec_command) VALUES ('exec', ?)",
|
|
||||||
exec_command
|
|
||||||
)
|
|
||||||
.execute(&db)
|
|
||||||
.await?
|
|
||||||
.last_insert_rowid())
|
|
||||||
}
|
|
||||||
Some("install") => {
|
|
||||||
let install_target = fields
|
|
||||||
.get("install_target")
|
|
||||||
.filter(|s| !s.is_empty())
|
|
||||||
.ok_or(ServerFnError::<NoCustomError>::ServerError(
|
|
||||||
"Install target cannot be empty".to_owned(),
|
|
||||||
))?;
|
|
||||||
|
|
||||||
Ok(sqlx::query!(
|
|
||||||
"INSERT INTO beacon_command (cmd_type, install_target) VALUES ('install', ?)",
|
|
||||||
install_target
|
|
||||||
)
|
|
||||||
.execute(&db)
|
|
||||||
.await?
|
|
||||||
.last_insert_rowid())
|
|
||||||
}
|
|
||||||
Some("upload") => {
|
|
||||||
let upload_src = fields
|
|
||||||
.get("upload_src")
|
|
||||||
.filter(|s| !s.is_empty())
|
|
||||||
.ok_or(ServerFnError::<NoCustomError>::ServerError(
|
|
||||||
"Upload file request path cannot be empty".to_owned(),
|
|
||||||
))?;
|
|
||||||
|
|
||||||
Ok(sqlx::query!(
|
|
||||||
"INSERT INTO beacon_command (cmd_type, upload_src) VALUES ('upload', ?)",
|
|
||||||
upload_src
|
|
||||||
)
|
|
||||||
.execute(&db)
|
|
||||||
.await?
|
|
||||||
.last_insert_rowid())
|
|
||||||
}
|
|
||||||
Some("download") => {
|
|
||||||
let download_path = fields
|
|
||||||
.get("download_path")
|
|
||||||
.filter(|s| !s.is_empty())
|
|
||||||
.ok_or(ServerFnError::<NoCustomError>::ServerError(
|
|
||||||
"Upload file request path cannot be empty".to_owned(),
|
|
||||||
))?;
|
|
||||||
|
|
||||||
let mut download_src = download_src
|
|
||||||
.ok_or(ServerFnError::<NoCustomError>::ServerError(
|
|
||||||
"File to upload cannot be empty".to_owned(),
|
|
||||||
))?;
|
|
||||||
|
|
||||||
let file_name = download_src.file_name().unwrap_or_default().to_string();
|
|
||||||
let file_id = uuid::Uuid::new_v4();
|
let file_id = uuid::Uuid::new_v4();
|
||||||
|
|
||||||
let mut target_file_path = expect_context::<std::path::PathBuf>();
|
let mut target_file_path = expect_context::<std::path::PathBuf>();
|
||||||
@ -220,7 +52,7 @@ pub async fn issue_command(
|
|||||||
.open(target_file_path)
|
.open(target_file_path)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
while let Ok(Some(chunk)) = download_src.chunk().await {
|
while let Ok(Some(chunk)) = field.chunk().await {
|
||||||
target_file.write(&chunk).await?;
|
target_file.write(&chunk).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -232,24 +64,83 @@ pub async fn issue_command(
|
|||||||
.execute(&db)
|
.execute(&db)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(sqlx::query!(
|
fields.insert(name, serde_json::to_value(sparse_actions::actions::FileId(file_id))?);
|
||||||
"INSERT INTO beacon_command (cmd_type, download_src, download_path) VALUES ('download', ?, ?)",
|
} else {
|
||||||
file_id,
|
let Ok(value) = field.text().await else { continue; };
|
||||||
download_path
|
let json = match serde_json::from_str(&value) {
|
||||||
)
|
Ok(v) => v,
|
||||||
.execute(&db)
|
Err(_) => serde_json::Value::String(value.clone())
|
||||||
.await?
|
};
|
||||||
.last_insert_rowid())
|
fields.insert(name, json);
|
||||||
}
|
}
|
||||||
_ => Err(ServerFnError::<NoCustomError>::ServerError(
|
}
|
||||||
"Unknown command type".to_owned(),
|
|
||||||
))
|
enum Target {
|
||||||
}?;
|
Beacon(String),
|
||||||
|
Category(i64)
|
||||||
|
}
|
||||||
|
|
||||||
|
tracing::debug!("Parameters provided: {:?}", serde_json::to_string(&fields));
|
||||||
|
|
||||||
|
let Some(target_beacons) = fields
|
||||||
|
.get("target_beacon_id")
|
||||||
|
.and_then(serde_json::Value::as_str)
|
||||||
|
.map(str::to_string)
|
||||||
|
.map(Target::Beacon)
|
||||||
|
.or(fields
|
||||||
|
.get("target_category_id")
|
||||||
|
.and_then(serde_json::Value::as_i64)
|
||||||
|
.map(Target::Category)) else {
|
||||||
|
return Err(ServerFnError::<NoCustomError>::ServerError(
|
||||||
|
"A beacon command cannot be made without a target".to_owned(),
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
|
fields.remove("target_beacon_id");
|
||||||
|
fields.remove("target_category_id");
|
||||||
|
|
||||||
|
let Some(command_builder) = sparse_actions::actions::ACTION_BUILDERS
|
||||||
|
.iter()
|
||||||
|
.find(|builder| Some(builder.name()) == fields.get("cmd_type").and_then(serde_json::Value::as_str)) else {
|
||||||
|
return Err(ServerFnError::<NoCustomError>::ServerError(
|
||||||
|
"No command type provided".to_owned(),
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
|
let fields = serde_json::Value::Object(fields);
|
||||||
|
|
||||||
|
command_builder.verify_json_body(fields.clone())?;
|
||||||
|
serde_json::from_value::<sparse_actions::actions::Actions>(fields.clone())?;
|
||||||
|
|
||||||
|
let serialized_fields = serde_json::to_string(&fields)?;
|
||||||
|
|
||||||
|
let command_id = sqlx::query!(
|
||||||
|
"INSERT INTO beacon_command (cmd_parameters) VALUES (?)",
|
||||||
|
serialized_fields
|
||||||
|
)
|
||||||
|
.execute(&db)
|
||||||
|
.await?
|
||||||
|
.last_insert_rowid();
|
||||||
|
|
||||||
let now = chrono::Utc::now();
|
let now = chrono::Utc::now();
|
||||||
|
|
||||||
match target_beacons {
|
match target_beacons {
|
||||||
Target::Beacon(bid) => {
|
Target::Beacon(bid) => {
|
||||||
|
let beacon_instance = sqlx::query!(
|
||||||
|
r#"SELECT version as "version: Version" FROM beacon_instance WHERE beacon_id = ?"#,
|
||||||
|
bid
|
||||||
|
)
|
||||||
|
.fetch_one(&db)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if beacon_instance.version < command_builder.required_version() {
|
||||||
|
return Err(ServerFnError::<NoCustomError>::ServerError(format!(
|
||||||
|
"Beacon does not meet the minimum required version to run that command ({} vs {})",
|
||||||
|
beacon_instance.version,
|
||||||
|
command_builder.required_version()
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
"INSERT INTO beacon_command_invocation (command_id, issue_date, invoker_id, beacon_id)
|
"INSERT INTO beacon_command_invocation (command_id, issue_date, invoker_id, beacon_id)
|
||||||
VALUES (?, ?, ?, ?)",
|
VALUES (?, ?, ?, ?)",
|
||||||
@ -262,16 +153,20 @@ pub async fn issue_command(
|
|||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
Target::Category(cid) => {
|
Target::Category(cid) => {
|
||||||
|
let version = command_builder.required_version();
|
||||||
|
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
"INSERT INTO beacon_command_invocation (command_id, issue_date, invoker_id, beacon_id)
|
"INSERT INTO beacon_command_invocation (command_id, issue_date, invoker_id, beacon_id)
|
||||||
SELECT ?, ?, ?, bi.beacon_id FROM beacon_category bc
|
SELECT ?, ?, ?, bi.beacon_id FROM beacon_category bc
|
||||||
INNER JOIN beacon_category_assignment bca ON bc.category_id = bca.category_id
|
INNER JOIN beacon_category_assignment bca ON bc.category_id = bca.category_id
|
||||||
INNER JOIN beacon_instance bi ON bca.beacon_id = bi.beacon_id
|
INNER JOIN beacon_instance bi ON bca.beacon_id = bi.beacon_id
|
||||||
WHERE bc.category_id = ?",
|
WHERE bc.category_id = ?
|
||||||
|
AND bi.version >= ?",
|
||||||
command_id,
|
command_id,
|
||||||
now,
|
now,
|
||||||
user.user_id,
|
user.user_id,
|
||||||
cid
|
cid,
|
||||||
|
version
|
||||||
)
|
)
|
||||||
.execute(&db)
|
.execute(&db)
|
||||||
.await?;
|
.await?;
|
||||||
@ -290,6 +185,8 @@ pub fn CommandForm(categories: Vec<Category>, beacon_id: Option<String>) -> impl
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let (current_cmd, set_current_cmd) = signal("Exec".to_owned());
|
||||||
|
|
||||||
view! {
|
view! {
|
||||||
{(categories.is_empty() && beacon_id.is_none())
|
{(categories.is_empty() && beacon_id.is_none())
|
||||||
.then(|| view! {
|
.then(|| view! {
|
||||||
@ -318,14 +215,14 @@ pub fn CommandForm(categories: Vec<Category>, beacon_id: Option<String>) -> impl
|
|||||||
<legend>"Issue new command"</legend>
|
<legend>"Issue new command"</legend>
|
||||||
{if let Some(bid) = beacon_id.clone() {
|
{if let Some(bid) = beacon_id.clone() {
|
||||||
Either::Left(view! {
|
Either::Left(view! {
|
||||||
<input name="beacon_id" type="hidden" value=bid />
|
<input name="target_beacon_id" type="hidden" value=bid />
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
Either::Right(view! {
|
Either::Right(view! {
|
||||||
<label>
|
<label>
|
||||||
"Select a category to command"
|
"Select a category to command"
|
||||||
</label>
|
</label>
|
||||||
<select name="category_id">
|
<select name="target_category_id">
|
||||||
{categories
|
{categories
|
||||||
.iter()
|
.iter()
|
||||||
.map(|cat| view! {
|
.map(|cat| view! {
|
||||||
@ -338,27 +235,34 @@ pub fn CommandForm(categories: Vec<Category>, beacon_id: Option<String>) -> impl
|
|||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
<label>"Type of command"</label>
|
<label>"Type of command"</label>
|
||||||
<select name="cmd_type">
|
<select
|
||||||
<option value="exec">"Execute command"</option>
|
name="cmd_type"
|
||||||
<option value="upload">"Upload/exfil file"</option>
|
on:change:target=move |ev| {
|
||||||
<option value="download">"Download/place file"</option>
|
set_current_cmd(ev.target().value().to_string())
|
||||||
<option value="update">"Update beacon"</option>
|
}
|
||||||
<option value="chdir">"Change directory"</option>
|
prop:value=move || current_cmd.get()
|
||||||
<option value="ls">"List files"</option>
|
>
|
||||||
<option value="install">"Install to a new binary"</option>
|
{sparse_actions::actions::ACTION_BUILDERS
|
||||||
|
.iter()
|
||||||
|
.map(|b| view! {
|
||||||
|
<option value=b.name()>{b.name()}</option>
|
||||||
|
})
|
||||||
|
.collect_view()}
|
||||||
</select>
|
</select>
|
||||||
<label class="cmd-exec">"Command"</label>
|
{move || sparse_actions::actions::ACTION_BUILDERS
|
||||||
<input class="cmd-exec" name="exec_command" />
|
.iter()
|
||||||
<label class="cmd-upload">"File to upload/exfil"</label>
|
.find(|b| b.name() == *current_cmd.read())
|
||||||
<input class="cmd-upload" name="upload_src" />
|
.map(|b| b
|
||||||
<label class="cmd-chdir">"Directory to change to"</label>
|
.form_elements()
|
||||||
<input class="cmd-chdir" name="chdir_target" />
|
.iter()
|
||||||
<label class="cmd-install">"Binary to infect"</label>
|
.map(|(label, name, itype)| view! {
|
||||||
<input class="cmd-install" name="install_target" />
|
<label>{label.to_string()}</label>
|
||||||
<label class="cmd-download">"Target location for file"</label>
|
<input
|
||||||
<input class="cmd-download" name="download_path"/>
|
name=name.to_string()
|
||||||
<label class="cmd-download">"File to download/place"</label>
|
type=itype.unwrap_or("text").to_string()
|
||||||
<input class="cmd-download" name="download_src" type="file" />
|
/>
|
||||||
|
})
|
||||||
|
.collect_view())}
|
||||||
<div></div>
|
<div></div>
|
||||||
<input type="submit" value="Submit" disabled=move ||command_action.pending().get()/>
|
<input type="submit" value="Submit" disabled=move ||command_action.pending().get()/>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|||||||
@ -52,6 +52,7 @@ pub struct CurrentBeaconInstance {
|
|||||||
pub operating_system: String,
|
pub operating_system: String,
|
||||||
pub userent: String,
|
pub userent: String,
|
||||||
pub hostname: String,
|
pub hostname: String,
|
||||||
|
pub version: sparse_actions::version::Version,
|
||||||
pub last_checkin: chrono::DateTime<chrono::Utc>,
|
pub last_checkin: chrono::DateTime<chrono::Utc>,
|
||||||
pub config_id: Option<i64>,
|
pub config_id: Option<i64>,
|
||||||
pub template_id: i64,
|
pub template_id: i64,
|
||||||
@ -588,6 +589,9 @@ pub fn BeaconSidebar() -> impl IntoView {
|
|||||||
<div class="beacon-instance-os">
|
<div class="beacon-instance-os">
|
||||||
<span>"OS: "</span> {beacon.operating_system.clone()}
|
<span>"OS: "</span> {beacon.operating_system.clone()}
|
||||||
</div>
|
</div>
|
||||||
|
<div class="beacon-instance-vers">
|
||||||
|
<span>"Version: "</span> {beacon.version.to_string()}
|
||||||
|
</div>
|
||||||
{(sort_method.get() != Some(SortMethod::Category))
|
{(sort_method.get() != Some(SortMethod::Category))
|
||||||
.then(|| -> Vec<String> {
|
.then(|| -> Vec<String> {
|
||||||
let BeaconResources {
|
let BeaconResources {
|
||||||
|
|||||||
@ -13,6 +13,7 @@ use serde::Deserialize;
|
|||||||
use sqlx::sqlite::SqlitePool;
|
use sqlx::sqlite::SqlitePool;
|
||||||
use tokio::signal;
|
use tokio::signal;
|
||||||
|
|
||||||
|
use sparse_actions::version::Version;
|
||||||
use sparse_server::app::*;
|
use sparse_server::app::*;
|
||||||
|
|
||||||
#[cfg(not(debug_assertions))]
|
#[cfg(not(debug_assertions))]
|
||||||
@ -362,7 +363,8 @@ async fn handle_listener_events(
|
|||||||
|
|
||||||
{
|
{
|
||||||
let beacons = sqlx::query!(
|
let beacons = sqlx::query!(
|
||||||
"SELECT beacon_id, template_id, peer_ip, nickname, cwd, operating_system, beacon_userent, hostname, config_id FROM beacon_instance"
|
r#"SELECT beacon_id, template_id, peer_ip, nickname, cwd, operating_system,
|
||||||
|
beacon_userent, hostname, config_id, version as "version: Version" FROM beacon_instance"#
|
||||||
)
|
)
|
||||||
.fetch_all(&state.db)
|
.fetch_all(&state.db)
|
||||||
.await?;
|
.await?;
|
||||||
@ -407,6 +409,7 @@ async fn handle_listener_events(
|
|||||||
userent: b.beacon_userent,
|
userent: b.beacon_userent,
|
||||||
hostname: b.hostname,
|
hostname: b.hostname,
|
||||||
config_id: b.config_id,
|
config_id: b.config_id,
|
||||||
|
version: b.version,
|
||||||
last_checkin: last_checkin
|
last_checkin: last_checkin
|
||||||
.iter()
|
.iter()
|
||||||
.find(|ch| ch.beacon_id == b.beacon_id)
|
.find(|ch| ch.beacon_id == b.beacon_id)
|
||||||
@ -440,8 +443,8 @@ async fn handle_listener_events(
|
|||||||
}
|
}
|
||||||
Ok(BeaconEvent::NewBeacon(bid)) => {
|
Ok(BeaconEvent::NewBeacon(bid)) => {
|
||||||
let beacon = sqlx::query!(
|
let beacon = sqlx::query!(
|
||||||
"SELECT template_id, peer_ip, nickname, cwd, operating_system, beacon_userent, hostname, config_id FROM beacon_instance
|
r#"SELECT template_id, peer_ip, nickname, cwd, operating_system, beacon_userent, hostname, config_id, version as "version: Version" FROM beacon_instance
|
||||||
WHERE beacon_id = ?",
|
WHERE beacon_id = ?"#,
|
||||||
bid
|
bid
|
||||||
)
|
)
|
||||||
.fetch_one(&state.db)
|
.fetch_one(&state.db)
|
||||||
@ -465,6 +468,7 @@ async fn handle_listener_events(
|
|||||||
userent: beacon.beacon_userent,
|
userent: beacon.beacon_userent,
|
||||||
hostname: beacon.hostname,
|
hostname: beacon.hostname,
|
||||||
config_id: beacon.config_id,
|
config_id: beacon.config_id,
|
||||||
|
version: beacon.version,
|
||||||
last_checkin: chrono::Utc::now(),
|
last_checkin: chrono::Utc::now(),
|
||||||
category_ids: category_ids.iter().map(|r| r.category_id).collect()
|
category_ids: category_ids.iter().map(|r| r.category_id).collect()
|
||||||
};
|
};
|
||||||
|
|||||||
@ -11,28 +11,4 @@ main.beacons div.commands {
|
|||||||
margin: 10px;
|
margin: 10px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.cmd-exec, .cmd-install, .cmd-upload, .cmd-download, .cmd-chdir {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
select[name="cmd_type"]:has(> option[value="exec"]:checked) ~ .cmd-exec {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
select[name="cmd_type"]:has(> option[value="upload"]:checked) ~ .cmd-upload {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
select[name="cmd_type"]:has(> option[value="download"]:checked) ~ .cmd-download {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
select[name="cmd_type"]:has(> option[value="chdir"]:checked) ~ .cmd-chdir {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
select[name="cmd_type"]:has(> option[value="install"]:checked) ~ .cmd-install {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,8 +10,8 @@ async-trait = "0.1.86"
|
|||||||
tokio = { version = "1.43.0", features = ["fs", "macros", "rt"] }
|
tokio = { version = "1.43.0", features = ["fs", "macros", "rt"] }
|
||||||
thiserror = "2.0.11"
|
thiserror = "2.0.11"
|
||||||
|
|
||||||
sparse-beacon = { version = "0.7.0", path = "../sparse-beacon", features = ["ring"] }
|
sparse-beacon = { path = "../sparse-beacon", features = ["ring"] }
|
||||||
sparse-actions = { version = "2.0.0", path = "../sparse-actions" }
|
sparse-actions = { path = "../sparse-actions", features = ["beacon"] }
|
||||||
|
|
||||||
[target.'cfg(target_os = "linux")'.dependencies]
|
[target.'cfg(target_os = "linux")'.dependencies]
|
||||||
nl-sys = { version = "0.1.0", path = "../nl-sys" }
|
nl-sys = { version = "0.1.0", path = "../nl-sys" }
|
||||||
|
|||||||
@ -2,7 +2,7 @@ use std::net::Ipv4Addr;
|
|||||||
|
|
||||||
use nl_sys::netlink;
|
use nl_sys::netlink;
|
||||||
|
|
||||||
use sparse_beacon::{
|
use sparse_actions::{
|
||||||
adapter::{BeaconAdapter, BeaconInterface, BeaconNetworkingInfo, BeaconRoute},
|
adapter::{BeaconAdapter, BeaconInterface, BeaconNetworkingInfo, BeaconRoute},
|
||||||
error,
|
error,
|
||||||
};
|
};
|
||||||
@ -13,7 +13,7 @@ pub enum LinuxAdapterError {
|
|||||||
Nl(#[from] nl_sys::error::Error),
|
Nl(#[from] nl_sys::error::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl sparse_beacon::error::AdapterError for LinuxAdapterError {}
|
impl sparse_actions::error::AdapterError for LinuxAdapterError {}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct LinuxAdapter;
|
pub struct LinuxAdapter;
|
||||||
|
|||||||
@ -2,8 +2,7 @@ use std::io::SeekFrom;
|
|||||||
|
|
||||||
use tokio::io::{AsyncReadExt, AsyncSeekExt};
|
use tokio::io::{AsyncReadExt, AsyncSeekExt};
|
||||||
|
|
||||||
use sparse_actions::payload_types::{Parameters, XOR_KEY};
|
use sparse_actions::{adapter::BeaconAdapter, payload_types::{Parameters, XOR_KEY}};
|
||||||
use sparse_beacon::adapter::BeaconAdapter;
|
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
mod linux;
|
mod linux;
|
||||||
@ -16,7 +15,7 @@ mod freebsd;
|
|||||||
use freebsd::FreeBsdAdapter as Adapter;
|
use freebsd::FreeBsdAdapter as Adapter;
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<(), sparse_beacon::BeaconError<<Adapter as BeaconAdapter>::Error>> {
|
async fn main() -> Result<(), sparse_actions::error::BeaconError<<Adapter as BeaconAdapter>::Error>> {
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
let mut binary_file = tokio::fs::OpenOptions::new()
|
let mut binary_file = tokio::fs::OpenOptions::new()
|
||||||
.read(true)
|
.read(true)
|
||||||
|
|||||||
@ -5,6 +5,6 @@ version.workspace = true
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
libc = "0.2.169"
|
libc = "0.2.169"
|
||||||
sparse-actions = { version = "2.0.0", path = "../sparse-actions" }
|
sparse-actions = { path = "../sparse-actions" }
|
||||||
errno = "0.3"
|
errno = "0.3"
|
||||||
cfg-if = "1.0.0"
|
cfg-if = "1.0.0"
|
||||||
|
|||||||
@ -8,6 +8,6 @@ errno = "0.3.10"
|
|||||||
hex = "0.4.3"
|
hex = "0.4.3"
|
||||||
libc = "0.2.169"
|
libc = "0.2.169"
|
||||||
rand = "0.9.0"
|
rand = "0.9.0"
|
||||||
sparse-actions = { version = "2.0.0", path = "../sparse-actions" }
|
sparse-actions = { path = "../sparse-actions" }
|
||||||
sparse-unix-infector = { version = "2.0.0", path = "../sparse-unix-infector" }
|
sparse-unix-infector = { path = "../sparse-unix-infector" }
|
||||||
structopt = "0.3.26"
|
structopt = "0.3.26"
|
||||||
|
|||||||
@ -16,8 +16,8 @@ windows-result = "0.3.0"
|
|||||||
windows-strings = "0.3.0"
|
windows-strings = "0.3.0"
|
||||||
winreg = "0.55"
|
winreg = "0.55"
|
||||||
|
|
||||||
sparse-actions = { version = "2.0.0", path = "../sparse-actions" }
|
sparse-actions = { path = "../sparse-actions" }
|
||||||
sparse-beacon = { version = "0.7.0", path = "../sparse-beacon", features = ["openssl"] }
|
sparse-beacon = { path = "../sparse-beacon", features = ["openssl"] }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
service = []
|
service = []
|
||||||
|
|||||||
@ -5,4 +5,4 @@ version.workspace = true
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
errno = "0.3.10"
|
errno = "0.3.10"
|
||||||
sparse-actions = { version = "2.0.0", path = "../sparse-actions" }
|
sparse-actions = { path = "../sparse-actions" }
|
||||||
|
|||||||
@ -7,9 +7,10 @@ version.workspace = true
|
|||||||
errno = "0.3.10"
|
errno = "0.3.10"
|
||||||
hex = "0.4.3"
|
hex = "0.4.3"
|
||||||
rand = "0.9.0"
|
rand = "0.9.0"
|
||||||
sparse-actions = { version = "2.0.0", path = "../sparse-actions" }
|
|
||||||
sparse-windows-infector = { version = "2.0.0", path = "../sparse-windows-infector" }
|
|
||||||
structopt = "0.3.26"
|
structopt = "0.3.26"
|
||||||
windows = { version = "0.59.0", features = ["Win32_System_Services"] }
|
windows = { version = "0.59.0", features = ["Win32_System_Services"] }
|
||||||
windows-strings = "0.3.0"
|
windows-strings = "0.3.0"
|
||||||
winreg = "0.55.0"
|
winreg = "0.55.0"
|
||||||
|
|
||||||
|
sparse-actions = { path = "../sparse-actions" }
|
||||||
|
sparse-windows-infector = { path = "../sparse-windows-infector" }
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user