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

Adds support to be compiled as a windows service. #604

Merged
merged 10 commits into from
Feb 17, 2024
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 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();

Check warning on line 19 in implants/imix/src/lib.rs

View check run for this annotation

Codecov / codecov/patch

implants/imix/src/lib.rs#L17-L19

Added lines #L17 - L19 were not covered by tests

if let Some(("install", _)) = Command::new("imix")
.subcommand(Command::new("install").about("Install imix"))
.get_matches()
.subcommand()

Check warning on line 24 in implants/imix/src/lib.rs

View check run for this annotation

Codecov / codecov/patch

implants/imix/src/lib.rs#L21-L24

Added lines #L21 - L24 were not covered by tests
{
install().await;
return;
}

Check warning on line 28 in implants/imix/src/lib.rs

View check run for this annotation

Codecov / codecov/patch

implants/imix/src/lib.rs#L26-L28

Added lines #L26 - L28 were not covered by tests

loop {
let cfg = Config::default();
let retry_interval = cfg.retry_interval;

Check warning on line 32 in implants/imix/src/lib.rs

View check run for this annotation

Codecov / codecov/patch

implants/imix/src/lib.rs#L30-L32

Added lines #L30 - L32 were not covered by tests
#[cfg(debug_assertions)]
log::info!("agent config initialized {:#?}", cfg.clone());

Check warning on line 34 in implants/imix/src/lib.rs

View check run for this annotation

Codecov / codecov/patch

implants/imix/src/lib.rs#L34

Added line #L34 was not covered by tests

match run(cfg).await {
Ok(_) => {}
Err(_err) => {

Check warning on line 38 in implants/imix/src/lib.rs

View check run for this annotation

Codecov / codecov/patch

implants/imix/src/lib.rs#L36-L38

Added lines #L36 - L38 were not covered by tests
#[cfg(debug_assertions)]
log::error!("callback loop fatal: {_err}");

Check warning on line 40 in implants/imix/src/lib.rs

View check run for this annotation

Codecov / codecov/patch

implants/imix/src/lib.rs#L40

Added line #L40 was not covered by tests

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

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

View check run for this annotation

Codecov / codecov/patch

implants/imix/src/lib.rs#L42

Added line #L42 was not covered by tests
}
}
}
}

Check warning on line 46 in implants/imix/src/lib.rs

View check run for this annotation

Codecov / codecov/patch

implants/imix/src/lib.rs#L46

Added line #L46 was not covered by tests

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

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

View check run for this annotation

Codecov / codecov/patch

implants/imix/src/lib.rs#L48-L52

Added lines #L48 - L52 were not covered by tests

#[cfg(debug_assertions)]
fn init_logging() {
pretty_env_logger::formatted_timed_builder()
.filter_level(log::LevelFilter::Info)
.parse_env("IMIX_LOG")
.init();
}

Check warning on line 60 in implants/imix/src/lib.rs

View check run for this annotation

Codecov / codecov/patch

implants/imix/src/lib.rs#L55-L60

Added lines #L55 - L60 were not covered by tests
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

Check warning on line 15 in implants/imix/src/main.rs

View check run for this annotation

Codecov / codecov/patch

implants/imix/src/main.rs#L14-L15

Added lines #L14 - L15 were not covered by tests
}

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
Loading