Skip to content
Merged
11 changes: 11 additions & 0 deletions docs/_docs/user-guide/imix.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ Imix has compile-time configuration, that may be specified using environment var
| IMIX_CALLBACK_URI | URI for initial callbacks (must specify a scheme, e.g. `http://`) | `http://127.0.0.1:80` | No |
| IMIX_CALLBACK_INTERVAL | Duration between callbacks, in seconds. | `5` | No |
| IMIX_RETRY_INTERVAL | Duration to wait before restarting the agent loop if an error occurs, in seconds. | `5` | No |
| IMIX_PROXY_URI | Overide system settings for proxy URI over HTTP(S) (must specify a scheme, e.g. `https://`) | No proxy | No |

## Logging

Expand All @@ -43,6 +44,16 @@ See the [Eldritch User Guide](/user-guide/eldritch) for more information.
Imix can execute up to 127 threads concurrently after that the main imix thread will block behind other threads.
Every callback interval imix will query each active thread for new output and rely that back to the c2. This means even long running tasks will report their status as new data comes in.

## Proxy support

Imix's default `grpc` transport supports http and https proxies for outbound communication.
By default imix will try to determine the systems proxy settings:

- On Linux reading the environment variables `http_proxy` and then `https_proxy`
- On Windows - we cannot automatically determine the default proxy
- On MacOS - we cannot automatically determine the default proxy
- On FreeBSD - we cannot automatically determine the default proxy

## Static cross compilation

### Linux
Expand Down
2 changes: 1 addition & 1 deletion implants/imix/src/agent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ impl Agent {
* Callback once using the configured client to claim new tasks and report available output.
*/
pub async fn callback(&mut self) -> Result<()> {
let transport = GRPC::new(self.cfg.callback_uri.clone())?;
let transport = GRPC::new(self.cfg.callback_uri.clone(), self.cfg.proxy_uri.clone())?;
self.claim_tasks(transport.clone()).await?;
self.report(transport.clone()).await?;

Expand Down
54 changes: 53 additions & 1 deletion implants/imix/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,17 @@ macro_rules! callback_uri {
}
};
}

/*
* Compile-time constant for the agent proxy URI, derived from the IMIX_PROXY_URI environment variable during compilation.
* Defaults to None if this is unset.
*/
macro_rules! proxy_uri {
() => {
option_env!("IMIX_PROXY_URI")
};
}

/*
* Compile-time constant for the agent callback URI, derived from the IMIX_CALLBACK_URI environment variable during compilation.
* Defaults to "http://127.0.0.1:80/grpc" if this is unset.
Expand Down Expand Up @@ -54,6 +65,7 @@ pub const RETRY_INTERVAL: &str = retry_interval!();
pub struct Config {
pub info: pb::c2::Beacon,
pub callback_uri: String,
pub proxy_uri: Option<String>,
pub retry_interval: u64,
}

Expand Down Expand Up @@ -92,6 +104,7 @@ impl Default for Config {
Config {
info,
callback_uri: String::from(CALLBACK_URI),
proxy_uri: get_system_proxy(),
retry_interval: match RETRY_INTERVAL.parse::<u64>() {
Ok(i) => i,
Err(_err) => {
Expand All @@ -100,13 +113,52 @@ impl Default for Config {
"failed to parse retry interval constant, defaulting to 5 seconds: {_err}"
);

5_u64
5
Copy link
Collaborator

Choose a reason for hiding this comment

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

is there a reason we removed the type hint?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yeah it confused me and I deleted it broke everything and so this is my attempt to confuse less ppl

}
},
}
}
}

fn get_system_proxy() -> Option<String> {
let proxy_uri_compile_time_override = proxy_uri!();
if let Some(proxy_uri) = proxy_uri_compile_time_override {
return Some(proxy_uri.to_string());
}

#[cfg(target_os = "linux")]
{
match std::env::var("http_proxy") {
Ok(val) => return Some(val),
Err(_e) => {
#[cfg(debug_assertions)]
log::debug!("Didn't find http_proxy env var: {}", _e);
}
}

match std::env::var("https_proxy") {
Ok(val) => return Some(val),
Err(_e) => {
#[cfg(debug_assertions)]
log::debug!("Didn't find https_proxy env var: {}", _e);
}
}
None
}
#[cfg(target_os = "windows")]
{
None
}
#[cfg(target_os = "macos")]
{
None
}
#[cfg(target_os = "freebsd")]
{
None
}
}

impl Config {
pub fn refresh_primary_ip(&mut self) {
let fresh_ip = get_primary_ip();
Expand Down
24 changes: 7 additions & 17 deletions implants/lib/eldritch/src/file/list_impl.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use super::super::insert_dict_kv;
use super::{File, FileType};
use anyhow::{Context, Result};
use chrono::{DateTime, NaiveDateTime, Utc};
use chrono::DateTime;
use glob::glob;
use starlark::{
collections::SmallMap,
Expand Down Expand Up @@ -98,7 +98,7 @@ fn create_file_from_pathbuf(path_entry: PathBuf) -> Result<File> {
}
};

let naive_datetime = match NaiveDateTime::from_timestamp_opt(timestamp, 0) {
let naive_datetime = match DateTime::from_timestamp(timestamp, 0) {
Some(local_naive_datetime) => local_naive_datetime,
None => {
return Err(anyhow::anyhow!(
Expand All @@ -107,7 +107,6 @@ fn create_file_from_pathbuf(path_entry: PathBuf) -> Result<File> {
))
}
};
let time_modified: DateTime<Utc> = DateTime::from_naive_utc_and_offset(naive_datetime, Utc);

Ok(File {
name: file_name,
Expand All @@ -117,7 +116,7 @@ fn create_file_from_pathbuf(path_entry: PathBuf) -> Result<File> {
owner: owner_username,
group: group_id.to_string(),
permissions: permissions.to_string(),
time_modified: time_modified.to_string(),
time_modified: naive_datetime.to_string(),
})
}

Expand Down Expand Up @@ -247,7 +246,6 @@ mod tests {
runtime.finish().await;

// Read Messages
let expected_output = format!("{}\n", path);
let mut found = false;
for msg in runtime.messages() {
if let Message::ReportText(m) = msg {
Expand Down Expand Up @@ -330,7 +328,7 @@ for f in file.list(input_params['path']):
.join(file);
std::fs::File::create(test_file)?;

// Run Eldritch (until finished)
// Run Eldritch (until finished) /tmp/.tmpabc123/down the/rabbit hole/win
let mut runtime = crate::start(
123,
Tome {
Expand All @@ -343,6 +341,7 @@ for f in file.list(input_params['path']):
String::from("path"),
test_dir
.path()
.join(expected_dir)
.join("*")
.join("win")
.to_str()
Expand All @@ -355,25 +354,16 @@ for f in file.list(input_params['path']):
.await;
runtime.finish().await;

let expected_output = format!(
"{}\n",
test_dir
.path()
.join(expected_dir)
.join(nested_dir)
.join(file)
.to_str()
.unwrap()
);
let mut found = false;
for msg in runtime.messages() {
if let Message::ReportText(m) = msg {
assert_eq!(123, m.id);
assert_eq!(expected_output, m.text);
assert!(m.text.contains(file));
log::debug!("text: {:?}", m.text);
found = true;
}
}
assert!(found);

Ok(())
}
Expand Down
10 changes: 7 additions & 3 deletions implants/lib/transport/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,16 @@ pb = { workspace = true }

anyhow = { workspace = true }
log = { workspace = true }
prost = { workspace = true}
prost = { workspace = true }
prost-types = { workspace = true }
tokio = { workspace = true, features = ["macros", "rt-multi-thread"] }
tokio-stream = { workspace = true }
tonic = { workspace = true, features = ["tls-roots"] }
tonic = { workspace = true, features = ["tls-webpki-roots"] }
trait-variant = { workspace = true }
hyper = { version = "0.14", features = [
"client",
] } # Had to user an older version of hyper to support hyper-proxy
hyper-proxy = "0.9.1"

# [feature = mock]
mockall = {workspace = true, optional = true }
mockall = { workspace = true, optional = true }
28 changes: 24 additions & 4 deletions implants/lib/transport/src/grpc.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use crate::Transport;
use anyhow::Result;
use hyper::Uri;
use pb::c2::*;
use std::str::FromStr;
use std::sync::mpsc::{Receiver, Sender};
use tonic::codec::ProstCodec;
use tonic::GrpcMethod;
Expand All @@ -21,12 +23,30 @@ pub struct GRPC {
}

impl Transport for GRPC {
fn new(callback: String) -> Result<Self> {
fn new(callback: String, proxy_uri: Option<String>) -> Result<Self> {
let endpoint = tonic::transport::Endpoint::from_shared(callback)?;

let channel = endpoint
.rate_limit(1, Duration::from_millis(25))
.connect_lazy();
let mut http = hyper::client::HttpConnector::new();
http.enforce_http(false);
http.set_nodelay(true);

let channel = match proxy_uri {
Some(proxy_uri_string) => {
let proxy: hyper_proxy::Proxy = hyper_proxy::Proxy::new(
hyper_proxy::Intercept::All,
Uri::from_str(proxy_uri_string.as_str())?,
);
let mut proxy_connector = hyper_proxy::ProxyConnector::from_proxy(http, proxy)?;
proxy_connector.set_tls(None);

endpoint
.rate_limit(1, Duration::from_millis(25))
.connect_with_connector_lazy(proxy_connector)
}
None => endpoint
.rate_limit(1, Duration::from_millis(25))
.connect_lazy(),
};

let grpc = tonic::client::Grpc::new(channel);
Ok(Self { grpc })
Expand Down
2 changes: 1 addition & 1 deletion implants/lib/transport/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ mock! {
fn clone(&self) -> Self;
}
impl super::Transport for Transport {
fn new(uri: String) -> Result<Self>;
fn new(uri: String, proxy_uri: Option<String>) -> Result<Self>;

async fn claim_tasks(&mut self, request: ClaimTasksRequest) -> Result<ClaimTasksResponse>;

Expand Down
2 changes: 1 addition & 1 deletion implants/lib/transport/src/transport.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use std::sync::mpsc::{Receiver, Sender};
#[trait_variant::make(Transport: Send)]
pub trait UnsafeTransport: Clone + Send {
// New will initialize a new instance of the transport using the provided URI.
fn new(uri: String) -> Result<Self>;
fn new(uri: String, proxy_uri: Option<String>) -> Result<Self>;

///
/// Contact the server for new tasks to execute.
Expand Down