Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/_docs/user-guide/imix.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ RUSTFLAGS="-C target-feature=+crt-static" cargo build --release --lib --target=x
cd realm/implants/imix/
# Build imix.exe
RUSTFLAGS="-C target-feature=+crt-static" cargo build --release --target=x86_64-pc-windows-gnu
# Build imix.svc.exe
RUSTFLAGS="-C target-feature=+crt-static" cargo build --release --features win_service --target=x86_64-pc-windows-gnu
# Build imix.dll
RUSTFLAGS="-C target-feature=+crt-static" cargo build --release --lib --target=x86_64-pc-windows-gnu
```
2 changes: 2 additions & 0 deletions implants/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,11 @@ trait-variant = "0.1.1"
uuid = "1.5.0"
which = "4.4.2"
whoami = "1.3.0"
windows-service = "0.6.0"
windows-sys = "0.45.0"
winreg = "0.51.0"


[profile.release]
strip = true # Automatically strip symbols from the binary.
opt-level = "z" # Optimize for size.
Expand Down
9 changes: 9 additions & 0 deletions implants/imix/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,18 @@ name = "imix"
version = "0.0.5"
edition = "2021"

[features]
# Check if compiled by imix
win_service = []
default = []

[dependencies]
eldritch = { workspace = true, features = ["imix"] }
pb = {workspace = true }
transport = { workspace = true }

anyhow = { workspace = true }
env_logger = "0.11.2"
chrono = { workspace = true , features = ["serde"] }
clap = { workspace = true }
default-net = { workspace = true }
Expand All @@ -27,6 +33,9 @@ tokio = { workspace = true, features = ["full"] }
uuid = { workspace = true, features = ["v4","fast-rng"] }
whoami = { workspace = true }

[target.'cfg(target_os = "windows")'.dependencies]
windows-service = "0.6.0"

[dev-dependencies]
httptest = { workspace = true }
tempfile = { workspace = true }
51 changes: 51 additions & 0 deletions implants/imix/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,58 @@ mod config;
mod install;
mod task;
mod version;
#[cfg(feature = "win_service")]
pub mod win_service;

use std::time::Duration;

pub use agent::Agent;
use clap::Command;
pub use config::Config;
pub use install::install;


pub async fn handle_main(){
#[cfg(debug_assertions)]
init_logging();

if let Some(("install", _)) = Command::new("imix")
.subcommand(Command::new("install").about("Install imix"))
.get_matches()
.subcommand()
{
install().await;
return;
}

loop {
let cfg = Config::default();
let retry_interval = cfg.retry_interval;
#[cfg(debug_assertions)]
log::info!("agent config initialized {:#?}", cfg.clone());

match run(cfg).await {
Ok(_) => {}
Err(_err) => {
#[cfg(debug_assertions)]
log::error!("callback loop fatal: {_err}");

tokio::time::sleep(Duration::from_secs(retry_interval)).await;
}
}
}
}

async fn run(cfg: Config) -> anyhow::Result<()> {
let mut agent = Agent::new(cfg)?;
agent.callback_loop().await?;
Ok(())
}

#[cfg(debug_assertions)]
fn init_logging() {
pretty_env_logger::formatted_timed_builder()
.filter_level(log::LevelFilter::Info)
.parse_env("IMIX_LOG")
.init();
}
76 changes: 33 additions & 43 deletions implants/imix/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,52 +1,42 @@
#![windows_subsystem = "windows"]
// #![windows_subsystem = "windows"]
#[cfg(all(feature = "win_service", windows))]
#[macro_use]
extern crate windows_service;

use anyhow::Result;
use clap::Command;
use imix::{Agent, Config};
use std::time::Duration;
use imix::handle_main;


// ============= Standard ===============

#[cfg(not(feature = "win_service"))]
#[tokio::main(flavor = "multi_thread", worker_threads = 128)]
async fn main() {
#[cfg(debug_assertions)]
init_logging();

if let Some(("install", _)) = Command::new("imix")
.subcommand(Command::new("install").about("Install imix"))
.get_matches()
.subcommand()
{
imix::install().await;
return;
}

loop {
let cfg = Config::default();
let retry_interval = cfg.retry_interval;
#[cfg(debug_assertions)]
log::info!("agent config initialized {:#?}", cfg.clone());

match run(cfg).await {
Ok(_) => {}
Err(_err) => {
#[cfg(debug_assertions)]
log::error!("callback loop fatal: {_err}");

tokio::time::sleep(Duration::from_secs(retry_interval)).await;
}
}
}

handle_main().await
}

async fn run(cfg: Config) -> Result<()> {
let mut agent = Agent::new(cfg)?;
agent.callback_loop().await?;
Ok(())

// ============ Windows Service =============

#[cfg(all(feature = "win_service", not(target_os = "windows")))]
compile_error!("Feature win_service is only available on windows targets");

#[cfg(feature = "win_service")]
define_windows_service!(ffi_service_main, service_main);

#[cfg(feature = "win_service")]
fn main() {
use windows_service::service_dispatcher;
service_dispatcher::start("imix", ffi_service_main).unwrap();
}

#[cfg(debug_assertions)]
fn init_logging() {
pretty_env_logger::formatted_timed_builder()
.filter_level(log::LevelFilter::Info)
.parse_env("IMIX_LOG")
.init();
#[cfg(feature = "win_service")]
#[tokio::main(flavor = "multi_thread", worker_threads = 128)]
async fn service_main(arguments: Vec<std::ffi::OsString>) {
use imix::win_service::handle_service_main;

handle_service_main(arguments);

handle_main().await;
}

45 changes: 45 additions & 0 deletions implants/imix/src/win_service.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
use std::{ffi::OsString, time::Duration};
use windows_service::{
service::{
ServiceControl, ServiceControlAccept, ServiceExitCode, ServiceState, ServiceStatus,
ServiceType,
},
service_control_handler::{self, ServiceControlHandlerResult},
};

pub fn handle_service_main(_arguments: Vec<OsString>) {
let event_handler = move |control_event| -> ServiceControlHandlerResult {
match control_event {
ServiceControl::Stop => {
// Handle stop event and return control back to the system.
ServiceControlHandlerResult::NoError
}
// All services must accept Interrogate even if it's a no-op.
ServiceControl::Interrogate => ServiceControlHandlerResult::NoError,
_ => ServiceControlHandlerResult::NotImplemented,
}
};

// Register system service event handler
let status_handle = service_control_handler::register("myservice", event_handler).unwrap();

let next_status = ServiceStatus {
// Should match the one from system service registry
service_type: ServiceType::OWN_PROCESS,
// The new state
current_state: ServiceState::Running,
// Accept stop events when running
controls_accepted: ServiceControlAccept::STOP,
// Used to report an error when starting or stopping only, otherwise must be zero
exit_code: ServiceExitCode::Win32(0),
// Only used for pending states, otherwise must be zero
checkpoint: 0,
// Only used for pending states, otherwise must be zero
wait_hint: Duration::default(),
// Process ID of the service This is only retrieved when querying the service status
process_id: None,
};

// Tell the system that the service is running now
status_handle.set_service_status(next_status).unwrap();
}
1 change: 0 additions & 1 deletion implants/lib/transport/src/grpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ use tonic::codec::ProstCodec;
use tonic::GrpcMethod;
use tonic::Request;

#[cfg(debug_assertions)]
use std::time::Duration;

static CLAIM_TASKS_PATH: &str = "/c2.C2/ClaimTasks";
Expand Down