mirror of
https://github.com/a2x/cs2-dumper.git
synced 2025-10-07 22:50:03 +08:00
Rewrote project in Rust
This commit is contained in:
5
src/remote/mod.rs
Normal file
5
src/remote/mod.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
pub use module::Module;
|
||||
pub use process::Process;
|
||||
|
||||
pub mod module;
|
||||
pub mod process;
|
179
src/remote/module.rs
Normal file
179
src/remote/module.rs
Normal file
@@ -0,0 +1,179 @@
|
||||
use std::ffi::CStr;
|
||||
use std::slice;
|
||||
|
||||
use windows::Win32::System::Diagnostics::Debug::*;
|
||||
use windows::Win32::System::SystemServices::*;
|
||||
|
||||
use crate::error::{Error, Result};
|
||||
|
||||
use super::Process;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Export {
|
||||
pub name: String,
|
||||
pub va: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Section {
|
||||
pub name: String,
|
||||
pub start_rva: usize,
|
||||
pub end_rva: usize,
|
||||
pub size: usize,
|
||||
}
|
||||
|
||||
pub struct Module<'a> {
|
||||
address: usize,
|
||||
nt_headers: &'a IMAGE_NT_HEADERS64,
|
||||
exports: Vec<Export>,
|
||||
sections: Vec<Section>,
|
||||
}
|
||||
|
||||
impl<'a> Module<'a> {
|
||||
pub fn new(process: &'a Process, address: usize) -> Result<Self> {
|
||||
let mut headers: [u8; 0x1000] = [0; 0x1000];
|
||||
|
||||
process.read_memory_raw(address, headers.as_mut_ptr() as *mut _, headers.len())?;
|
||||
|
||||
let dos_header = unsafe { &*(headers.as_ptr() as *const IMAGE_DOS_HEADER) };
|
||||
|
||||
if dos_header.e_magic != IMAGE_DOS_SIGNATURE {
|
||||
return Err(Error::InvalidMagic(dos_header.e_magic as u32));
|
||||
}
|
||||
|
||||
let nt_headers = unsafe {
|
||||
&*(headers.as_ptr().offset(dos_header.e_lfanew as isize) as *const IMAGE_NT_HEADERS64)
|
||||
};
|
||||
|
||||
if nt_headers.Signature != IMAGE_NT_SIGNATURE {
|
||||
return Err(Error::InvalidMagic(nt_headers.Signature));
|
||||
}
|
||||
|
||||
let exports = unsafe { Self::parse_exports(process, address, nt_headers)? };
|
||||
let sections = unsafe { Self::parse_sections(nt_headers) };
|
||||
|
||||
Ok(Self {
|
||||
address,
|
||||
nt_headers,
|
||||
exports,
|
||||
sections,
|
||||
})
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn address(&self) -> usize {
|
||||
self.address
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn exports(&self) -> &Vec<Export> {
|
||||
&self.exports
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn sections(&self) -> &Vec<Section> {
|
||||
&self.sections
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn export(&self, name: &str) -> Option<&Export> {
|
||||
self.exports.iter().find(|export| export.name == name)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn section(&self, name: &str) -> Option<&Section> {
|
||||
self.sections.iter().find(|section| section.name == name)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn size(&self) -> u32 {
|
||||
self.nt_headers.OptionalHeader.SizeOfImage
|
||||
}
|
||||
|
||||
unsafe fn parse_exports(
|
||||
process: &Process,
|
||||
address: usize,
|
||||
nt_headers: &IMAGE_NT_HEADERS64,
|
||||
) -> Result<Vec<Export>> {
|
||||
let export_data_directory =
|
||||
nt_headers.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT.0 as usize];
|
||||
|
||||
let export_directory_start = address + export_data_directory.VirtualAddress as usize;
|
||||
let export_directory_end = export_directory_start + export_data_directory.Size as usize;
|
||||
|
||||
let mut buffer: Vec<u8> = vec![0; export_data_directory.Size as usize];
|
||||
|
||||
process.read_memory_raw(
|
||||
export_directory_start,
|
||||
buffer.as_mut_ptr() as *mut _,
|
||||
buffer.len(),
|
||||
)?;
|
||||
|
||||
let export_directory = &*(buffer.as_ptr() as *const IMAGE_EXPORT_DIRECTORY);
|
||||
|
||||
let delta =
|
||||
export_directory as *const _ as usize - export_data_directory.VirtualAddress as usize;
|
||||
|
||||
let name_table = (delta + export_directory.AddressOfNames as usize) as *const u32;
|
||||
let ordinal_table = (delta + export_directory.AddressOfNameOrdinals as usize) as *const u16;
|
||||
let function_table = (delta + export_directory.AddressOfFunctions as usize) as *const u32;
|
||||
|
||||
let mut exports: Vec<Export> = Vec::with_capacity(export_directory.NumberOfNames as usize);
|
||||
|
||||
for i in 0..export_directory.NumberOfNames {
|
||||
let function_ordinal = *ordinal_table.offset(i as isize);
|
||||
let function_va = address + *function_table.offset(function_ordinal as isize) as usize;
|
||||
|
||||
// Skip forwarded exports.
|
||||
if function_va >= export_directory_start && function_va <= export_directory_end {
|
||||
continue;
|
||||
}
|
||||
|
||||
let name = CStr::from_ptr(
|
||||
delta.wrapping_add(*name_table.offset(i as isize) as usize) as *const i8
|
||||
)
|
||||
.to_string_lossy()
|
||||
.into_owned();
|
||||
|
||||
exports.push(Export {
|
||||
name,
|
||||
va: function_va,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(exports)
|
||||
}
|
||||
|
||||
unsafe fn parse_sections(nt_headers: &IMAGE_NT_HEADERS64) -> Vec<Section> {
|
||||
let optional_header_ptr = &nt_headers.OptionalHeader as *const _ as *const u8;
|
||||
|
||||
let section_header_ptr = optional_header_ptr
|
||||
.offset(nt_headers.FileHeader.SizeOfOptionalHeader as isize)
|
||||
as *const IMAGE_SECTION_HEADER;
|
||||
|
||||
let sections_raw = slice::from_raw_parts(
|
||||
section_header_ptr,
|
||||
nt_headers.FileHeader.NumberOfSections as usize,
|
||||
);
|
||||
|
||||
sections_raw
|
||||
.iter()
|
||||
.map(|section| {
|
||||
let name = CStr::from_ptr(section.Name.as_ptr() as *const _)
|
||||
.to_string_lossy()
|
||||
.into_owned();
|
||||
|
||||
let start_rva = section.VirtualAddress as usize;
|
||||
let end_rva = start_rva + section.Misc.VirtualSize as usize;
|
||||
let size = section.SizeOfRawData as usize;
|
||||
|
||||
Section {
|
||||
name,
|
||||
start_rva,
|
||||
end_rva,
|
||||
size,
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
255
src/remote/process.rs
Normal file
255
src/remote/process.rs
Normal file
@@ -0,0 +1,255 @@
|
||||
use std::ffi::{c_void, CStr};
|
||||
use std::mem;
|
||||
use std::ptr;
|
||||
|
||||
use windows::Win32::Foundation::*;
|
||||
use windows::Win32::System::Diagnostics::Debug::*;
|
||||
use windows::Win32::System::Diagnostics::ToolHelp::*;
|
||||
use windows::Win32::System::Threading::*;
|
||||
|
||||
use crate::error::{Error, Result};
|
||||
|
||||
use super::Module;
|
||||
|
||||
pub struct Process {
|
||||
process_id: u32,
|
||||
process_handle: HANDLE,
|
||||
}
|
||||
|
||||
impl Process {
|
||||
pub fn new(process_name: &str) -> Result<Self> {
|
||||
let process_id = Self::get_process_id_by_name(process_name)?;
|
||||
|
||||
let process_handle = unsafe { OpenProcess(PROCESS_ALL_ACCESS, false, process_id) }?;
|
||||
|
||||
Ok(Self {
|
||||
process_id,
|
||||
process_handle,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn find_pattern(&self, module_name: &str, pattern: &str) -> Result<usize> {
|
||||
let module = self.get_module_by_name(module_name)?;
|
||||
|
||||
let mut module_data: Vec<u8> = vec![0; module.size() as usize];
|
||||
|
||||
self.read_memory_raw(
|
||||
module.address(),
|
||||
module_data.as_mut_ptr() as *mut _,
|
||||
module_data.len(),
|
||||
)?;
|
||||
|
||||
let pattern_bytes = Self::pattern_to_bytes(pattern);
|
||||
|
||||
for i in 0..module.size() as usize - pattern.len() {
|
||||
let mut found = true;
|
||||
|
||||
for j in 0..pattern_bytes.len() {
|
||||
if module_data[i + j] != pattern_bytes[j] as u8 && pattern_bytes[j] != -1 {
|
||||
found = false;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if found {
|
||||
return Ok(module.address() + i);
|
||||
}
|
||||
}
|
||||
|
||||
Err(Error::PatternNotFound)
|
||||
}
|
||||
|
||||
pub fn get_loaded_modules(&self) -> Result<Vec<String>> {
|
||||
let snapshot = unsafe { CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, self.process_id) }?;
|
||||
|
||||
let mut entry = MODULEENTRY32 {
|
||||
dwSize: mem::size_of::<MODULEENTRY32>() as u32,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let mut modules = Vec::new();
|
||||
|
||||
unsafe {
|
||||
Module32First(snapshot, &mut entry)?;
|
||||
|
||||
while Module32Next(snapshot, &mut entry).is_ok() {
|
||||
let name = CStr::from_ptr(&entry.szModule as *const _ as *const _)
|
||||
.to_string_lossy()
|
||||
.into_owned();
|
||||
|
||||
modules.push(name);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(modules)
|
||||
}
|
||||
|
||||
pub fn get_module_by_name(&self, module_name: &str) -> Result<Module> {
|
||||
let snapshot = unsafe { CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, self.process_id) }?;
|
||||
|
||||
let mut entry = MODULEENTRY32 {
|
||||
dwSize: mem::size_of::<MODULEENTRY32>() as u32,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
unsafe {
|
||||
Module32First(snapshot, &mut entry)?;
|
||||
|
||||
while Module32Next(snapshot, &mut entry).is_ok() {
|
||||
let name = CStr::from_ptr(&entry.szModule as *const _ as *const _)
|
||||
.to_string_lossy()
|
||||
.into_owned();
|
||||
|
||||
if name == module_name {
|
||||
return Module::new(self, entry.modBaseAddr as usize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err(Error::ModuleNotFound)
|
||||
}
|
||||
|
||||
pub fn read_memory_raw(&self, address: usize, buffer: *mut c_void, size: usize) -> Result<()> {
|
||||
unsafe {
|
||||
ReadProcessMemory(
|
||||
self.process_handle,
|
||||
address as *const _,
|
||||
buffer,
|
||||
size,
|
||||
Some(ptr::null_mut()),
|
||||
)
|
||||
}
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
pub fn write_memory_raw(
|
||||
&self,
|
||||
address: usize,
|
||||
buffer: *const c_void,
|
||||
size: usize,
|
||||
) -> Result<()> {
|
||||
unsafe {
|
||||
WriteProcessMemory(
|
||||
self.process_handle,
|
||||
address as *const _,
|
||||
buffer,
|
||||
size,
|
||||
Some(ptr::null_mut()),
|
||||
)
|
||||
}
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
pub fn read_memory<T>(&self, address: usize) -> Result<T> {
|
||||
let mut buffer: T = unsafe { mem::zeroed() };
|
||||
|
||||
self.read_memory_raw(
|
||||
address,
|
||||
&mut buffer as *const _ as *mut _,
|
||||
mem::size_of::<T>(),
|
||||
)?;
|
||||
|
||||
Ok(buffer)
|
||||
}
|
||||
|
||||
pub fn write_memory<T>(&self, address: usize, value: T) -> Result<()> {
|
||||
self.write_memory_raw(address, &value as *const _ as *const _, mem::size_of::<T>())
|
||||
}
|
||||
|
||||
pub fn read_string(&self, address: usize, length: usize) -> Result<String> {
|
||||
let mut buffer: Vec<u8> = vec![0; length];
|
||||
|
||||
self.read_memory_raw(address, buffer.as_mut_ptr() as *mut _, length)?;
|
||||
|
||||
if let Some(end) = buffer.iter().position(|&x| x == 0) {
|
||||
buffer.truncate(end);
|
||||
}
|
||||
|
||||
Ok(String::from_utf8(buffer)?)
|
||||
}
|
||||
|
||||
pub fn resolve_jmp(&self, address: usize) -> Result<usize> {
|
||||
let displacement = self.read_memory::<i32>(address + 0x1)?;
|
||||
|
||||
Ok((address + 0x5) + displacement as usize)
|
||||
}
|
||||
|
||||
pub fn resolve_relative(&self, address: usize) -> Result<usize> {
|
||||
let displacement = self.read_memory::<i32>(address + 0x3)?;
|
||||
|
||||
Ok((address + 0x7) + displacement as usize)
|
||||
}
|
||||
|
||||
fn get_process_id_by_name(process_name: &str) -> Result<u32> {
|
||||
let snapshot = unsafe { CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) }?;
|
||||
|
||||
let mut entry = PROCESSENTRY32 {
|
||||
dwSize: mem::size_of::<PROCESSENTRY32>() as u32,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
unsafe {
|
||||
Process32First(snapshot, &mut entry)?;
|
||||
|
||||
while Process32Next(snapshot, &mut entry).is_ok() {
|
||||
let name = CStr::from_ptr(&entry.szExeFile as *const _ as *const _)
|
||||
.to_string_lossy()
|
||||
.into_owned();
|
||||
|
||||
if name == process_name {
|
||||
return Ok(entry.th32ProcessID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err(Error::ProcessNotFound)
|
||||
}
|
||||
|
||||
fn pattern_to_bytes(pattern: &str) -> Vec<i32> {
|
||||
let mut bytes = Vec::new();
|
||||
|
||||
let chars: Vec<char> = pattern.chars().collect();
|
||||
|
||||
let mut i = 0;
|
||||
|
||||
while i < chars.len() {
|
||||
if chars[i] == ' ' {
|
||||
i += 1;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if chars[i] == '?' {
|
||||
bytes.push(-1);
|
||||
|
||||
i += 1;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if i + 1 < chars.len() {
|
||||
let value = i32::from_str_radix(&pattern[i..i + 2], 16);
|
||||
|
||||
match value {
|
||||
Ok(v) => bytes.push(v),
|
||||
Err(_) => {}
|
||||
}
|
||||
|
||||
i += 1;
|
||||
}
|
||||
|
||||
i += 1;
|
||||
}
|
||||
|
||||
bytes
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Process {
|
||||
fn drop(&mut self) {
|
||||
if !self.process_handle.is_invalid() {
|
||||
unsafe { CloseHandle(self.process_handle).unwrap() }
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user