init
This commit is contained in:
476
src/lazy_importer.rs
Normal file
476
src/lazy_importer.rs
Normal file
@@ -0,0 +1,476 @@
|
||||
#![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),*)) => (
|
||||
#[inline(always)]
|
||||
#[allow(non_snake_case)]
|
||||
pub fn $fname ($($name: $arg),*) {
|
||||
const SEED: u64 = $crate::lazy_importer::make_seed!();
|
||||
static LI: $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();
|
||||
unsafe { core::mem::transmute::<_,extern $abi fn($($arg),*)>(LI.resolve())($($name),*) }
|
||||
}
|
||||
);
|
||||
($library:literal $abi:literal $($link_name:literal)? fn $fname:ident ($($name:ident : $arg:ty),*) -> $ret:ty) => (
|
||||
#[inline(always)]
|
||||
#[allow(non_snake_case)]
|
||||
pub fn $fname ($($name: $arg),*) -> $ret {
|
||||
const SEED: u64 = $crate::lazy_importer::make_seed!();
|
||||
static LI: $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();
|
||||
unsafe { core::mem::transmute::<_,extern $abi fn($($arg),*) -> $ret>(LI.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
|
||||
"mov {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 = 1; // const { XorSeed & 1 };
|
||||
|
||||
let mut cursor = module_link as usize;
|
||||
let mut module: usize = 0usize;
|
||||
loop {
|
||||
cursor = (cursor as *const usize).add(offsets).read();
|
||||
|
||||
// if we have gone all the way around and ended up at our module again, abort
|
||||
if cursor == module_link as usize { break; }
|
||||
|
||||
// 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 { break; }
|
||||
}
|
||||
|
||||
debug_assert!(module != 0, "Module not found");
|
||||
|
||||
let module = &*(module as *const ImageBase);
|
||||
|
||||
match module.exports() {
|
||||
None => {
|
||||
debug_assert!(false, "Module has no exports");
|
||||
core::arch::asm!("int 3", options(noreturn));
|
||||
}
|
||||
Some(exports) => {
|
||||
for export in exports {
|
||||
if hash_utf8::<XorSeed2>(export.1) == Import {
|
||||
return export.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
debug_assert!(false, "failed to find export");
|
||||
core::arch::asm!("int 3", 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>(bytes: &[u8]) -> 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
|
||||
}
|
||||
|
||||
//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,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for ExportIter<'a> {
|
||||
type Item = (usize, &'static [u8]);
|
||||
|
||||
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 = self.image.offset(*export_functions.add(export_ordinal as usize)) as usize;
|
||||
|
||||
self.export_index += 1;
|
||||
|
||||
Some((export_rva, u8_nul_terminated(export_name)))
|
||||
}
|
||||
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)
|
||||
};
|
||||
Some(ExportIter {
|
||||
image: self,
|
||||
export_dir: export_directory,
|
||||
export_index: 0,
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user