avatar

目录
使用Python读写游戏4(GTA)

EPIC送了GTA5,最近考完试和朋友一起玩了下,由于刚开始,等级低,朋友带我打任务的时候,完全就打不过,我的甲没几下就爆了,刚开始没什么。

但是今天去打末日3的时候,由于只有两个人,玩过的都知道,末日3是需要一个人去玩小游戏,然后其他人保护玩小游戏那个队友,末日3的NPC是会一直刷,怎么打都打不完,有一瞬间就变成了CSGO练爆头模式,完全没得打(打不过就充钱,没得充钱就变强)。后面搜了搜,发现别人歪瓜的截图,有锁血功能,就想去了解一下。

打开GTAV,进入故事模式。GTAV官方在故事模式中,有给出作弊代码,其中有一个无敌五分钟的命令。从这个命令入手,查找他的基址,没准能用。

作弊代码:PAINKILLER

Code
1
2
3
4
5
6
7
8
搜索思路
👇
开启无敌时候,可能会有数值增加,可能有一个数值或者开关,会变为1。这时搜索增加的数值
关闭无敌时候,可能会有数值减少,可能有一个数值或者开关,会变为0。这时搜索减少的数值

开启无敌时候,可能会有数值减少,可能有一个数值或者开关,某个数值减少。这时搜索减少的数值
关闭无敌时候,可能会有数值减少,可能有一个数值或者开关,某个数值增加。这时搜索增加的数值
👆

搜索基址

CE加载GTA5,先通过常规思路,无敌搜增加,关闭搜减少。

即使这游戏是64位的,但是依然扫4字节就够了。首先先扫一次未知的初始值。

进入GTA,按~键,输入作弊代码PAINKILLER

输入成功时候,右下角会有无敌时间,正常为五分钟。

这时候去CE搜索增加的数值

由于游戏很大,所以第一次搜增加的时候,会很卡,而且会扫很久,我的电脑要扫大概两分半钟,而且内存会被吃满

而且扫的次数多了,会产生很多临时文件

像我这样,C盘直接没位置了,所以建议备一个清理C盘的软件。时不时清理临时文件

看会小说或者刷会短视频,等扫描结果出来以后,就再次输入作弊代码,就可以将无敌关闭(我一开始不知道,傻傻得等了五分钟)。

如何查看是否关闭,看右下角的倒计时是否存在。如果没了,那就是关闭了。

这时候搜索减少的数值。

步骤循环,等结果少于5000的时候。就可以通过搜索未变动的数值,来开始查找最终的基址。

快捷键,CTRL+D,可以快速查看该地址反汇编地址。

因为我搜索过很多次,所以认得特征,当反汇编窗口的代码,是以下这串时候,我就知道找到了

Code
1
or [rax],al

为什么了,因为这个al在线上模式时候,他是0或者1,0就是关闭无敌,1就是开启无敌。

一开始我怎么找到这个的了,我当时扫了很多次,发现有一次,他的值不是al,而是0,然后当我开启无敌时候,他就变为1了。我就把它认住了。发现有的时候在线下模式,他就是会这样,抽风。

关闭无敌的默认初始值是:4194316

当你把他的数值改为这个初始值时,他这个al就会变为0

但是即使你把它改了,他还是会变,有时候会变为12,有时候会变很大的数,但是你测试一下,将00改为01时候,在去做一些扣血的行为时候,是不会扣血的,达到了无敌的效果。

基址找到了,那就开始找他的静态基址。

一开始尝试过一步一步找,然后发现,扫出来的基址过多,比较麻烦,所以后面决定使用指针搜索

我这里没调参数,直接确定了。

最后扫出来的结果,有9419539个,观察他的值,值等于初始值4194316或等于基址目前的值的时候,就把它双击,添加到地址表中。

一定要选多几个,因为进入线上模式时候,值会变,有的会变成?号,有的是不知道啥值,所以要选多几个加以观察,或者不关闭指针扫描结果窗口。

最终我找出来的基址

"GTA5.exe"+02D06110 +28+298+c8+a8+188五层偏移。


代码编写

游戏是64位的,所以要用64位的函数。

WOW64 函数

c++
1
2
3
4
5
ZwWow64QueryInformationProcess64
ZwWow64ReadVirtualMemory64
ZwWow64WriteVirtualMemory64
ZwWow64CallFunction64
32位程序几乎可以获取64位程序的全部函数调用功能。

首先导入函数

基本操作都没啥不同的。

python
1
2
3
4
5
6
7
8
9
10
11
12
# -*- coding:utf-8 -*-
"""
@author:
@file: GTA.py
@time: 2020-06-21 13:49
@desc: KeyboArd
"""
import win32process#进程模块
import win32api#主要用来注册热键
import ctypes#C语言类型
from win32gui import FindWindow#界面
from ctypes import c_long , c_int , c_void_p, windll, WinDLL, c_ulonglong, byref

初始化

python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
ntdll = WinDLL("ntdll.dll")#调用ntdll库
kernel32 = ctypes.windll.LoadLibrary("kernel32.dll")#调用kernel32库
GetLastError = kernel32.GetLastError#调用GetLastError函数
GTA = 0x7FF79B5D0000#GTA.EXE的基址,原本是打算通过模块名字获取的,但是不知道为什么返回-1.可能是64位游戏,涉及到一些权限问题,后面在研究一下,目前主要是为了达到目的。
STANDARD_RIGHTS_REQUIRED = 0x000F0000#这两个的值,是在C++那里获取过来的。
SYNCHRONIZE = 0x00100000#看图
PROCESS_ALL_ACCESS = (STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0xFFF)#看图

class PROCESS_BASIC_INFORMATION(ctypes.Structure):
_fields_ = [('ExitStatus', ctypes.c_ulonglong), # 接收进程终止状态
('PebBaseAddress', ctypes.c_ulonglong), # 接收进程环境块地址
('AffinityMask', ctypes.c_ulonglong), # 接收进程关联掩码
('BasePriority', ctypes.c_ulonglong), # 接收进程的优先级类
('UniqueProcessId', ctypes.c_ulonglong), # 接收进程ID
('InheritedFromUniqueProcessId', ctypes.c_ulonglong)] # 接收父进程ID

## OpenProcess
OpenProcess = windll.kernel32.OpenProcess
OpenProcess.argtypes = [c_void_p, c_int, c_long]
OpenProcess.rettype = c_long
## CloseHandle
CloseHandle = windll.kernel32.CloseHandle
CloseHandle.argtypes = [c_void_p]
CloseHandle.rettype = c_int

def _GetProcessId(className,windowName):
hGameWindow = FindWindow(className, windowName)
pid = win32process.GetWindowThreadProcessId(hGameWindow)[1]
return pid

def _GetPorcessHandle(pid):
hGameHandle = OpenProcess(PROCESS_ALL_ACCESS, 0, pid)
return hGameHandle

ProcessId = _GetProcessId("grcWindow", u"Grand Theft Auto V")
_hGameHandle = _GetPorcessHandle(ProcessId)

对64位内存读操作

python
1
2
3
4
5
6
def _ReadMemeryLong64(addr, bufflength):
addr = c_ulonglong(addr)#64位游戏,我测试过,如果只写c_ulong() 会报错,说有冲突。
ret = c_ulonglong()#所以这里也都保持同样的长度把
BufferLength = c_ulonglong(bufflength)
ntdll.ZwWow64ReadVirtualMemory64(int(_hGameHandle), addr, byref(ret), BufferLength, 0)#ZwWow64ReadVirtualMemory64和ReadProcessInt 这个函数操作类似。
return ret.value

64位通过模块名获取基址

python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
def GetBaseAddr(ModuleName):  #这是论坛里一位大佬写的,为了方便快捷,先复制粘贴过来,这代码我也没看懂,好像涉及到了pe知识,后面自己在 写一个。
# 传入需要查找的模块的名称,就可以返回相应的模块基址了
NumberOfBytesRead = c_ulong()
Buffer = PROCESS_BASIC_INFORMATION()
Size = c_ulong(48)
name_len = len(ModuleName)

ntdll.NtWow64QueryInformationProcess64(int(_hGameHandle), 0, byref(Buffer), Size,
byref(NumberOfBytesRead))
"""
这同样是一个未公开的api,可以通过他获取进程的信息,然后存入我们一开始定义的结构体中,他的五个参数分别是:
进程句柄,信息类型,缓冲指针,以字节为单位的缓冲大小, 写入缓冲的字节数
而至于下面为什么要这么写,其实涉及到了程序的PE结构,这里不做赘述,因为这个东西不是一会会说的清楚的,可以自行百度
"""
ret = _ReadMemeryLong64(Buffer.PebBaseAddress + 24, 8)
ret = _ReadMemeryLong64(ret + 24, 8)

for i in range(100000): # 这里用for循环其实是怕程序卡死,下面如果出了问题不能退出的话,循环结束一样可以退出
modulehandle = _ReadMemeryLong64(ret + 48, 8)
if modulehandle == 0:
break
nameaddr = _ReadMemeryLong64(ret + 96, 8)
name = ReadProcessMemory64_Wchar(nameaddr, name_len * 2 + 1, name_len)
if name == ModuleName:
return modulehandle
ret = _ReadMemeryLong64(ret + 8, 8)

对找到的基址进行读取

python
1
2
3
4
5
6
7
8
9
10
11
12
13
def get_invincible(hGameHandle):
#"GTA5.exe"+02D06110 +28+298+c8+a8+188 这是找到的基址
value = GTA+ 0x02D06110#GTA的基址等于0x7FF79B5D0000 ,如果重启了电脑,这个基址会变,所以一定要写64位通过模块名读取基址。
value1 = _ReadMemeryLong64(value,8)#先读取一下"GTA5.exe"+02D06110的基址。看下是否正确。
value2 = _ReadMemeryLong64(value1+0x28,8)
value3 = _ReadMemeryLong64(value2+0x298,8)
value4 = _ReadMemeryLong64(value3+0xc8,8)
value5 = _ReadMemeryLong64(value4+0xa8,8)
value6 = _ReadMemeryLong64(value5+0x188,8)
# #value1 = value++0x28+0x298+0xc8+0xa8+0x188
print("0x%08X"%value2)
print(value)
return value5

value:"GTA5.exe"+02D06110

value1:GTA5.exe"+02D06110

value2:GTA5.exe"+02D06110+0x28

value3:GTA5.exe"+02D06110+0x28+0x298

value4:GTA5.exe"+02D06110+0x28+0x298+0XC8

value5:GTA5.exe"+02D06110+0x28+0x298+0XC8+0xa8

value6:GTA5.exe"+02D06110+0x28+0x298+0XC8+0xa8+0x188

成功读取到该值,为什么这么写,因为一开始我直接省略麻烦,写一堆,发现读的都是什么东西,太乱了。这样子容易找到问题。

64位写操作

python
1
2
3
4
5
def WriteMemeryLong64(addr, bufflength, n):  
addr = c_ulonglong(addr)
ret = c_ulonglong(bufflength)
BufferLength = c_ulonglong(n)
ntdll.ZwWow64WriteVirtualMemory64(int(_hGameHandle), addr, byref(ret), BufferLength, 0)##写法也跟WriteProcessMemory这个函数相似

无敌

初始:4194316
无敌:4194572

python
1
2
3
4
5
6
7
8
def set_invincible(hGameHandle,address):
while True:
WriteMemeryLong64(hGameHandle,address+0x188,4194572,4)#开启无敌

if(win32api.GetAsyncKeyState(35)!=0):#当按下END键,就关闭无敌,将值写回正常
WriteMemeryLong64(hGameHandle, address + 0x188, 4194316, 4)#关闭无敌
CloseHandle(hGameHandle)#关闭句柄
exit(0)#退出程序

开启:

关闭:

完整代码

python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
# -*- coding:utf-8 -*-
"""
@author:
@file: GTA.py
@time: 2020-06-21 13:49
@desc: KeyboArd
"""
import win32process#进程模块
import win32api#主要用来注册热键
import ctypes#C语言类型
from win32gui import FindWindow#界面
from ctypes import c_long , c_int , c_void_p, windll, WinDLL, c_ulonglong, byref,Structure,c_char,POINTER,sizeof,pointer,c_ulong,c_wchar_p

ntdll = WinDLL("ntdll.dll")
kernel32 = ctypes.windll.LoadLibrary("kernel32.dll")
GetLastError = kernel32.GetLastError
#GTA = 0x7FF79B5D0000
STANDARD_RIGHTS_REQUIRED = 0x000F0000
SYNCHRONIZE = 0x00100000
PROCESS_ALL_ACCESS = (STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0xFFF)


class PROCESS_BASIC_INFORMATION(ctypes.Structure):
_fields_ = [('ExitStatus', ctypes.c_ulonglong), # 接收进程终止状态
('PebBaseAddress', ctypes.c_ulonglong), # 接收进程环境块地址
('AffinityMask', ctypes.c_ulonglong), # 接收进程关联掩码
('BasePriority', ctypes.c_ulonglong), # 接收进程的优先级类
('UniqueProcessId', ctypes.c_ulonglong), # 接收进程ID
('InheritedFromUniqueProcessId', ctypes.c_ulonglong)] # 接收父进程ID

## OpenProcess
OpenProcess = windll.kernel32.OpenProcess
OpenProcess.argtypes = [c_void_p, c_int, c_long]
OpenProcess.rettype = c_long
## CloseHandle
CloseHandle = windll.kernel32.CloseHandle
CloseHandle.argtypes = [c_void_p]
CloseHandle.rettype = c_int



def _GetProcessId(className,windowName):
hGameWindow = FindWindow(className, windowName)
pid = win32process.GetWindowThreadProcessId(hGameWindow)[1]
return pid

def _GetPorcessHandle(pid):
hGameHandle = OpenProcess(PROCESS_ALL_ACCESS, 0, pid)
return hGameHandle

ProcessId = _GetProcessId("grcWindow", u"Grand Theft Auto V")
_hGameHandle = _GetPorcessHandle(ProcessId)

def _ReadMemeryLong64(addr, bufflength):
addr = c_ulonglong(addr)
ret = c_ulonglong()
BufferLength = c_ulonglong(bufflength)
ntdll.ZwWow64ReadVirtualMemory64(int(_hGameHandle), addr, byref(ret), BufferLength, 0)
return ret.value

def WriteMemeryLong64(addr, bufflength, n): #
addr = c_ulonglong(addr)
ret = c_ulonglong(bufflength)
BufferLength = c_ulonglong(n)
ntdll.ZwWow64WriteVirtualMemory64(int(_hGameHandle), addr, byref(ret), BufferLength, 0)

def ReadProcessMemory64_Wchar(addr, n,length):#这个函数用以读取模块名称,与ReadVirtualMemory64不同的点还有一个是我们会传入一个缓冲区的长度length,用于定义一个c_wchar_p的缓冲区
addr = c_ulonglong(addr)
ret = c_wchar_p("0" * length)#这里选用wchar其实与编码有关,感兴趣的同学自行百度wchar,宽字符等关键字学习
BufferLength = c_ulonglong(n)
ntdll.NtWow64ReadVirtualMemory64(int(_hGameHandle), addr, ret, BufferLength, 0)
return ret.value

def GetBaseAddr(ModuleName): #
# 传入需要查找的模块的名称,就可以返回相应的模块基址了
NumberOfBytesRead = c_ulong()
Buffer = PROCESS_BASIC_INFORMATION()
Size = c_ulong(48)
name_len = len(ModuleName)

ntdll.NtWow64QueryInformationProcess64(int(_hGameHandle), 0, byref(Buffer), Size,
byref(NumberOfBytesRead))
"""
这同样是一个未公开的api,可以通过他获取进程的信息,然后存入我们一开始定义的结构体中,他的五个参数分别是:
进程句柄,信息类型,缓冲指针,以字节为单位的缓冲大小, 写入缓冲的字节数
而至于下面为什么要这么写,其实涉及到了程序的PE结构,这里不做赘述,因为这个东西不是一会会说的清楚的,可以自行百度
"""
ret = _ReadMemeryLong64(Buffer.PebBaseAddress + 24, 8)
ret = _ReadMemeryLong64(ret + 24, 8)

for i in range(100000): # 这里用for循环其实是怕程序卡死,下面如果出了问题不能退出的话,循环结束一样可以退出
modulehandle = _ReadMemeryLong64(ret + 48, 8)
if modulehandle == 0:
break
nameaddr = _ReadMemeryLong64(ret + 96, 8)
name = ReadProcessMemory64_Wchar(nameaddr, name_len * 2 + 1, name_len)
if name == ModuleName:
return modulehandle
ret = _ReadMemeryLong64(ret + 8, 8)


def get_invincible(hGameHandle,GTA):
#"GTA5.exe"+02D06110 +28+298+c8+a8+188
value = GTA+ 0x02D06110
value1 = _ReadMemeryLong64(value,8)#先读取一下"GTA5.exe"+02D06110的基址。看下是否正确。
value2 = _ReadMemeryLong64(value1+0x28,8)
value3 = _ReadMemeryLong64(value2+0x298,8)
value4 = _ReadMemeryLong64(value3+0xc8,8)
value5 = _ReadMemeryLong64(value4+0xa8,8)
value6 = _ReadMemeryLong64(value5+0x188,8)
#value1 = value++0x28+0x298+0xc8+0xa8+0x188
# print("0x%08X"%value6)
print(value6)
return value5


def set_invincible(hGameHandle,address):
while True:
WriteMemeryLong64(address+0x188,4194572,4)#开启无敌

if(win32api.GetAsyncKeyState(35)!=0):
WriteMemeryLong64(address + 0x188, 4194316, 4)#关闭无敌
CloseHandle(hGameHandle)
exit(0)

def main():
moudle = GetBaseAddr("GTA5.exe")
invincible = get_invincible(_hGameHandle, moudle)
#print("0x%08X" % moudle)
#print(moudle)
#print("0x%08X" % modulename)
set_invincible(_hGameHandle,invincible)

CloseHandle(_hGameHandle)

if __name__ == '__main__':
main()


文章作者: KeyboArd
文章链接: https://www.wrpzkb.cn/GTA/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 KeyboArd's Blog
打赏
  • 微信
    微信
  • 支付寶
    支付寶

评论