前言
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(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)
}
获取进程名
拿到进程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 target: u32 = 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(|(offset, window)| {
//是否为输入的值
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 target: u32 = input.trim().parse::<u32>().unwrap();
let target = target.to_ne_bytes();
//使用retain删除不满足要求的结果
locations.retain(|addr| match process.read_memory(*addr, target.len()) {
Ok(memory) => memory == target,
Err(_) => false,
});
}
修改内存数据
经过多次查找只剩下一个地址,我们可以修改成任意值达到作弊的效果(如定时器一直修改锁血)
修改成比较大的数再点击Hit me,Health就发生了变化
修改成比较大的数再点击Hit me,Health就发生了变化
pub fn write_memory(&self, address: usize, value: &[u8]) {
let mut write = 0;
let mut old_protect: u32 = 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;
}
}