preface
Cheat Engine is a game focused cheater. It can be used to scan the memory in the game and allow them to be modified. This tool may not have been heard of, but the famous Jinshan Ranger must have been used by everyone more than ten years ago.
By chance, I saw a project written by Rust. I liked his concise grammar and gathered the advantages of many programming languages. So I learned Rust in my spare time. I thought that WinAPI, which has not been used for many years, just took this opportunity to review, and I have the following content.
Build Rust environment
There are a lot of tutorials online, so I won't talk about them in detail here
https://www.runoob.com/rust/rust-setup.html
Enumeration process
The demo provided by CE is used here
Enumerate the processes, and let the user input the process PID to select the modified process
Because we need to use winapi on windows platform
Add cargo Toml dependency
[dependencies] winapi = { version = "0.3.9", features = ["psapi","processthreadsapi","handleapi","memoryapi"] } //All required API s are added here
Get all processes using EnumProcesses
pub fn enum_proc() -> io::Result<Vec<u32>> { let mut size = 0; //Returned structure size let mut pids = Vec::<DWORD>::with_capacity(2048); //Allocate pid array size if unsafe { /** BOOL EnumProcesses( [out] DWORD *lpidProcess, [in] DWORD cb, [out] LPDWORD lpcbNeeded ); */ winapi::um::psapi::EnumProcesses( pids.as_mut_ptr(), (pids.capacity() * std::mem::size_of::<DWORD>()) as u32, &mut size, ) } == FALSE { return Err(io::Error::last_os_error()); } //Calculate the number of processes obtained, using set_len set the number of arrays let count = size as usize / std::mem::size_of::<DWORD>(); unsafe { pids.set_len(count) } Ok(pids) }
Open process
pub fn open(pid: u32) -> io::Result<Self> { NonNull::new(unsafe { /* HANDLE OpenProcess( [in] DWORD dwDesiredAccess, [in] BOOL bInheritHandle, [in] DWORD dwProcessId ); */ winapi::um::processthreadsapi::OpenProcess( winnt::PROCESS_QUERY_INFORMATION | winnt::PROCESS_VM_READ | winnt::PROCESS_VM_WRITE | winnt::PROCESS_VM_OPERATION, FALSE, pid, ) }) .map(|handle| Self { pid, handle }) .ok_or_else(io::Error::last_os_error) }
Get process name
After you get the process pid, but you don't know what the process name is. The following code gets the process name
pub fn name(&self) -> io::Result<String> { let mut module = MaybeUninit::<HMODULE>::uninit(); let mut size = 0; if unsafe { /* BOOL EnumProcessModules( [in] HANDLE hProcess, [out] HMODULE *lphModule, [in] DWORD cb, [out] LPDWORD lpcbNeeded ); */ winapi::um::psapi::EnumProcessModules( self.handle.as_ptr(), module.as_mut_ptr(), size_of::<HMODULE>() as u32, &mut size, ) } == FALSE { return Err(io::Error::last_os_error()); } let module = unsafe { module.assume_init() }; //The longest process name is 1024 let mut buffer = Vec::<u8>::with_capacity(1024); let length = unsafe { /* DWORD GetModuleBaseNameA( [in] HANDLE hProcess, [in, optional] HMODULE hModule, [out] LPSTR lpBaseName, [in] DWORD nSize ); */ winapi::um::psapi::GetModuleBaseNameA( self.handle.as_ptr(), module, buffer.as_mut_ptr().cast(), buffer.capacity() as u32, ) }; if length == 0 { return Err(io::Error::last_os_error()); } //Chinese display not repaired unsafe { buffer.set_len(length as usize) }; return match String::from_utf8(buffer) { Ok(s) => Ok(s), Err(e) => Ok("".to_string()), }; }
Get memory area
In order to improve the speed of the first search, first obtain the memory area
pub fn memory_regions(&self) -> Vec<MEMORY_BASIC_INFORMATION> { let mut base = 0; let mut regions = Vec::new(); let mut info = MaybeUninit::uninit(); loop { /* SIZE_T VirtualQueryEx( [in] HANDLE hProcess, [in, optional] LPCVOID lpAddress, [out] PMEMORY_BASIC_INFORMATION lpBuffer, [in] SIZE_T dwLength ); */ let written = unsafe { winapi::um::memoryapi::VirtualQueryEx( self.handle.as_ptr(), base as *const _, info.as_mut_ptr(), size_of::<MEMORY_BASIC_INFORMATION>(), ) }; //end if written == 0 { break; } let info = unsafe { info.assume_init() }; //Calculate the next area base base = info.BaseAddress as usize + info.RegionSize; //preservation regions.push(info); } let mask = winnt::PAGE_EXECUTE_READWRITE | winnt::PAGE_EXECUTE_WRITECOPY | winnt::PAGE_READWRITE | winnt::PAGE_WRITECOPY; //Filter out system modules and invalid areas return regions.into_iter().filter(|x|!(x.BaseAddress as u32 > 0x70000000 && (x.BaseAddress as u32) < 0x80000000)).filter(|p| (p.Protect & mask) != 0).collect(); }
First search
We searched this 100
Get 262 addresses
The code is as follows:
//Get all memory regions let regions:Vec<MEMORY_BASIC_INFORMATION> = process.memory_regions(); println!("Scanning {} memory regions", regions.len()); println!("Which exact value to scan for?"); //Get the input value to bytes let mut input = String::new(); stdin().read_line(&mut input).unwrap(); let target: u32 = input.trim().parse::<u32>().unwrap(); let target = target.to_ne_bytes(); //Storage location array let mut locations = Vec::new(); for region in regions { match process.read_memory(region.BaseAddress as _, region.RegionSize){ Ok(mem)=>{ mem.windows(target.len()).enumerate().for_each(|(offset, window)| { //Is it the entered value if window == target { locations.push(region.BaseAddress as usize + offset); } }) }, Err(e)=>continue } }
Search again until you find the result
Click [hit me] and the result becomes 97. Let's visit the address we just searched to see which is not 97 and delete it
//There is only one result left to jump out of the loop while locations.len() != 1 { println!("Next Scan value:"); //Value entered let mut input = String::new(); stdin().read_line(&mut input).unwrap(); let target: u32 = input.trim().parse::<u32>().unwrap(); let target = target.to_ne_bytes(); //Use retain to delete results that do not meet the requirements locations.retain(|addr| match process.read_memory(*addr, target.len()) { Ok(memory) => memory == target, Err(_) => false, }); }
Modify memory data
After multiple searches, there is only one address left. We can modify it to any value to achieve the effect of cheating (for example, the timer has been modified to lock blood)
Modify it to a larger number and click hit me, and health will change
pub fn write_memory(&self, address: usize, value: &[u8]) { let mut write = 0; let mut old_protect: u32 = 0; if unsafe { //Unprotect memory using VirtualProtectEx /* BOOL VirtualProtectEx( [in] HANDLE hProcess, [in] LPVOID lpAddress, [in] SIZE_T dwSize, [in] DWORD flNewProtect, [out] PDWORD lpflOldProtect ); */ winapi::um::memoryapi::VirtualProtectEx( self.handle.as_ptr(), address as LPVOID, value.len(), PAGE_EXECUTE_READWRITE, &mut old_protect, ) } == FALSE { return; } if unsafe { //WriteProcessMemory writes specific data winapi::um::memoryapi::WriteProcessMemory( self.handle.as_ptr(), address as LPVOID, value.as_ptr().cast(), value.len(), &mut write, ) } == FALSE { return; } }
Code download: https://github.com/Mrack/rust_cheatengine
Pay attention to my technical official account.
Analyze various technical articles irregularly