Skip to content

Commit

Permalink
Merge pull request #9 from pseuxide/pattern_scan_performance_imp
Browse files Browse the repository at this point in the history
pattern scan with regex
  • Loading branch information
pseuxide committed Aug 13, 2022
2 parents d2fc03f + 631fc15 commit 33ecd29
Show file tree
Hide file tree
Showing 17 changed files with 249 additions and 317 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Expand Up @@ -16,6 +16,7 @@ toy-arms_derive = {path = "./toy-arms_derive", version = "0.1.1"}
thiserror = "1.0.30"
rustc-hash = "1.1.0"
smartstring = "0.2.9"
regex = "1.5.5"

[features]
default = ["internal"]
Expand Down
8 changes: 8 additions & 0 deletions README.md
Expand Up @@ -74,6 +74,11 @@ target = "i686-pc-windows-msvc"

Or put `--target i686-pc-windows-msvc` flag everytime when you build the code.

If you don't have toolchain for 32bit msvc, do following
```shell
rustup target add i686-pc-windows-msvc
```

# :scroll: Practical Examples

In this section I'll showcase you various examples for different situations with internal and external features. Find one fits your purpose.
Expand Down Expand Up @@ -427,3 +432,6 @@ To build examples in x86 arch:
```shell
cargo build --example EXAMPLE_NAME --target i686-pc-windows-msvc
```

# Acknowledge
[hazedumper-rs](https://github.com/frk1/hazedumper-rs) - referenced as role model of pattern scan
19 changes: 12 additions & 7 deletions examples/ex_get_localplayer_health.rs
@@ -1,3 +1,5 @@
use std::mem::size_of;
use winapi::shared::minwindef::LPVOID;
use toy_arms::external::read;
use toy_arms::external::module::Module;
use toy_arms::external::process::Process;
Expand Down Expand Up @@ -25,16 +27,19 @@ fn main() {

println!("module_base: {:x}", client.module_base_address);
println!("localplayer pointer: 0x{:x}", client.module_base_address + DW_LOCAL_PLAYER as usize);
let localplayer = read::<u32>(&csgo.process_handle, client.module_base_address + DW_LOCAL_PLAYER as usize);

match localplayer {
Ok(l) => {
println!("localplayer address: 0x{:x}", l);
let mut localplayer: u32 = 0;
let ok = read::<u32>(&csgo.process_handle, client.module_base_address + DW_LOCAL_PLAYER as usize, size_of::<LPVOID>(), localplayer as *mut u32);

match ok {
Ok(ok) => {
println!("localplayer address: 0x{:x}", localplayer);
let mut health = 0;
// 0x100 is the offset of the health in player entity class.
let health = read::<u16>(&csgo.process_handle, l as usize + 0x100);
match health {
let ok2 = read::<u16>(&csgo.process_handle, localplayer as usize + 0x100, size_of::<u16>(), health as *mut u16);
match ok2 {
// This is what we wanted.
Ok(h) => println!("localplayer's health: {}", h),
Ok(h) => println!("localplayer's health: {}", health),
Err(ReadMemoryFailed(e)) => println!("{}", e),
Err(_) => println!("some error"),
}
Expand Down
3 changes: 1 addition & 2 deletions examples/ex_pattern_scanning.rs
Expand Up @@ -10,8 +10,7 @@ fn main() {
let process = Process::from_process_name("csgo.exe").unwrap();

// You can get module information by using get_client
let client = process.get_module_info("client.dll").unwrap();
println!("{:#?}", client);
let mut client = process.get_module_info("client.dll").unwrap();

let address = client.find_pattern(DW_FORCE_ATTACK_PATTERN);
match address {
Expand Down
9 changes: 1 addition & 8 deletions examples/in_auto_shoot.rs
Expand Up @@ -8,13 +8,12 @@ The offset DW_FORCE_ATTACK works as of the day i wrote this but it might not be
use winapi::shared::minwindef::HMODULE;
use toy_arms::{detect_keypress, detect_keydown, VirtualKeyCode};
use toy_arms::cast;
use toy_arms::internal::{get_module_function_address};
use toy_arms::internal::utils::get_module_handle;

toy_arms::create_entrypoint!(hack_main_thread);

// This offset has to be up to date.
const DW_FORCE_ATTACK: usize = 0x31FE33C;
const DW_FORCE_ATTACK: usize = 0x3207FE8;

fn hack_main_thread() {
let mut once = false;
Expand All @@ -23,12 +22,6 @@ fn hack_main_thread() {
let module_handle: HMODULE = get_module_handle("client.dll").unwrap();
println!("module handle = {:?}", module_handle as usize);

unsafe {
// Gets function address
let function_address = get_module_function_address("USER32.dll", "MessageBoxA").unwrap();
println!("function address = {:?}", function_address);
}

let shoot_flag = cast!(mut module_handle as usize + DW_FORCE_ATTACK, u8);

loop {
Expand Down
2 changes: 1 addition & 1 deletion examples/in_get_localplayer_health.rs
Expand Up @@ -5,7 +5,7 @@ also, the offset of DW_LOCAL_PLAYER works as of the day i wrote this but it migh
*/
use toy_arms::GameObject;
use toy_arms::{cast, create_entrypoint, VirtualKeyCode};
use toy_arms::internal::Module;
use toy_arms::internal::module::Module;
use toy_arms_derive::GameObject;

create_entrypoint!(hack_main_thread);
Expand Down
2 changes: 1 addition & 1 deletion examples/in_pattern_scanning.rs
Expand Up @@ -7,7 +7,7 @@ The model pattern is for dwForceAttack.
use toy_arms::{
detect_keypress,
internal::{
Module
module::Module
},
VirtualKeyCode
};
Expand Down
16 changes: 10 additions & 6 deletions src/external/external.rs
Expand Up @@ -2,7 +2,6 @@ use std::{
mem::size_of,
ptr::null_mut,
};
use std::mem::size_of_val;

use winapi::{
shared::{
Expand All @@ -22,8 +21,11 @@ use crate::pattern_scan_common::is_page_readable;
use super::error::{ReadWriteMemoryFailedDetail, TAExternalError };

/// read fetches the value that given address is holding.
/// * `process_handle` - handle of the process that module belongs to.
/// * `base_address` - the address that is supposed to have the value you want
pub fn read<T>(process_handle: &HANDLE, base_address: usize) -> Result<T, TAExternalError> {
/// * `buffer` - the buffer to be filled with read value. must have identical type as T.
pub fn read<'a, T>(process_handle: &'a HANDLE, base_address: usize, size: usize, buffer: *mut T) -> Result<(), TAExternalError>
{
unsafe {
let mut memory_info: MEMORY_BASIC_INFORMATION = MEMORY_BASIC_INFORMATION::default();
VirtualQueryEx(*process_handle, base_address as LPCVOID, &mut memory_info, std::mem::size_of::<MEMORY_BASIC_INFORMATION>());
Expand All @@ -33,17 +35,19 @@ pub fn read<T>(process_handle: &HANDLE, base_address: usize) -> Result<T, TAExte
if !is_readable {
VirtualProtectEx(*process_handle, base_address as LPVOID, size_of::<LPVOID>(), new_protect, &mut old_protect as *mut DWORD);
}
let mut buffer: T = std::mem::zeroed::<T>();

let ok = ReadProcessMemory(
*process_handle,
base_address as LPCVOID,
&mut buffer as *mut _ as LPVOID,
size_of_val(&buffer) as SIZE_T,
buffer as *mut T as LPVOID,
size as SIZE_T,
null_mut::<SIZE_T>(),
);

if !is_readable {
VirtualProtectEx(*process_handle, base_address as LPVOID, size_of::<LPVOID>(), old_protect, &mut new_protect as *mut DWORD);
}

if ok == FALSE {
let error_code = GetLastError();
return match error_code {
Expand All @@ -53,7 +57,7 @@ pub fn read<T>(process_handle: &HANDLE, base_address: usize) -> Result<T, TAExte
_ => Err(TAExternalError::ReadMemoryFailed(ReadWriteMemoryFailedDetail::UnknownError { error_code })),
}
}
Ok(buffer)
Ok(())
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/external/mod.rs
@@ -1,13 +1,13 @@
#[cfg(feature = "external")]
pub mod external;
#[cfg(feature = "external")]
mod pattern_scan;
#[cfg(feature = "external")]
pub mod error;
#[cfg(feature = "external")]
pub mod module;
#[cfg(feature = "external")]
pub mod process;
#[cfg(feature = "external")]
pub mod pattern_scan;

#[cfg(feature = "external")]
pub use external::*;
50 changes: 22 additions & 28 deletions src/external/module.rs
@@ -1,5 +1,5 @@
use std::{fmt, fmt::Debug};
use std::convert::TryInto;
use std::{ fmt, fmt::Debug };
use std::mem::{ size_of_val };

use winapi::{
shared::{
Expand All @@ -15,16 +15,18 @@ use winapi::{

use crate::utils_common::read_null_terminated_string;
use smartstring::alias::String;
use crate::external::error::TAExternalError;
use crate::external::read;

#[derive(Debug)]
pub struct Module<'a> {
process_handle: &'a HANDLE,
pub process_handle: &'a HANDLE,
pub module_size: u32,
pub module_base_address: usize,
pub module_handle: HMODULE,
pub module_name: &'a str,
pub module_path: String,
pub data: Vec<u8>,
}

impl<'a> fmt::Display for Module<'a> {
Expand All @@ -42,45 +44,37 @@ impl<'a> Default for Module<'a> {
module_handle: 0x0 as HMODULE,
module_name: "",
module_path: String::default(),
data: vec![0u8; 80000000],
}
}
}

impl<'a> Module<'a> {
pub(crate) fn from_module_entry(process_handle: &'a HANDLE, module_entry: &MODULEENTRY32, module_name: &'a str) -> Self {
Module {
pub(crate) fn from_module_entry(process_handle: &'a HANDLE, module_entry: &MODULEENTRY32, module_name: &'a str) -> Result<Self, TAExternalError> {
let mut module = Module {
process_handle,
module_size: module_entry.modBaseSize,
module_base_address: module_entry.modBaseAddr as usize,
module_handle: module_entry.hModule,
module_name,
// This is allowed because szExePath.as_ptr() is the address within module_entry variable, not the address in the target process.
module_path: unsafe { read_null_terminated_string(module_entry.szExePath.as_ptr() as usize) }.unwrap().parse().unwrap(),
data: vec![0u8; module_entry.modBaseSize as usize]
};
let ok = read::<Vec<u8>>(module.process_handle, module.module_base_address, module.data.len(), module.data.as_mut_ptr() as *mut Vec<u8>);
match ok {
Err(e) => Err(e),
_ => Ok(module),
}
}

pub fn find_pattern(&self, pattern: &str) -> Option<usize> {
let base = self.module_base_address;
let end = self.module_base_address + self.module_size as usize;
unsafe { crate::external::pattern_scan::boyer_moore_horspool(self.process_handle, pattern, base, end) }
}

/// pattern scan basically be for calculating offset of some value. It adds the offset to the pattern-matched address, dereferences, and add the `extra`.
/// * `pattern` - pattern string you're looking for. format: "8D 34 85 ? ? ? ? 89 15 ? ? ? ? 8B 41 08 8B 48 04 83 F9 FF"
/// * `offset` - offset of the address from pattern's base.
/// * `extra` - offset of the address from dereferenced address.
pub fn pattern_scan<T>(&self, pattern: &str, offset: usize, extra: usize) -> Option<T>
where T: std::ops::Add<Output = T>,
T: std::ops::Sub<Output = T>,
T: std::convert::TryFrom<usize>,
<T as std::convert::TryFrom<usize>>::Error: Debug,
{
let address = self.find_pattern(pattern)?;
let address = address + offset;
Some(read::<T>(self.process_handle, address).expect("READ FAILED IN PATTERN SCAN") - self.module_base_address.try_into().unwrap() + extra.try_into().unwrap())
}
pub(crate) fn ensure_data_populated(&mut self) {
if size_of_val(&self.data) == 80000000 {
self.data.resize(self.module_size as usize, 0u8);
}

pub fn find_pattern_specific_range(&self, pattern: &str, start: usize, end: usize) -> Option<usize> {
unsafe { crate::external::pattern_scan::boyer_moore_horspool(self.process_handle, pattern, start, end) }
if self.data.iter().all(|&val| val == 0) {
read::<Vec<u8>>(self.process_handle, self.module_base_address, self.data.len(), self.data.as_mut_ptr() as *mut Vec<u8>);
}
}
}
}
88 changes: 34 additions & 54 deletions src/external/pattern_scan.rs
@@ -1,60 +1,40 @@
use winapi::shared::minwindef::LPCVOID;
use winapi::um::memoryapi::VirtualQueryEx;
use winapi::um::winnt::{HANDLE, MEMORY_BASIC_INFORMATION};
use crate::external::read;
use crate::pattern_scan_common::{build_bad_match_table, is_page_readable, process_pattern_from_str};
pub mod module {
use std::convert::TryInto;
use std::fmt::Debug;
use std::mem::zeroed;
use std::mem::size_of;
use regex::bytes::Regex;
use crate::external::module::Module;
use crate::external::read;

pub(crate) unsafe fn boyer_moore_horspool(
process_handle: &HANDLE,
pattern: &str,
start: usize,
end: usize,
) -> Option<usize> {
let pattern_vec = process_pattern_from_str(pattern);
let pattern = &pattern_vec;

let right_most_wildcard_index = if let Some(x) = pattern.iter().rev().position(|&x| x == b'\x3F') {
x
} else {
pattern.len()
};
let bmt = build_bad_match_table(pattern, right_most_wildcard_index);

let mut current = start + (pattern.len() as isize - 1) as usize;
let mut memory_info: MEMORY_BASIC_INFORMATION = MEMORY_BASIC_INFORMATION::default();
let mut next_page_base = 0x0;

while current < end {
if current <= next_page_base {
VirtualQueryEx(*process_handle, current as LPCVOID, &mut memory_info, std::mem::size_of::<MEMORY_BASIC_INFORMATION>());
next_page_base = memory_info.BaseAddress as usize + memory_info.RegionSize as usize;
if !is_page_readable(&memory_info) {
current = memory_info.BaseAddress as usize
+ memory_info.RegionSize as usize
+ pattern.len();
continue;
}
impl<'a> Module<'a> {
fn generate_regex(&self, pattern: &str) -> Option<Regex> {
let mut regex = pattern
.split_whitespace()
.map(|val| if val == "?" { ".".to_string() } else { format!("\\x{}", val)}).collect::<Vec<_>>().join("");
regex.insert_str(0, "(?s-u)");
Regex::new(&regex).ok()
}

let mut pattern_match_num = 0;
for (i, p) in pattern.iter().rev().enumerate() {
let current_byte = read::<u8>(process_handle, current).expect("READ FAILED");
if *p == b'\x3F' || *p == current_byte {
pattern_match_num += 1;
if pattern_match_num == pattern.len() {
return Some(current);
}
current = current - 1;
} else {
let movement_num = if let Some(i) = bmt.get(&current_byte) {
i.clone()
} else {
right_most_wildcard_index
};
current = current + movement_num + i;
break;
}
pub fn find_pattern(&mut self, pattern: &str) -> Option<usize> {
self.generate_regex(pattern)
.and_then(|f| f.find(&self.data)).and_then(|f| Some(f.start()))
}
// pattern scan basically be for calculating offset of some value. It adds the offset to the pattern-matched address, dereferences, and add the `extra`.
// * `pattern` - pattern string you're looking for. format: "8D 34 85 ? ? ? ? 89 15 ? ? ? ? 8B 41 08 8B 48 04 83 F9 FF"
// * `offset` - offset of the address from pattern's base.
// * `extra` - offset of the address from dereferenced address.
pub fn pattern_scan<T>(&mut self, pattern: &str, offset: usize, extra: usize) -> Option<T>
where T: std::ops::Add<Output = T>,
T: std::ops::Sub<Output = T>,
T: std::convert::TryFrom<usize>,
<T as std::convert::TryFrom<usize>>::Error: Debug,
{
let address = self.find_pattern(pattern)?;
let address = address + offset;
let mut target_buffer: T = unsafe { zeroed::<T>() };
read::<T>(self.process_handle, self.module_base_address + address, size_of::<T>(), &mut target_buffer as *mut T).expect("READ FAILED IN PATTERN SCAN");
Some( target_buffer - self.module_base_address.try_into().unwrap() + extra.try_into().unwrap())
}
}
None
}

0 comments on commit 33ecd29

Please sign in to comment.