Author:linshao
V公众
目录
一.目的
二. 相关要求
三.具体方法
一、目的:
通过开发简单游戏辅助来加深对windows api操作,以及程序内存的理解
二、相关要求:》》1.golang编程基础/C++基础
》》2.简单理解PE在内存中的知识,基址(BaseAddress),偏移(RVA)
》》3.windows Api
》》4.一颗好用的脑子
三、具体方法0x01.准备工作:
工具:
》》go
》》Cheat Engine 7.4(CE神器)
》》idea++(写程序用)
游戏:单机游戏:Dissonance(steam上下载)
0x02.查找偏移
进入游戏
ce打开进程
进行扫描,我是知道值是4字节所以为了快速记录直接搜索了
查找游戏基质
多捡几个塑料瓶子,方便没血了喝。(诺,就这个玩意):
去找这个漩涡,手电照一下它就有伤害性了,别问我为什么不去找怪,那玩意把握不住死了就白忙活了
像这样
对着血条搜索,不行了就嗑药,
先首次扫描,再掉血,去搜索
最终找到少量数据,右键添加到地址栏
选中几个右键锁定,再次尝试发现不掉血了就对了,那血量的地址就在这几个之中,
分别锁定几个地址后找到唯一能有效锁定血量的值
对唯一地址进行指针扫描,找到偏移路径
扫描结果。这些都是基址经过偏移量到达血量的路径
这个地址可能会有重启游戏就不能用的,所以需要筛选,操作如下:
重启游戏后再次找到血量地址,使用这个指针文件去扫描,然后筛选血量值
如果有一样的,那说明这个链路可以使用。
重启游戏,ce打开该进程,点击查看内存->工具->指针扫描
打开之前保存的文件进行扫描
扫描后可以看到最后一列有些是空值,那就是失效的指针
像红框的那种有数值且很多是一样的,那就是血量的地址的值,选择一条单击后添加到地址栏
可以修改为其他值看血量有无变化,有变化说明对了
然后双击值复制它,便于后面过滤无效指针
点击工具栏的Point Scanner->Rescan memory->value=1来过滤所有值为272549262的指针,这6604条指针偏移路径都是可以使用
回到主界面,记录一下偏移(RVA相对虚拟地址),
获取到有效的地址偏移后就可以开始写程序了
C++会更简单,但是我有写过go的枚举pe模块基址的程序,代码拿来就用 ,多好哈哈哈
0x03.实现思路:
1.先通过执行wmic命令获取游戏的pid(ProcessId)
2.调用windows的OpenProcess函数打开目标进程,获取返回到的句柄
3.调用EnumProcessModules函数枚举目标进程的模块,查找ResonanceEAE-Win64-Shipping.exe并返回它的地址
4.获取到基址后,使用它加上之前得到的偏移路径,一步一步相加获取到最终角色的血量的真实地址
5.调用ReadProcessMemory函数读取该地址就可以获取到角色血量
6.调用WriteProcessMemory函数修改该地址
7.在程序实现中可以:开子线程来循环读取血量的值有无改变。有变化就修改回来,就达到了锁血的功能
主要windows Api
码子在文末,注意编译最好不要命令行go build main.go,尽量使用ide工具编译,
不然就会这样:
看一下效果,其他功能什么飞天遁地呀可以自己花时间去找偏移(我找的时候从第3关,穿越到了第6关哈哈哈)
0x04.代码:
package main
import (
"fmt"
"os/exec"
"regexp"
"strconv"
"strings"
"syscall"
"time"
"unsafe"
)
type obj struct {
address int
}
var (
flag1 int
flag2=0
kernel32=syscall.MustLoadDLL("kernel32.dll")
OpenProcess=kernel32.MustFindProc("OpenProcess")
ReadProcessMemory=kernel32.MustFindProc("ReadProcessMemory")
WriteProcessMemory=kernel32.MustFindProc("WriteProcessMemory") //写入内存
Psapi=syscall.MustLoadDLL("Psapi.dll")
EnumProcessModules =Psapi.MustFindProc("EnumProcessModules")
GetModuleBaseNameA=Psapi.MustFindProc("GetModuleBaseNameA")
)
func main(){
var pid int64
var modelName string
modelName="ResonanceEAE-Win64-Shipping.exe"
pid=getProcPid(modelName)
fmt.Println(pid)
//打开进程句柄
handle:=getModelHandle(pid)
//fmt.Println("句柄",handle)
if handle<=0{fmt.Printf(" 打开句柄失败",handle)
return}
//获取模块基址
BaseAddress:=GetProcessMoudleBase(handle,modelName)
//fmt.Printf("[+] 基址%X\n",BaseAddress)
fmt.Printf("\n************************************************\n")
fmt.Printf("\n\t\t==linshao--功能面板==\t\t")
RVA2:=[...]int64{0x04D98A40,0x118,0xA0,0x2B0,0x20,0x1A0,0x90,0xB8}
//RVA2:=[...]int64{0x04CF3798,0x68,0x110,0xE0,0x260,0x1A0,0x90,0xB8}
//其他偏移路径
// 04CF3798 120 110 F8 20 1A0 90 B8
// 04DB5400 400 8 78 A0 1A0 90 B8
xueAddr,xueValue:=readXue(handle,BaseAddress,RVA2)
fmt.Printf("[!]当前游戏角色血量--->%d(%f%%)\n",xueValue,(float32(xueValue)/1120403456)*100)
for true{
fmt.Printf("1开启/0关闭锁血/2退出->")
var gn int
fmt.Scanln(&gn)
if gn==1{
flag2=1
go suoxie(handle,xueAddr,xueValue)
if flag1==1{
fmt.Printf("----------》锁满血成功\n")
}
}
if gn==0 {
flag2=0
}
if gn==2{
return
}
fmt.Printf("----------------------\n")
fmt.Printf("当前信息:\n")
if flag2==0{
fmt.Printf("锁血[关闭]\n")
}
if flag2==1{
fmt.Printf("锁血[开启]\n")
}
fmt.Printf("----------------------\n")
}
}
func suoxie(handle uintptr,xueAddr int64,xueValue int){
defer func() {
err:=recover()
if err!=nil{
fmt.Println("成功捕获一个异常")
}
}()
for flag2!=0{
value:=0
ReadProcessMemory.Call(handle, uintptr(xueAddr),uintptr(unsafe.Pointer(&value)),unsafe.Sizeof(value),0)
if value!=1272549262{
xueValue=1272549262
a,_,_:=WriteProcessMemory.Call(handle,uintptr(xueAddr),uintptr(unsafe.Pointer(&xueValue)),unsafe.Sizeof(xueValue))
if a>0{
flag1=1
}
}
time.Sleep(100*time.Millisecond)
}
}
func readXue(handle uintptr,BaseAddress int64,RVA2 [8]int64) (int64,int){
fmt.Printf("\n************************************************\n")
var CurrentAddress int64
//fmt.Println("模块名",modelName)
fmt.Printf("[*] 使用的RVA偏移量链 -》%X\n",RVA2)
CurrentAddress=BaseAddress
var value int
var tmp64 int64
for _,x:=range RVA2{
value=0
tmp64=CurrentAddress+x
ReadProcessMemory.Call(handle, uintptr(tmp64),uintptr(unsafe.Pointer(&value)),unsafe.Sizeof(value),0)
fmt.Printf("访问0x%X >> (RVA) %X -> (0x%X) %X\n",CurrentAddress,x,tmp64,value)
CurrentAddress=int64(value) //把偏移后获取到的实际地址值放入下一次循环计算的基地址
}
return tmp64,value
}
func GetProcessMoudleBase( hProcess uintptr, moduleName string)int64{
//异常处理
defer func() {
//捕获异常
err := recover()
if err != nil {
fmt.Println("[!]发生异常")
}
}()
// 遍历进程模块,
var hModel =[10000]int64{0}
var lpcbNeeded int=0 //将所有模块句柄存储在 lphModule 数组中所需的字节数
var cb= int(unsafe.Sizeof(hModel)) //lphModule 数组的大小,以字节为单位。
isok,_,_:=EnumProcessModules .Call(hProcess, uintptr(unsafe.Pointer(&hModel)), uintptr(cb), uintptr(unsafe.Pointer(&lpcbNeeded)))
num:=lpcbNeeded/int(unsafe.Sizeof(hModel[0]))
//fmt.Println("----------lpcbNeeded所需字节",lpcbNeeded,"当前",cb,"需要长度",num)
if isok<=0 {
fmt.Println("[!] 枚举模块失败")
}
fmt.Printf("[+] 枚举进程模块成功,共%d个\n",num)//,hModel)
tmp:=[50]byte{}
a:=""
for i:=0;i<num;i++{
GetModuleBaseNameA.Call(hProcess, uintptr(hModel[i]),uintptr(unsafe.Pointer(&tmp)),50)
for _,v:=range tmp{
if(v==0){continue}else {a+=string(v)}
}
if(strings.EqualFold(moduleName, a)) {
fmt.Printf("[+] find! 模块名字%s 地址:0x%X\n", a, hModel[i])
return int64(hModel[i])
}
fmt.Printf(" > %s \t--->: 0x%X",a, hModel[i])
fmt.Printf(" \n ")
a=""
tmp=[50]byte{}
}
return 0
}
func getProcPid(PROCESS string)int64{
task:=exec.Command("cmd","/c","wmic", "process", "get", "name,","ProcessId","|","findstr",PROCESS)
data2, _ := task.CombinedOutput()
res:=strings.Split(string(data2),"\n")[0]//取第一行程序结果
re := regexp.MustCompile("[0-9]+")
pid,_:=strconv.ParseInt(re.FindAllString(res,-1)[1],10,64)
return pid
}
func getModelHandle(_pid int64)uintptr{
hand,_, err :=OpenProcess.Call(2097151, uintptr(0), uintptr(_pid))
fmt.Println("[+] 打开目标进程成功",hand)
checkErr(err)
return hand
}
func checkErr(err error){
if err!=nil {
if err.Error() != "The operation completed successfully." {
fmt.Println("报错:",err.Error())
syscall.Exit(1)
return
}
}
}
不来一下子??
V: