安全技术系列之Rust自制内存作弊器


 

前言

Cheat Engine是一款专注于游戏的作弊器。它可以用来扫描游戏中的内存,并允许修改它们。这个工具也许没有听说过,但是十几年前大名鼎鼎的金山游侠想必每个人都用过。

Rust具有快速,跨平台,低资源占用的优点,偶然机会下看到了一个用Rust写的项目,喜欢于他的语法简练,聚集很多编程语言优点于一身。于是借着空闲时间去学习Rust,想到很多年没用的WinApi正好借着这个机会复习下,便有了以下的内容。

搭建Rust环境

可参考https://www.runoob.com/rust/rust-setup.html

枚举进程

这里使用的是CE自带的demo

枚举进程,让用户输入进程PID来选择修改的进程这里由于是在windows平台需要用到winapi

添加Cargo.toml依赖

[dependencies]

winapi = { version = "0.3.9"features = ["psapi","processthreadsapi","handleapi","memoryapi"] }

//这里将所有所需的API添加上去了

使用EnumProcesses获取所有进程

pub fn enum_proc() -> io::Result<Vec<u32>> {

   let mut size = 0//返回的结构体大小

   let mut pids = Vec::<DWORD>::with_capacity(2048); //分配pid数组大小

   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());

  }

   

   //计算获取到的进程数,使用set_len设置数组个数

   let count = size as usize / std::mem::size_of::<DWORD>();

   unsafe { pids.set_len(count) }

   Ok(pids)

}

打开进程

   pub fn open(pidu32-> 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(|handleSelf { pidhandle })

      .ok_or_else(io::Error::last_os_error)

  }

获取进程名

拿到进程pid后但是并不清楚进程名是什么,以下代码获取进程名

 

拿到进程pid后但是并不清楚进程名是什么,以下代码获取进程名

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() };

       //进程名最长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());

      }

       //中文显示未修复

       unsafe { buffer.set_len(length as usize) };

       return match String::from_utf8(buffer) {

           Ok(s=> Ok(s),

           Err(e=> Ok("".to_string()),

      };

}

获取内存区域

为了提高首次搜索的速度,先获取内存区域

   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>(),

              )

          };

           //结束

           if written == 0 {

               break;

          }

           let info = unsafe { info.assume_init() };

           //计算下一块区域base

           base = info.BaseAddress as usize + info.RegionSize;

           

           //保存

           regions.push(info);

      }

       let mask = winnt::PAGE_EXECUTE_READWRITE

      | winnt::PAGE_EXECUTE_WRITECOPY

      | winnt::PAGE_READWRITE

      | winnt::PAGE_WRITECOPY;

       //过滤掉系统模块,无效区域

       return regions.into_iter().filter(|x|!(x.BaseAddress as u32 > 0x70000000

      && (x.BaseAddress as u32< 0x80000000)).filter(|p| (p.Protect & mask!= 0).collect();

  }

首次搜索

我们搜索这个100,得到262个地址

 

代码如下:

   //获取所有内存区域

   let regions:Vec<MEMORY_BASIC_INFORMATION> = process.memory_regions();

   

   println!("Scanning {} memory regions"regions.len());

   println!("Which exact value to scan for?");

   

   //得到输入值转bytes

   let mut input = String::new();

   stdin().read_line(&mut input).unwrap();

   let targetu32 = input.trim().parse::<u32>().unwrap();

   let target = target.to_ne_bytes();

   

   //存储位置数组    

   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(|(offsetwindow)| {

                   //是否为输入的值

                   if window == target {

                       locations.push(region.BaseAddress as usize + offset);

                  }

              })

          },

           Err(e)=>continue

      }

  }

再次搜索,直到找到结果

点击[hit me]结果变成97了,我们再去访问刚刚搜索的地址看哪个不是97就将他删除掉

 

    //只剩一个结果跳出循环

   while locations.len() != 1 {

       println!("Next Scan value:");

       

       //输入的值

       let mut input = String::new();

       stdin().read_line(&mut input).unwrap();

       let targetu32 = input.trim().parse::<u32>().unwrap();

       let target = target.to_ne_bytes();

       //使用retain删除不满足要求的结果

       locations.retain(|addrmatch process.read_memory(*addrtarget.len()) {

           Ok(memory=> memory == target,

           Err(_=> false,

      });

  }

修改内存数据

经过多次查找只剩下一个地址,我们可以修改成任意值达到作弊的效果(如定时器一直修改锁血)

 

修改成比较大的数再点击Hit me,Health就发生了变化

 

修改成比较大的数再点击Hit me,Health就发生了变化

 

pub fn write_memory(&selfaddressusizevalue: &[u8]) {

       let mut write = 0;

       let mut old_protectu32 = 0;

       if unsafe {

           //使用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写入特定的数据

           winapi::um::memoryapi::WriteProcessMemory(

               self.handle.as_ptr(),

               address as LPVOID,

               value.as_ptr().cast(),

               value.len(),

              &mut write,

          )

      } == FALSE

      {

           return;

      }

  }