├── src ├── esp │ ├── gl_bindings.h │ ├── gl_bindings.rs │ ├── ebox.rs │ └── mod.rs ├── util │ ├── mod.rs │ ├── shellcode.rs │ └── math.rs ├── aimbot │ ├── bot_trampoline.cpp │ ├── norecoil.rs │ ├── mod.rs │ └── autoshoot.rs ├── process │ ├── helpers │ │ └── mod.rs │ ├── instantion │ │ └── mod.rs │ ├── mod.rs │ ├── modules │ │ └── mod.rs │ └── memory │ │ ├── mod.rs │ │ └── memdata.rs ├── player │ ├── infiniteammo.rs │ ├── mod.rs │ └── godmode.rs └── lib.rs ├── .gitignore ├── esp-screenshot.png ├── Cargo.toml └── README.md /src/esp/gl_bindings.h: -------------------------------------------------------------------------------- 1 | #include -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /.idea 3 | Cargo.lock 4 | /ac 5 | linux_64_client.bndb 6 | -------------------------------------------------------------------------------- /esp-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scannells/ac_rhack/HEAD/esp-screenshot.png -------------------------------------------------------------------------------- /src/esp/gl_bindings.rs: -------------------------------------------------------------------------------- 1 | // This includes openGL C bindings made available to us through the build Process, 2 | // more specifically the amazing "bindgen" crate 3 | #![allow(non_upper_case_globals)] 4 | #![allow(non_camel_case_types)] 5 | #![allow(non_snake_case)] 6 | #![allow(non_snake_case)] 7 | #![allow(dead_code)] 8 | include!(concat!(env!("OUT_DIR"), "/gl_bindings.rs")); -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ac_rhack" 3 | version = "0.1.0" 4 | authors = ["Simon Scannell "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | [lib] 9 | name = "ac_rhack" 10 | crate_type = ["cdylib"] 11 | 12 | [dependencies] 13 | ctor = "0.1.16" 14 | nix = "0.19.0" 15 | libloading = "0.6" 16 | 17 | 18 | [build-dependencies] 19 | bindgen = "0.53.1" 20 | cc = "1.0" -------------------------------------------------------------------------------- /src/util/mod.rs: -------------------------------------------------------------------------------- 1 | 2 | 3 | mod shellcode; 4 | pub use shellcode::*; 5 | 6 | mod math; 7 | pub use math::*; 8 | 9 | use crate::Process; 10 | 11 | static mut GAME_BASE: Option = None; 12 | 13 | /// returns the base address of the laoded AssaultCube process 14 | pub fn game_base() -> usize { 15 | unsafe { 16 | if GAME_BASE.is_none() { 17 | let process = Process::current().expect("Failed to use /proc to obtain process information"); 18 | GAME_BASE = Some( 19 | process.module("linux_64_client") 20 | .expect("Could not find game module in current process") 21 | .base 22 | ); 23 | } 24 | GAME_BASE.unwrap() 25 | } 26 | } 27 | 28 | -------------------------------------------------------------------------------- /src/aimbot/bot_trampoline.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | // this CPP function serves as a trampoline to call BotIsVisible() of AC 5 | // IsVisible() has a weird calling convention Rust can't deal with. 6 | // We could have used Rust inline ASM but that would require the nightly 7 | // rust build at the time of writing, so we just use this CPP trampoline. 8 | struct vec 9 | { 10 | union 11 | { 12 | struct { float x, y, z; }; 13 | float v[3]; 14 | int i[3]; 15 | }; 16 | }; 17 | 18 | typedef uint8_t (*IsVisible_t)(vec from, vec to, void *tracer, bool skipTags); 19 | 20 | extern "C" char bot_isvisible(unsigned long isVisibleAddr, vec *from, vec *to) { 21 | vec a = *from; 22 | vec b = *to; 23 | IsVisible_t hook = (IsVisible_t)isVisibleAddr; 24 | return hook(a, b, NULL, false); 25 | } -------------------------------------------------------------------------------- /src/process/helpers/mod.rs: -------------------------------------------------------------------------------- 1 | use std::path::{Path}; 2 | use std::fs::{DirEntry, read_link}; 3 | 4 | 5 | pub fn path_basename(dir: &DirEntry) -> String { 6 | let name = dir.file_name(); 7 | let name = name.into_string(); 8 | match name { 9 | Ok(basename) => basename, 10 | Err(_) => panic!("Can't convert OsString to String"), 11 | } 12 | } 13 | 14 | pub fn filename_basename(file_name: &str) -> String { 15 | let path = Path::new(file_name); 16 | let file_name = path.file_name(); 17 | if let Some(file_name) = file_name { 18 | return file_name.to_os_string().into_string().unwrap(); 19 | } else { 20 | return String::from(""); 21 | } 22 | } 23 | 24 | 25 | 26 | 27 | pub fn read_exe(proc_dir: &DirEntry) -> (bool, String) { 28 | let mut path = proc_dir.path(); 29 | path.push("exe"); 30 | let path = path.as_path(); 31 | 32 | let exe = read_link(path); 33 | if let Ok(exe) = exe { 34 | return (true, exe.into_os_string().into_string().unwrap()); 35 | } else { 36 | return (false, String::from("")); 37 | } 38 | } 39 | 40 | -------------------------------------------------------------------------------- /src/process/instantion/mod.rs: -------------------------------------------------------------------------------- 1 | use std::fs::{DirEntry, read_dir}; 2 | use std::path::Path; 3 | use std::process; 4 | 5 | use crate::{Process, ProcessErrors}; 6 | use crate::process::helpers::*; 7 | 8 | fn from_proc_dir(dir: &DirEntry, is_internal: bool) -> Result { 9 | let exe = read_exe(dir); 10 | if !exe.0 { 11 | return Err(ProcessErrors::ProcInvalid); 12 | } 13 | 14 | let exe = exe.1; 15 | 16 | let pid = path_basename(dir).parse(); 17 | if let Err(_) = pid { 18 | return Err(ProcessErrors::ProcInvalid); 19 | } 20 | 21 | let process = Process { 22 | pid: pid.unwrap(), 23 | proc_dir: dir.path().into_os_string().into_string().unwrap(), 24 | exe: exe, 25 | is_internal: is_internal, 26 | }; 27 | 28 | Ok(process) 29 | } 30 | 31 | 32 | pub fn from_current() -> Result { 33 | let curr_pid = process::id(); 34 | let proc_dir = std::format!("/proc/{}", curr_pid); 35 | let proc_root = Path::new("/proc"); 36 | for entry in read_dir(proc_root).expect("Failed to read /proc dir") { 37 | let entry = entry.expect("Failed to get next entry in /proc dir"); 38 | if entry.path().into_os_string().into_string().unwrap() == proc_dir { 39 | return from_proc_dir(&entry, true); 40 | } 41 | } 42 | 43 | Err(ProcessErrors::NotFound) 44 | } 45 | -------------------------------------------------------------------------------- /src/process/mod.rs: -------------------------------------------------------------------------------- 1 | 2 | use std::collections::HashMap; 3 | 4 | /** 5 | * Finds the assault cube process and returns information about it: base of the different sections, pid etc. 6 | * This module can also inject an arbitrary SO file into a a process as a new thread and run a specified function from it. 7 | */ 8 | mod instantion; 9 | mod memory; 10 | mod modules; 11 | mod helpers; 12 | 13 | // export all public symbols of the sub modules 14 | pub use memory::*; 15 | pub use modules::*; 16 | //pub use injection::*; 17 | 18 | /// represents a loaded binary or shared object file (e.g. the binary itself or libc) 19 | #[derive(Clone)] 20 | pub struct Module { 21 | name: String, 22 | pub file: String, 23 | pub base: usize, 24 | size: Option 25 | } 26 | 27 | #[derive(Clone)] 28 | pub struct Process { 29 | pub pid: usize, 30 | proc_dir: String, 31 | pub exe: String, 32 | is_internal: bool, 33 | } 34 | 35 | /// Indicates the reason for a failure on a process operation 36 | #[derive(Debug)] 37 | pub enum ProcessErrors { 38 | /// On Linux systems, this indicates a failure to interact with /proc 39 | ProcDirFailure, 40 | 41 | /// A process became invalid (e.g. it exited) 42 | ProcInvalid, 43 | 44 | /// When a PID or exe name could not be linked to a valid process 45 | NotFound, 46 | 47 | /// Permissions are insufficient to get access to the target process 48 | Permissions, 49 | 50 | /// Failed to open a file backing a module 51 | ModuleFileErr, 52 | } 53 | 54 | 55 | /// Main struct 56 | impl Process { 57 | 58 | pub fn current() -> Result { 59 | instantion::from_current() 60 | } 61 | 62 | pub fn module(&self, module_name: &str) -> Result { 63 | modules::get_module(self, module_name) 64 | } 65 | 66 | pub fn modules(&self) -> Result, ProcessErrors> { 67 | modules::parse_modules(self) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/aimbot/norecoil.rs: -------------------------------------------------------------------------------- 1 | use crate::ProcMem; 2 | use crate::util::game_base; 3 | 4 | // offset to the Recoil function 5 | const RECOIL_PATCH_OFF: usize = 0xbd220; 6 | 7 | 8 | pub struct NoRecoilSpread { 9 | patch_addr: usize, 10 | enabled: bool, 11 | saved_instr: Option, 12 | 13 | // the reason we use procmem here is that memory writes via /proc/mem 14 | // bypass write protection on executable pages 15 | mem: ProcMem 16 | } 17 | 18 | impl NoRecoilSpread { 19 | pub fn new() -> Self { 20 | NoRecoilSpread { 21 | patch_addr: game_base() + RECOIL_PATCH_OFF, 22 | enabled: false, 23 | saved_instr: None, 24 | mem: ProcMem::init() 25 | } 26 | } 27 | 28 | pub fn enable(&mut self) { 29 | println!("recoil addr=0x{:x}", self.patch_addr); 30 | // nothing to do 31 | if self.enabled { 32 | return 33 | } 34 | 35 | // If this is the first time patching, make sure to have saved the instruction before 36 | // so that we can restore the code 37 | if !self.saved_instr.is_some() { 38 | self.saved_instr = Some({ 39 | self.mem.read(self.patch_addr) 40 | }); 41 | } 42 | 43 | // patch the instruction with a simple ret 44 | self.mem.write(self.patch_addr, 0xc3 as u8); 45 | 46 | // keep a record that this hook is enabled 47 | self.enabled = true; 48 | } 49 | 50 | pub fn disable(&mut self) { 51 | // nothing to do if this patch is already enabled 52 | if !self.enabled { 53 | return 54 | } 55 | 56 | // make sure the code can't accidentally disable without having 57 | // read the original instructions before 58 | if !self.saved_instr.is_some() { 59 | panic!("Tried to disable Recoil / Spread without ever having enabled it"); 60 | } 61 | 62 | // simply write back the original bytes 63 | self.mem.write(self.patch_addr, self.saved_instr.unwrap()); 64 | 65 | self.enabled = false; 66 | } 67 | 68 | pub fn toggle(&mut self) { 69 | if self.enabled { 70 | self.disable(); 71 | } else { 72 | self.enable(); 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/player/infiniteammo.rs: -------------------------------------------------------------------------------- 1 | use crate::{ProcMem}; 2 | use crate::util::game_base; 3 | 4 | const AMMO_PATCH_OFF: usize = 0xbf50b; 5 | 6 | /** 7 | * InfiniteAmmo works by patching the instruction that subtracts the number of 8 | * shot rounds from the current mag with a NOP 9 | */ 10 | pub struct InfiniteAmmo { 11 | patch_addr: usize, 12 | enabled: bool, 13 | saved_instr: Option, 14 | 15 | // the reason we use procmem here is that memory writes via /proc/mem 16 | // bypass write protection on executable pages 17 | mem: ProcMem 18 | } 19 | 20 | impl InfiniteAmmo { 21 | pub fn new() -> Self { 22 | InfiniteAmmo { 23 | patch_addr: game_base() + AMMO_PATCH_OFF, 24 | enabled: false, 25 | saved_instr: None, 26 | mem: ProcMem::init() 27 | } 28 | } 29 | 30 | pub fn enable(&mut self) { 31 | // nothing to do 32 | if self.enabled { 33 | return 34 | } 35 | 36 | // If this is the first time patching, make sure to have saved the instruction before 37 | // so that we can restore the code 38 | if !self.saved_instr.is_some() { 39 | self.saved_instr = Some({ 40 | self.mem.read(self.patch_addr) 41 | }); 42 | } 43 | 44 | // patch the instruction with 3 bytes of NOPs (1x 16 bytes and 1x 8 byte write) 45 | self.mem.write(self.patch_addr, 0x90_90 as u16); 46 | self.mem.write(self.patch_addr + 2 as usize, 0x90 as u8); 47 | 48 | // keep a record that this hook is enabled 49 | self.enabled = true; 50 | } 51 | 52 | pub fn disable(&mut self) { 53 | // nothing to do if this patch is already enabled 54 | if !self.enabled { 55 | return 56 | } 57 | 58 | // make sure the code can't accidentally disable without having 59 | // read the original instructions before 60 | if !self.saved_instr.is_some() { 61 | panic!("Tried to disable infinite ammo without ever having enabled it"); 62 | } 63 | 64 | // simply write back the original bytes 65 | self.mem.write(self.patch_addr, self.saved_instr.unwrap()); 66 | 67 | self.enabled = false; 68 | } 69 | 70 | pub fn toggle(&mut self) { 71 | if self.enabled { 72 | self.disable(); 73 | } else { 74 | self.enable(); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/util/shellcode.rs: -------------------------------------------------------------------------------- 1 | use std::process::Command; 2 | use std::fs::{File, remove_file}; 3 | use std::io::prelude::*; 4 | use std::path::Path; 5 | 6 | extern crate nix; 7 | use nix::sys::mman::{mmap, ProtFlags, MapFlags}; 8 | 9 | use core::ffi::c_void; 10 | 11 | /// returns a pointer to an executable page where we can copy shellcode to 12 | pub fn get_executable_map(size: usize) -> *mut c_void { 13 | let mut prot_flags = ProtFlags::empty(); 14 | prot_flags.insert(ProtFlags::PROT_READ); 15 | prot_flags.insert(ProtFlags::PROT_EXEC); 16 | 17 | let mut map_flags = MapFlags::empty(); 18 | map_flags.insert(MapFlags::MAP_PRIVATE); 19 | map_flags.insert(MapFlags::MAP_ANON); 20 | let rw_page = unsafe { 21 | mmap(0 as *mut c_void, // 0 = NULL means allocate at any address 22 | size, // 4096 is the smallest possible page 23 | prot_flags, // R/W 24 | map_flags, // Not backed by a file 25 | -1, // -1 explicit for no FD 26 | 0 // no offset required 27 | ).expect("Failed to allocate executable map for shellcode") 28 | }; 29 | 30 | rw_page 31 | } 32 | 33 | 34 | /// Takes in a string of x86 intel assembly and uses nasm to return a vec of bytes 35 | /// of raw instructions corresponding to the shellcode 36 | pub fn gen_shellcode(shellcode: String) -> Vec{ 37 | 38 | // write the shellcode to a file 39 | let mut asm_file = File::create("/tmp/ac_hack_asm.S").expect("Failed to write to /tmp"); 40 | asm_file.write_all(shellcode.as_bytes()).expect("Failed to write assembly code to /tmp file"); 41 | 42 | // assemble it 43 | Command::new("nasm") 44 | .arg("-f") 45 | .arg("bin") 46 | .arg("/tmp/ac_hack_asm.S") 47 | .arg("-o") 48 | .arg("/tmp/ac_hack_asm") 49 | .status() 50 | .expect("This hack requires NASM to gen shellcode dynamically. Please install it"); 51 | 52 | 53 | // delete the assembly file 54 | remove_file(Path::new("/tmp/ac_hack_asm.S")).expect("Could not clean shellcode file"); 55 | 56 | 57 | // read the resulting opcodes into a u8 vec - start with a 4096byte buffer 58 | let mut asm_file = File::open("/tmp/ac_hack_asm").expect("Something went wrong when assembling shellcode\n"); 59 | let size = asm_file.metadata().unwrap().len(); 60 | 61 | let mut res: Vec = vec![0; size as usize]; 62 | asm_file.read(&mut res).expect("Failed to read assembly dump"); 63 | 64 | // delete the assembled code 65 | remove_file(Path::new("/tmp/ac_hack_asm")).expect("Could not clean assembled file"); 66 | res 67 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This basic cheat is the result of a side-project I did for fun. It is an Internal Hack for AssaultCube on Linux written in Rust. 2 | 3 | 4 | ![alt text](./esp-screenshot.png "Screenshot of the ESP") 5 | 6 | ## Features 7 | 8 | * ESP 9 | * Aimbot 10 | * Autoshoot 11 | * No Recoil 12 | * No Spread 13 | * Infinite Ammo 14 | * Invincibility 15 | * 1-shot-1-kill 16 | 17 | 18 | ## Disclaimer 19 | 20 | This hack does not work in multiplayer on purpose and will panic if it is loaded into a non-local game. 21 | The purpose of this cheat was not to ruin other people's fun but to simply play around with a GameCheat in 22 | Rust and on Linux, as most cheats are either written for Windows or in C++ or both. 23 | 24 | This software contains no Assault Cube intellectual property, and is not affiliated in any way. 25 | 26 | ## Usage 27 | 28 | ### Building 29 | 30 | The build system of this cheat is cargo. External library dependencies are libGL headers and libSDL. 31 | 32 | On Ubuntu, you can install all prerquisites with: 33 | 34 | ```bash 35 | #/bin/bash 36 | # install openGL header files 37 | sudo apt install libgl-dev -y 38 | 39 | # you will probably need to install libSDL-image to run the game 40 | sudo apt-install libsdl-image1.2-dev -y 41 | ``` 42 | 43 | My `rustc` and cargo versions at the time of building were `cargo 1.47.0 (f3c7e066a 2020-08-28)` and 44 | `rustc 1.47.0 (18bf6b4f0 2020-10-07)`. The build can fail when using versions previous to `1.47.*`. 45 | 46 | Then, simply run 47 | 48 | ```bash 49 | cargo build --release 50 | ``` 51 | 52 | 53 | ### Loading the cheat 54 | 55 | This cheat uses the Linux `LD_PRELOAD` technique to load the binary into the target process and to hook 56 | `SDL_GL_SwapBuffers()`. 57 | 58 | After building the cheat, run the following command from root directory of this cheat: 59 | 60 | ```bash 61 | LD_PRELOAD=./target/release/libac_rhack.so PATH/TO/AC/assaultcube.sh 62 | ``` 63 | 64 | ### Target binary 65 | 66 | I developed this hack on Ubuntu 18.04 on x86_64 architectures. 67 | At the time of writing, the game can be downloaded from https://assault.cubers.net/. 68 | Within the root of the game directory, there will be an `assaultcube.sh` file that is used to launch 69 | the game. Internally, this script executes `bin/linux_64_client`. This hack was developed for this 70 | binary and at the time of writing, these are the file hashes for it: 71 | 72 | * **MD5**: `819c849fd087ed2c218a8dc660d0f389` 73 | * **SHA-1**: `1d8c23acde3373c10b0ef0dd03ab63194a4537e6` 74 | 75 | It matters to execute that version, as offsets will be invalidated by future updates. 76 | 77 | 78 | 79 | ## Documentation 80 | 81 | A documentation can be generated via 82 | 83 | ```bash 84 | cargo doc 85 | ``` 86 | 87 | Alternatively, just read the source code. I made lots of comments. 88 | -------------------------------------------------------------------------------- /src/process/modules/mod.rs: -------------------------------------------------------------------------------- 1 | use std::fs::read_to_string; 2 | use std::collections::HashMap; 3 | 4 | 5 | use crate::{Process, ProcessErrors, Module}; 6 | use crate::process::helpers::*; 7 | 8 | 9 | fn parse_module(maps_line: &str) -> Result { 10 | let columns: Vec<&str> = maps_line.split_whitespace().collect(); 11 | 12 | // the format is the following: start_addr-end_addr perms pgoff major:minor inode binary (6 columns) 13 | // in some cases a memory segment exists without a module. We are not interested in these and ignore them by ignoring all modules that do not have 6 columns 14 | if columns.len() == 5 { 15 | return Err(()); 16 | } 17 | 18 | // get the backing file of the module 19 | let mut file = String::from(columns[5]); 20 | 21 | // in case the file is a special one such as [stack] or [heap], create a module for that. Otherwise only get the base of the module (the segment that contains the executable bit). 22 | // we can obtain information about the other segments from the ELF file 23 | let is_special = file == "[stack]" || file == "[heap]"; 24 | if is_special { 25 | file = String::from(file).replace("[", "").replace("]", ""); 26 | } 27 | 28 | 29 | let mut executable = false; 30 | if let Some(_) = columns[1].find('x') { 31 | executable = true 32 | } 33 | 34 | // vsyscall meets our filter criteria but is uninteresting 35 | if (!is_special && !executable) || file == "[vsyscall]" { 36 | return Err(()); 37 | } 38 | 39 | // parse the base address 40 | let range: Vec<&str> = columns[0].split('-').collect(); 41 | let start = usize::from_str_radix(range[0], 16).unwrap(); 42 | 43 | Ok(Module { 44 | name: filename_basename(&file), 45 | file: String::from(file), 46 | base: start, 47 | size: None, 48 | }) 49 | } 50 | 51 | pub fn parse_modules(process: &Process) -> Result, ProcessErrors> { 52 | 53 | let mut res: HashMap = HashMap::new(); 54 | 55 | let maps_path = format!("{}/maps", &process.proc_dir); 56 | let modules = read_to_string(maps_path); 57 | if let Err(_) = modules { 58 | return Err(ProcessErrors::ProcInvalid); 59 | } 60 | let modules = modules.unwrap(); 61 | 62 | for line in modules.lines() { 63 | let module = parse_module(line); 64 | if let Ok(module) = module { 65 | res.insert(module.name.clone(), module); 66 | } 67 | } 68 | 69 | Ok(res) 70 | } 71 | 72 | pub fn get_module(process: &Process, module_name: &str) -> Result { 73 | let modules = parse_modules(process); 74 | 75 | if let Ok(modules) = modules { 76 | let module = modules.get(module_name); 77 | 78 | if let Some(module) = module { 79 | return Ok(module.clone()); 80 | } else { 81 | return Err(ProcessErrors::NotFound); 82 | } 83 | } else { 84 | return Err(ProcessErrors::ProcInvalid); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/esp/ebox.rs: -------------------------------------------------------------------------------- 1 | 2 | use super::*; 3 | 4 | use crate::{ 5 | Player, 6 | ViewMatrix 7 | }; 8 | 9 | 10 | // Copied these values and this ESP scaling method from a tutorial on GuidedHacking (https://www.youtube.com/watch?v=kGDKQXgxIrY&t=1125s) 11 | // they are used for scaling an ESP box 12 | const VIRTUAL_SCREEN_WIDTH: i32 = 800; 13 | const GAME_UNIT_MAGIC: usize = 400; 14 | const PLAYER_HEIGHT: f32 = 7.25; 15 | const PLAYER_WIDTH: f32 = 3.5; 16 | const PLAYER_ASPECT_RATIO: f32 = PLAYER_HEIGHT / PLAYER_WIDTH; 17 | 18 | /// Represents an ESP Box on the screen 19 | pub struct ESPBox { 20 | enemy_color: [GLubyte; 3], 21 | team_color: [GLubyte; 3], 22 | } 23 | 24 | impl ESPBox { 25 | pub fn new(default_enemy_color: [GLubyte; 3], default_team_color: [GLubyte; 3]) -> Self { 26 | ESPBox { 27 | enemy_color: default_enemy_color, 28 | team_color: default_team_color, 29 | } 30 | } 31 | 32 | // scales an ESP box to the distance of the enemy and the width of the window 33 | fn scale(&self, distance: f32, window_width: i32) -> f32 { 34 | (GAME_UNIT_MAGIC as f32 / distance) * (window_width / VIRTUAL_SCREEN_WIDTH) as f32 35 | } 36 | 37 | // draws an ESP box relative to the player position 38 | pub fn draw_box(&self, client: &Player, player: &Player, window_dimensions: (GLint, GLint)) { 39 | 40 | // red for enemies, green for team mates 41 | let colors = if player.enemy_of(client) {self.enemy_color} else {self.team_color}; 42 | let line_width: f32 = 0.75; 43 | 44 | // get the position of the enemy 45 | let client_pos = client.get_pos(); 46 | 47 | // get the corresponding 2D coordinates 48 | let pos = ViewMatrix::new() 49 | .world_to_screen(client_pos, window_dimensions.0, window_dimensions.1); 50 | 51 | // if the enemy is behind us, don't bother drawing 52 | if !pos.0 { 53 | return 54 | } 55 | 56 | let x = pos.1; 57 | let y = pos.2; 58 | 59 | // get the distance and scale 60 | let distance = player.distance_to(client); 61 | let scale = self.scale(distance, window_dimensions.0); 62 | 63 | let x = x - scale; 64 | let y = y - scale; 65 | let width = scale * 2.0; 66 | let height = scale * PLAYER_ASPECT_RATIO * 2.0; 67 | 68 | 69 | // draw the ESP box 70 | unsafe { 71 | glLineWidth(line_width); 72 | glColor3ub(colors[0], colors[1], colors[2]); 73 | glBegin(GL_LINE_STRIP); 74 | glVertex2f(x, y); 75 | glVertex2f(x + width, y); 76 | glVertex2f(x + width, y + height); 77 | glVertex2f(x, y + height); 78 | glVertex2f(x, y); 79 | glEnd(); 80 | } 81 | } 82 | } -------------------------------------------------------------------------------- /src/process/memory/mod.rs: -------------------------------------------------------------------------------- 1 | 2 | use std::path::Path; 3 | use std::io::prelude::*; 4 | use std::fs::{OpenOptions, File}; 5 | use std::io::SeekFrom; 6 | 7 | mod memdata; 8 | pub use memdata::MemData; 9 | 10 | use crate::Process; 11 | 12 | 13 | /// A wrapper for writing / reading memory through /proc/mem. This is needed when 14 | /// trying to write to a r-x page for example, as this method bypasses rwx protections. 15 | pub struct ProcMem { 16 | handle: File, 17 | } 18 | 19 | impl ProcMem { 20 | pub fn init() -> Self { 21 | let process = Process::current().unwrap(); 22 | let mempath = format!("{}/mem", &process.proc_dir); 23 | let mempath = Path::new(&mempath); 24 | let memhandle = OpenOptions::new() 25 | .read(true) 26 | .write(true) 27 | .open(mempath) 28 | .expect("Could not open /proc/self/mem for memory operations"); 29 | 30 | ProcMem { 31 | handle: memhandle 32 | } 33 | } 34 | 35 | pub fn write(&mut self, addr: usize, data: T) { 36 | self.handle.seek(SeekFrom::Start(addr as u64)) 37 | .expect("Could not seek /proc/self/mem file"); 38 | 39 | self.handle.write(&data.get_vec()) 40 | .expect("Could not write to /proc/self/mem file"); 41 | } 42 | 43 | pub fn read(&mut self, addr: usize) -> T { 44 | self.handle.seek(SeekFrom::Start(addr as u64)) 45 | .expect("Could not seek /proc/self/mem file"); 46 | 47 | let mut _buf = T::make_buf(); 48 | self.handle.read(&mut _buf) 49 | .expect("Could not read from /proc/self/mem file"); 50 | 51 | T::from_vec(&_buf) 52 | } 53 | 54 | // a basic memcpy() for larger data buffers. This is called when copying shellcode 55 | pub fn write_n(&mut self, addr: usize, data: &[u8]) { 56 | let mut rest = data.len(); 57 | let mut curr = 0; 58 | while rest != 0 { 59 | let size = { 60 | if rest % 8 == 0 { 61 | let bytes = u64::from_vec(&Vec::from(&data[curr..curr + 8])); 62 | self.write(addr + curr, bytes); 63 | 8 64 | } else if rest % 4 == 0 { 65 | let bytes = u32::from_vec(&Vec::from(&data[curr..curr + 4])); 66 | self.write(addr + curr, bytes); 67 | 4 68 | } else if rest % 2 == 0 { 69 | let bytes = u16::from_vec(&Vec::from(&data[curr..curr + 2])); 70 | self.write(addr + curr, bytes); 71 | 2 72 | } else { 73 | let bytes = data[curr]; 74 | self.write(addr + curr, bytes); 75 | 1 76 | } 77 | }; 78 | 79 | rest -= size; 80 | curr += size; 81 | } 82 | } 83 | } 84 | 85 | /// a wrapper for reading and writing dynamic data through pointers 86 | #[derive(Clone)] 87 | pub struct InternalMemory {} 88 | 89 | 90 | impl InternalMemory { 91 | pub fn write(addr: usize, data: T) { 92 | let ptr: *mut T = addr as *mut T; 93 | unsafe { *ptr = data }; 94 | } 95 | 96 | pub fn read(addr: usize) -> T { 97 | let ptr: *const T = addr as *const T; 98 | let ret: T = unsafe { *ptr }; 99 | ret 100 | } 101 | } 102 | 103 | #[derive(Debug)] 104 | pub enum MemoryError { 105 | ProcInvalid, 106 | InvalidTechnique, 107 | } 108 | 109 | 110 | -------------------------------------------------------------------------------- /src/util/math.rs: -------------------------------------------------------------------------------- 1 | use std::ops::Sub; 2 | use crate::{game_base, InternalMemory}; 3 | 4 | /// represents a point a 3D environment 5 | #[derive(Debug, Copy, Clone, PartialEq)] 6 | #[repr(C)] 7 | pub struct Vec3 { 8 | pub x: f32, 9 | pub y: f32, 10 | pub z: f32 11 | } 12 | 13 | 14 | impl Vec3 { 15 | /// Creates a 3D point from raw float values 16 | pub fn from(pos: [f32; 3]) -> Self { 17 | Self { 18 | x: pos[0], 19 | y: pos[1], 20 | z: pos[2] 21 | } 22 | } 23 | 24 | /// Returns the eclidian distance between to points 25 | pub fn distance(a: Vec3, b: Vec3) -> f32 { 26 | let vector = a - b; 27 | 28 | // return the eclidian distance of the vector 29 | f32::sqrt( 30 | vector.x.powi(2) + 31 | vector.y.powi(2) + 32 | vector.z.powi(2) 33 | ) 34 | } 35 | } 36 | 37 | 38 | impl Sub for Vec3 { 39 | type Output = Self; 40 | 41 | fn sub(self, other: Vec3) -> Vec3 { 42 | Vec3 { 43 | x: self.x - other.x, 44 | y: self.y - other.y, 45 | z: self.z - other.z 46 | } 47 | } 48 | } 49 | 50 | 51 | /// Offset to the point the player looks at 52 | const PLAYER_VIEW_OFF: usize = 0x13745c; 53 | /// Offset to the view matrix (relative to the player view) 54 | const VIEW_MATRIX_OFF: usize = PLAYER_VIEW_OFF - 0x80; 55 | 56 | 57 | // Thanks to GuidedHacking for this W2S function 58 | pub struct ViewMatrix { 59 | base: usize, 60 | } 61 | 62 | impl ViewMatrix { 63 | pub fn new() -> Self { 64 | ViewMatrix { 65 | base: game_base() + VIEW_MATRIX_OFF, 66 | } 67 | } 68 | 69 | fn read_matrix(&self) -> [[f32; 4]; 4] { 70 | let mut ret = [[0.0f32, 0.0, 0.0, 0.0]; 4]; 71 | let mut row = 0; 72 | let mut col = 0; 73 | for i in 0usize..16 { 74 | if col == 4 { 75 | col = 0; 76 | row += 1; 77 | } 78 | ret[row][col] = InternalMemory::read(self.base + i * 4); 79 | col += 1; 80 | } 81 | 82 | ret 83 | } 84 | 85 | pub fn world_to_screen(&self, pos: Vec3, width: i32, height: i32) -> (bool, f32, f32) { 86 | let matrix = self.read_matrix(); 87 | 88 | let screen_x = (pos.x * matrix[0][0]) + (pos.y * matrix[1][0]) + (pos.z * matrix[2][0]) + matrix[3][0]; 89 | let screen_y = (pos.x * matrix[0][1]) + (pos.y * matrix[1][1]) + (pos.z * matrix[2][1]) + matrix[3][1]; 90 | let screen_z = (pos.x * matrix[0][2]) + (pos.y * matrix[1][2]) + (pos.z * matrix[2][2]) + matrix[3][2]; 91 | let screen_w = (pos.x * matrix[0][3]) + (pos.y * matrix[1][3]) + (pos.z * matrix[2][3]) + matrix[3][3]; 92 | 93 | if screen_w < 0.1 { 94 | return (false, 0.0, 0.0); 95 | } 96 | 97 | let mut ndc = Vec3::from([screen_x, screen_y, screen_z]); 98 | ndc.x = screen_x / screen_w; 99 | ndc.y = screen_y / screen_w; 100 | ndc.z = screen_z / screen_w; 101 | 102 | let x = (width as f32 / 2.0 * ndc.x) + (ndc.x + width as f32 / 2.0); 103 | let y = -(height as f32 / 2.0 * ndc.y) + (ndc.y + height as f32 / 2.0); 104 | 105 | (true, x ,y) 106 | } 107 | } -------------------------------------------------------------------------------- /src/esp/mod.rs: -------------------------------------------------------------------------------- 1 | // bindgen is used to generate C bindings for libGL! 2 | mod gl_bindings; 3 | use gl_bindings::*; 4 | 5 | use crate::Player; 6 | 7 | mod ebox; 8 | use ebox::ESPBox; 9 | 10 | // some RGB values 11 | const ENEMY_ESP_COLOR: [GLubyte; 3] = [252, 18 , 10]; 12 | const TEAM_ESP_COLOR: [GLubyte; 3] = [38, 217 , 50]; 13 | 14 | /// COnfigures and handles ESP logic 15 | pub struct ESP { 16 | player: Player, 17 | esp_box: ESPBox, 18 | } 19 | 20 | impl ESP { 21 | 22 | /// Creates a new ESP 23 | pub fn new() -> Self { 24 | ESP { 25 | player: Player::player1(), 26 | esp_box: ESPBox::new(ENEMY_ESP_COLOR, TEAM_ESP_COLOR), 27 | } 28 | } 29 | 30 | // switches the openGL mode into a 2D matrix and pushes the current state onto a stack 31 | // so that we can pop it later. It also returns the current window dimensions 32 | fn switch_to_2d(&self) -> (GLint, GLint) { 33 | unsafe { 34 | // save the current state 35 | gl_bindings::glPushAttrib(GL_ALL_ATTRIB_BITS); 36 | 37 | // save the current matrix 38 | gl_bindings::glPushMatrix(); 39 | 40 | // obtain and set the current viewport (position and dimensions of the window) 41 | // for the new matrix 42 | let mut viewport: [GLint; 4] = [0; 4]; 43 | let viewport_ptr = &mut viewport[0] as *mut GLint; 44 | gl_bindings::glGetIntegerv(GL_VIEWPORT, viewport_ptr); 45 | gl_bindings::glViewport(0, 0, viewport[2], viewport[3]); 46 | 47 | // go into projection mode 48 | gl_bindings::glMatrixMode(GL_PROJECTION); 49 | 50 | // loads a blank matrix 51 | gl_bindings::glLoadIdentity(); 52 | 53 | gl_bindings::glOrtho(0.0, viewport[2].into(), viewport[3].into(), 0.0, -1.0, 1.0); 54 | 55 | gl_bindings::glMatrixMode(GL_MODELVIEW); 56 | gl_bindings::glLoadIdentity(); 57 | gl_bindings::glDisable(GL_DEPTH_TEST); 58 | 59 | (viewport[2], viewport[3]) 60 | } 61 | } 62 | 63 | // restores the attributes before leaving the hook 64 | fn restore(&self) { 65 | unsafe { 66 | gl_bindings::glPopMatrix(); 67 | gl_bindings::glPopAttrib(); 68 | } 69 | } 70 | 71 | /// If enabled, draw ESP boxes 72 | pub fn draw(&mut self) { 73 | // save the current GL state, switch to 2D mode and obtain the window dimenstions 74 | let win_dimensions = self.switch_to_2d(); 75 | 76 | // obtain a list of all bots 77 | let players = Player::players(); 78 | 79 | for p in players.iter() { 80 | // filter out dead enemies 81 | if !p.is_alive() { 82 | continue 83 | } 84 | 85 | // draw ESP boxes for the remaining 86 | self.esp_box.draw_box(p, &self.player, win_dimensions) 87 | } 88 | 89 | // go back into 3D projection mode and let the game carry on 90 | self.restore(); 91 | } 92 | 93 | // can be used by other modules to get information about the window 94 | pub fn window_dimensions() -> (i32, i32) { 95 | let mut viewport: [GLint; 4] = [0; 4]; 96 | unsafe { 97 | let viewport_ptr = &mut viewport[0] as *mut GLint; 98 | gl_bindings::glGetIntegerv(GL_VIEWPORT, viewport_ptr); 99 | }; 100 | (viewport[2], viewport[3]) 101 | } 102 | } -------------------------------------------------------------------------------- /src/process/memory/memdata.rs: -------------------------------------------------------------------------------- 1 | use std::mem::transmute; 2 | 3 | fn fill_array(arr: &mut [u8], vec: &Vec) { 4 | let mut idx: usize = 0; 5 | for elem in vec.iter() { 6 | arr[idx] = *elem; 7 | idx += 1; 8 | } 9 | } 10 | 11 | pub trait MemData { 12 | fn get_vec(self) -> Vec; 13 | fn from_vec(vec: &Vec) -> Self; 14 | fn make_buf() -> Vec; 15 | } 16 | 17 | impl MemData for u8 { 18 | fn get_vec(self) -> Vec { 19 | Vec::from([self]) 20 | } 21 | 22 | fn from_vec(vec: &Vec) -> Self { 23 | vec[0] 24 | } 25 | 26 | fn make_buf() -> Vec { 27 | vec![0 as u8; 1] 28 | } 29 | } 30 | 31 | impl MemData for i8 { 32 | fn get_vec(self) -> Vec { 33 | Vec::from([self as u8]) 34 | } 35 | 36 | fn from_vec(vec: &Vec) -> Self { 37 | vec[0] as i8 38 | } 39 | 40 | fn make_buf() -> Vec { 41 | vec![0 as u8; 1] 42 | } 43 | } 44 | 45 | impl MemData for u16 { 46 | fn get_vec(self) -> Vec { 47 | let data = unsafe { 48 | transmute::(self) 49 | }; 50 | Vec::from(data) 51 | } 52 | 53 | fn from_vec(vec: &Vec) -> Self { 54 | let mut arr: [u8; 2] = [0; 2]; 55 | fill_array(&mut arr, vec); 56 | let discrete = unsafe { 57 | transmute::<[u8; 2], u16>(arr) 58 | }; 59 | discrete 60 | } 61 | 62 | fn make_buf() -> Vec { 63 | vec![0 as u8; 2] 64 | } 65 | } 66 | 67 | impl MemData for i16 { 68 | fn get_vec(self) -> Vec { 69 | let data = unsafe { 70 | transmute::(self) 71 | }; 72 | Vec::from(data) 73 | } 74 | 75 | fn from_vec(vec: &Vec) -> Self { 76 | let mut arr: [u8; 2] = [0; 2]; 77 | fill_array(&mut arr, vec); 78 | let discrete = unsafe { 79 | transmute::<[u8; 2], i16>(arr) 80 | }; 81 | discrete 82 | } 83 | 84 | fn make_buf() -> Vec { 85 | vec![0 as u8; 2] 86 | } 87 | } 88 | 89 | impl MemData for u32 { 90 | fn get_vec(self) -> Vec { 91 | let data = unsafe { 92 | transmute::(self) 93 | }; 94 | Vec::from(data) 95 | } 96 | 97 | fn from_vec(vec: &Vec) -> Self { 98 | let mut arr: [u8; 4] = [0; 4]; 99 | fill_array(&mut arr, vec); 100 | let discrete = unsafe { 101 | transmute::<[u8; 4], u32>(arr) 102 | }; 103 | discrete 104 | } 105 | 106 | fn make_buf() -> Vec { 107 | vec![0 as u8; 4] 108 | } 109 | } 110 | 111 | impl MemData for i32 { 112 | fn get_vec(self) -> Vec { 113 | let data = unsafe { 114 | transmute::(self) 115 | }; 116 | Vec::from(data) 117 | } 118 | 119 | fn from_vec(vec: &Vec) -> Self { 120 | let mut arr: [u8; 4] = [0; 4]; 121 | fill_array(&mut arr, vec); 122 | let discrete = unsafe { 123 | transmute::<[u8; 4], i32>(arr) 124 | }; 125 | discrete 126 | } 127 | 128 | fn make_buf() -> Vec { 129 | vec![0 as u8; 4] 130 | } 131 | } 132 | 133 | impl MemData for u64 { 134 | fn get_vec(self) -> Vec { 135 | let data = unsafe { 136 | transmute::(self) 137 | }; 138 | Vec::from(data) 139 | } 140 | 141 | fn from_vec(vec: &Vec) -> Self { 142 | let mut arr: [u8; 8] = [0; 8]; 143 | fill_array(&mut arr, vec); 144 | let discrete = unsafe { 145 | transmute::<[u8; 8], u64>(arr) 146 | }; 147 | discrete 148 | } 149 | 150 | fn make_buf() -> Vec { 151 | vec![0 as u8; 8] 152 | } 153 | } 154 | 155 | impl MemData for i64 { 156 | fn get_vec(self) -> Vec { 157 | let data = unsafe { 158 | transmute::(self) 159 | }; 160 | Vec::from(data) 161 | } 162 | 163 | fn from_vec(vec: &Vec) -> Self { 164 | let mut arr: [u8; 8] = [0; 8]; 165 | fill_array(&mut arr, vec); 166 | let discrete = unsafe { 167 | transmute::<[u8; 8], i64>(arr) 168 | }; 169 | discrete 170 | } 171 | 172 | fn make_buf() -> Vec { 173 | vec![0 as u8; 8] 174 | } 175 | } 176 | 177 | impl MemData for f32 { 178 | fn get_vec(self) -> Vec { 179 | let data = unsafe { 180 | transmute::(self) 181 | }; 182 | Vec::from(data) 183 | } 184 | 185 | fn from_vec(vec: &Vec) -> Self { 186 | let mut arr: [u8; 4] = [0; 4]; 187 | fill_array(&mut arr, vec); 188 | let discrete = unsafe { 189 | transmute::<[u8; 4], f32>(arr) 190 | }; 191 | discrete 192 | } 193 | 194 | fn make_buf() -> Vec { 195 | vec![0 as u8; 4] 196 | } 197 | } 198 | 199 | impl MemData for f64 { 200 | fn get_vec(self) -> Vec { 201 | let data = unsafe { 202 | transmute::(self) 203 | }; 204 | Vec::from(data) 205 | } 206 | 207 | fn from_vec(vec: &Vec) -> Self { 208 | let mut arr: [u8; 8] = [0; 8]; 209 | fill_array(&mut arr, vec); 210 | let discrete = unsafe { 211 | transmute::<[u8; 8], f64>(arr) 212 | }; 213 | discrete 214 | } 215 | 216 | fn make_buf() -> Vec { 217 | vec![0 as u8; 8] 218 | } 219 | } 220 | 221 | 222 | -------------------------------------------------------------------------------- /src/aimbot/mod.rs: -------------------------------------------------------------------------------- 1 | mod norecoil; 2 | use norecoil::NoRecoilSpread; 3 | 4 | use crate::player::Player; 5 | use crate::process::InternalMemory; 6 | use crate::util::game_base; 7 | use crate::util::Vec3; 8 | 9 | use std::f32::consts::PI; 10 | 11 | // offset from the game's base to a pointer that points at camera1 12 | const CAMERA1_OFF: usize = 0x1371b0; 13 | 14 | // offset to the horizontal angle of the camera 15 | const YAW_OFF: usize = 0x44; 16 | 17 | // offset to the vertical angle of the camera 18 | const PITCH_OFF: usize = 0x48; 19 | 20 | // offset to the IsVisible function of AssaultCube 21 | const IS_VISIBLE_OFF: usize = 0xda520; 22 | 23 | /// Handles AimBot configuration and logic 24 | pub struct AimBot { 25 | /// information about the current player 26 | player: Player, 27 | 28 | /// no recoild and no spread hook 29 | pub norecoil_spread: NoRecoilSpread, 30 | 31 | /// shoot automatically if an enemy is in the crosshair 32 | autoshoot: bool, 33 | 34 | /// enables / disables the aimbot 35 | enabled: bool, 36 | } 37 | 38 | #[link(name="bot_trampoline", kind="static")] 39 | extern "C" { 40 | fn bot_isvisible(func_addr: usize, from: *const Vec3, to: *const Vec3) -> u8; 41 | } 42 | 43 | impl AimBot { 44 | /// Creates a new aimbot 45 | pub fn new() -> AimBot { 46 | let player = Player::player1(); 47 | AimBot { 48 | autoshoot: false, 49 | player, 50 | norecoil_spread: NoRecoilSpread::new(), 51 | enabled: false, 52 | } 53 | } 54 | 55 | 56 | // returns the address of the camera1 object 57 | fn camera1() -> usize { 58 | InternalMemory::read::(game_base() + CAMERA1_OFF) as usize 59 | } 60 | 61 | // Enables autoshoot 62 | pub fn enable_autoshoot(&mut self) { 63 | self.autoshoot = true 64 | } 65 | 66 | // calculates the position the player will be in the next frame when it moves and returns 67 | // the angle we need to set the camera to in order to point at this enemy 68 | fn enemy_to_angle(&self, enemy: &Player) -> (f32, f32) { 69 | let target_pos = enemy.get_new_pos(); 70 | let self_pos = self.player.get_new_pos(); 71 | let dx = target_pos.x - self_pos.x; 72 | let dy = target_pos.y - self_pos.y; 73 | let dz = target_pos.z - self_pos.z; 74 | 75 | // horizontal angle to player 76 | let yaw = dy.atan2(dx) * 180.0 / PI; 77 | 78 | let distance = self.player.distance_to(enemy); 79 | let pitch = dz.atan2(distance) * 180.0 / PI; 80 | 81 | (yaw + 90.0, pitch) 82 | } 83 | 84 | // returns true when an enemy can be shot at 85 | // to do this, we call the function "IsVisible" of AssaultCube and 86 | // let the game do the math :) 87 | // The problem with this function is that it requires C++ calling 88 | // conentions. For this reason, there is a trampoline written in C++ 89 | // that we are calling here 90 | fn is_visible(&self, enemy: &Player) -> bool { 91 | let res = unsafe { 92 | let is_visible_addr = game_base() + IS_VISIBLE_OFF; 93 | let from = &self.player.get_pos() as *const Vec3; 94 | let to = &enemy.get_pos() as *const Vec3; 95 | bot_isvisible(is_visible_addr, from, to) 96 | }; 97 | res == 1 98 | } 99 | 100 | 101 | /// Called after each frame by the main SwapBuffer hook. Handles findings a target 102 | /// to aim at and updating camera perspective 103 | pub fn logic(&mut self) { 104 | 105 | // stop shooting if we are not locked onto a target 106 | if self.autoshoot { 107 | self.player.stop_shoot(); 108 | } 109 | 110 | // don't to anything if the aimbot is disabled 111 | if !self.enabled { 112 | return 113 | } 114 | 115 | // obtain a list of all enemies which are alive 116 | let players: Vec = Player::players() 117 | .into_iter() 118 | .filter(|p| p.is_alive() && self.player.enemy_of(p)) 119 | .collect(); 120 | 121 | 122 | // no need to do anything if no enemies are alive 123 | if players.len() == 0 { 124 | return 125 | } 126 | 127 | // find the closest enemy that we can shoot at 128 | let mut best_dist = f32::INFINITY; 129 | let mut target = None; 130 | for p in players.iter() { 131 | let pdist = self.player.distance_to(p); 132 | if pdist < best_dist && self.is_visible(p) { 133 | best_dist = pdist; 134 | target = Some(p); 135 | } 136 | } 137 | 138 | // verify that a target was found to point at 139 | if target.is_none() { 140 | return 141 | } 142 | 143 | let target = target.unwrap(); 144 | 145 | // update the camera position to point at the enemy 146 | let (yaw, pitch) = self.enemy_to_angle(target); 147 | 148 | // verify camera1 is valid! 149 | if Self::camera1() == 0x0 { 150 | return; 151 | } 152 | 153 | // update the camera position 154 | InternalMemory::write(Self::camera1() + YAW_OFF, yaw); 155 | InternalMemory::write(Self::camera1() + PITCH_OFF, pitch); 156 | 157 | // kill something 158 | if self.autoshoot { 159 | self.player.shoot(); 160 | } 161 | } 162 | 163 | pub fn enable(&mut self) { 164 | self.enabled = true; 165 | } 166 | 167 | pub fn disable(&mut self) { 168 | self.enabled = false; 169 | } 170 | 171 | pub fn toggle(&mut self) { 172 | if self.enabled { 173 | self.disable(); 174 | } else { 175 | self.enable(); 176 | } 177 | } 178 | } -------------------------------------------------------------------------------- /src/aimbot/autoshoot.rs: -------------------------------------------------------------------------------- 1 | use core::ffi::c_void; 2 | 3 | use crate::{ProcMem}; 4 | 5 | use crate::{get_executable_map, gen_shellcode}; 6 | use crate::util::game_base; 7 | 8 | /* Autoshoot works by hooking the AC function "playerincrosshair". 9 | * This function takes in the current view of the player and returns a pointer 10 | * to a player entity if the crosshair is on it. 11 | * Here, we will build a hook that checks if the pointer is of an enemy 12 | * by reading the team field and if so, we set the "attacking" property of our own player 13 | * to shoot. If we are not on an enemy, we set this property to false to stop shooting. 14 | * Lucky for us, following the 'ret' instruction of this function comes a 12 byte padding 15 | * This means , by patching the ret as well as the padding we can insert code such as 16 | * push rax 17 | * mov rax, SHELLCODE_LOCATION 18 | * jmp rax 19 | */ 20 | 21 | /// offset to the "playerincrosshair" function 22 | const CROSSHAIR_OFF: usize = 0xbad63; 23 | 24 | /// offset to the ->attacking field. When set, the player will shoot 25 | const PLAYER_ATTACKING_OFF: usize = 0x23c; 26 | 27 | pub struct AutoShoot { 28 | patch_addr: usize, 29 | enabled: bool, 30 | 31 | // the reason we use procmem here is that memory writes via /proc/mem 32 | // bypass write protection on executable pages 33 | mem: ProcMem, 34 | 35 | // a reference to the executable map containing the shellcode for this feature 36 | page: Option<*mut c_void>, 37 | 38 | // the address of the health of the player. It will be used in the shellcode 39 | player_base: usize, 40 | 41 | // the shellcode used to detour the damage taking function 42 | patch_shellcode: Option>, 43 | } 44 | 45 | impl AutoShoot { 46 | pub fn new(player_base: usize) -> Self { 47 | AutoShoot { 48 | patch_addr: game_base() + CROSSHAIR_OFF, 49 | enabled: false, 50 | mem: ProcMem::init(), 51 | page: None, 52 | player_base: player_base, 53 | patch_shellcode: None, 54 | } 55 | } 56 | 57 | pub fn enable(&mut self) { 58 | // nothing to do 59 | if self.enabled { 60 | return 61 | } 62 | 63 | // If this is the first time patching, make sure to prepare the shellcodes 64 | if !self.page.is_some() { 65 | /* we need to allocate a r-x page for our payload hook 66 | * 1. allocate a r-x map at any address 67 | * 2. patch the damage function so that instead of doing damage jump to the page 68 | * where our shellcode will be located 69 | * 3. we will patch the last instruction of the function, which is a ret 70 | * that is followed by some padding. If we overwrite the padding, we can jump 71 | * to our shellcode 72 | */ 73 | self.page = Some(get_executable_map(4096)); 74 | 75 | // TODO: Add team check 76 | let shellcode = format!( 77 | "BITS 64; ; NASM stuff\n\ 78 | \ 79 | ; restore rax after we used it to jump - it contains the resulting pointer\n\ 80 | pop rax\n\ 81 | ; save rbx, we will use it as a pointer to our player\n\ 82 | push rbx\n\ 83 | mov QWORD rbx, 0x{:x}\n\ 84 | ; check if rax is NULL. If so there is no player in the crosshair. Stop shooting\n\ 85 | test rax, rax\n\ 86 | jnz attack\n\ 87 | ; stop shooting by setting ->attacking to 0\n\ 88 | mov BYTE [rbx + 0x{:x}], 0\n\ 89 | jmp exit\n\ 90 | attack:\n\ 91 | ; if an enemy is in our crosshair, shoot by setting ->attacking to 1\n\ 92 | mov BYTE [rbx + 0x{:x}], 1\n\ 93 | exit:\n\ 94 | pop rbx\n\ 95 | ; this will also return to the main code as we jmped into this\n\ 96 | ret\n\ 97 | " 98 | , self.player_base, PLAYER_ATTACKING_OFF, PLAYER_ATTACKING_OFF); 99 | 100 | let shellcode = gen_shellcode(shellcode); 101 | 102 | // now copy the shellcode to the executable map 103 | self.mem.write_n(self.page.unwrap() as usize, &shellcode); 104 | 105 | 106 | // the patch we will write will jump to the page 107 | // these instructions are 13 bytes large, the instructions we are patching 108 | // are 14 bytes in size. So add a NOP for padding 109 | let patchcode = format!( 110 | "BITS 64; ; NASM stuff\n\ 111 | push rax ; save rax\n\ 112 | mov rax, QWORD 0x{:x} ; move the address to the page into rax\n\ 113 | jmp rax ; jump to the shellcode in our map\n\ 114 | " 115 | , self.page.unwrap() as usize); 116 | 117 | 118 | 119 | // assemble the patchcode 120 | self.patch_shellcode = Some(gen_shellcode(patchcode)); 121 | } 122 | 123 | // patch the instruction with with the shellcode that jumps to the function hook 124 | self.mem.write_n(self.patch_addr, &self.patch_shellcode.as_ref().unwrap()); 125 | println!("patching address at 0x{:x} with len {} (for autoshoot)", self.patch_addr, &self.patch_shellcode.as_ref().unwrap().len()); 126 | 127 | // keep a record that this hook is enabled 128 | self.enabled = true; 129 | } 130 | 131 | pub fn disable(&mut self) { 132 | // nothing to do if this patch is already enabled 133 | if !self.enabled { 134 | return 135 | } 136 | 137 | // simply write back the original ret instruction 138 | self.mem.write(self.patch_addr, 0xc3 as u8); 139 | 140 | self.enabled = false; 141 | } 142 | 143 | pub fn toggle(&mut self) { 144 | if self.enabled { 145 | self.disable(); 146 | } else { 147 | self.enable(); 148 | } 149 | } 150 | 151 | } 152 | -------------------------------------------------------------------------------- /src/player/mod.rs: -------------------------------------------------------------------------------- 1 | 2 | mod godmode; 3 | pub use godmode::GodMode; 4 | 5 | mod infiniteammo; 6 | pub use infiniteammo::InfiniteAmmo; 7 | use crate::{InternalMemory, ESP}; 8 | use crate::util::{game_base, Vec3, ViewMatrix}; 9 | 10 | /// offset to the player1 pointer from the base of the loaded game 11 | const PLAYER1_OFF: usize = 0x128328; 12 | 13 | /// offset to the vector of player pointers from the base of the loaded game 14 | const PLAYERS_OFF: usize = 0x128330; 15 | 16 | // there can be only 32 players in a match 17 | const MAX_PLAYERS: usize = 32; 18 | 19 | 20 | /// offset from the player base to the team field (int) 21 | const TEAM_OFF: usize = 0x344; 22 | 23 | /// offset from the player base to the state (alive, dead etc.) a player has (u8) 24 | const STATE_OFF: usize = 0x86; 25 | 26 | /// offset to the player position (an array of 3 32bit floats) 27 | const PLAYER_POS_OFF: usize = 0x8; 28 | 29 | const PLAYER_NEWPOS_OFF: usize = 0x38; 30 | const PLAYER_EYEHEIGHT_OFF: usize = 0x60; 31 | 32 | const PLAYER_ATTACKING_OFF: usize = 0x23c; 33 | 34 | const GAMEMODE_OFF: usize = 0x128294; 35 | 36 | // this value represents a living player (used for aimbot / ESP) 37 | const ALIVE_STATE: u8 = 0; 38 | 39 | 40 | enum GameModes { 41 | GmodeBotTeamdeathMatch = 7, 42 | GmodeBotDeathMatch = 8, 43 | GmodeBotOneShotOneKill = 12, 44 | GmodeBotPistolFrenzy = 18, 45 | GmodeBotlss = 19, 46 | GmodeBotSurvivor = 20, 47 | GmodeBotTeamOneShotOneKill = 21, 48 | } 49 | 50 | impl GameModes { 51 | fn from_i32(int: i32) -> GameModes { 52 | match int { 53 | 7 => GameModes::GmodeBotTeamdeathMatch, 54 | 8 => GameModes::GmodeBotDeathMatch, 55 | 12 => GameModes::GmodeBotOneShotOneKill, 56 | 18 => GameModes::GmodeBotPistolFrenzy, 57 | 19 => GameModes::GmodeBotlss, 58 | 20 => GameModes::GmodeBotSurvivor, 59 | 21 => GameModes::GmodeBotTeamOneShotOneKill, 60 | _ => panic!("Unsupported Game mode") 61 | } 62 | } 63 | } 64 | 65 | pub struct Player { 66 | pub base: usize, 67 | } 68 | 69 | // AssaultCube has a custom vector that holds pointers to enemies, 70 | // which are also Players. We use this struct to read the enemy player's positions 71 | #[repr(C)] 72 | #[derive(Clone, Copy)] 73 | struct AcVector { 74 | player_addresses: usize, // pointer to the buffer of pointers to the enemies 75 | capacity: i32, // max size of the buffer 76 | elements: i32 // how many elements there actually are 77 | } 78 | 79 | impl Player { 80 | 81 | /// Creates a struct representing and giving access to the current player 82 | pub fn player1() -> Self { 83 | // There is a global variable called "player1", which is a pointer 84 | // to the actual, dynamically allocated player struct. 85 | // In order to obtain the address of the player, just dereference the global pointer 86 | let ac_base = game_base(); 87 | let player1_ptr = ac_base + PLAYER1_OFF; 88 | let player1_base: u64 = InternalMemory::read(player1_ptr); 89 | 90 | Player { 91 | base: player1_base as usize 92 | } 93 | } 94 | 95 | 96 | /// Returns a vector of all other players in the lobby 97 | pub fn players() -> Vec { 98 | let players_base = game_base() + PLAYERS_OFF; 99 | 100 | let vec_of_players = unsafe { 101 | let vec_ptr = players_base as *const AcVector; 102 | *vec_ptr 103 | }; 104 | 105 | let mut players = Vec::with_capacity(MAX_PLAYERS); 106 | 107 | // fill the vector of enemies 108 | for i in 0..vec_of_players.elements { 109 | let player_addr: u64 = InternalMemory::read(vec_of_players.player_addresses + (i * 8) as usize); 110 | 111 | // sometimes pointers are NULL 112 | if player_addr == 0x0 { 113 | continue; 114 | } 115 | players.push(Player { 116 | base: player_addr as usize 117 | }); 118 | } 119 | 120 | players 121 | } 122 | 123 | 124 | /// reads the position of a player and returns it as 3D coordinates 125 | pub fn get_pos(&self) -> Vec3 { 126 | let mut head: [f32; 3] = [0.0; 3]; 127 | for i in 0..3 { 128 | head[i] = InternalMemory::read(self.base + PLAYER_POS_OFF + i * 4); 129 | } 130 | Vec3::from(head) 131 | } 132 | 133 | fn get_eyeheight(&self) -> f32 { 134 | InternalMemory::read(self.base + PLAYER_EYEHEIGHT_OFF) 135 | } 136 | 137 | fn get_gamemode() -> GameModes { 138 | GameModes::from_i32( 139 | InternalMemory::read(game_base() + GAMEMODE_OFF) 140 | ) 141 | } 142 | 143 | fn is_free_for_all() -> bool { 144 | let gamemode = Self::get_gamemode(); 145 | match gamemode { 146 | GameModes::GmodeBotDeathMatch => true, 147 | GameModes::GmodeBotOneShotOneKill => true, 148 | GameModes::GmodeBotSurvivor => true, 149 | GameModes::GmodeBotlss => true, 150 | _ => false 151 | } 152 | } 153 | 154 | /// returns true if the two players are enemies 155 | pub fn enemy_of(&self, other: &Player) -> bool { 156 | // first, check the game mode against a list of game modes where 157 | // the team does not matter 158 | if Self::is_free_for_all() { 159 | return true; 160 | } 161 | 162 | self.get_team() != other.get_team() 163 | } 164 | 165 | /// returns the position a player will be located at in the next frame. 166 | /// This is needed for a reliable aimbot 167 | pub fn get_new_pos(&self) -> Vec3 { 168 | let mut foot: [f32; 3] = [0.0; 3]; 169 | for i in 0..3 { 170 | foot[i] = InternalMemory::read(self.base + PLAYER_NEWPOS_OFF + i * 4); 171 | } 172 | let mut vec = Vec3::from(foot); 173 | vec.z += self.get_eyeheight(); 174 | vec 175 | } 176 | 177 | /// Calculates the distance between to players in a 3D space 178 | pub fn distance_to(&self, other: &Player) -> f32 { 179 | let self_pos = self.get_pos(); 180 | let other_pos = other.get_pos(); 181 | 182 | Vec3::distance(self_pos, other_pos) 183 | } 184 | 185 | /// retuns the team the player is in 186 | fn get_team(&self) -> i32 { 187 | InternalMemory::read(self.base + TEAM_OFF) 188 | } 189 | 190 | 191 | /// returns true if the player is alive 192 | pub fn is_alive(&self) -> bool { 193 | InternalMemory::read::(self.base + STATE_OFF) == ALIVE_STATE 194 | } 195 | 196 | 197 | /// returns true if a player is infront of the player on the 2D screen 198 | pub fn is_in_view(&self) -> bool { 199 | let pos = self.get_pos(); 200 | let (window_width, window_height) = ESP::window_dimensions(); 201 | ViewMatrix::new().world_to_screen(pos, window_width, window_height).0 202 | } 203 | 204 | /// triggers the ->attacking state of the player to start shooting 205 | pub fn shoot(&mut self) { 206 | InternalMemory::write(self.base + PLAYER_ATTACKING_OFF, 1 as u8) 207 | } 208 | 209 | /// stops shooting after having started through autoshoot 210 | pub fn stop_shoot(&mut self) { 211 | InternalMemory::write(self.base + PLAYER_ATTACKING_OFF, 0 as u8) 212 | } 213 | 214 | } 215 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | /** 2 | * This hack is relatively simple. It is loaded into the AssaultCube process through 3 | * the LD_PRELOAD technique (e.g.) LD_PRELOAD=./hack.so ./assaultcube.sh in the main AC directory. 4 | * There is a constructor, which runs at load time. It is used to initialize the hack by 5 | * - verifying this library is actually loaded into the game and not for example /bin/sh when 6 | launching AC through ./assaultcube.sh 7 | * - finding offsets of code to patch 8 | * - generating shellcode on the fly through nasm for hooks 9 | * - prepares hooks 10 | * - initialized the global AC_HACK variable 11 | * - dynamically loads libSDL and obtains a pointer to the SDL_GL_SwapBuffers() function 12 | * 13 | * By using the LD_PRELOAD technique, this hack hooks the SDL_GL_SwapBuffers() function. 14 | * This function will then use the initialized, static variable AC_HACK to perform the logic 15 | * it needs to do such as getting player positions, draw ESP boxes etc. 16 | * The reason we use statics here is that we don't want to reload the entire hack 17 | * for each frame 18 | */ 19 | use std::thread; 20 | use std::time::Duration; 21 | 22 | extern crate libloading; 23 | extern crate ctor; 24 | use ctor::ctor; 25 | 26 | 27 | // include all the different sub modules of this hack as pub for the documentation 28 | pub mod process; 29 | pub mod player; 30 | pub mod aimbot; 31 | pub mod esp; 32 | pub mod util; 33 | 34 | // make all their symbols available to the other submodules through 'crate::' 35 | pub use esp::*; 36 | pub use aimbot::*; 37 | pub use player::*; 38 | pub use process::*; 39 | pub use util::*; 40 | 41 | /// This is a static reference to the initialized hack. It is initialized at load time of the library 42 | /// and used for every frame of the game (SDL_GL_SwapBuffers()) 43 | static mut AC_HACK: Option = None; 44 | 45 | /// a reference to the dynamiclly loaded libSDL. We use this dynamically loaded library 46 | /// to keep a ference to the real SDL_GL_SwapBuffers() so that the hack can call it after 47 | /// the hook has finished. 48 | static mut SDL_DYLIB: Option = None; 49 | 50 | /// The main struct containing the current configuration of the cheat 51 | struct AcHack { 52 | /// Exposes an interface to interact with the AC player struct 53 | pub player: Player, 54 | 55 | /// Enables GodMode (invincible, 1-shot-1kill 56 | pub god_mode: GodMode, 57 | 58 | /// Hooks the shooting function and enables infinite ammo 59 | pub infinite_ammo: InfiniteAmmo, 60 | 61 | /// Used to configure the aimbot 62 | pub aimbot: AimBot, 63 | 64 | /// Used to configure the ESP 65 | pub esp: ESP, 66 | } 67 | 68 | 69 | impl AcHack { 70 | /// Creates a new instance of the AcHack struct 71 | fn new() -> Self { 72 | // get a handle to the current process 73 | let player = Player::player1(); 74 | AcHack { 75 | aimbot: AimBot::new(), 76 | esp: ESP::new(), 77 | god_mode: GodMode::new(), 78 | infinite_ammo: InfiniteAmmo::new(), 79 | player, 80 | } 81 | } 82 | 83 | /// Initializes default settings and launches a new thread that will listen for keyboard 84 | /// bindings 85 | fn init() ->Self { 86 | let mut hack = Self::new(); 87 | 88 | // all the following are default settings for this hack 89 | hack.aimbot.enable(); 90 | hack.aimbot.norecoil_spread.toggle(); 91 | hack.aimbot.enable_autoshoot(); 92 | hack.infinite_ammo.toggle(); 93 | hack.god_mode.toggle(); 94 | 95 | hack 96 | } 97 | } 98 | 99 | /// This function is executed when the hack is loaded into the game 100 | /// it is used to initialize the hack, launch a new thread that listens for keyboard bindings etc 101 | #[ctor] 102 | fn load() { 103 | 104 | // Check if the current process has a linux_64_client module (the main AC binary) 105 | // otherwise don't load the cheat here 106 | let process = Process::current().expect("Could not use /proc to obtain process information"); 107 | if let Err(_e) = process.module("linux_64_client") { 108 | return; 109 | } 110 | 111 | // load libSDL dynamically by finding the module it is loaded at, get it's path and 112 | // use the libloading crate to dynamically load a pointer to the real SDL_GL_SwapBuffers() 113 | // function 114 | let mut found = false; 115 | let modules = process.modules().expect("Could not parse the loaded modules"); 116 | for module_name in modules.keys() { 117 | if module_name.contains("libSDL-1.2") { 118 | unsafe { 119 | SDL_DYLIB = Some( 120 | libloading::Library::new(module_name) 121 | .expect("Could not load libSDL") 122 | ) 123 | }; 124 | 125 | found = true; 126 | } 127 | } 128 | 129 | // this should not happen 130 | if !found { 131 | panic!("Could not find libSDL-1.2 in current process"); 132 | } 133 | 134 | // let the user know we are loaded 135 | println!("Successfully loaded the hack into the game..."); 136 | println!("Waiting 5 seconds for the game to initialize it self before touching anything."); 137 | 138 | 139 | // Wait 5 seconds in a new thread for the game to initialize 140 | // If we don't do this step, we might break something as some pointers might be uninitialized 141 | thread::spawn(|| { 142 | // Wait around 5 seconds to let the game actually load so that pointers are valid. 143 | thread::sleep(Duration::from_secs(5)); 144 | 145 | // Load the cheat! 146 | unsafe { 147 | AC_HACK = Some(AcHack::init()); 148 | } 149 | }); 150 | } 151 | 152 | 153 | /// Calls the real SDL_GL_SwapBuffers() to render a game frame 154 | fn forward_to_orig_sdl_swap_buffers() -> i64 { 155 | // this function is always initialized as we panic in the loading function 156 | // if it can't be initialized 157 | unsafe { 158 | // verify that SDL_DYLIB has already been initialized 159 | let libsdl = &SDL_DYLIB; 160 | if !libsdl.is_some() { 161 | // in case it has not, just return 0. This will render a black screen 162 | // in the AssaultCube window 163 | return 0; 164 | } 165 | 166 | let orig_sdl_swap_buffers: 167 | libloading::Symbol i64> 168 | = SDL_DYLIB 169 | .as_ref() 170 | .unwrap() 171 | .get(b"SDL_GL_SwapBuffers\0") 172 | .expect("Could not find SDL_GL_SwapBuffers() in libSDL"); 173 | orig_sdl_swap_buffers() 174 | } 175 | } 176 | 177 | /// This is the "main" function of this cheat. 178 | /// SDL_GL_SwapBuffers() is called by the game for each frame that is generated. 179 | #[no_mangle] 180 | pub extern "C" fn SDL_GL_SwapBuffers() -> i64 { 181 | 182 | // rustc falsely detects this as an unused mutable 183 | #![allow(unused_mut)] 184 | let hack = unsafe { 185 | &mut AC_HACK 186 | }; 187 | 188 | // verify that the AC_HACK has been loaded and initialized already 189 | // otherwise just render the frame 190 | if !hack.is_some() { 191 | return forward_to_orig_sdl_swap_buffers(); 192 | } 193 | let mut hack = hack.as_mut().unwrap(); 194 | 195 | // here comes the logic of the hack 196 | 197 | // handle ESP logic 198 | hack.esp.draw(); 199 | 200 | // handle aimbot logic 201 | hack.aimbot.logic(); 202 | 203 | // call the real SDL_GL_SwapBuffers() to render the frame and continue with the logic 204 | forward_to_orig_sdl_swap_buffers() 205 | } 206 | 207 | 208 | 209 | 210 | -------------------------------------------------------------------------------- /src/player/godmode.rs: -------------------------------------------------------------------------------- 1 | use core::ffi::c_void; 2 | 3 | use crate::{ProcMem, Player}; 4 | 5 | use crate::{get_executable_map, gen_shellcode}; 6 | use crate::util::game_base; 7 | 8 | /* Enabling gode mode works by patching the instruction that writes to the health of a 9 | * player. We can't just add a NOP here, as that would mean no one can die anymore. 10 | * For this reason we must allocate an executable page where we write code that will 11 | * check if the health address that is supposed to be written to is the health address 12 | * of the current player. If it is, we just NOP 13 | * If it isn't, we set the damage to be 100 and proceed to subtract the damage 14 | * the instruction that subtracts the damage is 15 | * sub dword [rbx+0x110], ebp 16 | * We will patch this instruction and some instructions around it to jump to that page and then 17 | * restore registers 18 | */ 19 | const DAMAGE_PATCH_OFF: usize = 0x1c2e6; 20 | const HOOK_SIZE: usize = 16; 21 | 22 | pub struct GodMode { 23 | patch_addr: usize, 24 | enabled: bool, 25 | saved_instr: Option<[u8; HOOK_SIZE]>, 26 | 27 | // the reason we use procmem here is that memory writes via /proc/mem 28 | // bypass write protection on executable pages 29 | mem: ProcMem, 30 | 31 | // a reference to the executable map containing the shellcode for this feature 32 | page: Option<*mut c_void>, 33 | 34 | // the address of the health of the player. It will be used in the shellcode 35 | player_base: usize, 36 | 37 | // the shellcode used to detour the damage taking function 38 | patch_shellcode: Option>, 39 | } 40 | 41 | impl GodMode { 42 | pub fn new() -> Self { 43 | GodMode { 44 | patch_addr: game_base() + DAMAGE_PATCH_OFF, 45 | enabled: false, 46 | saved_instr: None, 47 | mem: ProcMem::init(), 48 | page: None, 49 | player_base: Player::player1().base, 50 | patch_shellcode: None, 51 | } 52 | } 53 | 54 | pub fn enable(&mut self) { 55 | // nothing to do 56 | if self.enabled { 57 | return 58 | } 59 | 60 | // If this is the first time patching, make sure to have saved the instruction before 61 | // so that we can restore the code 62 | if !self.saved_instr.is_some() { 63 | /* we need to allocate a rwx page for our payload hook 64 | * 1. allocate a r-x map at any address 65 | * 2. patch the damage function so that instead of doing damage jump to the page 66 | * where our shellcode will be located 67 | * 3. we will replace 4 instructions to make space for our hook: 68 | * sub eax, ecx # needed for space 69 | * mov dword [rbx+0x114], eax # this instruction has nothing to do with our code but is needed for space 70 | * sub ebp, edx # ebp will contain the damage 71 | * sub dword [rbx+0x110], ebp # this is the instruction that does damage 72 | * this means the shellcode we will jump to has to include these instructions and 73 | * set and return the registers cleanly so that no difference is made 74 | */ 75 | self.page = Some(get_executable_map(4096)); 76 | 77 | // TODO: Check if the bot is an enemy 78 | let shellcode = format!( 79 | "BITS 64; ; NASM stuff\n\ 80 | \ 81 | ; restore rax after we used it to jump\n\ 82 | pop rax\n\ 83 | ; this instruction was patched out by the hook so include it here\n\ 84 | sub eax, ecx\n\ 85 | ; this instruction was patched out by the hook so include it here\n\ 86 | mov DWORD [rbx+0x114],eax\n\ 87 | ; move the pointer to the player base into rax\n\ 88 | mov QWORD rax, QWORD 0x{:x}\n\ 89 | ; rbx contains the pointer to the player struct this damage should bedded on\n\ 90 | ; compare the pointer to the base of the player to see if the player is supposed\n\ 91 | ; to take damage\n\ 92 | cmp rax, rbx\n\ 93 | ; if they are equal, turn this into a NOP by jumping to the ret\n\ 94 | jz exit\n\ 95 | ; if this is another player, enable 1-hit kills by increasing the damage to over 9000\n\ 96 | ; the damage will be contained in ebp after ebp - edx\n\ 97 | sub ebp, edx\n\ 98 | ; save rbp in case the value is needed later for something else\n\ 99 | push rbp\n\ 100 | mov ebp, 9001\n\ 101 | ; do the damage\n\ 102 | sub DWORD [rbx+0x110],ebp\n\ 103 | pop rbp\n\ 104 | \ 105 | exit:\n\ 106 | ; return by pushing to the address of the instructions after the patch (patch_addr + 14 bytes)\n\ 107 | ; onto the stack. This way all registers are preserved. Rax will be restored by the hook\n\ 108 | mov QWORD rax, QWORD 0x{:x}\n\ 109 | push rax\n\ 110 | ret\n" 111 | , self.player_base, self.patch_addr + HOOK_SIZE); 112 | println!("patch add = 0x{:x}", self.patch_addr + HOOK_SIZE); 113 | 114 | let shellcode = gen_shellcode(shellcode); 115 | 116 | // now copy the shellcode to the executable map 117 | self.mem.write_n(self.page.unwrap() as usize, &shellcode); 118 | 119 | 120 | // the patch we will write will jump to the page 121 | // these instructions are 13 bytes large, the instructions we are patching 122 | // are 14 bytes in size. So add a NOP for padding 123 | let patchcode = format!( 124 | "BITS 64; ; NASM stuff\n\ 125 | push rax ; save rax\n\ 126 | mov rax, QWORD 0x{:x} ; move the address to the page into rax\n\ 127 | jmp rax ; jump to the shellcode in our map\n\ 128 | pop rax ; restore rax\n\ 129 | NOP ; NOP for padding\n\ 130 | NOP ; NOP for padding\n" 131 | , self.page.unwrap() as usize); 132 | 133 | 134 | 135 | // assemble the patchcode 136 | self.patch_shellcode = Some(gen_shellcode(patchcode)); 137 | 138 | // before overwriting the patch address, we need to save it 139 | let mut saved: [u8; HOOK_SIZE] = [0; HOOK_SIZE]; 140 | for i in 0..saved.len() { 141 | saved[i] = self.mem.read(self.patch_addr); 142 | } 143 | 144 | self.saved_instr = Some(saved); 145 | 146 | } 147 | 148 | // patch the instruction with with the shellcode that jumps to the function hook 149 | self.mem.write_n(self.patch_addr, &self.patch_shellcode.as_ref().unwrap()); 150 | println!("patching address at 0x{:x} with len {}", self.patch_addr, &self.patch_shellcode.as_ref().unwrap().len()); 151 | 152 | // keep a record that this hook is enabled 153 | self.enabled = true; 154 | } 155 | 156 | pub fn disable(&mut self) { 157 | // nothing to do if this patch is already enabled 158 | if !self.enabled { 159 | return 160 | } 161 | 162 | // make sure the code can't accidentally disable without having 163 | // read the original instructions before 164 | if !self.saved_instr.is_some() { 165 | panic!("Tried to disable infinite ammo without ever having enabled it"); 166 | } 167 | 168 | // simply write back the original bytes 169 | self.mem.write_n(self.patch_addr, &self.saved_instr.unwrap()); 170 | 171 | self.enabled = false; 172 | } 173 | 174 | pub fn toggle(&mut self) { 175 | if self.enabled { 176 | self.disable(); 177 | } else { 178 | self.enable(); 179 | } 180 | } 181 | 182 | } 183 | --------------------------------------------------------------------------------