Rewrote project in Rust

This commit is contained in:
a2x
2023-09-26 00:46:10 +10:00
parent a8d3318d94
commit 369ebcf238
136 changed files with 47374 additions and 47187 deletions

5
src/remote/mod.rs Normal file
View 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
View 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
View 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() }
}
}
}