Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[feature] Refactor Concurrency #599

Merged
merged 14 commits into from
Feb 16, 2024
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,7 @@
"rust-analyzer"
],
"rust-analyzer.check.command": "clippy",
"rust-analyzer.linkedProjects": [
"./implants/lib/transport/Cargo.toml"
KCarretto marked this conversation as resolved.
Show resolved Hide resolved
],
}
31 changes: 31 additions & 0 deletions docs/_docs/user-guide/eldritch.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ It currently contains seven modules:
- `file` - Used to interact with files on the system.
- `pivot` - Used to identify and move between systems.
- `process` - Used to interact with processes on the system.
- `report` - Structured data reporting capabilities.
- `sys` - General system capabilities can include loading libraries, or information about the current context.
- `time` - General functions for obtaining and formatting time, also add delays into code.

Expand Down Expand Up @@ -635,6 +636,36 @@ The <b>process.netstat</b> method returns all information on TCP, UDP, and Unix

---

## Report

The report library is designed to enabled reporting structured data to Tavern. It's API is still in the active development phase, so **future versions of Eldritch may break tomes that rely on this API**.
KCarretto marked this conversation as resolved.
Show resolved Hide resolved

### report.file

`report.file(path: str) -> None`

Reports a file from the host that an Eldritch Tome is being evaluated on (e.g. a compromised system) to Tavern. It has a 1GB size limit, and will report the file in 1MB chunks. This process happens asynchronously, so after `report.file()` returns **there are no guarantees about when this file will be reported**. This means that if you delete the file immediately after reporting it, it may not be reported at all (race condition).

### report.process_list

`report.process_list(list: List<Dict>) -> None`
KCarretto marked this conversation as resolved.
Show resolved Hide resolved

Reports a snapshot of the currently running processes on the host system. This should only be called with the entire process list (e.g. from calling `process.list()`), as it will replace Tavern's current list of processes for the host with this new snapshot.

### report.ssh_key

`report.ssh_key(username: str, key: str) -> None`

Reports a captured SSH Key credential to Tavern. It will automatically be associated with the host that the Eldritch Tome was being evaluated on.

### report.user_password

`report.user_password(username: str, password: str) -> None`

Reports a captured username & password combination to Tavern. It will automatically be associated with the host that the Eldritch Tome was being evaluated on.

---

## Sys

### sys.dll_inject
Expand Down
12 changes: 8 additions & 4 deletions implants/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
[workspace]
members = ["imix", "golem", "lib/eldritch", "lib/c2"]
members = ["imix", "golem", "lib/eldritch", "lib/transport", "lib/pb"]
resolver = "2"

[workspace.dependencies]
transport = { path = "./lib/transport" }
eldritch = { path = "./lib/eldritch" }
pb = { path = "./lib/pb" }

aes = "0.8.3"
allocative = "0.3.2"
allocative_derive = "0.3.2"
Expand All @@ -15,7 +19,6 @@ chrono = "0.4.24"
clap = "3.2.23"
default-net = "0.13.1"
derive_more = "0.99.17"
eldritch = { path = "./lib/eldritch" }
eval = "0.4.3"
flate2 = "1.0.24"
gazebo = "0.8.1"
Expand All @@ -29,6 +32,7 @@ itertools = "0.10"
lsp-types = "0.93.0"
log = "0.4.20"
md5 = "0.7.0"
mockall = "0.12.1"
KCarretto marked this conversation as resolved.
Show resolved Hide resolved
netstat2 = "0.9.1"
network-interface = "1.0.1"
nix = "0.26.1"
Expand Down Expand Up @@ -58,15 +62,15 @@ structopt = "0.3.23"
sys-info = "0.9.1"
sysinfo = "0.29.7"
tar = "0.4.38"
tonic-build = "0.10"
c2 = { path = "./lib/c2" }
tempfile = "3.3.0"
tera = "1.17.1"
thiserror = "1.0.30"
tokio = "1.19.1"
tokio-stream = "0.1.9"
tokio-test = "*"
tonic = { git = "https://github.com/hyperium/tonic.git", rev = "07e4ee1" }
tonic-build = "0.10"
trait-variant = "0.1.1"
uuid = "1.5.0"
which = "4.4.2"
whoami = "1.3.0"
Expand Down
4 changes: 3 additions & 1 deletion implants/golem/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ version = "0.0.5"
edition = "2021"

[dependencies]
starlark_lsp = "0.12.0"
pb = { workspace = true }
eldritch = { workspace = true, features = ["print_stdout"] }

starlark_lsp = "0.12.0"
tokio = { workspace = true, features = ["macros"] }
clap = { workspace = true }
starlark = { workspace = true }
Expand Down
32 changes: 21 additions & 11 deletions implants/golem/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ mod inter;

use anyhow::{anyhow, Result};
use clap::{Arg, Command};
use eldritch::pb::Tome;
use eldritch::runtime::Message;
use pb::eldritch::Tome;
use std::collections::HashMap;
use std::fs;
use std::process;
Expand All @@ -16,25 +17,34 @@ struct ParsedTome {

async fn run_tomes(tomes: Vec<ParsedTome>) -> Result<Vec<String>> {
let mut runtimes = Vec::new();
let mut idx = 1;
KCarretto marked this conversation as resolved.
Show resolved Hide resolved
for tome in tomes {
let runtime = eldritch::start(Tome {
eldritch: tome.eldritch,
parameters: HashMap::new(),
file_names: Vec::new(),
})
let runtime = eldritch::start(
idx,
Tome {
eldritch: tome.eldritch,
parameters: HashMap::new(),
file_names: Vec::new(),
},
)
.await;
runtimes.push(runtime);
idx += 1;
}

let mut result = Vec::new();
for runtime in &mut runtimes {
runtime.finish().await;
let mut out = runtime.collect_text();
let errors = runtime.collect_errors();
if !errors.is_empty() {
return Err(anyhow!("tome execution failed: {:?}", errors));

for msg in runtime.messages() {
match msg {
KCarretto marked this conversation as resolved.
Show resolved Hide resolved
Message::ReportText(m) => result.push(m.text()),
Message::ReportError(m) => {
return Err(anyhow!("{}", m.error));
}
_ => {}
}
}
result.append(&mut out);
}

Ok(result)
Expand Down
6 changes: 4 additions & 2 deletions implants/imix/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@ version = "0.0.5"
edition = "2021"

[dependencies]
eldritch = { workspace = true, features = ["imix"] }
pb = {workspace = true }
transport = { workspace = true }
KCarretto marked this conversation as resolved.
Show resolved Hide resolved

anyhow = { workspace = true }
chrono = { workspace = true , features = ["serde"] }
clap = { workspace = true }
default-net = { workspace = true }
eldritch = { workspace = true, features = ["imix"] }
hyper = { workspace = true }
log = {workspace = true}
openssl = { workspace = true, features = ["vendored"] }
Expand All @@ -19,7 +22,6 @@ reqwest = { workspace = true, features = ["blocking", "stream", "json"] }
serde = { workspace = true, features = ["derive"] }
serde_json = {workspace = true}
sys-info = { workspace = true }
c2 = { workspace = true }
tonic = { workspace = true }
tokio = { workspace = true, features = ["full"] }
uuid = { workspace = true, features = ["v4","fast-rng"] }
Expand Down
8 changes: 3 additions & 5 deletions implants/imix/src/agent.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
use crate::{config::Config, task::TaskHandle};
use anyhow::Result;
use c2::{
pb::{Beacon, ClaimTasksRequest},
Transport, GRPC,
};
use pb::c2::{Beacon, ClaimTasksRequest};
use std::time::{Duration, Instant};
use transport::{Transport, GRPC};

/*
* Agent contains all relevant logic for managing callbacks to a c2 server.
Expand Down Expand Up @@ -51,7 +49,7 @@
}
};

let runtime = eldritch::start(tome).await;
let runtime = eldritch::start(task.id, tome).await;

Check warning on line 52 in implants/imix/src/agent.rs

View check run for this annotation

Codecov / codecov/patch

implants/imix/src/agent.rs#L52

Added line #L52 was not covered by tests
self.handles.push(TaskHandle::new(task.id, runtime));

#[cfg(debug_assertions)]
Expand Down
14 changes: 8 additions & 6 deletions implants/imix/src/config.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::version::VERSION;
use c2::pb::host::Platform;
use pb::c2::host::Platform;
use std::{
fs::{self, File},
io::Write,
Expand Down Expand Up @@ -52,7 +52,7 @@
*/
#[derive(Debug, Clone)]
pub struct Config {
pub info: c2::pb::Beacon,
pub info: pb::c2::Beacon,
pub callback_uri: String,
pub retry_interval: u64,
}
Expand All @@ -62,18 +62,18 @@
*/
impl Default for Config {
fn default() -> Self {
let agent = c2::pb::Agent {
let agent = pb::c2::Agent {

Check warning on line 65 in implants/imix/src/config.rs

View check run for this annotation

Codecov / codecov/patch

implants/imix/src/config.rs#L65

Added line #L65 was not covered by tests
identifier: format!("imix-v{}", VERSION),
};

let host = c2::pb::Host {
let host = pb::c2::Host {

Check warning on line 69 in implants/imix/src/config.rs

View check run for this annotation

Codecov / codecov/patch

implants/imix/src/config.rs#L69

Added line #L69 was not covered by tests
name: whoami::hostname(),
identifier: get_host_id(get_host_id_path()),
platform: get_host_platform() as i32,
primary_ip: get_primary_ip(),
};

let info = c2::pb::Beacon {
let info = pb::c2::Beacon {

Check warning on line 76 in implants/imix/src/config.rs

View check run for this annotation

Codecov / codecov/patch

implants/imix/src/config.rs#L76

Added line #L76 was not covered by tests
identifier: String::from(Uuid::new_v4()),
principal: whoami::username(),
interval: match CALLBACK_INTERVAL.parse::<u64>() {
Expand Down Expand Up @@ -155,7 +155,9 @@
// Read Existing Host ID
let path = Path::new(file_path.as_str());
if path.exists() {
if let Ok(host_id) = fs::read_to_string(path) { return host_id.trim().to_string() }
if let Ok(host_id) = fs::read_to_string(path) {
return host_id.trim().to_string();
}

Check warning on line 160 in implants/imix/src/config.rs

View check run for this annotation

Codecov / codecov/patch

implants/imix/src/config.rs#L158-L160

Added lines #L158 - L160 were not covered by tests
}

// Generate New
Expand Down
33 changes: 24 additions & 9 deletions implants/imix/src/install.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use anyhow::{anyhow, Result};
use eldritch::pb::Tome;
use std::collections::HashMap;
use eldritch::runtime::Message;
use pb::eldritch::Tome;
use std::{collections::HashMap, fmt::Write};

pub async fn install() {
#[cfg(debug_assertions)]
Expand Down Expand Up @@ -31,17 +32,31 @@
// Run tome
#[cfg(debug_assertions)]
log::info!("running tome {embedded_file_path}");
let mut runtime = eldritch::start(Tome {
eldritch,
parameters: HashMap::new(),
file_names: Vec::new(),
})
let mut runtime = eldritch::start(
0,
Tome {
eldritch,
parameters: HashMap::new(),
file_names: Vec::new(),
},
)

Check warning on line 42 in implants/imix/src/install.rs

View check run for this annotation

Codecov / codecov/patch

implants/imix/src/install.rs#L35-L42

Added lines #L35 - L42 were not covered by tests
.await;
runtime.finish().await;

let _output = runtime.collect_text().join("");
#[cfg(debug_assertions)]
log::info!("{_output}");
let mut output = String::new();

Check warning on line 47 in implants/imix/src/install.rs

View check run for this annotation

Codecov / codecov/patch

implants/imix/src/install.rs#L47

Added line #L47 was not covered by tests

#[cfg(debug_assertions)]
for msg in runtime.collect() {
if let Message::ReportText(m) = msg {
if let Err(err) = output.write_str(m.text().as_str()) {

Check warning on line 52 in implants/imix/src/install.rs

View check run for this annotation

Codecov / codecov/patch

implants/imix/src/install.rs#L50-L52

Added lines #L50 - L52 were not covered by tests
#[cfg(debug_assertions)]
log::error!("failed to write text: {}", err);
}
}

Check warning on line 56 in implants/imix/src/install.rs

View check run for this annotation

Codecov / codecov/patch

implants/imix/src/install.rs#L54-L56

Added lines #L54 - L56 were not covered by tests
}
#[cfg(debug_assertions)]
log::info!("{output}");

Check warning on line 59 in implants/imix/src/install.rs

View check run for this annotation

Codecov / codecov/patch

implants/imix/src/install.rs#L59

Added line #L59 was not covered by tests
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's unclear what's happening here - but I think we want imix install to print in realtime similar to golem.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does not change any behavior, but currently this will print all output after evaluation. The print_stdout is a feature flag we likely don't want enabled for imix.

}
}
}
Expand Down
Loading
Loading