Some improvements

This commit is contained in:
a2x
2023-10-05 19:24:14 +10:00
parent 85e6224275
commit d392a319ac
79 changed files with 653 additions and 426 deletions

View File

@@ -2,9 +2,10 @@ use std::io::{Result, Write};
use super::FileBuilder;
pub struct CppBuilder;
#[derive(Debug, PartialEq)]
pub struct CppFileBuilder;
impl FileBuilder for CppBuilder {
impl FileBuilder for CppFileBuilder {
fn extension(&mut self) -> &str {
"hpp"
}

View File

@@ -2,9 +2,10 @@ use std::io::{Result, Write};
use super::FileBuilder;
pub struct CSharpBuilder;
#[derive(Debug, PartialEq)]
pub struct CSharpFileBuilder;
impl FileBuilder for CSharpBuilder {
impl FileBuilder for CSharpFileBuilder {
fn extension(&mut self) -> &str {
"cs"
}

View File

@@ -1,15 +1,24 @@
use std::io::{Result, Write};
use serde_json::{json, Value};
use serde_json::{json, Map, Value};
use super::FileBuilder;
#[derive(Default)]
#[derive(Debug, PartialEq)]
pub struct JsonFileBuilder {
json: Value,
namespace: String,
}
impl Default for JsonFileBuilder {
fn default() -> Self {
Self {
json: Value::Object(Map::new()),
namespace: String::new(),
}
}
}
impl FileBuilder for JsonFileBuilder {
fn extension(&mut self) -> &str {
"json"
@@ -32,13 +41,11 @@ impl FileBuilder for JsonFileBuilder {
value: usize,
_comment: Option<&str>,
) -> Result<()> {
if let Some(json_as_map) = self.json.as_object_mut() {
let namespace_entry = json_as_map
.entry(self.namespace.clone())
.or_insert_with(|| json!({}));
if let Some(map) = self.json.as_object_mut() {
let entry = map.entry(&self.namespace).or_insert_with(|| json!({}));
if let Some(namespace_object) = namespace_entry.as_object_mut() {
namespace_object.insert(name.to_string(), json!(value));
if let Some(object) = entry.as_object_mut() {
object.insert(name.to_string(), json!(value));
}
}
@@ -47,12 +54,7 @@ impl FileBuilder for JsonFileBuilder {
fn write_closure(&mut self, output: &mut dyn Write, eof: bool) -> Result<()> {
if eof {
write!(
output,
"{}",
serde_json::to_string_pretty(&self.json).unwrap()
)
.unwrap();
write!(output, "{}", serde_json::to_string_pretty(&self.json)?)?;
self.json = json!({});
}

View File

@@ -1,7 +1,7 @@
pub use std::io::{Result, Write};
pub use cpp_file_builder::CppBuilder;
pub use csharp_file_builder::CSharpBuilder;
pub use cpp_file_builder::CppFileBuilder;
pub use csharp_file_builder::CSharpFileBuilder;
pub use file_builder::FileBuilder;
pub use json_file_builder::JsonFileBuilder;
pub use rust_file_builder::RustFileBuilder;
@@ -12,39 +12,25 @@ pub mod file_builder;
pub mod json_file_builder;
pub mod rust_file_builder;
#[derive(Debug, PartialEq)]
pub enum FileBuilderEnum {
CppBuilder(CppBuilder),
CSharpBuilder(CSharpBuilder),
CppFileBuilder(CppFileBuilder),
CSharpFileBuilder(CSharpFileBuilder),
JsonFileBuilder(JsonFileBuilder),
RustFileBuilder(RustFileBuilder),
}
impl FileBuilder for FileBuilderEnum {
fn extension(&mut self) -> &str {
match self {
FileBuilderEnum::CppBuilder(builder) => builder.extension(),
FileBuilderEnum::CSharpBuilder(builder) => builder.extension(),
FileBuilderEnum::JsonFileBuilder(builder) => builder.extension(),
FileBuilderEnum::RustFileBuilder(builder) => builder.extension(),
}
self.as_mut().extension()
}
fn write_top_level(&mut self, output: &mut dyn Write) -> Result<()> {
match self {
FileBuilderEnum::CppBuilder(builder) => builder.write_top_level(output),
FileBuilderEnum::CSharpBuilder(builder) => builder.write_top_level(output),
FileBuilderEnum::JsonFileBuilder(builder) => builder.write_top_level(output),
FileBuilderEnum::RustFileBuilder(builder) => builder.write_top_level(output),
}
self.as_mut().write_top_level(output)
}
fn write_namespace(&mut self, output: &mut dyn Write, name: &str) -> Result<()> {
match self {
FileBuilderEnum::CppBuilder(builder) => builder.write_namespace(output, name),
FileBuilderEnum::CSharpBuilder(builder) => builder.write_namespace(output, name),
FileBuilderEnum::JsonFileBuilder(builder) => builder.write_namespace(output, name),
FileBuilderEnum::RustFileBuilder(builder) => builder.write_namespace(output, name),
}
self.as_mut().write_namespace(output, name)
}
fn write_variable(
@@ -54,28 +40,21 @@ impl FileBuilder for FileBuilderEnum {
value: usize,
comment: Option<&str>,
) -> Result<()> {
match self {
FileBuilderEnum::CppBuilder(builder) => {
builder.write_variable(output, name, value, comment)
}
FileBuilderEnum::CSharpBuilder(builder) => {
builder.write_variable(output, name, value, comment)
}
FileBuilderEnum::JsonFileBuilder(builder) => {
builder.write_variable(output, name, value, comment)
}
FileBuilderEnum::RustFileBuilder(builder) => {
builder.write_variable(output, name, value, comment)
}
}
self.as_mut().write_variable(output, name, value, comment)
}
fn write_closure(&mut self, output: &mut dyn Write, eof: bool) -> Result<()> {
self.as_mut().write_closure(output, eof)
}
}
impl FileBuilderEnum {
fn as_mut(&mut self) -> &mut dyn FileBuilder {
match self {
FileBuilderEnum::CppBuilder(builder) => builder.write_closure(output, eof),
FileBuilderEnum::CSharpBuilder(builder) => builder.write_closure(output, eof),
FileBuilderEnum::JsonFileBuilder(builder) => builder.write_closure(output, eof),
FileBuilderEnum::RustFileBuilder(builder) => builder.write_closure(output, eof),
FileBuilderEnum::CppFileBuilder(builder) => builder,
FileBuilderEnum::CSharpFileBuilder(builder) => builder,
FileBuilderEnum::JsonFileBuilder(builder) => builder,
FileBuilderEnum::RustFileBuilder(builder) => builder,
}
}
}

View File

@@ -2,6 +2,7 @@ use std::io::{Result, Write};
use super::FileBuilder;
#[derive(Debug, Default, PartialEq)]
pub struct RustFileBuilder;
impl FileBuilder for RustFileBuilder {

View File

@@ -3,12 +3,26 @@ use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize, Serialize)]
#[serde(tag = "type", rename_all = "camelCase")]
pub enum Operation {
Add { value: usize },
Dereference { times: Option<u16> },
Jmp,
Offset { position: usize },
RipRelative,
Subtract { value: usize },
Add {
value: usize,
},
Dereference {
times: Option<u16>,
},
Jmp {
offset: Option<usize>,
length: Option<usize>,
},
Offset {
offset: usize,
},
RipRelative {
offset: Option<usize>,
length: Option<usize>,
},
Subtract {
value: usize,
},
}
#[derive(Debug, Deserialize, Serialize)]

View File

@@ -3,7 +3,7 @@ use crate::dumpers::Entry;
use crate::error::Result;
use crate::remote::Process;
use super::{generate_file, Entries};
use super::{generate_files, Entries};
pub fn dump_interfaces(builders: &mut Vec<FileBuilderEnum>, process: &Process) -> Result<()> {
let module_names = process.get_loaded_modules()?;
@@ -16,7 +16,8 @@ pub fn dump_interfaces(builders: &mut Vec<FileBuilderEnum>, process: &Process) -
log::info!("Dumping interfaces in {}...", module_name);
if let Some(create_interface_export) = module.export("CreateInterface") {
let create_interface_address = process.resolve_rip(create_interface_export.va)?;
let create_interface_address =
process.resolve_rip(create_interface_export.va, None, None)?;
let mut interface_registry_ptr = process
.read_memory::<usize>(create_interface_address)
@@ -53,9 +54,7 @@ pub fn dump_interfaces(builders: &mut Vec<FileBuilderEnum>, process: &Process) -
}
}
for builder in builders {
generate_file(builder, "interfaces", &entries)?;
}
generate_files(builders, &entries, "interfaces")?;
Ok(())
}

View File

@@ -23,9 +23,13 @@ pub type Entries = BTreeMap<String, Vec<Entry>>;
pub fn generate_file(
builder: &mut FileBuilderEnum,
file_name: &str,
entries: &Entries,
file_name: &str,
) -> Result<()> {
if entries.is_empty() {
return Ok(());
}
let file_path = format!("generated/{}.{}", file_name, builder.extension());
let mut file = File::create(file_path)?;
@@ -59,3 +63,15 @@ pub fn generate_file(
Ok(())
}
pub fn generate_files(
builders: &mut Vec<FileBuilderEnum>,
entries: &Entries,
file_name: &str,
) -> Result<()> {
for builder in builders {
generate_file(builder, &entries, file_name)?;
}
Ok(())
}

View File

@@ -1,12 +1,13 @@
use std::fs::File;
use crate::builder::FileBuilderEnum;
use crate::config::{Config, Operation};
use crate::config::{Config, Operation::*};
use crate::dumpers::Entry;
use crate::error::{Error, Result};
use crate::mem::Address;
use crate::remote::Process;
use super::{generate_file, Entries};
use super::{generate_files, Entries};
pub fn dump_offsets(builders: &mut Vec<FileBuilderEnum>, process: &Process) -> Result<()> {
let file = File::open("config.json")?;
@@ -20,30 +21,33 @@ pub fn dump_offsets(builders: &mut Vec<FileBuilderEnum>, process: &Process) -> R
for signature in config.signatures {
let module = process.get_module_by_name(&signature.module)?;
let mut address = process.find_pattern(&signature.module, &signature.pattern)?;
let mut address =
Address::from(process.find_pattern(&signature.module, &signature.pattern)?);
let mut offset: Option<u16> = None;
let mut offset: Option<u32> = None;
for operation in signature.operations {
match operation {
Operation::Add { value } => {
Add { value } => {
address += value;
}
Operation::Dereference { times } => {
Dereference { times } => {
for _ in 0..times.unwrap_or(1) {
address = process.read_memory::<usize>(address)?;
address = process.read_memory::<usize>(address.0)?.into();
}
}
Operation::Jmp => {
address = process.resolve_jmp(address)?;
Jmp { offset, length } => {
address = process.resolve_jmp(address.0, offset, length)?.into();
}
Operation::Offset { position } => {
offset = Some(process.read_memory::<u16>(address + position)?);
Offset {
offset: start_offset,
} => {
offset = Some(process.read_memory::<u32>(address.0 + start_offset)?);
}
Operation::RipRelative => {
address = process.resolve_rip(address)?;
RipRelative { offset, length } => {
address = process.resolve_rip(address.0, offset, length)?.into();
}
Operation::Subtract { value } => {
Subtract { value } => {
address -= value;
}
}
@@ -62,7 +66,7 @@ pub fn dump_offsets(builders: &mut Vec<FileBuilderEnum>, process: &Process) -> R
address - module.address()
);
(signature.name, address - module.address())
(signature.name, address.0 - module.address())
};
entries
@@ -75,9 +79,7 @@ pub fn dump_offsets(builders: &mut Vec<FileBuilderEnum>, process: &Process) -> R
});
}
for builder in builders {
generate_file(builder, "offsets", &entries)?;
}
generate_files(builders, &entries, "offsets")?;
Ok(())
}

View File

@@ -1,36 +1,14 @@
use std::collections::HashMap;
use regex::Regex;
use crate::builder::FileBuilderEnum;
use crate::dumpers::Entry;
use crate::error::Result;
use crate::remote::Process;
use crate::sdk::SchemaSystem;
use super::{generate_file, Entries};
use super::{generate_files, Entries};
pub fn dump_schemas(builders: &mut Vec<FileBuilderEnum>, process: &Process) -> Result<()> {
let schema_system = SchemaSystem::new(&process)?;
let type_map = HashMap::from([
("uint8", "uint8_t"),
("uint16", "uint16_t"),
("uint32", "uint32_t"),
("uint64", "uint64_t"),
("int8", "int8_t"),
("int16", "int16_t"),
("int32", "int32_t"),
("int64", "int64_t"),
("float32", "float"),
("float64", "double"),
]);
let regex_map: HashMap<String, Regex> = type_map
.iter()
.map(|(k, _v)| ((k.to_string()), Regex::new(&format!(r"\b{}\b", k)).unwrap()))
.collect();
for type_scope in schema_system.type_scopes()? {
let module_name = type_scope.module_name()?;
@@ -44,20 +22,13 @@ pub fn dump_schemas(builders: &mut Vec<FileBuilderEnum>, process: &Process) -> R
for field in class.fields()? {
let field_name = field.name()?;
let field_offset = field.offset()?;
let mut type_name = field.r#type()?.name()?.replace(" ", "");
for k in type_map.keys() {
let re = &regex_map[*k];
type_name = re.replace_all(&type_name, type_map[*k]).to_string();
}
let field_type_name = field.r#type()?.name()?;
log::debug!(
" └─ {} = {:#X} // {}",
field_name,
field_offset,
type_name
field_type_name
);
entries
@@ -66,16 +37,12 @@ pub fn dump_schemas(builders: &mut Vec<FileBuilderEnum>, process: &Process) -> R
.push(Entry {
name: field_name,
value: field_offset as usize,
comment: Some(type_name),
comment: Some(field_type_name),
});
}
}
if !entries.is_empty() {
for builder in builders.iter_mut() {
generate_file(builder, &module_name, &entries)?;
}
}
generate_files(builders, &entries, &module_name)?;
}
Ok(())

View File

@@ -16,15 +16,13 @@ mod builder;
mod config;
mod dumpers;
mod error;
mod mem;
mod remote;
mod sdk;
#[derive(Debug, Parser)]
#[command(author, version, about, long_about = None)]
struct Args {
#[arg(long)]
all: bool,
#[arg(short, long)]
interfaces: bool,
@@ -40,7 +38,6 @@ struct Args {
fn main() -> Result<()> {
let Args {
all,
interfaces,
offsets,
schemas,
@@ -66,13 +63,13 @@ fn main() -> Result<()> {
fs::create_dir_all("generated")?;
let mut builders: Vec<FileBuilderEnum> = vec![
FileBuilderEnum::CppBuilder(CppBuilder),
FileBuilderEnum::CSharpBuilder(CSharpBuilder),
FileBuilderEnum::CppFileBuilder(CppFileBuilder),
FileBuilderEnum::CSharpFileBuilder(CSharpFileBuilder),
FileBuilderEnum::JsonFileBuilder(JsonFileBuilder::default()),
FileBuilderEnum::RustFileBuilder(RustFileBuilder),
];
let all = all || !(interfaces || offsets || schemas);
let all = !(interfaces || offsets || schemas);
if schemas || all {
dump_schemas(&mut builders, &process)?;

104
src/mem/address.rs Normal file
View File

@@ -0,0 +1,104 @@
use std::fmt::{LowerHex, UpperHex};
use std::ops::{Add, AddAssign, Sub, SubAssign};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[repr(transparent)]
pub struct Address(pub usize);
impl Address {
pub fn add(&self, offset: usize) -> Self {
Self(self.0 + offset)
}
pub fn sub(&self, offset: usize) -> Self {
Self(self.0 - offset)
}
pub fn as_ptr<T>(&self) -> *const T {
self.0 as *const T
}
pub fn as_mut_ptr<T>(&self) -> *mut T {
self.0 as *mut T
}
}
impl From<usize> for Address {
fn from(value: usize) -> Self {
Self(value)
}
}
impl From<Address> for usize {
fn from(value: Address) -> Self {
value.0
}
}
impl Add<usize> for Address {
type Output = Self;
fn add(self, rhs: usize) -> Self::Output {
Self(self.0 + rhs)
}
}
impl Add<Address> for Address {
type Output = Self;
fn add(self, rhs: Address) -> Self::Output {
Self(self.0 + rhs.0)
}
}
impl AddAssign<usize> for Address {
fn add_assign(&mut self, rhs: usize) {
self.0 += rhs;
}
}
impl AddAssign<Address> for Address {
fn add_assign(&mut self, rhs: Address) {
self.0 += rhs.0;
}
}
impl Sub<usize> for Address {
type Output = Self;
fn sub(self, rhs: usize) -> Self::Output {
Self(self.0 - rhs)
}
}
impl Sub<Address> for Address {
type Output = Self;
fn sub(self, rhs: Address) -> Self::Output {
Self(self.0 - rhs.0)
}
}
impl SubAssign<usize> for Address {
fn sub_assign(&mut self, rhs: usize) {
self.0 -= rhs;
}
}
impl SubAssign<Address> for Address {
fn sub_assign(&mut self, rhs: Address) {
self.0 -= rhs.0;
}
}
impl UpperHex for Address {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:#X}", self.0)
}
}
impl LowerHex for Address {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:#x}", self.0)
}
}

3
src/mem/mod.rs Normal file
View File

@@ -0,0 +1,3 @@
pub use address::Address;
pub mod address;

View File

@@ -171,16 +171,26 @@ impl Process {
Ok(String::from_utf8(buffer)?)
}
pub fn resolve_jmp(&self, address: usize) -> Result<usize> {
let displacement = self.read_memory::<i32>(address + 0x1)?;
pub fn resolve_jmp(
&self,
address: usize,
offset: Option<usize>,
length: Option<usize>,
) -> Result<usize> {
let displacement = self.read_memory::<i32>(address + offset.unwrap_or(0x1))?;
Ok((address + 0x5) + displacement as usize)
Ok((address + length.unwrap_or(0x5)) + displacement as usize)
}
pub fn resolve_rip(&self, address: usize) -> Result<usize> {
let displacement = self.read_memory::<i32>(address + 0x3)?;
pub fn resolve_rip(
&self,
address: usize,
offset: Option<usize>,
length: Option<usize>,
) -> Result<usize> {
let displacement = self.read_memory::<i32>(address + offset.unwrap_or(0x3))?;
Ok((address + 0x7) + displacement as usize)
Ok((address + length.unwrap_or(0x7)) + displacement as usize)
}
fn get_process_id_by_name(process_name: &str) -> Result<u32> {

View File

@@ -14,15 +14,15 @@ impl<'a> SchemaClassFieldData<'a> {
}
pub fn name(&self) -> Result<String> {
self.process
.read_string(self.process.read_memory::<usize>(self.address)?)
let name_ptr = self.process.read_memory::<usize>(self.address)?;
self.process.read_string(name_ptr)
}
pub fn r#type(&self) -> Result<SchemaType> {
Ok(SchemaType::new(
self.process,
self.process.read_memory::<usize>(self.address + 0x8)?,
))
let type_ptr = self.process.read_memory::<usize>(self.address + 0x8)?;
Ok(SchemaType::new(self.process, type_ptr))
}
pub fn offset(&self) -> Result<u16> {

View File

@@ -14,7 +14,7 @@ impl<'a> SchemaClassInfo<'a> {
Self {
process,
address,
class_name: class_name.into(),
class_name: class_name.to_string(),
}
}
@@ -26,15 +26,16 @@ impl<'a> SchemaClassInfo<'a> {
pub fn fields(&self) -> Result<Vec<SchemaClassFieldData>> {
let count = self.fields_count()?;
let fields: Vec<SchemaClassFieldData> = (0..count as usize)
.filter_map(|i| {
let address = self
.process
.read_memory::<usize>(self.address + 0x28)
.ok()?
+ (i * 0x20);
let base_address = self.process.read_memory::<usize>(self.address + 0x28)?;
(address != 0).then(|| SchemaClassFieldData::new(self.process, address))
let fields: Vec<SchemaClassFieldData> = (0..count as usize)
.map(|i| base_address + (i * 0x20))
.filter_map(|address| {
if address != 0 {
Some(SchemaClassFieldData::new(self.process, address))
} else {
None
}
})
.collect();

View File

@@ -1,3 +1,5 @@
use std::mem;
use crate::error::Result;
use crate::remote::Process;
@@ -15,7 +17,7 @@ impl<'a> SchemaSystem<'a> {
"48 8D 0D ? ? ? ? E9 ? ? ? ? CC CC CC CC 48 8D 0D ? ? ? ? E9 ? ? ? ? CC CC CC CC 48 83 EC 28"
)?;
address = process.resolve_rip(address)?;
address = process.resolve_rip(address, None, None)?;
Ok(Self { process, address })
}
@@ -29,7 +31,7 @@ impl<'a> SchemaSystem<'a> {
self.process.read_memory_raw(
data,
addresses.as_mut_ptr() as *mut _,
addresses.len() * std::mem::size_of::<usize>(),
addresses.len() * mem::size_of::<usize>(),
)?;
let type_scopes: Vec<SchemaSystemTypeScope> = addresses

View File

@@ -22,12 +22,14 @@ impl<'a> SchemaSystemTypeScope<'a> {
.elements(self.process)?
.iter()
.filter_map(|&address| {
let declared_class = SchemaTypeDeclaredClass::new(self.process, address as usize);
let address = address as usize;
let declared_class = SchemaTypeDeclaredClass::new(self.process, address);
declared_class
.name()
.ok()
.map(|name| SchemaClassInfo::new(self.process, address as usize, &name))
.map(|name| SchemaClassInfo::new(self.process, address, &name))
})
.collect();

View File

@@ -1,6 +1,37 @@
use std::collections::HashMap;
use lazy_static::lazy_static;
use regex::Regex;
use crate::error::Result;
use crate::remote::Process;
const TYPE_MAP: &[(&'static str, &'static str)] = &[
("uint8", "uint8_t"),
("uint16", "uint16_t"),
("uint32", "uint32_t"),
("uint64", "uint64_t"),
("int8", "int8_t"),
("int16", "int16_t"),
("int32", "int32_t"),
("int64", "int64_t"),
("float32", "float"),
("float64", "double"),
];
lazy_static! {
static ref REGEX_MAP: HashMap<&'static str, Regex> = {
let mut map = HashMap::with_capacity(TYPE_MAP.len());
for (k, _v) in TYPE_MAP.iter() {
map.insert(*k, Regex::new(&format!(r"\b{}\b", k)).unwrap());
}
map
};
}
pub struct SchemaType<'a> {
process: &'a Process,
address: usize,
@@ -12,7 +43,26 @@ impl<'a> SchemaType<'a> {
}
pub fn name(&self) -> Result<String> {
self.process
.read_string(self.process.read_memory::<usize>(self.address + 0x8)?)
let name_ptr = self.process.read_memory::<usize>(self.address + 0x8)?;
let name = self
.process
.read_string(name_ptr)?
.replace(" ", "")
.to_string();
Ok(Self::convert_type_name(&name))
}
fn convert_type_name(type_name: &str) -> String {
let mut result = type_name.to_string();
for (k, v) in TYPE_MAP.iter() {
let re = REGEX_MAP.get(*k).unwrap();
result = re.replace_all(&result, &v.to_string()).to_string();
}
result
}
}

View File

@@ -12,7 +12,8 @@ impl<'a> SchemaTypeDeclaredClass<'a> {
}
pub fn name(&self) -> Result<String> {
self.process
.read_string(self.process.read_memory::<usize>(self.address + 0x8)?)
let name_ptr = self.process.read_memory::<usize>(self.address + 0x8)?;
self.process.read_string(name_ptr)
}
}