Rust homemade memory cheater

Posted by Bobulous on Mon, 14 Feb 2022 02:59:42 +0100

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

Topics: Back-end Rust