feat: adding a bind shell example with more stuff
adding a bind shell that can allow for more practice with future features such as multiple transports, encryption, transferring files, and a more robust client interface
This commit is contained in:
20
sparse-05/README.md
Normal file
20
sparse-05/README.md
Normal file
@@ -0,0 +1,20 @@
|
||||
<!--
|
||||
Copyright (C) 2023 Andrew Rioux
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
# Sparse 0.5
|
||||
|
||||
Sparse 0.5 is a stopgap
|
||||
15
sparse-05/sparse-05-client/Cargo.toml
Normal file
15
sparse-05/sparse-05-client/Cargo.toml
Normal file
@@ -0,0 +1,15 @@
|
||||
[package]
|
||||
name = "sparse-05-client"
|
||||
version = "0.5.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.75"
|
||||
ed25519-dalek = "1.0.1"
|
||||
rand = "0.7"
|
||||
rmp-serde = "1.1.2"
|
||||
serde = { version = "1.0.188", features = ["derive"] }
|
||||
sparse-05-common = { version = "0.1.0", path = "../sparse-05-common" }
|
||||
sparse-05-server = { version = "0.5.0", path = "../sparse-05-server" }
|
||||
structopt = { version = "0.3.26", features = ["paw"] }
|
||||
tokio = { version = "1.32.0", features = ["io-std", "net", "fs", "macros", "rt"] }
|
||||
27
sparse-05/sparse-05-client/src/commands/connect/mod.rs
Normal file
27
sparse-05/sparse-05-client/src/commands/connect/mod.rs
Normal file
@@ -0,0 +1,27 @@
|
||||
use std::{net::SocketAddr, path::PathBuf, sync::Arc};
|
||||
|
||||
use ed25519_dalek::{Keypair, Signer};
|
||||
use sparse_05_common::messages::CONNECT_MESSAGE;
|
||||
use tokio::{fs, net::UdpSocket};
|
||||
|
||||
use crate::configs::ClientConfig;
|
||||
|
||||
enum State {
|
||||
Ready,
|
||||
UploadingFile,
|
||||
DownloadingFile,
|
||||
}
|
||||
|
||||
pub async fn connect(config: PathBuf, ip: SocketAddr) -> anyhow::Result<()> {
|
||||
let config = fs::read(&config).await?;
|
||||
let config: ClientConfig = rmp_serde::from_slice(&config)?;
|
||||
|
||||
let remote = Arc::new(UdpSocket::bind("0.0.0.0:0").await?);
|
||||
|
||||
let connect_signature = config.keypair.sign(CONNECT_MESSAGE).to_bytes();
|
||||
let connect_msg = &[&connect_signature, CONNECT_MESSAGE].concat();
|
||||
|
||||
remote.send_to(connect_msg, ip).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
48
sparse-05/sparse-05-client/src/commands/generate.rs
Normal file
48
sparse-05/sparse-05-client/src/commands/generate.rs
Normal file
@@ -0,0 +1,48 @@
|
||||
use std::{ffi::OsString, path::PathBuf};
|
||||
|
||||
use ed25519_dalek::Keypair;
|
||||
use sparse_05_common::CONFIG_SEPARATOR;
|
||||
use tokio::{fs, io::AsyncWriteExt};
|
||||
|
||||
use crate::configs::ClientConfig;
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
pub const SPARSE_SERVER_BINARY: &'static [u8] =
|
||||
include_bytes!("../../../../target/debug/sparse-05-server");
|
||||
#[cfg(not(debug_assertions))]
|
||||
pub const SPARSE_SERVER_BINARY: &'static [u8] =
|
||||
include_bytes!("../../../../target/release/sparse-05-server");
|
||||
|
||||
pub async fn generate(mut name: PathBuf, port: u16) -> anyhow::Result<()> {
|
||||
let mut csprng = rand::thread_rng();
|
||||
let keypair = Keypair::generate(&mut csprng);
|
||||
|
||||
let mut file = fs::OpenOptions::new()
|
||||
.write(true)
|
||||
.create(true)
|
||||
.mode(0o755)
|
||||
.open(&name)
|
||||
.await?;
|
||||
|
||||
file.write_all(SPARSE_SERVER_BINARY).await?;
|
||||
file.write_all(CONFIG_SEPARATOR).await?;
|
||||
file.write_all(&port.to_be_bytes()[..]).await?;
|
||||
file.write_all(keypair.public.as_bytes()).await?;
|
||||
|
||||
let config = ClientConfig { keypair, port };
|
||||
|
||||
let mut file_part = name.file_name().unwrap().to_owned();
|
||||
file_part.push(OsString::from(".conf"));
|
||||
name.pop();
|
||||
name.push(file_part);
|
||||
let mut file = fs::OpenOptions::new()
|
||||
.write(true)
|
||||
.create(true)
|
||||
.open(&name)
|
||||
.await?;
|
||||
|
||||
let config = rmp_serde::to_vec(&config)?;
|
||||
file.write_all(&config).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
2
sparse-05/sparse-05-client/src/commands/mod.rs
Normal file
2
sparse-05/sparse-05-client/src/commands/mod.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
pub mod connect;
|
||||
pub mod generate;
|
||||
8
sparse-05/sparse-05-client/src/configs.rs
Normal file
8
sparse-05/sparse-05-client/src/configs.rs
Normal file
@@ -0,0 +1,8 @@
|
||||
use ed25519_dalek::Keypair;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct ClientConfig {
|
||||
pub keypair: Keypair,
|
||||
pub port: u16,
|
||||
}
|
||||
17
sparse-05/sparse-05-client/src/main.rs
Normal file
17
sparse-05/sparse-05-client/src/main.rs
Normal file
@@ -0,0 +1,17 @@
|
||||
use structopt::StructOpt;
|
||||
|
||||
mod commands;
|
||||
mod configs;
|
||||
mod options;
|
||||
|
||||
use options::{Command, Options};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
let options = Options::from_args();
|
||||
|
||||
match options.command {
|
||||
Command::Generate { name, port } => commands::generate::generate(name, port).await,
|
||||
Command::Connect { config, ip } => commands::connect::connect(config, ip).await,
|
||||
}
|
||||
}
|
||||
43
sparse-05/sparse-05-client/src/options.rs
Normal file
43
sparse-05/sparse-05-client/src/options.rs
Normal file
@@ -0,0 +1,43 @@
|
||||
use std::{
|
||||
net::{Ipv4Addr, SocketAddr, ToSocketAddrs},
|
||||
path::PathBuf,
|
||||
};
|
||||
|
||||
use structopt::{self, StructOpt};
|
||||
|
||||
fn to_socket_addr(src: &str) -> Result<SocketAddr, std::io::Error> {
|
||||
use std::io::{Error, ErrorKind};
|
||||
|
||||
src.to_socket_addrs()?.next().ok_or(Error::new(
|
||||
ErrorKind::Other,
|
||||
"could not get a valid socket address",
|
||||
))
|
||||
}
|
||||
|
||||
#[derive(StructOpt)]
|
||||
pub enum Command {
|
||||
Generate {
|
||||
#[structopt(parse(from_os_str))]
|
||||
name: PathBuf,
|
||||
|
||||
#[structopt(default_value = "54248")]
|
||||
port: u16,
|
||||
},
|
||||
Connect {
|
||||
#[structopt(parse(from_os_str))]
|
||||
config: PathBuf,
|
||||
|
||||
#[structopt(parse(try_from_str = to_socket_addr))]
|
||||
ip: SocketAddr,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(StructOpt)]
|
||||
#[structopt(
|
||||
name = "sparse-client",
|
||||
about = "Client to and generator of sparse shells"
|
||||
)]
|
||||
pub struct Options {
|
||||
#[structopt(subcommand)]
|
||||
pub command: Command,
|
||||
}
|
||||
11
sparse-05/sparse-05-common/Cargo.toml
Normal file
11
sparse-05/sparse-05-common/Cargo.toml
Normal file
@@ -0,0 +1,11 @@
|
||||
[package]
|
||||
name = "sparse-05-common"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
ed25519-dalek = { version = "1.0.1", features = ["serde"] }
|
||||
rand = "0.7"
|
||||
serde = { version = "1.0.188", features = ["derive"] }
|
||||
93
sparse-05/sparse-05-common/src/lib.rs
Normal file
93
sparse-05/sparse-05-common/src/lib.rs
Normal file
@@ -0,0 +1,93 @@
|
||||
pub const CONFIG_SEPARATOR: &'static [u8] =
|
||||
b"79101092eb6a40268337875896f1009da0af4fe4ae0d4c67834c54fe735f1763";
|
||||
|
||||
pub mod messages {
|
||||
pub const CONNECT_MESSAGE: &'static [u8] = b"CONNECT";
|
||||
|
||||
use std::{ffi::OsString, path::PathBuf};
|
||||
|
||||
use ed25519_dalek::Signature;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct CommandWrapper {
|
||||
sig: Signature,
|
||||
command: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub enum Command {
|
||||
SendData(Vec<u8>),
|
||||
|
||||
Cd(PathBuf),
|
||||
Ls(PathBuf),
|
||||
|
||||
OpenTTY,
|
||||
CloseTTY,
|
||||
|
||||
StartUploadFile(PathBuf, u64),
|
||||
SendFileSegment(u64, u64, Vec<u8>),
|
||||
|
||||
StartDownloadFile(PathBuf, u64),
|
||||
DownloadFileStatus(Result<(), Vec<u8>>),
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub enum FileType {
|
||||
File,
|
||||
Dir,
|
||||
Symlink(PathBuf),
|
||||
Fifo,
|
||||
Socket,
|
||||
Block,
|
||||
Char,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct UnixMetadata {
|
||||
pub mode: u32,
|
||||
pub uid: u32,
|
||||
pub gid: u32,
|
||||
pub ctime: i64,
|
||||
pub mtime: i64,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct DirEntry {
|
||||
pub name: OsString,
|
||||
pub size: u64,
|
||||
pub unix: Option<UnixMetadata>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub enum Response {
|
||||
Connected(Capabilities),
|
||||
SendData(Vec<u8>),
|
||||
|
||||
CdDone,
|
||||
LsResults(Vec<DirEntry>),
|
||||
|
||||
OpenedTTY,
|
||||
ClosedTTY,
|
||||
|
||||
UploadFileID(u64),
|
||||
UploadFileStatus(Result<(), Vec<u8>>),
|
||||
|
||||
DownloadFileSegment(u64, u64, Vec<u8>),
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub enum TransportType {
|
||||
RawUdp,
|
||||
Udp,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct Capabilities {
|
||||
pub docker_container: bool,
|
||||
pub docker_breakout: bool,
|
||||
pub setuid: bool,
|
||||
pub root: bool,
|
||||
pub transport: TransportType,
|
||||
}
|
||||
}
|
||||
23
sparse-05/sparse-05-server/Cargo.toml
Normal file
23
sparse-05/sparse-05-server/Cargo.toml
Normal file
@@ -0,0 +1,23 @@
|
||||
[package]
|
||||
name = "sparse-05-server"
|
||||
version = "0.5.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
pcap-sys = { path = "../../pcap-sys" }
|
||||
anyhow = "1.0.70"
|
||||
ed25519-dalek = "1.0.1"
|
||||
log = "0.4.17"
|
||||
simple_logger = "4.1.0"
|
||||
libc = { version = "0.2.147" }
|
||||
serde = { version = "1.0.188", features = ["derive"] }
|
||||
rmp-serde = "1.1.2"
|
||||
catconf = "0.1.2"
|
||||
sparse-05-common = { version = "0.1.0", path = "../sparse-05-common" }
|
||||
|
||||
[build-dependencies]
|
||||
cc = "1.0"
|
||||
|
||||
[features]
|
||||
docker-breakout = []
|
||||
exit = []
|
||||
60
sparse-05/sparse-05-server/src/capabilities.rs
Normal file
60
sparse-05/sparse-05-server/src/capabilities.rs
Normal file
@@ -0,0 +1,60 @@
|
||||
use std::ffi::c_int;
|
||||
|
||||
use sparse_05_common::messages::{Capabilities, TransportType};
|
||||
|
||||
const CAP_SETUID: u32 = 1 << 7;
|
||||
const CAP_NET_RAW: u32 = 1 << 13;
|
||||
const SYS_capget: i64 = 125;
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
#[repr(C)]
|
||||
#[derive(Debug)]
|
||||
struct cap_user_header_t {
|
||||
version: u32,
|
||||
pid: c_int,
|
||||
}
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
#[repr(C)]
|
||||
#[derive(Debug)]
|
||||
struct cap_user_data_t {
|
||||
effective: u32,
|
||||
permitted: u32,
|
||||
inheritable: u32,
|
||||
}
|
||||
|
||||
pub fn get_capabilities() -> anyhow::Result<Capabilities> {
|
||||
let mut header = cap_user_header_t {
|
||||
version: 0x20080522,
|
||||
pid: 0,
|
||||
};
|
||||
let mut data = cap_user_data_t {
|
||||
effective: 0,
|
||||
permitted: 0,
|
||||
inheritable: 0,
|
||||
};
|
||||
let oscapabilities =
|
||||
unsafe { libc::syscall(SYS_capget, &mut header as *const _, &mut data as *mut _) };
|
||||
|
||||
if oscapabilities == -1 {
|
||||
return Err(std::io::Error::last_os_error())?;
|
||||
}
|
||||
|
||||
let docker_container = false;
|
||||
let docker_breakout = false;
|
||||
let root = unsafe { libc::getuid() } == 0;
|
||||
let setuid = data.effective & CAP_SETUID != 0;
|
||||
let transport = if data.effective & CAP_NET_RAW != 0 || root {
|
||||
TransportType::RawUdp
|
||||
} else {
|
||||
TransportType::Udp
|
||||
};
|
||||
|
||||
Ok(Capabilities {
|
||||
docker_container,
|
||||
docker_breakout,
|
||||
setuid,
|
||||
root,
|
||||
transport,
|
||||
})
|
||||
}
|
||||
10
sparse-05/sparse-05-server/src/connection.rs
Normal file
10
sparse-05/sparse-05-server/src/connection.rs
Normal file
@@ -0,0 +1,10 @@
|
||||
use std::sync::mpsc::Sender;
|
||||
|
||||
use pcap_sys::packets::EthernetPacket;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ConnectionHandle {}
|
||||
|
||||
pub fn spawn_connection_handler(packet_sender: Sender<EthernetPacket>) -> ConnectionHandle {
|
||||
ConnectionHandle {}
|
||||
}
|
||||
37
sparse-05/sparse-05-server/src/interface.rs
Normal file
37
sparse-05/sparse-05-server/src/interface.rs
Normal file
@@ -0,0 +1,37 @@
|
||||
use std::{net::UdpSocket, sync::Arc};
|
||||
|
||||
pub enum Interface {
|
||||
RawUdp(pcap_sys::Interface<pcap_sys::DevActivated>),
|
||||
Udp(UdpSocket),
|
||||
}
|
||||
|
||||
impl Interface {
|
||||
pub fn split(self) -> (InterfaceSender, InterfaceReceiver) {
|
||||
match self {
|
||||
Self::RawUdp(interface) => {
|
||||
let arc = Arc::new(interface);
|
||||
(
|
||||
InterfaceSender::RawUdp(Arc::clone(&arc)),
|
||||
InterfaceReceiver::RawUdp(arc),
|
||||
)
|
||||
}
|
||||
Self::Udp(interface) => {
|
||||
let other = interface.try_clone().unwrap();
|
||||
(
|
||||
InterfaceSender::Udp(interface),
|
||||
InterfaceReceiver::Udp(other),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum InterfaceSender {
|
||||
RawUdp(Arc<pcap_sys::Interface<pcap_sys::DevActivated>>),
|
||||
Udp(UdpSocket),
|
||||
}
|
||||
|
||||
pub enum InterfaceReceiver {
|
||||
RawUdp(Arc<pcap_sys::Interface<pcap_sys::DevActivated>>),
|
||||
Udp(UdpSocket),
|
||||
}
|
||||
96
sparse-05/sparse-05-server/src/main.rs
Normal file
96
sparse-05/sparse-05-server/src/main.rs
Normal file
@@ -0,0 +1,96 @@
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
net::Ipv4Addr,
|
||||
sync::{mpsc::channel, Arc},
|
||||
thread,
|
||||
};
|
||||
|
||||
use anyhow::{anyhow, bail, Context};
|
||||
use connection::ConnectionHandle;
|
||||
use ed25519_dalek::PublicKey;
|
||||
|
||||
use pcap_sys::packets::EthernetPacket;
|
||||
use sparse_05_common::CONFIG_SEPARATOR;
|
||||
|
||||
mod capabilities;
|
||||
mod connection;
|
||||
mod interface;
|
||||
|
||||
fn main() -> anyhow::Result<()> {
|
||||
simple_logger::SimpleLogger::new()
|
||||
.with_level(log::LevelFilter::Off)
|
||||
.with_module_level("sparse-05-server", log::LevelFilter::Info)
|
||||
.init()?;
|
||||
|
||||
let capabilities = capabilities::get_capabilities()?;
|
||||
|
||||
let config_bytes = catconf::read_from_exe(CONFIG_SEPARATOR, 512)?;
|
||||
if config_bytes.len() != 34 {
|
||||
bail!("could not load configuration");
|
||||
}
|
||||
|
||||
let (port, pubkey) = {
|
||||
let port = u16::from_be_bytes(config_bytes[..2].try_into().unwrap());
|
||||
let pubkey = PublicKey::from_bytes(&config_bytes[2..])
|
||||
.context("could not parse public key from configuration")?;
|
||||
|
||||
(port, Arc::new(pubkey))
|
||||
};
|
||||
|
||||
let mut interfaces = pcap_sys::PcapDevIterator::new()?;
|
||||
let interface_name = interfaces
|
||||
.find(|eth| eth.starts_with("eth") || eth.starts_with("en"))
|
||||
.ok_or(anyhow!("could not get an ethernet interface"))?;
|
||||
|
||||
let mut interface = loop {
|
||||
macro_rules! retry {
|
||||
($e:expr) => {{
|
||||
match $e {
|
||||
Ok(res) => res,
|
||||
Err(e) => {
|
||||
eprintln!(
|
||||
"unable to open interface, sleeping for one second... ({:?})",
|
||||
e
|
||||
);
|
||||
std::thread::sleep(std::time::Duration::from_millis(1000));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
let mut interface = retry!(pcap_sys::Interface::<pcap_sys::DevDisabled>::new(
|
||||
&interface_name
|
||||
));
|
||||
|
||||
retry!(interface.set_buffer_size(8192));
|
||||
retry!(interface.set_non_blocking(false));
|
||||
retry!(interface.set_promisc(false));
|
||||
retry!(interface.set_timeout(10));
|
||||
|
||||
let mut interface = retry!(interface.activate());
|
||||
|
||||
retry!(interface.set_filter(&format!("inbound and port {port}"), true, None));
|
||||
|
||||
if interface.datalink() != pcap_sys::consts::DLT_EN10MB {
|
||||
bail!("interface does not properly support ethernet");
|
||||
}
|
||||
|
||||
break Arc::new(interface);
|
||||
};
|
||||
|
||||
let mut connections: HashMap<(Ipv4Addr, u16), ConnectionHandle> = HashMap::new();
|
||||
let (send_eth_packet, recv_eth_packet) = channel::<EthernetPacket>();
|
||||
|
||||
{
|
||||
let interface = Arc::clone(&interface);
|
||||
thread::spawn(move || loop {
|
||||
let Ok(packet) = recv_eth_packet.recv() else { continue };
|
||||
if let Err(_) = interface.sendpacket(packet.pkt()) {}
|
||||
});
|
||||
}
|
||||
|
||||
interface.listen(move |_, _| Ok(false), false, -1);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user