563 lines
19 KiB
Rust
563 lines
19 KiB
Rust
#![allow(unused, nonstandard_style)]
|
|
|
|
// Utility macro to generata a random seed
|
|
// this has to be done in a macro so that the input values
|
|
// file, line, column
|
|
// use the callers location
|
|
#[macro_export]
|
|
macro_rules! make_seed {
|
|
() => {
|
|
{
|
|
let seed
|
|
= $crate::lazy_importer::hash_utf8::<0x00>(file!().as_bytes())
|
|
^ $crate::lazy_importer::Prng::new((column!() << 12) as u64 | (line!() as u64)).next();
|
|
seed
|
|
}
|
|
}
|
|
}
|
|
|
|
pub use make_seed;
|
|
|
|
#[macro_export]
|
|
macro_rules! link {
|
|
($library:literal $abi:literal $($link_name:literal)? fn $fname:ident ($($name:ident : $arg:ty),*)) => (
|
|
|
|
#[allow(non_snake_case)]
|
|
pub mod $fname {
|
|
pub const SEED: u64 = $crate::lazy_importer::make_seed!();
|
|
pub static IMPORT: $crate::lazy_importer::LazyImport<
|
|
{ $crate::lazy_importer::hash_utf8::<SEED>($library.as_bytes()) },
|
|
{ $crate::lazy_importer::hash_utf8::<SEED>(stringify!($fname).as_bytes()) },
|
|
SEED, ()
|
|
> = $crate::lazy_importer::LazyImport::new();
|
|
}
|
|
|
|
#[inline(always)]
|
|
#[allow(non_snake_case)]
|
|
pub fn $fname ($($name: $arg),*) {
|
|
unsafe { core::mem::transmute::<_,extern $abi fn($($arg),*)>($fname::IMPORT.resolve())($($name),*) }
|
|
}
|
|
);
|
|
($library:literal $abi:literal $($link_name:literal)? fn $fname:ident ($($name:ident : $arg:ty),*) -> $ret:ty) => (
|
|
|
|
#[allow(non_snake_case)]
|
|
pub mod $fname {
|
|
pub const SEED: u64 = $crate::lazy_importer::make_seed!();
|
|
pub static IMPORT: $crate::lazy_importer::LazyImport<
|
|
{ $crate::lazy_importer::hash_utf8::<SEED>($library.as_bytes()) },
|
|
{ $crate::lazy_importer::hash_utf8::<SEED>(stringify!($fname).as_bytes()) },
|
|
SEED, ()
|
|
> = $crate::lazy_importer::LazyImport::new();
|
|
}
|
|
|
|
#[inline(always)]
|
|
#[allow(non_snake_case)]
|
|
pub fn $fname ($($name: $arg),*) -> $ret {
|
|
unsafe { core::mem::transmute::<_,extern $abi fn($($arg),*) -> $ret>($fname::IMPORT.resolve())($($name),*) }
|
|
}
|
|
)
|
|
}
|
|
|
|
|
|
// ┌─────────────────────────────────────────────────────────────────────────┐
|
|
// │ the main lazy import struct itself │
|
|
// └─────────────────────────────────────────────────────────────────────────┘
|
|
|
|
|
|
pub struct LazyImport<
|
|
const Module: u64,
|
|
const Import: u64,
|
|
const XorSeed: u64,
|
|
Value
|
|
>(
|
|
core::cell::UnsafeCell<u64>,
|
|
core::marker::PhantomData<Value>,
|
|
);
|
|
|
|
unsafe impl<const M: u64, const I: u64, const X: u64, V> Sync for LazyImport<M, I, X, V> {}
|
|
// unsafe impl<const M: u64, const I: u64, const X: u64, V> Send for LazyImport<M, I, X, V> {}
|
|
|
|
impl<const M: u64, const I: u64, const X: u64, V> LazyImport<M, I, X, V> {
|
|
|
|
const CIPHER: u64 = const { Prng::new(X).next() };
|
|
|
|
pub const fn new() -> Self {
|
|
Self(
|
|
core::cell::UnsafeCell::new(Self::CIPHER),
|
|
core::marker::PhantomData,
|
|
)
|
|
}
|
|
|
|
#[inline(always)]
|
|
pub fn resolve(&self) -> usize {
|
|
unsafe {
|
|
if *self.0.get() != Self::CIPHER {
|
|
(*self.0.get() ^ Self::CIPHER) as usize
|
|
} else {
|
|
let ptr = import::<M, I, X, X>();
|
|
*self.0.get() = ptr as u64 ^ Self::CIPHER;
|
|
ptr
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
impl<const M: u64, const I: u64, const X: u64, V> core::ops::Deref for LazyImport<M, I, X, V> {
|
|
type Target = V;
|
|
|
|
#[inline(always)]
|
|
fn deref(&self) -> &Self::Target {
|
|
unsafe { &*(self.resolve() as *const V) }
|
|
}
|
|
}
|
|
|
|
// ┌─────────────────────────────────────────────────────────────────────────┐
|
|
// │ the function actually responsible for everything │
|
|
// └─────────────────────────────────────────────────────────────────────────┘
|
|
#[inline(always)]
|
|
pub unsafe fn import<
|
|
const Module: u64,
|
|
const Import: u64,
|
|
const XorSeed: u64,
|
|
const XorSeed2: u64,
|
|
>() -> usize {
|
|
unsafe {
|
|
|
|
// get the ldr data table entries
|
|
let module_link: *const usize;
|
|
core::arch::asm!(
|
|
"mov {x}, gs:[60h]", // TEB->PEB
|
|
"mov {x}, [{x} + 18h]", // PEB->LDR
|
|
"lea {x}, [{x} + 10h]", // LDR->InLoadOrderModuleList
|
|
x = out(reg) module_link,
|
|
);
|
|
|
|
// 0x0 = next, 0x8 = prev, use the xor seed to flip the direction of the iterator
|
|
let offsets = const { XorSeed & 1 } as usize;
|
|
|
|
// in case of forwarded imports we will change these...
|
|
let mut module_hash = Module;
|
|
let mut import_hash = Import;
|
|
|
|
'search_for_import: loop {
|
|
|
|
let mut cursor = module_link as usize;
|
|
let end = (cursor as *const usize).add(offsets ^ 1).read();
|
|
let mut module: usize = 0usize;
|
|
|
|
#[cfg(debug_assertions)]
|
|
let mut found = false;
|
|
|
|
while cursor != end {
|
|
cursor = (cursor as *const usize).add(offsets).read();
|
|
|
|
// extract the appropriate fields
|
|
let name_len = ((cursor + 0x58) as *const u16).read();
|
|
let name_ptr = ((cursor + 0x60) as *const *const u16).read();
|
|
module = ((cursor + 0x30) as *const usize).read();
|
|
|
|
// calculate the hash for the module
|
|
let hash = hash_utf16::<XorSeed>(core::slice::from_raw_parts(
|
|
name_ptr, (name_len >> 1) as usize));
|
|
|
|
if hash == module_hash {
|
|
|
|
#[cfg(debug_assertions)]
|
|
if true {
|
|
found = true;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
#[cfg(debug_assertions)]
|
|
debug_assert!(found, "Failed to find module");
|
|
let module = &*(module as *const ImageBase);
|
|
|
|
match module.exports() {
|
|
None => {
|
|
debug_assert!(false, "Module has no exports");
|
|
core::arch::asm!("", options(noreturn));
|
|
}
|
|
Some(exports) => {
|
|
for (vaddr, name, forwarded) in exports {
|
|
|
|
if hash_utf8::<XorSeed2,1>([name]) == import_hash {
|
|
|
|
// the import is forwarded... oh boy...
|
|
if forwarded {
|
|
|
|
// strlen and convert to a slice
|
|
let fwd = {
|
|
let ptr = vaddr as *const u8;
|
|
let len = (0usize..)
|
|
.position(|i| ptr.add(i).read() == 0)
|
|
.unwrap_or(0);
|
|
core::slice::from_raw_parts(ptr, len)
|
|
};
|
|
|
|
|
|
let Some(split) = fwd.iter().position(|i| *i == b'.') else {
|
|
debug_assert!(false, "Forwarded input lacks . separator");
|
|
core::arch::asm!("", options(noreturn))
|
|
};
|
|
|
|
// split our bytes into two slices at the .
|
|
let (module, import) = (&fwd[..split], &fwd[split+1..]);
|
|
|
|
// to prevent bullshit where people can sig scan for .dll or xref it
|
|
// I do this hacky encrypt
|
|
let dll_crypt = const {
|
|
let mut p = Prng::new(XorSeed);
|
|
p.skip(4);
|
|
u32::from_le_bytes(*b".dll") ^ p.next() as u32
|
|
};
|
|
|
|
// use blackbox so it's not decrypted at compile time
|
|
let dll = core::hint::black_box(dll_crypt) ^ const {
|
|
let mut p = Prng::new(XorSeed);
|
|
p.skip(4);
|
|
p.next() as u32
|
|
};
|
|
|
|
let dll = dll.to_le_bytes();
|
|
|
|
module_hash = hash_utf8::<XorSeed,2>([module, &dll]);
|
|
import_hash = hash_utf8::<XorSeed2, 1>([import]);
|
|
|
|
// restart the loop but with our new hashes now...
|
|
continue 'search_for_import;
|
|
}
|
|
|
|
return vaddr;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
debug_assert!(false, "function not found");
|
|
core::arch::asm!("", options(noreturn))
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// ┌─────────────────────────────────────────────────────────────────────────┐
|
|
// │ Helper PRNG functions │
|
|
// └─────────────────────────────────────────────────────────────────────────┘
|
|
|
|
pub struct Prng(u64);
|
|
|
|
impl Prng {
|
|
pub const fn new(seed: u64) -> Self {
|
|
Self(seed)
|
|
}
|
|
|
|
pub const fn next(&mut self) -> u64 {
|
|
self.skip(1);
|
|
let result = self.0.wrapping_mul(0x2545F4914F6CDD1D);
|
|
result
|
|
}
|
|
|
|
pub const fn size(&mut self, limit: usize) -> usize {
|
|
self.next() as usize % limit
|
|
}
|
|
|
|
pub const fn skip(&mut self, mut cycles: usize) {
|
|
let mut state = self.0;
|
|
while cycles > 0 {
|
|
cycles = cycles.saturating_sub(1);
|
|
state ^= state >> 12;
|
|
state ^= state << 25;
|
|
state ^= state >> 27;
|
|
}
|
|
self.0 = state;
|
|
}
|
|
}
|
|
|
|
// ┌─────────────────────────────────────────────────────────────────────────┐
|
|
// │ Hash Algorithm │
|
|
// └─────────────────────────────────────────────────────────────────────────┘
|
|
|
|
const INITIAL_STATE: u64 = 0xcbf29ce484222325;
|
|
const PRIME: u64 = 0x100000001b3;
|
|
|
|
//noinspection DuplicatedCode
|
|
#[inline(always)]
|
|
pub const fn hash_utf8<const SALT: u64, const S: usize>(bytes: [&[u8]; S]) -> u64 {
|
|
|
|
// make the initial state based on the salt
|
|
let mut hash = const {
|
|
let mut hash = INITIAL_STATE;
|
|
// inject pre_seed
|
|
let b = SALT.to_le_bytes();
|
|
let mut i = 0;
|
|
while i < 7 {
|
|
hash ^= b[i] as u64;
|
|
hash = hash.wrapping_mul(PRIME);
|
|
i += 1;
|
|
}
|
|
i = 0;
|
|
hash
|
|
};
|
|
|
|
|
|
let mut j = 0;
|
|
while j < bytes.len() {
|
|
let bytes = bytes[j];
|
|
let mut i = 0;
|
|
while i < bytes.len() {
|
|
let char = match bytes[i] {
|
|
0x40..=0x5A => bytes[i] + 0x20,
|
|
_ => bytes[i],
|
|
} as u64;
|
|
|
|
hash = hash ^ (char);
|
|
hash = hash.wrapping_mul(PRIME);
|
|
i += 1;
|
|
}
|
|
j += 1;
|
|
}
|
|
|
|
hash
|
|
}
|
|
|
|
//noinspection DuplicatedCode
|
|
pub const fn hash_utf16<const SALT: u64>(bytes: &[u16]) -> u64 {
|
|
|
|
// make the initial state based on the salt
|
|
let mut hash = const {
|
|
let mut hash = INITIAL_STATE;
|
|
// inject pre_seed
|
|
let b = SALT.to_le_bytes();
|
|
let mut i = 0;
|
|
while i < 7 {
|
|
hash ^= b[i] as u64;
|
|
hash = hash.wrapping_mul(PRIME);
|
|
i += 1;
|
|
}
|
|
i = 0;
|
|
hash
|
|
};
|
|
|
|
let mut i = 0;
|
|
while i < bytes.len() {
|
|
let char = match bytes[i] {
|
|
0x40..=0x5A => bytes[i] + 0x20,
|
|
_ => bytes[i],
|
|
} as u64;
|
|
|
|
hash = hash ^ (char);
|
|
hash = hash.wrapping_mul(PRIME);
|
|
i += 1;
|
|
}
|
|
hash
|
|
}
|
|
|
|
// ┌─────────────────────────────────────────────────────────────────────────┐
|
|
// │ Pe Data Structures │
|
|
// └─────────────────────────────────────────────────────────────────────────┘
|
|
|
|
#[repr(C)]
|
|
pub struct ImageBase(());
|
|
|
|
impl ImageBase {
|
|
pub fn offset<'a, T>(&'a self, offset: T) -> *const u8
|
|
where
|
|
&'a Self: core::ops::Add<T, Output=usize>,
|
|
{
|
|
(self + offset) as *const u8
|
|
}
|
|
|
|
pub fn dos(&self) -> &ImageDOSHeader {
|
|
unsafe { &*(self as *const _ as *const ImageDOSHeader) }
|
|
}
|
|
|
|
pub fn nt_header(&self) -> &'static ImageNTHeaders64 {
|
|
unsafe { &*((self + self.dos().e_lfanew) as *const ImageNTHeaders64) }
|
|
}
|
|
}
|
|
|
|
impl core::ops::Add<usize> for &ImageBase {
|
|
type Output = usize;
|
|
fn add(self, rhs: usize) -> Self::Output {
|
|
(self as *const _ as usize) + rhs
|
|
}
|
|
}
|
|
|
|
impl core::ops::Add<u32> for &ImageBase {
|
|
type Output = usize;
|
|
fn add(self, rhs: u32) -> Self::Output {
|
|
(self as *const _ as usize) + (rhs as usize)
|
|
}
|
|
}
|
|
|
|
#[repr(C)]
|
|
pub struct ImageDOSHeader {
|
|
pub e_magic: u16,
|
|
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 e_res: [u16; 4],
|
|
pub e_oemid: u16,
|
|
pub e_oeminfo: u16,
|
|
pub e_res2: [u16; 10],
|
|
pub e_lfanew: u32,
|
|
}
|
|
|
|
#[repr(C)]
|
|
pub struct ImageNTHeaders64 {
|
|
pub signature: u32,
|
|
pub file_header: ImageFileHeader,
|
|
pub optional_header: ImageOptionalHeader64,
|
|
}
|
|
|
|
#[repr(C)]
|
|
pub struct ImageFileHeader {
|
|
pub machine: u16,
|
|
pub number_of_sections: u16,
|
|
pub timestamp: u32,
|
|
pub pointer_to_symbol_table: u32,
|
|
pub number_of_symbols: u32,
|
|
pub size_of_optional_header: u16,
|
|
pub characteristics: u16,
|
|
}
|
|
|
|
#[repr(C)]
|
|
pub struct ImageOptionalHeader64 {
|
|
pub magic: u16,
|
|
pub major_linker_version: u8,
|
|
pub minor_linker_version: u8,
|
|
pub size_of_code: u32,
|
|
pub size_of_initialized_data: u32,
|
|
pub size_of_uninitialized_data: u32,
|
|
pub address_of_entry_point: u32,
|
|
pub base_of_code: u32,
|
|
pub image_base: u64,
|
|
pub section_alignment: u32,
|
|
pub file_alignment: u32,
|
|
pub major_operating_system_version: u16,
|
|
pub minor_operating_system_version: u16,
|
|
pub major_image_version: u16,
|
|
pub minor_image_version: u16,
|
|
pub major_subsystem_version: u16,
|
|
pub minor_subsystem_version: u16,
|
|
pub win32_version_value: u32,
|
|
pub size_of_image: u32,
|
|
pub size_of_headers: u32,
|
|
pub checksum: u32,
|
|
pub subsystem: u16,
|
|
pub dll_characteristics: u16,
|
|
pub size_of_stack_reserve: u64,
|
|
pub size_of_stack_commit: u64,
|
|
pub size_of_heap_reserve: u64,
|
|
pub size_of_heap_commit: u64,
|
|
pub loader_flags: u32,
|
|
pub number_of_rva_and_sizes: u32,
|
|
pub data_directory: [ImageDataDirectory; 16],
|
|
}
|
|
|
|
#[repr(C)]
|
|
pub struct ImageDataDirectory {
|
|
pub virtual_address: u32,
|
|
pub size: u32,
|
|
}
|
|
|
|
#[repr(C)]
|
|
pub struct ImageExportDirectory {
|
|
/* 0x00 */pub export_flags: u32,
|
|
/* 0x04 */
|
|
pub timestamp: u32,
|
|
/* 0x06 */
|
|
pub major_version: u16,
|
|
/* 0x08 */
|
|
pub minor_version: u16,
|
|
/* 0x0C */
|
|
pub name_rva: u32,
|
|
/* 0x10 */
|
|
pub ordinal_base: u32,
|
|
/* 0x14 */
|
|
pub address_table_entries: u32,
|
|
/* 0x18 */
|
|
pub number_of_name_pointers: u32,
|
|
/* 0x1C */
|
|
pub export_address_table_rva: u32,
|
|
/* 0x20 */
|
|
pub name_pointer_rva: u32,
|
|
/* 0x24 */
|
|
pub ordinal_table_rva: u32,
|
|
}
|
|
|
|
|
|
pub struct ExportIter<'a> {
|
|
image: &'a ImageBase,
|
|
export_dir: &'a ImageExportDirectory,
|
|
export_index: usize,
|
|
ext_range: core::ops::Range<usize>,
|
|
}
|
|
|
|
impl<'a> Iterator for ExportIter<'a> {
|
|
type Item = (usize, &'static [u8], bool);
|
|
|
|
fn next(&mut self) -> Option<Self::Item> {
|
|
match self.export_index < self.export_dir.number_of_name_pointers as usize {
|
|
true => unsafe {
|
|
#[inline(always)]
|
|
unsafe fn u8_nul_terminated(ptr: *const u8) -> &'static [u8] {
|
|
unsafe {
|
|
let mut end = ptr;
|
|
while *end != 0 { end = end.add(1) }
|
|
let len = (end as usize) - (ptr as usize);
|
|
&*core::ptr::slice_from_raw_parts(ptr, len)
|
|
}
|
|
}
|
|
|
|
let export_functions = self.image.offset(self.export_dir.export_address_table_rva) as *const u32;
|
|
let export_names = self.image.offset(self.export_dir.name_pointer_rva) as *const u32;
|
|
let export_ordinals = self.image.offset(self.export_dir.ordinal_table_rva) as *const u16;
|
|
|
|
let export_name = self.image.offset(*export_names.add(self.export_index));
|
|
let export_ordinal = *export_ordinals.add(self.export_index);
|
|
let export_rva = *export_functions.add(export_ordinal as usize);
|
|
let export_va = self.image.offset(export_rva) as usize;
|
|
|
|
self.export_index += 1;
|
|
|
|
let is_forwarded = self.ext_range.contains(&(export_rva as usize));
|
|
Some((export_va, u8_nul_terminated(export_name), is_forwarded))
|
|
}
|
|
false => None,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl ImageBase {
|
|
pub fn exports(&self) -> Option<ExportIter> {
|
|
let directory = &self.nt_header().optional_header.data_directory[0];
|
|
if directory.size == 0 || directory.virtual_address == 0 { return None; }
|
|
let export_directory = unsafe {
|
|
&*((self + directory.virtual_address) as *const ImageExportDirectory)
|
|
};
|
|
|
|
let start = directory.virtual_address as usize;
|
|
let end = start + directory.size as usize;
|
|
|
|
Some(ExportIter {
|
|
image: self,
|
|
export_dir: export_directory,
|
|
export_index: 0,
|
|
ext_range: start..end,
|
|
})
|
|
}
|
|
} |