commit a5fe71ebb37eebb2c5c928e087068f180e6c14ef Author: numbers Date: Mon Sep 4 06:26:35 2023 -0400 initial diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..64f336f --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/target +/Cargo.lock +/proc_macro/target \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/X.iml b/.idea/X.iml new file mode 100644 index 0000000..99d1619 --- /dev/null +++ b/.idea/X.iml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..5926fc9 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..44fc263 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "x" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[features] +win32 = [] +no_relocs = [] + +[dependencies] +xgen = { path = "proc_macro"} \ No newline at end of file diff --git a/proc_macro/Cargo.lock b/proc_macro/Cargo.lock new file mode 100644 index 0000000..1b02f74 --- /dev/null +++ b/proc_macro/Cargo.lock @@ -0,0 +1,53 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "proc-macro2" +version = "1.0.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "syn" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "unicode-ident" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" + +[[package]] +name = "unicode-xid" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" + +[[package]] +name = "xgen" +version = "0.0.0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/proc_macro/Cargo.toml b/proc_macro/Cargo.toml new file mode 100644 index 0000000..b5ac2b5 --- /dev/null +++ b/proc_macro/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "xgen" +version = "0.0.0" +authors = [""] +description = "common macros" +keywords = [] +edition = "2018" + +[lib] +proc-macro = true + +[dependencies] +quote = "1" +proc-macro2 = "1.0.66" +syn = { version = "1.0", features = ["full"] } + diff --git a/proc_macro/src/lib.rs b/proc_macro/src/lib.rs new file mode 100644 index 0000000..62ac1d9 --- /dev/null +++ b/proc_macro/src/lib.rs @@ -0,0 +1,12 @@ +#![feature(proc_macro_quote)] +#![feature(proc_macro_span)] +#![feature(custom_inner_attributes)] + +use proc_macro::*; + +mod sigscan; + +#[proc_macro] +pub fn signature(input: TokenStream) -> TokenStream { + sigscan::macro_proc(input) +} \ No newline at end of file diff --git a/proc_macro/src/sigscan.rs b/proc_macro/src/sigscan.rs new file mode 100644 index 0000000..5b22f78 --- /dev/null +++ b/proc_macro/src/sigscan.rs @@ -0,0 +1,50 @@ +extern crate proc_macro; +use proc_macro::*; +use syn::*; + +enum Operation { + Byte(u8), + Wildcard, +} + +fn generate_scanner(operations: Vec) -> TokenStream { + let mut output: String = "| b: &[u8] | -> Option {\n".to_string(); + + output.push_str(format!("for i in 0..b.len()-{} {{",operations.len()).as_str()); + for (i,op) in operations.iter().enumerate() { + if let Operation::Byte(byte) = op { + output.push_str(format!("if b[i+{}] != 0x{:x} {{ continue; }}", i, byte).as_str()); + } + } + + output.push_str("return Some(i);"); + output.push_str("};"); + + output.push_str("None}"); + output.parse().unwrap() +} + +pub fn macro_proc(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as LitStr); + let input: String = input.value(); + + let mut operations = vec![]; + + for hex in input.split(" ") { + let op = if hex.eq("?") { + Operation::Wildcard + } else { + match u8::from_str_radix(hex, 16) { + Ok(hex) => { + Operation::Byte(hex) + } + Err(e) => { + panic!("{}", e); + } + } + }; + operations.push(op); + } + + return generate_scanner(operations); +} \ No newline at end of file diff --git a/src/branching.rs b/src/branching.rs new file mode 100644 index 0000000..f5a84ea --- /dev/null +++ b/src/branching.rs @@ -0,0 +1,20 @@ + +#[macro_export] +macro_rules! invoke_once { + () => { + unsafe { + static mut __VALUE: bool = false; + if __VALUE { false } else { __VALUE = true; true } + } + }; + + // if this is used inside of an unsafe codeblock + // use this branch to avoid the unnecessary unsafe block warning + (unsafe) => { + { + static mut __VALUE: bool = false; + if __VALUE { false } else { __VALUE = true; true } + } + } +} + diff --git a/src/cffi.rs b/src/cffi.rs new file mode 100644 index 0000000..e377c5e --- /dev/null +++ b/src/cffi.rs @@ -0,0 +1,40 @@ + +/// appends zeroes to the end of the string and converts it into a pointer +/// useful for quick ffi +#[macro_export] +macro_rules! cstr { + ($str:expr) => { + concat!($str,"\0\0").as_ptr() as *const i8 + } +} + + +/// utility macro for inline c functions +/// +/// | macro | rust | +/// |-----------|----------| +/// | ```cfn!( () -> usize )``` | ``` extern "C" fn() -> usize``` | +/// | ```cfn!( (usize) -> usize )``` | ``` extern "C" fn(usize) -> usize``` | +/// | ```cfn!( (usize) )``` | ``` extern "C" fn(usize)``` | +/// | ```cfn!( (u32, usize, usize) -> u32 )``` | ``` extern "C" fn(u32, usize, usize) -> u32``` | +#[macro_export] +macro_rules! cfn { + ( ($($t:ty),*)) => { + extern "C" fn($( $t ),* ) + }; + ( ($($t:ty),*) -> $r:ty) => { + extern "C" fn($( $t ),* ) -> $r + } +} + +/// utility macro for pointer chains +#[macro_export] +macro_rules! ptr_chain { + ($x: expr, $y:expr) => {{ + (*core::mem::transmute::<_,*const usize>(core::mem::transmute::<_,usize>($x) + $y)) + }}; + + ($x: expr, $y:expr, $( $z:expr ),+ ) => {{ + $crate::ptr_chain!( $crate::ptr_chain!($x, $y), $( $z ),+ ) + }}; +} \ No newline at end of file diff --git a/src/data.rs b/src/data.rs new file mode 100644 index 0000000..d71f9eb --- /dev/null +++ b/src/data.rs @@ -0,0 +1,40 @@ +use core::cmp::Ordering; +use crate::upcast::IntoUsize; + +//noinspection SpellCheckingInspection +/// Converts reference of struct to binary slice +pub fn slicify(value: &T) -> &[u8] { + let ptr = value as *const T as *const u8; + unsafe { core::slice::from_raw_parts(ptr, core::mem::size_of::()) } +} + +/// Converts reference of struct to binary slice +pub unsafe fn slicify_mut(value: &mut T) -> &mut [u8] { + let ptr = value as *mut T as *mut u8; + core::slice::from_raw_parts_mut(ptr, core::mem::size_of::()) +} + +/// converts a non mutable reference into a mutable one +pub unsafe fn mutify(nr: &T) -> &mut T { + &mut *(nr as *const T as *mut T) +} + +/// converts a reference of any lifetime to 'static +pub unsafe fn statify<'a, T>(nr: &'a T) -> &'static T { + &*(nr as *const T) +} + +/// converts mutable a reference of any lifetime to 'static +pub unsafe fn statify_mut<'a, T>(nr: &'a mut T) -> &'static mut T { + &mut *(nr as *mut T) +} + +/// gets the distance between two references +pub fn distance(p1: impl IntoUsize, p2: impl IntoUsize) -> usize { + let (p1, p2) = (p1.into_usize(), p2.into_usize()); + match p1.cmp(&p2) { + Ordering::Less => p2 - p1, + Ordering::Greater => p1 - p2, + Ordering::Equal => 0, + } +} \ No newline at end of file diff --git a/src/hash.rs b/src/hash.rs new file mode 100644 index 0000000..66ebb24 --- /dev/null +++ b/src/hash.rs @@ -0,0 +1,74 @@ +const INITIAL_STATE: u64 = 0xcbf29ce484222325; +const PRIME: u64 = 0x100000001b3; + +//noinspection DuplicatedCode +pub const fn hash(bytes: &[u8]) -> u64 { + let mut hash = INITIAL_STATE; + let mut i = 0; + while i < bytes.len() { + hash = hash ^ bytes[i] as u64; + hash = hash.wrapping_mul(PRIME); + i += 1; + } + hash +} + +//noinspection DuplicatedCode +pub const fn hash32(bytes: &[u32]) -> u64 { + let mut hash = INITIAL_STATE; + let mut i = 0; + while i < bytes.len() { + hash = hash ^ bytes[i] as u64; + hash = hash.wrapping_mul(PRIME); + i += 1; + } + hash +} + +//noinspection DuplicatedCode +pub const fn hash64(bytes: &[u32]) -> u64 { + let mut hash = INITIAL_STATE; + let mut i = 0; + while i < bytes.len() { + hash = hash ^ bytes[i] as u64; + hash = hash.wrapping_mul(PRIME); + i += 1; + } + hash +} + +//noinspection DuplicatedCode +pub const fn hash_utf8(bytes: &[u8]) -> u64 { + let mut hash = INITIAL_STATE; + 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(bytes: &[u16]) -> u64 { + let mut hash = INITIAL_STATE; + 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 +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..56cdc7e --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,43 @@ +#![feature(decl_macro)] +#![no_std] + +/// Virtual Struct Offset +mod vso; +pub use vso::*; + + +/// upcast trait +mod upcast; +pub use upcast::Upcast; +pub use upcast::IntoUsize; + +/// data manipulation utilities +mod data; +pub use data::*; + + +/// utility macros for branching +/// invoke_once, etc +mod branching; +pub use branching::*; + +/// Utility macros for c ffi +mod cffi; +pub use cffi::*; + +/// win32 utilities +#[cfg(feature = "win32")] +pub mod win32; + +#[cfg(feature = "win32")] +pub use win32::{ image_base, image_header, find_kernel32 }; + + +/// re-export the signature macro +pub use xgen::signature; + +mod time; +pub use time::*; + +mod hash; +pub use hash::*; \ No newline at end of file diff --git a/src/time.rs b/src/time.rs new file mode 100644 index 0000000..1ca717c --- /dev/null +++ b/src/time.rs @@ -0,0 +1,52 @@ +#![allow(unused)] + +use core::time::Duration; + +#[allow(nonstandard_style)] +pub mod __time_units { + + pub mod units { + pub(super) type Milli = u64; + pub(super) type Second = u64; + pub(super) type Minute = u64; + pub(super) type Hour = u64; + pub(super) type Day = u64; + pub(super) type Week = u64; + } + + pub mod plural { + use super::units::*; + pub const milliseconds : Milli = 1; + pub const seconds : Second = 1_000; + pub const hours : Hour = 60_000; + pub const minutes : Minute = 3_600_000; + pub const days : Day = 86_400_000; + pub const weeks : Week = 604_800_000; + } + pub mod singular { + use super::units::*; + pub const millisecond : Milli = super::plural::milliseconds; + pub const second : Second = super::plural::seconds; + pub const hour : Hour = super::plural::hours; + pub const minute : Minute = super::plural::minutes; + pub const day : Day = super::plural::days; + pub const week : Week = super::plural::weeks; + } +} + + +#[macro_export] +macro_rules! time_unit2 { + (1, $unit:ident) => { $crate::__time_units::singular::$unit }; + ($number:literal, $unit:ident) => { $number * $crate::__time_units::plural::$unit } +} + +#[allow(unused)] +macro time_unit( $number:tt, $unit:ident ) { + crate::time_unit2!($number, $unit) +} + +#[allow(unused)] +macro dur($($amount:tt $unit:ident $(,)?)+) { + core::time::Duration::from_millis(0u64 $( + (time_unit!( $amount, $unit)) )+ ) +} \ No newline at end of file diff --git a/src/upcast.rs b/src/upcast.rs new file mode 100644 index 0000000..aa35d55 --- /dev/null +++ b/src/upcast.rs @@ -0,0 +1,79 @@ + +pub trait Upcast { + fn upcast(self) -> T; +} + +pub trait IntoUsize { + fn into_usize(self) -> usize; +} + +macro_rules! gen_upcast { + ($from:ty => $into:ty) => { + impl Upcast<$into> for $from { + fn upcast(self) -> $into { + self as $into + } + } + } +} + +// Unsigned +gen_upcast!(u8 => u8); +gen_upcast!(u8 => u16); +gen_upcast!(u8 => u32); +gen_upcast!(u8 => u64); +gen_upcast!(u16 => u16); +gen_upcast!(u16 => u32); +gen_upcast!(u16 => u64); +gen_upcast!(u32 => u32); +gen_upcast!(u32 => u64); +gen_upcast!(u64 => u64); + +// Signed +gen_upcast!(i8 => i8); +gen_upcast!(i8 => i16); +gen_upcast!(i8 => i32); +gen_upcast!(i8 => i64); +gen_upcast!(i16 => i16); +gen_upcast!(i16 => i32); +gen_upcast!(i16 => i64); +gen_upcast!(i32 => i32); +gen_upcast!(i32 => i64); +gen_upcast!(i64 => i64); + +// Pointer Sized + +gen_upcast!(u8 => usize); +gen_upcast!(i8 => isize); +gen_upcast!(u16 => usize); +gen_upcast!(i16 => isize); + +#[cfg(any(target_pointer_width = "64", target_pointer_width = "32"))] +gen_upcast!(u32 => usize); + +#[cfg(any(target_pointer_width = "64", target_pointer_width = "32"))] +gen_upcast!(i32 => isize); + +gen_upcast!(isize => isize); +gen_upcast!(usize => usize); + + +impl IntoUsize for usize { + fn into_usize(self) -> usize { self } +} + +impl IntoUsize for *const T { + fn into_usize(self) -> usize { self as usize } +} + +impl IntoUsize for *mut T { + fn into_usize(self) -> usize { self as usize } +} + +impl IntoUsize for &T { + fn into_usize(self) -> usize { self as *const _ as usize } +} + +impl IntoUsize for &mut T { + fn into_usize(self) -> usize { self as *mut _ as usize } +} \ No newline at end of file diff --git a/src/vso.rs b/src/vso.rs new file mode 100644 index 0000000..3a48b41 --- /dev/null +++ b/src/vso.rs @@ -0,0 +1,100 @@ +#![allow(unused)] + +use core::fmt::{Debug, Display, Formatter, UpperHex}; +use core::marker::PhantomData; +use core::mem::transmute; +use core::ops::{ControlFlow, Deref, DerefMut, Index, IndexMut}; + +pub struct VirtualOffset(PhantomData); + +impl VirtualOffset { + + #[inline(always)] + pub(crate) fn vo_as_ptr(&self) -> *mut T { + ((self as *const _ as usize) + O) as *mut T + } + + #[inline(always)] + pub(crate) fn offset() -> usize { + return O; + } + + /// gets a ref to the underlying type + /// just an alias for the deref trait so it doesnt need to be imported + pub fn r#ref(&self) -> &T { + self.deref() + } + +} + +impl Deref for VirtualOffset { + type Target = T; + + #[inline(always)] + fn deref(&self) -> &Self::Target { + unsafe { transmute(((self as *const _ as usize) + O) as *const T)} + } +} + +impl DerefMut for VirtualOffset { + + #[inline(always)] + fn deref_mut(&mut self) -> &mut Self::Target { + unsafe { transmute(((self as *mut _ as usize) + O) as *mut T)} + } +} + +// ============================== +// Index +// ============================== + +impl, const O: usize> Index for VirtualOffset { + type Output = T::Output; + fn index(&self, index: I) -> &Self::Output { + unsafe { &*self.vo_as_ptr() }.index(index) + } +} + +impl, const O: usize> IndexMut for VirtualOffset { + fn index_mut(&mut self, index: I) -> &mut Self::Output { + unsafe { &mut *self.vo_as_ptr() }.index_mut(index) + } +} + +// ============================== +// Display + Debug +// ============================== + +// Proxy the Display trait +impl Display for VirtualOffset { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + self.deref().fmt(f) + } +} + +// Proxy the UpperHex trait +impl UpperHex for VirtualOffset { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + self.deref().fmt(f) + } +} + +// Proxy the Debug trait (in debug builds) +#[cfg(not(feature="production"))] +impl Debug for VirtualOffset { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + self.deref().fmt(f) + } +} + +// ============================== +// Macro +// ============================== + +#[macro_export] +macro_rules! struct_offset { + ($offset:literal, $type:ty) => { + $crate::VirtualOffset<$type, $offset> + } +} + diff --git a/src/win32/importer.rs b/src/win32/importer.rs new file mode 100644 index 0000000..ab1fd72 --- /dev/null +++ b/src/win32/importer.rs @@ -0,0 +1,62 @@ +use crate::hash_utf8; + +#[inline(always)] +pub unsafe fn find_import_hashed(module_name: u64, func_name: u64) -> Option { + crate::win32::loaded_modules() + .filter( |&(_,slice)|crate::hash_utf16(slice) == module_name).next() + .and_then(|(i,_)|i.exports()) + .and_then(|v|{ + v.filter_map(|(p,n)|(hash_utf8(n) == func_name).then_some(p)).next() + }) +} + +#[macro_export] +#[cfg(not(feature = "no_relocs"))] +macro_rules! lazy_import { + ( $module:literal $($v:vis fn $name:ident($($pname:ident: $ptype:ty),*) $(-> $rtype:ty)?; )* ) => { + $( + #[allow(unused, non_snake_case)] #[inline(always)] + $v fn $name($($pname: $ptype),*) $(-> $rtype)? { + type FTYPE = extern "C" fn($($pname: $ptype),*) $(-> $rtype)?; + static mut FUNC: FTYPE = __initial; + extern "C" fn __initial($($pname: $ptype),*) $(-> $rtype)? { + unsafe { + let import = $crate::win32::importer::find_import_hashed( + $crate::hash_utf8($module.as_bytes()), + $crate::hash_utf8(stringify!($name).as_bytes()) + ).unwrap_or(0); + FUNC = core::mem::transmute(import); + FUNC($($pname),*) + } + } + unsafe { FUNC($($pname),*) } + } + )* + }; +} + +#[macro_export] +#[cfg(feature = "no_relocs")] +macro_rules! lazy_import { + ( $module:literal $($v:vis fn $name:ident($($pname:ident: $ptype:ty),*) $(-> $rtype:ty)?; )* ) => { + $( + #[allow(unused, non_snake_case)] #[inline(always)] + $v fn $name($($pname: $ptype),*) $(-> $rtype)? { + type FTYPE = extern "C" fn($($pname: $ptype),*) $(-> $rtype)?; + static mut FUNC: Option = None; + let function = match unsafe { FUNC } { + Some(function) => function, + None => unsafe { + let import = core::mem::transmute($crate::win32::importer::find_import_hashed( + x::hash_utf8($module.as_bytes()), + x::hash_utf8(stringify!($name).as_bytes()) + ).unwrap_or(0)); + FUNC = Some(import); + import + } + }; + function($($pname),*) + } + )* + }; +} \ No newline at end of file diff --git a/src/win32/mod.rs b/src/win32/mod.rs new file mode 100644 index 0000000..765c1d2 --- /dev/null +++ b/src/win32/mod.rs @@ -0,0 +1,66 @@ +pub mod pe_image; +pub mod module_iter; +pub mod importer; + +pub use pe_image::*; +pub use module_iter::*; + +pub mod tls; + +#[inline(always)] +pub unsafe fn find_kernel32() -> usize { + let mut _k32: usize = 0; + core::arch::asm!( + "mov {x}, gs:[60h]", // TEB->PEB + "mov {x}, [{x} + 18h]", // PEB->LDR + "mov {x}, [{x} + 10h]", // LDR->InLoadOrderModuleList + "mov {x}, [{x}]", + "mov {x}, [{x}]", + "mov {x}, [{x} + 30h]", + x = out(reg) _k32, + ); + _k32 +} + +#[inline(always)] +pub unsafe fn loaded_modules() -> ModuleIter { + let mut module_link: *const LDR_DATA_TABLE_ENTRY; + 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, + ); + ModuleIter { + entry: (*module_link).prev, + head: (*module_link).prev, + } +} + +extern "C" { + #[link_name = "__ImageBase"] + static ImageBaseDosHeader: ImageDOSHeader; + #[link_name = "__ImageBase"] + static ImageBasePtr: [u8;0]; +} + +#[inline(always)] +pub fn image_base() -> &'static ImageBase { + unsafe { &*(ImageBasePtr.as_ptr() as *const ImageBase) } +} + +#[inline(always)] +pub fn image_header() -> &'static ImageDOSHeader { + unsafe { &ImageBaseDosHeader } +} + +#[inline(always)] +pub unsafe fn process_executable() -> &'static ImageBase { + let mut process_exe: *const ImageBase; + core::arch::asm!( + "mov {x}, gs:[60h]", // TEB->PEB + "mov {x}, [{x} + 10h]", // PEB->ImageBaseAddress + x = out(reg) process_exe, + ); + &*process_exe +} diff --git a/src/win32/module_iter.rs b/src/win32/module_iter.rs new file mode 100644 index 0000000..6dd93c2 --- /dev/null +++ b/src/win32/module_iter.rs @@ -0,0 +1,52 @@ +use crate::win32::ImageBase; + +#[repr(C)] +#[derive(Copy, Clone)] +#[allow(non_camel_case_types)] +pub struct UNICODE_STRING { + pub length: u16, + pub capacity: u16, + pub buffer: *const u16, +} + +#[repr(C)] +#[allow(non_camel_case_types)] +pub struct LDR_DATA_TABLE_ENTRY { + /* 0x00 */ pub next: *const LDR_DATA_TABLE_ENTRY, + /* 0x08 */ pub prev: *const LDR_DATA_TABLE_ENTRY, + /* 0x10 */ pub reserved2: [usize;4], + /* 0x30 */ pub module: *const ImageBase, + /* 0x38 */ pub entry_point: *const (), + /* 0x40 */ pub reserved3: usize, + /* 0x48 */ pub path: UNICODE_STRING, + /* 0x58 */ pub name: UNICODE_STRING, +} + +impl UNICODE_STRING { + pub fn as_slice(&self) -> &'static [u16] { + unsafe { core::slice::from_raw_parts(self.buffer, (self.length / 2) as usize) } + } +} + +pub struct ModuleIter { + pub entry: *const LDR_DATA_TABLE_ENTRY, + pub head: *const LDR_DATA_TABLE_ENTRY, +} + +impl Iterator for ModuleIter { + type Item = (&'static ImageBase, &'static [u16]); + + fn next(&mut self) -> Option { + unsafe { + self.entry = (&*self.entry).next; + match self.entry == self.head { + true => { None } + false => { + let module = (*self.entry).module; + let name = (*self.entry).name.as_slice(); + Some((&*module,name)) + } + } + } + } +} \ No newline at end of file diff --git a/src/win32/pe_image.rs b/src/win32/pe_image.rs new file mode 100644 index 0000000..c2f8b41 --- /dev/null +++ b/src/win32/pe_image.rs @@ -0,0 +1,265 @@ +// ============================== +// PE stuff +// ============================== + +use crate::upcast::Upcast; + +#[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; ImageBase::NUMBEROF_DIRECTORY_ENTRIES] +} + +#[repr(C)] +pub struct ImageDataDirectory { + pub virtual_address: u32, + pub size: u32, +} + +#[repr(C)] +pub struct ImageSectionHeader { + pub name: [u8;8], + pub virtual_size: u32, + pub size_of_raw_data: u32, + pub pointer_to_raw_data: u32, + pub pointer_to_relocations: u32, + pub pointer_to_linenumbers: u32, + pub number_of_relocations: u16, + pub number_of_linenumbers: u16, + pub characteristics: u32, +} + +#[repr(C)] +pub struct ImageImportDescriptor { + pub original_first_thunk: u32, + pub timestamp: u32, + pub forwarder_chain: u32, + pub name: u32, + pub first_thunk: u32, +} + +#[repr(C)] +pub struct ImageBoundImportDescriptor { + pub timestamp: u32, + pub offset_module_name: u16, + pub number_of_module_forwarder_refs: u16, +} + +#[repr(C)] +pub struct ImageImportByName { + pub hint: u16, + pub name: [u8] +} + +#[repr(C)] +pub struct ImageBaseRelocation { + pub virtual_address: u32, + pub size_of_block: u32, +} + +#[repr(C)] +pub struct ImageExportDirectory { + pub export_flags: u32, + pub timestamp: u32, + pub major_version: u16, + pub minor_version: u16, + pub name_rva: u32, + pub ordinal_base: u32, + pub address_table_entries: u32, + pub number_of_name_pointers: u32, + pub export_address_table_rva: u32, + pub name_pointer_rva: u32, + pub ordinal_table_rva: u32, +} + +#[repr(C)] +pub struct ImageExportAddressEntry { + pub export_rva: u32, + pub forwarder_rva: u32, +} + +#[repr(C)] +pub struct ImageBase(()); + +impl ImageDataDirectory { + pub fn contains(&self, offset: u32) -> bool { + // offset - self.virtual_address < self.size + offset.overflowing_sub(self.virtual_address).0 < self.size + } +} + +impl ImageBase { + + pub const PE_SIGNATURE: u32 = 0x00004550; + pub const DOS_MAGIC: u16 = 0x5A4D; + + pub const IOH_MAGIC_PE32: u16 = 0x10B; // 32 bit executable + pub const IOH_MAGIC_PE64: u16 = 0x20B; // 64 bit executable + pub const IOH_MAGIC_ROM: u16 = 0x107; // Yes! + + pub const NUMBEROF_DIRECTORY_ENTRIES: usize = 16; + + pub const DIRECTORY_ENTRY_EXPORT: usize = 0x0; // Export Directory + pub const DIRECTORY_ENTRY_IMPORT: usize = 0x1; // Import Directory + pub const DIRECTORY_ENTRY_RESOURCE: usize = 0x2; // Resource Directory + pub const DIRECTORY_ENTRY_EXCEPTION: usize = 0x3; // Exception Directory + pub const DIRECTORY_ENTRY_SECURITY: usize = 0x4; // Security Directory + pub const DIRECTORY_ENTRY_BASERELOC: usize = 0x5; // Base Relocation Table + pub const DIRECTORY_ENTRY_DEBUG: usize = 0x6; // Debug Directory + pub const DIRECTORY_ENTRY_ARCHITECTURE: usize = 0x7; // Architecture Specific Data + pub const DIRECTORY_ENTRY_GLOBALPTR: usize = 0x8; // RVA of GP + pub const DIRECTORY_ENTRY_TLS: usize = 0x9; // TLS Directory + pub const DIRECTORY_ENTRY_LOAD_CONFIG: usize = 0xA; // Load Configuration Directory + pub const DIRECTORY_ENTRY_BOUND_IMPORT: usize = 0xB; // Bound Import Directory in headers + pub const DIRECTORY_ENTRY_IAT: usize = 0xC; // Import Address Table + pub const DIRECTORY_ENTRY_DELAY_IMPORT: usize = 0xD; // Delay Load Import Descriptors + pub const DIRECTORY_ENTRY_COM_DESCRIPTOR: usize = 0xE; // COM Runtime descriptor + + pub const IMAGE_FILE_MACHINE_I386: u16 = 0x014c; + pub const IMAGE_FILE_MACHINE_IA64: u16 = 0x0200; + pub const IMAGE_FILE_MACHINE_AMD64: u16 = 0x8664; + + #[inline(always)] + pub fn as_ptr(&self) -> usize { + self as *const _ as usize + } + + #[inline(always)] + fn offset>(&self, offset: T) -> *const u8 { + unsafe { (self as *const _ as *const u8).add(offset.upcast()) } + } + + pub unsafe fn dos(&self) -> &ImageDOSHeader { + &*(self as *const _ as *const ImageDOSHeader) + } + + pub unsafe fn dos_mut(&mut self) -> &mut ImageDOSHeader { + &mut *(self as *mut _ as *mut ImageDOSHeader) + } + + pub unsafe fn nt_header(&self) -> &'static ImageNTHeaders64 { + &*(self.offset(self.dos().e_lfanew) as *const ImageNTHeaders64) + } + + pub unsafe fn nt_header_mut(&mut self) -> &'static mut ImageNTHeaders64 { + &mut *(self.offset(self.dos().e_lfanew) as *mut ImageNTHeaders64) + } + + pub unsafe fn exports(&self) -> Option { + let directory = &self.nt_header().optional_header.data_directory[ImageBase::DIRECTORY_ENTRY_EXPORT]; + if directory.size == 0 || directory.virtual_address == 0 { return None; } + let export_directory = &*(self.offset(directory.virtual_address) as *const ImageExportDirectory); + Some(ExportIter { + image: self, + export_dir: export_directory, + export_index: 0, + }) + } + +} + +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 { + 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] { + 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, + } + } +} diff --git a/src/win32/tls.rs b/src/win32/tls.rs new file mode 100644 index 0000000..c54c71f --- /dev/null +++ b/src/win32/tls.rs @@ -0,0 +1,56 @@ +/// read the value in the given tls slot +pub unsafe fn read_tls(index: u32) -> usize { + let mut tls_slot: usize; + core::arch::asm!( + "mov {x}, gs:[1480h + {y:r} * 8]", + x = out(reg) tls_slot, + y = in(reg) index + ); + tls_slot +} + +/// write a value into the given tls slot +pub unsafe fn write_tls(index: u32, value: usize) { + core::arch::asm!( + "mov gs:[1480h + {y:r} * 8], {x}", + x = in(reg) value, + y = in(reg) index + ); +} + +#[inline(always)] +unsafe fn read_tls_bitmap() -> u64 { + let mut _tls: u64 = 0; + core::arch::asm!( + "mov {x}, gs:[60h]", // TEB->PEB + "mov {x}, [{x} + 80h]", // PEB->TlsBitmap + x = out(reg) _tls, + ); + _tls +} + +#[inline(always)] +unsafe fn write_tls_bitmap(value: u64) { + let peb: u64 = 0; + core::arch::asm!( + "mov {x}, gs:[60h]", // TEB->PEB + "mov [{x} + 80h], {y}", // PEB->TlsBitmap + x = in(reg) peb, + y = in(reg) value, + ); +} + +/// acquires a tls slot +pub unsafe fn acquire_tls() -> Option { + let bitmap = read_tls_bitmap(); + (0..64) + .filter(|i| bitmap & ( 1 << i ) == 0) + .inspect(|i|{ + write_tls_bitmap(bitmap | ( 1 << i)) + }).next() +} + +/// free's the given tls slot +pub unsafe fn release_tls(slot: u32) { + write_tls_bitmap(read_tls_bitmap() & !(1 << slot )) +} \ No newline at end of file diff --git a/tests/test_data.rs b/tests/test_data.rs new file mode 100644 index 0000000..c0afdd8 --- /dev/null +++ b/tests/test_data.rs @@ -0,0 +1,15 @@ + + + + +#[test] +pub fn test_distance() { + + + let a = [0u8,2,3]; + let p1 = &a[0]; + let p2 = &a[2]; + assert_eq!(x::distance(p1, p2), 2); + assert_eq!(x::distance(p2, p1), 2); + assert_eq!(x::distance(p1, p1), 0); +} \ No newline at end of file diff --git a/tests/test_image_base.rs b/tests/test_image_base.rs new file mode 100644 index 0000000..e2dc0e5 --- /dev/null +++ b/tests/test_image_base.rs @@ -0,0 +1,24 @@ + +#[link(name = "kernel32")] +extern "C" { + pub fn GetModuleHandleA(module_name: *const i8) -> &'static x::win32::ImageBase; +} + + +#[test] +#[cfg(feature = "win32")] +pub fn test_image_base() { + let a = unsafe { GetModuleHandleA(std::ptr::null()) } as *const _ as usize; + let b = x::win32::image_base() as *const _ as usize; + assert_eq!(a, b, "image_base didn't match"); + + +} + +#[test] +#[cfg(feature = "win32")] +pub fn test_image_base2() { + let a = unsafe { x::win32::process_executable() } as *const _ as usize; + let b = x::win32::image_base() as *const _ as usize; + assert_eq!(a, b, "image_base didn't match"); +} \ No newline at end of file diff --git a/tests/test_lazy_import.rs b/tests/test_lazy_import.rs new file mode 100644 index 0000000..0e2bef2 --- /dev/null +++ b/tests/test_lazy_import.rs @@ -0,0 +1,15 @@ +use x::IntoUsize; + +x::lazy_import! { "kernel32.dll" + pub fn LoadLibraryA(module_name: *const i8) -> &'static x::win32::ImageBase; + pub fn GetModuleHandleA(module_name: *const i8) -> &'static x::win32::ImageBase; + pub fn GetLastError() -> usize; +} + + +#[test] +#[cfg(feature = "win32")] +pub fn test_exports() { + let a = LoadLibraryA(x::cstr!("user32.dll")); + println!("{} -> {}",a.into_usize(), unsafe { a.nt_header().optional_header.size_of_image }); +} \ No newline at end of file