This commit is contained in:
Albert24GG 2024-03-06 10:54:43 +02:00 committed by GitHub
commit b1d6187874
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 498 additions and 34 deletions

View File

@ -30,5 +30,8 @@ features = [
"Win32_System_Threading",
]
[target.'cfg(target_os = "linux")'.dependencies]
procfs = "0.16.0"
[profile.release]
strip = true

132
config_linux.json Normal file
View File

@ -0,0 +1,132 @@
{
"signatures": [
{
"name": "dwBuildNumber",
"module": "libengine2.so",
"pattern": "89 15 ? ? ? ? 48 83 ? ? 48 8D 3D ? ? ? ?",
"operations": [
{
"type": "rip",
"offset": 2,
"length": 6
}
]
},
{
"name": "dwNetworkGameClient_deltaTick",
"module": "libengine2.so",
"pattern": "89 83 ? ? ? ? B8 01",
"operations": [
{
"type": "slice",
"start": 2,
"end": 4
}
]
},
{
"name": "dwEntityList",
"module": "libclient.so",
"pattern": "48 8B 3D ? ? ? ? 44 89 E3 45 89 E6 C1 ? 0E",
"operations": [
{
"type": "rip"
}
]
},
{
"name": "dwForceAttack",
"module": "libclient.so",
"pattern": "4c 8d ? ? ? ? ? 4d 89 ? 48 89 ? ? ? ? ? eb",
"operations": [
{
"type": "rip"
},
{
"type": "add",
"value": 48
}
]
},
{
"name": "dwForceAttack2",
"module": "libclient.so",
"pattern": "4c 8d ? ? ? ? ? 48 8d ? ? ? ? ? 48 89 ? ? ? ? ? e8 ? ? ? ? 49 ? ? ? 31 d2 48 c7 ? ? ? ? ? ? 08",
"operations": [
{
"type": "rip"
},
{
"type": "add",
"value": 48
}
]
},
{
"name": "dwGameEntitySystem",
"module": "libclient.so",
"pattern": "48 89 3d ? ? ? ? e9 ? ? ? ? 55",
"operations": [
{
"type": "rip"
}
]
},
{
"name": "dwGameEntitySystem_getHighestEntityIndex",
"module": "libclient.so",
"pattern": "8b 87 ? ? ? ? c3 66 ? ? ? ? ? ? ? ? 8b 97",
"operations": [
{
"type": "slice",
"start": 2,
"end": 4
}
]
},
{
"name": "dwGameRules",
"module": "libclient.so",
"pattern": "48 89 3d ? ? ? ? 8b",
"operations": [
{
"type": "rip"
}
]
},
{
"name": "dwLocalPlayerController",
"module": "libclient.so",
"pattern": "48 8B 15 ? ? ? ? 31 C0 48 85 D2 74 5C",
"operations": [
{
"type": "rip"
}
]
},
{
"name": "dwLocalPlayerPawn",
"module": "libclient.so",
"pattern": "48 8D 05 ? ? ? ? C3 ? ? ? 00 00 00 00 00 C7 47 40",
"operations": [
{
"type": "rip"
},
{
"type": "add",
"value": 56
}
]
},
{
"name": "dwViewMatrix",
"module": "libclient.so",
"pattern": "4c 8d 05 ? ? ? ? 48 ? ? 48 8d ? ? ? ? ? e8 ? ? ? ? 8b",
"operations": [
{
"type": "rip"
}
]
}
]
}

View File

@ -39,3 +39,42 @@ pub struct Signature {
pub pattern: String,
pub operations: Vec<Operation>,
}
#[derive(Debug)]
pub struct SchemaSystemConfig {
pub module_name: &'static str,
pub pattern: &'static str,
pub type_scope_size_offset: usize,
pub type_scope_data_offset: usize,
pub declared_classes_offset: usize,
}
#[cfg(target_os = "windows")]
pub const SCHEMA_CONF: SchemaSystemConfig = SchemaSystemConfig {
module_name: "schemasystem.dll",
pattern: "48 8D 0D ? ? ? ? E9 ? ? ? ? CC CC CC CC 48 8D 0D ? ? ? ? E9 ? ? ? ? CC CC CC CC 48 83 EC 28",
type_scope_size_offset: 0x190,
type_scope_data_offset: 0x198,
declared_classes_offset: 0x5B8,
};
#[cfg(target_os = "linux")]
pub const SCHEMA_CONF: SchemaSystemConfig = SchemaSystemConfig {
module_name: "libschemasystem.so",
pattern: "48 8D 05 ? ? ? ? c3 ? ? ? 00 00 00 00 00 48 8d 05 ? ? ? ? c3 ? ? ? 00 00 00 00 00 48 ? ? ? c3",
type_scope_size_offset: 0x1f8,
type_scope_data_offset: 0x200,
declared_classes_offset: 0x620,
};
#[cfg(target_os = "windows")]
pub const PROC_NAME: &str = "cs2.exe";
#[cfg(target_os = "linux")]
pub const PROC_NAME: &str = "cs2";
#[cfg(target_os = "windows")]
pub const OFFSETS_CONF: &str = "config.json";
#[cfg(target_os = "linux")]
pub const OFFSETS_CONF: &str = "config_linux.json";

View File

@ -58,8 +58,21 @@ pub fn dump_interfaces(
if let Some(create_interface_export) = module.export_by_name("CreateInterface") {
info!("Dumping interfaces in <blue>{}</>...", module.name);
let create_interface_address =
process.resolve_rip(create_interface_export, None, None)?;
let create_interface_address;
#[cfg(target_os = "windows")]
{
create_interface_address =
process.resolve_rip(create_interface_export, None, None)?;
}
#[cfg(target_os = "linux")]
{
let create_interface_fn =
process.resolve_jmp(create_interface_export, None, None)?;
create_interface_address =
process.resolve_rip(create_interface_fn + 0x10, None, None)?;
}
let mut node = process.read_memory::<*mut InterfaceNode>(create_interface_address)?;

View File

@ -7,8 +7,8 @@ use simplelog::{debug, error, info};
use super::{generate_files, Entries, Entry};
use crate::builder::FileBuilderEnum;
use crate::config::Config;
use crate::config::Operation::*;
use crate::config::{self, Config};
use crate::os::Process;
pub fn dump_offsets(
@ -17,7 +17,7 @@ pub fn dump_offsets(
file_path: &str,
indent: usize,
) -> Result<()> {
let file = File::open("config.json")?;
let file = File::open(config::OFFSETS_CONF)?;
let config: Config = serde_json::from_reader(file)?;
@ -149,8 +149,6 @@ mod tests {
fn setup() -> Result<Process> {
let mut process = Process::new("cs2.exe")?;
process.initialize()?;
Ok(process)
}

View File

@ -86,14 +86,14 @@ fn main() -> Result<()> {
TermLogger::init(log_level, config, TerminalMode::Mixed, ColorChoice::Auto)?;
if !Path::new("config.json").exists() {
bail!("Missing config.json file");
if !Path::new(config::OFFSETS_CONF).exists() {
bail!("Missing {} file", config::OFFSETS_CONF);
}
// Create the output directory if it doesn't exist.
fs::create_dir_all(&output)?;
let mut process = Process::new("cs2.exe")?;
let mut process = Process::new(config::PROC_NAME)?;
let now = Instant::now();

View File

@ -1,3 +1,6 @@
#[cfg(target_os = "linux")]
pub use module::ModuleEntry;
pub use module::Module;
pub use process::Process;

View File

@ -1,18 +1,43 @@
use anyhow::Result;
use goblin::pe::export::Export;
use goblin::pe::import::Import;
use goblin::pe::options::ParseOptions;
use goblin::pe::section_table::SectionTable;
use goblin::pe::PE;
#[cfg(target_os = "windows")]
use goblin::pe::{
export::Export, import::Import, options::ParseOptions, section_table::SectionTable, PE,
};
#[cfg(target_os = "linux")]
use goblin::elf::{sym, Elf, SectionHeader};
#[cfg(target_os = "linux")]
use std::path::PathBuf;
#[cfg(target_os = "linux")]
#[derive(Debug)]
pub struct ModuleEntry {
pub path: PathBuf,
pub start_addr: usize,
pub data: Vec<u8>,
}
/// Represents a module loaded in a Windows process.
pub struct Module<'a> {
/// The name of the module.
pub name: &'a str,
/// A reference to a slice of bytes containing the module data.
#[cfg(target_os = "windows")]
pub data: &'a [u8],
#[cfg(target_os = "linux")]
pub module_info: &'a ModuleEntry,
#[cfg(target_os = "windows")]
/// The PE file format representation of the module.
pub pe: PE<'a>,
#[cfg(target_os = "linux")]
pub elf: Elf<'a>,
}
impl<'a> Module<'a> {
#[cfg(target_os = "windows")]
pub fn parse(name: &'a str, data: &'a [u8]) -> Result<Self> {
let pe = PE::parse_with_opts(
data,
@ -25,22 +50,67 @@ impl<'a> Module<'a> {
Ok(Self { name, data, pe })
}
// parse the elf
#[cfg(target_os = "linux")]
pub fn parse(name: &'a str, module_entry: &'a ModuleEntry) -> Result<Self> {
let elf = Elf::parse(&module_entry.data)?;
Ok(Self {
name,
module_info: module_entry,
elf,
})
}
#[inline]
#[cfg(target_os = "windows")]
pub fn base(&self) -> usize {
self.pe.image_base
}
#[inline]
#[cfg(target_os = "linux")]
pub fn base(&self) -> usize {
self.module_info.start_addr
}
#[inline]
#[cfg(target_os = "windows")]
pub fn exports(&self) -> &[Export] {
&self.pe.exports
}
#[inline]
#[cfg(target_os = "linux")]
pub fn exports(&self) -> Vec<sym::Sym> {
let exports: Vec<sym::Sym> = self
.elf
.dynsyms
.iter()
.filter(|sym| sym.st_bind() == sym::STB_GLOBAL || sym.st_bind() == sym::STB_WEAK)
.collect();
exports
}
#[inline]
#[cfg(target_os = "windows")]
pub fn imports(&self) -> &[Import] {
&self.pe.imports
}
#[inline]
#[cfg(target_os = "linux")]
pub fn imports(&self) -> Vec<sym::Sym> {
let imports: Vec<sym::Sym> = self
.elf
.dynsyms
.iter()
.filter(|sym| sym.is_import())
.collect();
imports
}
#[inline]
#[cfg(target_os = "windows")]
pub fn export_by_name(&self, name: &str) -> Option<usize> {
self.pe
.exports
@ -50,6 +120,21 @@ impl<'a> Module<'a> {
}
#[inline]
#[cfg(target_os = "linux")]
pub fn export_by_name(&self, name: &str) -> Option<usize> {
let base_addr: usize = self.base();
self.elf
.dynsyms
.iter()
.find(|sym| {
(sym.st_bind() == sym::STB_GLOBAL || sym.st_bind() == sym::STB_WEAK)
&& self.elf.dynstrtab.get_at(sym.st_name) == Some(name)
})
.map(|sym| (base_addr as u64 + sym.st_value) as usize)
}
#[inline]
#[cfg(target_os = "windows")]
pub fn import_by_name(&self, name: &str) -> Option<usize> {
self.pe
.imports
@ -59,11 +144,30 @@ impl<'a> Module<'a> {
}
#[inline]
#[cfg(target_os = "linux")]
pub fn get_import_by_name(&self, name: &str) -> Option<usize> {
let base_addr: usize = self.base().into();
self.elf
.dynsyms
.iter()
.find(|sym| sym.is_import() && self.elf.dynstrtab.get_at(sym.st_name) == Some(name))
.map(|sym| (base_addr as u64 + sym.st_value) as usize)
}
#[inline]
#[cfg(target_os = "windows")]
pub fn sections(&self) -> &[SectionTable] {
&self.pe.sections
}
#[inline]
#[cfg(target_os = "linux")]
pub fn sections(&self) -> &[SectionHeader] {
self.elf.section_headers.as_slice()
}
#[inline]
#[cfg(target_os = "windows")]
pub fn size(&self) -> u32 {
self.pe
.header

View File

@ -1,25 +1,54 @@
use std::collections::HashMap;
use std::ffi::{c_void, CStr};
use std::mem;
use std::ptr;
use super::Module;
#[cfg(target_os = "linux")]
use super::ModuleEntry;
use anyhow::{bail, Result};
use windows::Win32::Foundation::{CloseHandle, HANDLE};
use windows::Win32::System::Diagnostics::Debug::ReadProcessMemory;
use windows::Win32::System::Diagnostics::ToolHelp::*;
use windows::Win32::System::Threading::{OpenProcess, PROCESS_ALL_ACCESS};
use std::collections::HashMap;
use std::ffi::c_void;
use std::mem;
use super::Module;
#[cfg(target_os = "windows")]
use std::ffi::CStr;
#[cfg(target_os = "windows")]
use std::ptr;
#[cfg(target_os = "windows")]
use windows::Win32::{
Foundation::{CloseHandle, HANDLE},
System::Diagnostics::Debug::ReadProcessMemory,
System::Diagnostics::ToolHelp::*,
System::Threading::{OpenProcess, PROCESS_ALL_ACCESS},
};
#[cfg(target_os = "linux")]
use procfs::process::{self, all_processes};
#[cfg(target_os = "linux")]
use std::fs::File;
#[cfg(target_os = "linux")]
use std::io::{Read, Seek, SeekFrom};
#[cfg(target_os = "linux")]
use std::path::{Path, PathBuf};
/// Represents a Windows process.
#[derive(Debug)]
pub struct Process {
/// ID of the process.
id: u32,
#[cfg(target_os = "windows")]
/// Handle to the process.
handle: HANDLE,
/// A HashMap containing the name of each module and its corresponding raw data.
#[cfg(target_os = "windows")]
modules: HashMap<String, Vec<u8>>,
#[cfg(target_os = "linux")]
modules: HashMap<String, ModuleEntry>,
}
impl Process {
#[cfg(target_os = "windows")]
pub fn new(name: &str) -> Result<Self> {
let id = Self::get_process_id_by_name(name)?;
@ -30,12 +59,21 @@ impl Process {
handle,
modules: HashMap::new(),
};
process.parse_loaded_modules()?;
Ok(process)
}
#[cfg(target_os = "linux")]
pub fn new(name: &str) -> Result<Self> {
let id = Self::get_process_id_by_name(name)?;
let mut process = Self {
id,
modules: HashMap::new(),
};
process.parse_loaded_modules()?;
Ok(process)
}
#[cfg(target_os = "windows")]
pub fn find_pattern(&self, module_name: &str, pattern: &str) -> Option<usize> {
let module = self.get_module_by_name(module_name)?;
@ -54,6 +92,30 @@ impl Process {
None
}
#[cfg(target_os = "linux")]
pub fn find_pattern(&self, module_name: &str, pattern: &str) -> Option<usize> {
let module = self.get_module_by_name(module_name)?;
let pattern_bytes = Self::pattern_to_bytes(pattern);
for (i, window) in module
.module_info
.data
.windows(pattern_bytes.len())
.enumerate()
{
if window
.iter()
.zip(&pattern_bytes)
.all(|(&x, &y)| x == y as u8 || y == -1)
{
return Some(module.base() + i);
}
}
None
}
pub fn get_module_by_name<'a>(&'a self, name: &'a str) -> Option<Module<'a>> {
self.modules
.get(name)
@ -82,6 +144,7 @@ impl Process {
Ok(buffer)
}
#[cfg(target_os = "windows")]
pub fn read_memory_raw(&self, address: usize, buffer: *mut c_void, size: usize) -> Result<()> {
unsafe {
ReadProcessMemory(
@ -95,6 +158,21 @@ impl Process {
.map_err(|e| e.into())
}
#[cfg(target_os = "linux")]
pub fn read_memory_raw(&self, address: usize, buffer: *mut c_void, size: usize) -> Result<()> {
let proc_mem_path = format!("/proc/{}/mem", self.id);
let mut mem_file = File::open(proc_mem_path)?;
// Go to the start address
mem_file.seek(SeekFrom::Start(address as u64))?;
let buffer_slice = unsafe { std::slice::from_raw_parts_mut(buffer as *mut u8, size) };
// Try to read the data
mem_file.read_exact(buffer_slice)?;
Ok(())
}
pub fn read_string(&self, address: usize) -> Result<String> {
let mut buffer = Vec::new();
@ -129,7 +207,12 @@ impl Process {
// The displacement value can be negative.
let displacement = self.read_memory::<i32>(address + offset.unwrap_or(0x1))?;
Ok((address + displacement as usize) + length.unwrap_or(0x5))
let final_address = if displacement.is_negative() {
address - displacement.wrapping_abs() as usize
} else {
address + displacement as usize
} + length.unwrap_or(0x5);
Ok(final_address)
}
pub fn resolve_rip(
@ -141,9 +224,15 @@ impl Process {
// The displacement value can be negative.
let displacement = self.read_memory::<i32>(address + offset.unwrap_or(0x3))?;
Ok((address + displacement as usize) + length.unwrap_or(0x7))
let final_address = if displacement.is_negative() {
address - displacement.wrapping_abs() as usize
} else {
address + displacement as usize
} + length.unwrap_or(0x7);
Ok(final_address)
}
#[cfg(target_os = "windows")]
fn get_process_id_by_name(process_name: &str) -> Result<u32> {
let snapshot = unsafe { CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) }?;
@ -167,6 +256,27 @@ impl Process {
bail!("Process not found: {}", process_name)
}
#[cfg(target_os = "linux")]
fn get_process_id_by_name(process_name: &str) -> Result<u32> {
use std::io::{BufRead, BufReader};
for process_iter in all_processes()? {
let Ok(process) = process_iter else { continue };
let comm_path = format!("/proc/{}/comm", process.pid());
if let Ok(comm_file) = File::open(Path::new(&comm_path)) {
let mut comm = String::new();
if BufReader::new(comm_file).read_line(&mut comm).is_ok() {
comm.pop();
if comm == process_name && process.pid() > 0 {
return Ok(process.pid() as u32);
}
}
}
}
bail!("Process not found: {}", process_name);
}
#[cfg(target_os = "windows")]
fn parse_loaded_modules(&mut self) -> Result<()> {
let snapshot = unsafe { CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, self.id) }?;
@ -196,6 +306,58 @@ impl Process {
Ok(())
}
#[cfg(target_os = "linux")]
fn parse_loaded_modules(&mut self) -> Result<()> {
let process = process::Process::new(self.id as i32)?;
let mut modules_info: HashMap<String, ((u64, u64), PathBuf)> = HashMap::new();
for mmap in process.maps()? {
let module_path = match mmap.pathname {
process::MMapPath::Path(path) => path,
_ => continue,
};
let get_module_name = |path: &PathBuf| -> Option<String> {
path.file_name()
.and_then(|name| name.to_str())
.filter(|name| name.starts_with("lib") && name.ends_with(".so"))
.map(|name| name.to_string())
};
if let Some(module_name) = get_module_name(&module_path) {
let module_entry = modules_info
.entry(module_name)
.or_insert_with(|| (mmap.address, module_path));
module_entry.0 = (
std::cmp::min(mmap.address.0, module_entry.0 .0),
std::cmp::max(mmap.address.1, module_entry.0 .1),
);
}
}
for (module_name, (address_space, path)) in modules_info.into_iter() {
let (start, end) = address_space;
let read_elf_file = |path: &PathBuf| -> Result<Vec<u8>> {
let mut file = File::open(path)?;
let mut data = Vec::new();
file.read_to_end(&mut data)?;
Ok(data)
};
if let Ok(data) = read_elf_file(&path) {
self.modules.insert(
module_name,
ModuleEntry {
path: path.clone(),
start_addr: start as usize,
data: data,
},
);
}
}
Ok(())
}
fn pattern_to_bytes(pattern: &str) -> Vec<i32> {
pattern
.split_whitespace()
@ -210,6 +372,7 @@ impl Process {
}
}
#[cfg(target_os = "windows")]
impl Drop for Process {
fn drop(&mut self) {
if !self.handle.is_invalid() {

View File

@ -6,6 +6,8 @@ use super::SchemaSystemTypeScope;
use crate::os::Process;
use crate::config::SCHEMA_CONF;
pub struct SchemaSystem<'a> {
process: &'a Process,
address: usize,
@ -13,10 +15,9 @@ pub struct SchemaSystem<'a> {
impl<'a> SchemaSystem<'a> {
pub fn new(process: &'a Process) -> Result<Self> {
let mut address = process.find_pattern(
"schemasystem.dll",
"48 8D 0D ? ? ? ? E9 ? ? ? ? CC CC CC CC 48 8D 0D ? ? ? ? E9 ? ? ? ? CC CC CC CC 48 83 EC 28"
).expect("unable to find schema system pattern");
let mut address = process
.find_pattern(SCHEMA_CONF.module_name, SCHEMA_CONF.pattern)
.expect("unable to find schema system pattern");
address = process.resolve_rip(address, None, None)?;
@ -24,13 +25,17 @@ impl<'a> SchemaSystem<'a> {
}
pub fn type_scopes(&self) -> Result<Vec<SchemaSystemTypeScope>> {
let size = self.process.read_memory::<u32>(self.address + 0x190)?;
let size = self
.process
.read_memory::<u32>(self.address + SCHEMA_CONF.type_scope_size_offset)?;
if size == 0 {
bail!("no type scopes found");
}
let data = self.process.read_memory::<usize>(self.address + 0x198)?;
let data = self
.process
.read_memory::<usize>(self.address + SCHEMA_CONF.type_scope_data_offset)?;
let mut addresses = vec![0; size as usize];

View File

@ -4,6 +4,8 @@ use super::{SchemaClassInfo, SchemaTypeDeclaredClass, UtlTsHash};
use crate::os::Process;
use crate::config::SCHEMA_CONF;
pub struct SchemaSystemTypeScope<'a> {
process: &'a Process,
address: usize,
@ -17,7 +19,9 @@ impl<'a> SchemaSystemTypeScope<'a> {
pub fn classes(&self) -> Result<Vec<SchemaClassInfo>> {
let declared_classes = self
.process
.read_memory::<UtlTsHash<*mut SchemaTypeDeclaredClass>>(self.address + 0x5B8)?;
.read_memory::<UtlTsHash<*mut SchemaTypeDeclaredClass>>(
self.address + SCHEMA_CONF.declared_classes_offset,
)?;
let classes: Vec<SchemaClassInfo> = declared_classes
.elements(self.process)?