From c2c8b96aeedc0abc4697779cf571b9667f01fb04 Mon Sep 17 00:00:00 2001 From: LByrgeCP <43020092+LByrgeCP@users.noreply.github.com> Date: Sat, 3 May 2025 21:09:51 -0500 Subject: [PATCH 01/13] Added registry host_unique First time doing rust, will this work chat? --- implants/lib/host_unique/Cargo.toml | 3 + implants/lib/host_unique/src/lib.rs | 4 +- implants/lib/host_unique/src/registry.rs | 105 +++++++++++++++++++++++ 3 files changed, 111 insertions(+), 1 deletion(-) create mode 100644 implants/lib/host_unique/src/registry.rs diff --git a/implants/lib/host_unique/Cargo.toml b/implants/lib/host_unique/Cargo.toml index 08793b50e..c9b1901df 100644 --- a/implants/lib/host_unique/Cargo.toml +++ b/implants/lib/host_unique/Cargo.toml @@ -9,6 +9,9 @@ edition = "2021" uuid = { workspace = true, features = ["v4", "fast-rng"] } log = { workspace = true } +[target.'cfg(target_os = "windows")'.dependencies] +winreg = "0.55" + [dev-dependencies] pretty_env_logger = "0.5.0" tempfile = { workspace = true } diff --git a/implants/lib/host_unique/src/lib.rs b/implants/lib/host_unique/src/lib.rs index b39060751..3b5ed4aa2 100644 --- a/implants/lib/host_unique/src/lib.rs +++ b/implants/lib/host_unique/src/lib.rs @@ -4,6 +4,8 @@ mod env; pub use env::Env; mod file; pub use file::File; +mod registry; +pub use registry::Registry; pub trait HostIDSelector { fn get_name(&self) -> String; @@ -33,5 +35,5 @@ pub fn get_id_with_selectors(selectors: Vec>) -> Uuid { // List is evaluated in order and will take the first successful // result. pub fn defaults() -> Vec> { - vec![Box::::default(), Box::::default()] + vec![Box::::default(), Box::::default(), Box::::default()] } diff --git a/implants/lib/host_unique/src/registry.rs b/implants/lib/host_unique/src/registry.rs new file mode 100644 index 000000000..78d14ab40 --- /dev/null +++ b/implants/lib/host_unique/src/registry.rs @@ -0,0 +1,105 @@ +use uuid::Uuid; +use crate::HostIDSelector; + +#[derive(Default)] +pub struct Registry { + subkey: Option, + value_name: Option, +} + +impl Registry { + /* + * Returns a predefined path to the host id registry key. + */ + pub fn with_subkey(mut self, path: impl Into) -> Self { + self.subkey = Some(path.into()); + self + } + + pub fn with_value_name(mut self, name: impl Into) -> Self { + self.value_name = Some(name.into()); + self + } + + fn key_path(&self) -> &str { + self.subkey + .as_deref() + .unwrap_or("SOFTWARE\\Imix") + } + + fn val_name(&self) -> &str { + self.value_name + .as_deref() + .unwrap_or("HostId") + } +} + +impl HostIDSelector for Registry { + fn get_name(&self) -> String { + "registry".into() + } + + fn get_host_id(&self) -> Option { + // On non‑Windows targets this selector is unavailable + #[cfg(not(target_os = "windows"))] + { + return None; + } + + #[cfg(target_os = "windows")] + { + use winreg::{enums::HKEY_LOCAL_MACHINE, RegKey}; + + // Open or create our key under HKLM + let hklm = RegKey::predef(HKEY_LOCAL_MACHINE); + let (key, _) = match hklm.create_subkey(self.key_path()) { + Ok(pair) => pair, + Err(err) => { + #[cfg(debug_assertions)] + log::debug!("could not open/create registry key: {:?}", err); + return None; + } + }; + + // Try to read existing value + if let Ok(stored) = key.get_value::(self.val_name()) { + if let Ok(uuid) = Uuid::parse_str(&stored) { + return Some(uuid); + } else { + #[cfg(debug_assertions)] + log::debug!("invalid UUID in registry: {:?}", stored); + } + } + + // Otherwise generate a new one and persist it + let new_uuid = Uuid::new_v4(); + let s = new_uuid.to_string(); + if let Err(err) = key.set_value(self.val_name(), &s) { + #[cfg(debug_assertions)] + log::debug!("failed to write registry value: {:?}", err); + } + Some(new_uuid) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use winreg::RegKey; + use winreg::enums::HKEY_LOCAL_MACHINE; + + #[test] + fn test_registry() { + let selector = Registry::default(); + let id_one = selector.get_host_id(); + let id_two = selector.get_host_id(); + + assert!(id_one.is_some()); + assert!(id_two.is_some()); + assert_eq!(id_one, id_two); + + let hklm = RegKey::predef(HKEY_LOCAL_MACHINE); + let _ = hklm.delete_subkey_all(selector.key_path()); + } +} \ No newline at end of file From 50359c3de4d19a708c24facb9096cbe340849b60 Mon Sep 17 00:00:00 2001 From: LByrgeCP <43020092+LByrgeCP@users.noreply.github.com> Date: Sun, 4 May 2025 00:37:12 -0500 Subject: [PATCH 02/13] Change keyname --- implants/lib/host_unique/src/registry.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/implants/lib/host_unique/src/registry.rs b/implants/lib/host_unique/src/registry.rs index 78d14ab40..76ce5c02e 100644 --- a/implants/lib/host_unique/src/registry.rs +++ b/implants/lib/host_unique/src/registry.rs @@ -30,7 +30,7 @@ impl Registry { fn val_name(&self) -> &str { self.value_name .as_deref() - .unwrap_or("HostId") + .unwrap_or("system-id") } } @@ -54,9 +54,9 @@ impl HostIDSelector for Registry { let hklm = RegKey::predef(HKEY_LOCAL_MACHINE); let (key, _) = match hklm.create_subkey(self.key_path()) { Ok(pair) => pair, - Err(err) => { + Err(_err) => { #[cfg(debug_assertions)] - log::debug!("could not open/create registry key: {:?}", err); + log::debug!("could not open/create registry key: {:?}", _err); return None; } }; @@ -74,9 +74,9 @@ impl HostIDSelector for Registry { // Otherwise generate a new one and persist it let new_uuid = Uuid::new_v4(); let s = new_uuid.to_string(); - if let Err(err) = key.set_value(self.val_name(), &s) { + if let Err(_err) = key.set_value(self.val_name(), &s) { #[cfg(debug_assertions)] - log::debug!("failed to write registry value: {:?}", err); + log::debug!("failed to write registry value: {:?}", _err); } Some(new_uuid) } From 5b8b09c68fd75d5ab8ace280e9b5871a753bc792 Mon Sep 17 00:00:00 2001 From: LByrgeCP <43020092+LByrgeCP@users.noreply.github.com> Date: Sun, 4 May 2025 01:16:08 -0500 Subject: [PATCH 03/13] Working for all users now --- implants/lib/host_unique/src/registry.rs | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/implants/lib/host_unique/src/registry.rs b/implants/lib/host_unique/src/registry.rs index 76ce5c02e..e0b72cab6 100644 --- a/implants/lib/host_unique/src/registry.rs +++ b/implants/lib/host_unique/src/registry.rs @@ -49,14 +49,27 @@ impl HostIDSelector for Registry { #[cfg(target_os = "windows")] { use winreg::{enums::HKEY_LOCAL_MACHINE, RegKey}; + use std::io::ErrorKind; - // Open or create our key under HKLM let hklm = RegKey::predef(HKEY_LOCAL_MACHINE); - let (key, _) = match hklm.create_subkey(self.key_path()) { - Ok(pair) => pair, + + // Try to open the key for reading + let key = match hklm.open_subkey(self.key_path()) { + Ok(k) => k, + Err(_err) if _err.kind() == ErrorKind::NotFound => { + // If it doesn't exist, create it + match hklm.create_subkey(self.key_path()) { + Ok((k, _disp)) => k, + Err(_err) => { + #[cfg(debug_assertions)] + log::debug!("failed to create registry key: {:?}", _err); + return None; + } + } + } Err(_err) => { #[cfg(debug_assertions)] - log::debug!("could not open/create registry key: {:?}", _err); + log::debug!("failed to open registry key: {:?}", _err); return None; } }; From 3517998d58b2c26193243a7522b2d63bc6f20e15 Mon Sep 17 00:00:00 2001 From: LByrgeCP <43020092+LByrgeCP@users.noreply.github.com> Date: Sun, 4 May 2025 01:25:32 -0500 Subject: [PATCH 04/13] cargo fmt is crazy --- implants/lib/host_unique/src/lib.rs | 6 +++++- implants/lib/host_unique/src/registry.rs | 12 +++++------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/implants/lib/host_unique/src/lib.rs b/implants/lib/host_unique/src/lib.rs index 3b5ed4aa2..f1d67c970 100644 --- a/implants/lib/host_unique/src/lib.rs +++ b/implants/lib/host_unique/src/lib.rs @@ -35,5 +35,9 @@ pub fn get_id_with_selectors(selectors: Vec>) -> Uuid { // List is evaluated in order and will take the first successful // result. pub fn defaults() -> Vec> { - vec![Box::::default(), Box::::default(), Box::::default()] + vec![ + Box::::default(), + Box::::default(), + Box::::default(), + ] } diff --git a/implants/lib/host_unique/src/registry.rs b/implants/lib/host_unique/src/registry.rs index e0b72cab6..7f101647b 100644 --- a/implants/lib/host_unique/src/registry.rs +++ b/implants/lib/host_unique/src/registry.rs @@ -1,5 +1,5 @@ -use uuid::Uuid; use crate::HostIDSelector; +use uuid::Uuid; #[derive(Default)] pub struct Registry { @@ -28,9 +28,7 @@ impl Registry { } fn val_name(&self) -> &str { - self.value_name - .as_deref() - .unwrap_or("system-id") + self.value_name.as_deref().unwrap_or("system-id") } } @@ -48,8 +46,8 @@ impl HostIDSelector for Registry { #[cfg(target_os = "windows")] { - use winreg::{enums::HKEY_LOCAL_MACHINE, RegKey}; use std::io::ErrorKind; + use winreg::{enums::HKEY_LOCAL_MACHINE, RegKey}; let hklm = RegKey::predef(HKEY_LOCAL_MACHINE); @@ -99,8 +97,8 @@ impl HostIDSelector for Registry { #[cfg(test)] mod tests { use super::*; - use winreg::RegKey; use winreg::enums::HKEY_LOCAL_MACHINE; + use winreg::RegKey; #[test] fn test_registry() { @@ -115,4 +113,4 @@ mod tests { let hklm = RegKey::predef(HKEY_LOCAL_MACHINE); let _ = hklm.delete_subkey_all(selector.key_path()); } -} \ No newline at end of file +} From 5982837a5242b344b1c6755ac515ba9905293ab6 Mon Sep 17 00:00:00 2001 From: LByrgeCP <43020092+LByrgeCP@users.noreply.github.com> Date: Sun, 4 May 2025 01:28:34 -0500 Subject: [PATCH 05/13] more formatting --- implants/lib/host_unique/src/registry.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/implants/lib/host_unique/src/registry.rs b/implants/lib/host_unique/src/registry.rs index 7f101647b..cee2526e9 100644 --- a/implants/lib/host_unique/src/registry.rs +++ b/implants/lib/host_unique/src/registry.rs @@ -22,9 +22,7 @@ impl Registry { } fn key_path(&self) -> &str { - self.subkey - .as_deref() - .unwrap_or("SOFTWARE\\Imix") + self.subkey.as_deref().unwrap_or("SOFTWARE\\Imix") } fn val_name(&self) -> &str { From 272781a36f3cafef5031960ae5b5c43048f0f9c7 Mon Sep 17 00:00:00 2001 From: LByrgeCP <43020092+LByrgeCP@users.noreply.github.com> Date: Sun, 4 May 2025 01:34:55 -0500 Subject: [PATCH 06/13] Better tests? --- implants/lib/host_unique/src/registry.rs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/implants/lib/host_unique/src/registry.rs b/implants/lib/host_unique/src/registry.rs index cee2526e9..92c21dcb9 100644 --- a/implants/lib/host_unique/src/registry.rs +++ b/implants/lib/host_unique/src/registry.rs @@ -95,11 +95,13 @@ impl HostIDSelector for Registry { #[cfg(test)] mod tests { use super::*; - use winreg::enums::HKEY_LOCAL_MACHINE; - use winreg::RegKey; + #[cfg(target_os = "windows")] #[test] fn test_registry() { + use winreg::enums::HKEY_LOCAL_MACHINE; + use winreg::RegKey; + let selector = Registry::default(); let id_one = selector.get_host_id(); let id_two = selector.get_host_id(); @@ -111,4 +113,12 @@ mod tests { let hklm = RegKey::predef(HKEY_LOCAL_MACHINE); let _ = hklm.delete_subkey_all(selector.key_path()); } + + #[cfg(not(target_os = "windows"))] + #[test] + fn test_registry_non_windows() { + let selector = Registry::default(); + // on non‑Windows we expect registry lookup to be None + assert!(selector.get_host_id().is_none()); + } } From fef99ac946c4186585855d307d1f94bc8deba062 Mon Sep 17 00:00:00 2001 From: LByrgeCP <43020092+LByrgeCP@users.noreply.github.com> Date: Sun, 4 May 2025 01:42:06 -0500 Subject: [PATCH 07/13] Fix warning of dead code --- implants/lib/host_unique/src/registry.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/implants/lib/host_unique/src/registry.rs b/implants/lib/host_unique/src/registry.rs index 92c21dcb9..11bdc873e 100644 --- a/implants/lib/host_unique/src/registry.rs +++ b/implants/lib/host_unique/src/registry.rs @@ -21,10 +21,12 @@ impl Registry { self } + #[cfg(target_os = "windows")] fn key_path(&self) -> &str { self.subkey.as_deref().unwrap_or("SOFTWARE\\Imix") } + #[cfg(target_os = "windows")] fn val_name(&self) -> &str { self.value_name.as_deref().unwrap_or("system-id") } From c4ef2588daae3dd47c61ad058f317cd463cfc83c Mon Sep 17 00:00:00 2001 From: LByrgeCP <43020092+LByrgeCP@users.noreply.github.com> Date: Sun, 4 May 2025 01:45:34 -0500 Subject: [PATCH 08/13] Doc typo --- docs/_docs/user-guide/imix.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/_docs/user-guide/imix.md b/docs/_docs/user-guide/imix.md index 281a1e20d..010734afd 100644 --- a/docs/_docs/user-guide/imix.md +++ b/docs/_docs/user-guide/imix.md @@ -64,7 +64,7 @@ Imix uses the `host_unique` library under `implants/lib/host_unique` to determin We recommend that you use the `File` for the most reliability: - Exists across reboots -- Garunteed to be unique per host (because the bot creates it) +- Guaranteed to be unique per host (because the bot creates it) - Can be used by multiple instances of the beacon on the same host. If you cannot use the `File` selector we highly recommend manually setting the `Env` selector with the environment variable `IMIX_HOST_ID`. This will override the `File` one avoiding writes to disk but must be managed by the operators. From b6349900081f6a1055db4b20bb6a4ba68016a4a5 Mon Sep 17 00:00:00 2001 From: LByrgeCP <43020092+LByrgeCP@users.noreply.github.com> Date: Sun, 4 May 2025 23:25:47 -0500 Subject: [PATCH 09/13] Remove `Registry` from the defaults --- implants/lib/host_unique/src/lib.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/implants/lib/host_unique/src/lib.rs b/implants/lib/host_unique/src/lib.rs index f1d67c970..7598a500e 100644 --- a/implants/lib/host_unique/src/lib.rs +++ b/implants/lib/host_unique/src/lib.rs @@ -4,8 +4,6 @@ mod env; pub use env::Env; mod file; pub use file::File; -mod registry; -pub use registry::Registry; pub trait HostIDSelector { fn get_name(&self) -> String; @@ -37,7 +35,6 @@ pub fn get_id_with_selectors(selectors: Vec>) -> Uuid { pub fn defaults() -> Vec> { vec![ Box::::default(), - Box::::default(), Box::::default(), ] } From 679b37896923030c7f0fd976ab3d39bb204d959a Mon Sep 17 00:00:00 2001 From: LByrgeCP <43020092+LByrgeCP@users.noreply.github.com> Date: Sun, 4 May 2025 23:27:51 -0500 Subject: [PATCH 10/13] Add documentation --- docs/_docs/dev-guide/imix.md | 2 ++ docs/_docs/user-guide/imix.md | 2 ++ 2 files changed, 4 insertions(+) diff --git a/docs/_docs/dev-guide/imix.md b/docs/_docs/dev-guide/imix.md index ff4f38206..65fed4035 100644 --- a/docs/_docs/dev-guide/imix.md +++ b/docs/_docs/dev-guide/imix.md @@ -25,6 +25,8 @@ Out of the box realm comes with two options `File` and `Env` to determine what h `Env` will read from the agent environment variables looking for `IMIX_HOST_ID` if it's set it will use the UUID4 string set there. +There is a third option available on Windows systems to store the UUID value inside a registry key. Follow the steps below to update `lib.rs` to include `Registry` as a default before `File` to enable it. On hosts that are not Windows, imix will simply skip `Registry`. + If no selectors succeed a random UUID4 ID will be generated and used for the bot. This should be avoided. ## Develop A Host Uniqueness Selector diff --git a/docs/_docs/user-guide/imix.md b/docs/_docs/user-guide/imix.md index 010734afd..fe9b31f8a 100644 --- a/docs/_docs/user-guide/imix.md +++ b/docs/_docs/user-guide/imix.md @@ -69,6 +69,8 @@ We recommend that you use the `File` for the most reliability: If you cannot use the `File` selector we highly recommend manually setting the `Env` selector with the environment variable `IMIX_HOST_ID`. This will override the `File` one avoiding writes to disk but must be managed by the operators. +For Windows hosts, a `Registry` selector is available, but must be enabled before compilation. See the [imix dev guide](/dev-guide/imix#host-selector) on how to enable it. + If all uniqueness selectors fail imix will randomly generate a UUID to avoid crashing. This isn't ideal as in the UI each new beacon will appear as thought it were on a new host. From 1b7f9097dbf569b7858cc4e769a674153ca2c3fb Mon Sep 17 00:00:00 2001 From: LByrgeCP <43020092+LByrgeCP@users.noreply.github.com> Date: Sun, 4 May 2025 23:34:04 -0500 Subject: [PATCH 11/13] fmt ?????? --- implants/lib/host_unique/src/lib.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/implants/lib/host_unique/src/lib.rs b/implants/lib/host_unique/src/lib.rs index 7598a500e..b39060751 100644 --- a/implants/lib/host_unique/src/lib.rs +++ b/implants/lib/host_unique/src/lib.rs @@ -33,8 +33,5 @@ pub fn get_id_with_selectors(selectors: Vec>) -> Uuid { // List is evaluated in order and will take the first successful // result. pub fn defaults() -> Vec> { - vec![ - Box::::default(), - Box::::default(), - ] + vec![Box::::default(), Box::::default()] } From a66e265206add84e12fc68e9c51fcd33e02d72a7 Mon Sep 17 00:00:00 2001 From: LByrgeCP <43020092+LByrgeCP@users.noreply.github.com> Date: Mon, 5 May 2025 09:31:14 -0500 Subject: [PATCH 12/13] Convert winreg to workspace --- implants/lib/host_unique/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/implants/lib/host_unique/Cargo.toml b/implants/lib/host_unique/Cargo.toml index c9b1901df..a6d808192 100644 --- a/implants/lib/host_unique/Cargo.toml +++ b/implants/lib/host_unique/Cargo.toml @@ -10,7 +10,7 @@ uuid = { workspace = true, features = ["v4", "fast-rng"] } log = { workspace = true } [target.'cfg(target_os = "windows")'.dependencies] -winreg = "0.55" +winreg = { workspace = true } [dev-dependencies] pretty_env_logger = "0.5.0" From e45af2ac7201f38aeb6983e7e409bc30d4ad4ae0 Mon Sep 17 00:00:00 2001 From: LByrgeCP <43020092+LByrgeCP@users.noreply.github.com> Date: Mon, 5 May 2025 09:31:55 -0500 Subject: [PATCH 13/13] Import registry --- implants/lib/host_unique/src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/implants/lib/host_unique/src/lib.rs b/implants/lib/host_unique/src/lib.rs index b39060751..724434b34 100644 --- a/implants/lib/host_unique/src/lib.rs +++ b/implants/lib/host_unique/src/lib.rs @@ -4,6 +4,8 @@ mod env; pub use env::Env; mod file; pub use file::File; +mod registry; +pub use registry::Registry; pub trait HostIDSelector { fn get_name(&self) -> String;