经过前面二节的介绍,相信大家已经学会如何从窗口名或类获取游戏PID并且读写指定位置的值了。今天就上一个代码:传颂之物修改器基础上,修改一下,来写一个真正可用的修改器,因为传颂之物的那个能力点数的地址是变化的,所以我们需要改比较多的代码,稍微复杂了一些,呵呵,以后我们再慢慢讲,先用这个红色警戒的温习一下知识,来个实战!
欲善其事,先利其器,先说说用到的工具:
1、游戏修改器:
用来查找金钱所在地址,必不可少
2、跟踪工具:
用来跟踪金钱地址被哪个位置的代码更改,分析这些代码,并改写他们,就能达到我们的目的(汇编,头疼,我也不会,不过这个例子实在是简单,继续~~~)
以上2者一般分别用金山游侠(它的搜索能力至今我没找到一个可以媲美的,无论速度,还是其跟踪,这个跟踪我想大家都有体会,其他游戏修改器查不到的,它却能查到,也许是内置了代码分析功能吧,悍);而说起跟踪,大家都会马上想到动态调试利器SOFTICE;这两款软件确实是相当强,不过我是用不来的,装了虚拟机,装98,结果虚拟机的驱动竟然DW测试不通过,晕,换2K,测试通过,可BPM断点又拦不下来,看起来这些好东东和我无缘啊。不过你也别着急,既然修改器写出来了,就有其他办法可以施展,介绍一款游戏修改调试工具:
CE,呵呵,可谓大名鼎鼎了,全名是Cheat Engine我用的5.2版本,虽然刚刚上手,还不熟悉,不过已经够我们写修改器了,如果你真没用过,去下一个并且完成它的教程,大概也就几分钟。
窗口类及名称获取工具:
SPY+++(不是我打多了,是一个SPY++模仿版本,华军上有下的,比较和我的口味)
编程工具:
VB6+SP6
在你的电脑上开始做吧!跟我的步骤来:
1、运行RA2(我的是1.06版本),建立一个地图,哈,岛屿群,2个玩家,嘿嘿,这样即使下面步骤进行的慢电脑也不至于灭了我吧
2、打开SPY+++,点上面第2个按钮,获取进程列表,打开GAME.EXE进程树,单击列表查看窗口类名和窗口名
3、打开CE
a、点左上的小电脑,选择进程为GAME.EXE(别怀疑,就是它,不是RA2.EXE)
b、4字节方式搜索金钱地址(这个过程最好别建立矿厂哦,可能影响CE的汇编定位)最后有3个地址,选地址值比较长的那个,呵呵,选其他的也没关系,试验一下就知道了,你还会回来选长的那个的。
c、双击该地址,添加到CE下方列表,右键菜单中选择Find out what writes to this address,弹出对话框中选OK
d、回到游戏,花点钱,切回到CE,发现对话框里已经出现了一个汇编语句:004e53af - 89 83 4c 02 00 00 - mov [ebx+0000024c],eax,点More information可以查看到改汇编地址上下2行的汇编代码,发现上面有一个SUB,呵呵,一定是个关键喽(当然你也可以点Show disassembler查看整个汇编代码),读一读这些关键代码,反正我不是非常明白,总之,改改看了,据说90大法比葵花宝典用来舒服!
e、点Show disassembler,向上移动一行汇编窗口就可以看见004e53a9 - 2b c7 - sub eax,edi了,右键选择Replace with code that does nothing ,清空代码
f、回到游戏,花钱,钱已经不减少了。不放心的话可以多建一些建筑,多透支一些,呵呵。
4、分析过程基本结束了,需要我们记住的有以下数据:
a、SPY+++中,GAME.EXE列表里面Red Alert 2字段,这里窗口名与类名相同
b、004e53a9 - 2b c7 - sub eax,edi ,这个说明了004e53a9 地址代码为2字节:2b c7也就是说,004e53a9 为2b004e53aa 为c7,这里004e53a9和2字节是我们必要的信息
分析完毕,写代码吧,用我们前面一节介绍的代码来改就可以了!
模块如下:
Option Explicit
'查找窗体写内存等
Private Declare Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As Long
Private Declare Function GetWindowThreadProcessId Lib "user32" (ByVal hwnd As Long, lpdwProcessId As Long) As Long
Private Declare Function OpenProcess Lib "kernel32" (ByVal dwDesiredAccess As Long, ByVal bInheritHandle As Long, ByVal dwProcessId As Long) As Long
Private Declare Function ReadProcessMemory Lib "kernel32" (ByVal hProcess As Long, ByVal lpBaseAddress As Any, ByVal lpBuffer As Any, ByVal nSize As Long, lpNumberOfBytesWritten As Long) As Long
Private Declare Function WriteProcessMemory Lib "kernel32" (ByVal hProcess As Long, lpBaseAddress As Any, lpBuffer As Any, ByVal nSize As Long, lpNumberOfBytesWritten As Long) As Long
Private Declare Function CloseHandle Lib "kernel32" (ByVal hObject As Long) As Long
Private Const STANDARD_RIGHTS_REQUIRED = &HF0000
Private Const SYNCHRONIZE = &H100000
Private Const SPECIFIC_RIGHTS_ALL = &HFFFF
Private Const STANDARD_RIGHTS_ALL = &H1F0000
Private Const PROCESS_ALL_ACCESS = STANDARD_RIGHTS_REQUIRED Or SYNCHRONIZE Or &HFFF
Private Const PROCESS_VM_OPERATION = &H8&
Private Const PROCESS_VM_READ = &H10&
Private Const PROCESS_VM_WRITE = &H20&
'权限提升
Private Declare Function GetCurrentProcess Lib "kernel32" () As Long
Private Declare Function LookupPrivilegeValue Lib "advapi32.dll" Alias "LookupPrivilegeValueA" (ByVal lpSystemName As String, ByVal lpName As String, lpLuid As LUID) As Long
Private Declare Function AdjustTokenPrivileges Lib "advapi32.dll" (ByVal TokenHandle As Long, ByVal DisableAllPrivileges As Long, NewState As TOKEN_PRIVILEGES, ByVal BufferLength As Long, PreviousState As TOKEN_PRIVILEGES, ReturnLength As Long) As Long
Private Declare Function OpenProcessToken Lib "advapi32.dll" (ByVal ProcessHandle As Long, ByVal DesiredAccess As Long, TokenHandle As Long) As Long
Private Const TOKEN_ASSIGN_PRIMARY = &H1
Private Const TOKEN_DUPLICATE = (&H2)
Private Const TOKEN_IMPERSONATE = (&H4)
Private Const TOKEN_QUERY = (&H8)
Private Const TOKEN_QUERY_SOURCE = (&H10)
Private Const TOKEN_ADJUST_PRIVILEGES = (&H20)
Private Const TOKEN_ADJUST_GROUPS = (&H40)
Private Const TOKEN_ADJUST_DEFAULT = (&H80)
Private Const TOKEN_ALL_ACCESS = (STANDARD_RIGHTS_REQUIRED Or TOKEN_ASSIGN_PRIMARY Or _
TOKEN_DUPLICATE Or TOKEN_IMPERSONATE Or TOKEN_QUERY Or TOKEN_QUERY_SOURCE Or _
TOKEN_ADJUST_PRIVILEGES Or TOKEN_ADJUST_GROUPS Or TOKEN_ADJUST_DEFAULT)
Private Const SE_PRIVILEGE_ENABLED = &H2
Private Const ANYSIZE_ARRAY = 1
Private Type LUID
lowpart As Long
highpart As Long
End Type
Private Type LUID_AND_ATTRIBUTES
pLuid As LUID
Attributes As Long
End Type
Private Type TOKEN_PRIVILEGES
PrivilegeCount As Long
Privileges(ANYSIZE_ARRAY) As LUID_AND_ATTRIBUTES
End Type
'获取占用内存
Private Declare Function GetProcessMemoryInfo Lib "PSAPI.DLL" (ByVal hProcess As Long, ppsmemCounters As PROCESS_MEMORY_COUNTERS, ByVal cb As Long) As Long
Private Type PROCESS_MEMORY_COUNTERS
cb As Long
PageFaultCount As Long
PeakWorkingSetSize As Long
workingSetSize As Long
QuotaPeakPagedPoolUsage As Long
QuotaPagedPoolUsage As Long
QuotaPeakNonPagedPoolUsage As Long
QuotaNonPagedPoolUsage As Long
PagefileUsage As Long
PeakPagefileUsage As Long
End Type
'提升权限为高
Public Function ToKen() As Boolean
Dim hdlProcessHandle As Long
Dim hdlTokenHandle As Long
Dim tmpLuid As LUID
Dim tkp As TOKEN_PRIVILEGES
Dim tkpNewButIgnored As TOKEN_PRIVILEGES
Dim lBufferNeeded As Long
Dim lP As Long
hdlProcessHandle = GetCurrentProcess()
lP = OpenProcessToken(hdlProcessHandle, TOKEN_ALL_ACCESS, hdlTokenHandle)
lP = LookupPrivilegeValue("", "SeDebugPrivilege", tmpLuid)
tkp.PrivilegeCount = 1
tkp.Privileges(0).pLuid = tmpLuid
tkp.Privileges(0).Attributes = SE_PRIVILEGE_ENABLED
lP = AdjustTokenPrivileges(hdlTokenHandle, False, tkp, Len(tkpNewButIgnored), tkpNewButIgnored, lBufferNeeded)
ToKen = lP
End Function
'获取内存内容
Public Function GetData(ByVal lppid As Long, ByVal lpADDress As Long, SaveData() As Byte, Optional ByVal dtLen As Long = 4)
Dim pHandle As Long ' 储存进程句柄
' 使用进程标识符取得进程句柄
pHandle = OpenProcess(PROCESS_ALL_ACCESS, False, lppid)
' 在内存地址中读取数据
ReadProcessMemory pHandle, ByVal lpADDress, ByVal VarPtr(SaveData(0)), dtLen, 0&
' 关闭进程句柄
CloseHandle pHandle
End Function
'将修改内存
Public Function SetData(ByVal lppid As Long, ByVal lpDestAddr As Long, lpSrcAddr() As Byte, Optional ByVal dtLen As Long = 4) As Boolean
On Error GoTo mErr
Dim lBytesReadWrite As Long
Dim pHandle As Long ' 储存进程句柄
' 使用进程标识符取得进程句柄
pHandle = OpenProcess(PROCESS_ALL_ACCESS, False, lppid)
WriteProcessMemory pHandle, ByVal lpDestAddr, ByVal VarPtr(lpSrcAddr(0)), dtLen, 0&
' 关闭进程句柄
CloseHandle pHandle
SetData = True
mErr:
End Function
Public Function GetPid(lpClassName As String, lpWindowName As String) As Long
' 取得进程标识符
GetWindowThreadProcessId FindWindow(lpClassName, lpWindowName), GetPid
End Function
'获取占用内存大小
Public Function GetMemorySize(ByVal lppid As Long) As Long
Dim tPMC As PROCESS_MEMORY_COUNTERS
Dim pHandle As Long ' 储存进程句柄
pHandle = OpenProcess(PROCESS_ALL_ACCESS, False, lppid)
GetProcessMemoryInfo pHandle, tPMC, Len(tPMC)
CloseHandle pHandle
GetMemorySize = tPMC.workingSetSize
End Function
主要修改了GETDATE函数,使之能够返回一个数组,其实这不是必须的,我们记下2b c7就可以了,不用去读,因为这是程序代码而不是数据,它的位置不会变,数据也不会变(当然是没有该死的保护程序时,这个以后等俺会了再告诉大家咋办吧,据说不修改代码而修改寄存器是不会被发现的哦,不过有的游戏已经把寄存器地址保护了,哎,写外挂还真不容易),把修改后的代码放在这里,也是想大家能复习一下SETDATA函数里面使用的方法:把数组作为参数交给程序处理后作为返回值,这样做有很多好处,相信大家以后编程时会感受到。
以下是窗体,(仍然是保存为 *.FRM文件)
VERSION 5.00
Begin VB.Form Form1
AutoRedraw = -1 'True
BorderStyle = 3 'Fixed Dialog
Caption = "★传颂之物修改器 VB DEMO"
ClientHeight = 885
ClientLeft = 45
ClientTop = 330
ClientWidth = 2670
LinkTopic = "侠义道补药"
MaxButton = 0 'False
MinButton = 0 'False
ScaleHeight = 885
ScaleWidth = 2670
ShowInTaskbar = 0 'False
StartUpPosition = 3 '窗口缺省
Begin VB.CommandButton Command1
Caption = "解除"
Height = 375
Left = 1440
TabIndex = 2
Top = 360
Width = 975
End
Begin VB.CommandButton Command2
Caption = "开始"
Height = 375
Left = 120
TabIndex = 0
Top = 360
Width = 975
End
Begin VB.Label Label4
Height = 255
Left = 0
TabIndex = 1
Top = 0
Width = 2655
End
End
Attribute VB_Name = "Form1"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = True
Attribute VB_Exposed = False
'请保留作者信息:
'ZCSOR于06-8-30开发
'E-MAIL:shaoyan5@163.com
Option Explicit
Dim SaveArr(1) As Byte
Dim GamePid As Long ' 储存进程标识符( Process Id )
Private Sub Command1_Click()
On Error GoTo m_Err
SetSub SaveArr()
Label4.Caption = "解除成功!"
m_Err:
Label4.Caption = "解除失败啦!"
Exit Sub
End Sub
Private Sub Command2_Click()
On Error GoTo m_Err
GamePid = GetPid("Red Alert 2", "Red Alert 2")
If GamePid = 0 Then
MsgBox "游戏未启动", 48
Exit Sub
End If
'获取写入金钱数据SUB实际内容(其实每次都一样啦是&H2B &HC7)
GetSub
Dim mBuff(1) As Byte '要写入的NOP指令
mBuff(0) = "&H90"
mBuff(1) = "&H90"
SetSub mBuff()
Label4.Caption = "锁定成功:)"
m_Err:
Label4.Caption = "锁定失败啦!"
Exit Sub
End Sub
Private Sub Form_Load()
ToKen
End Sub
Sub GetSub()
GetData GamePid, &H4E53A9, SaveArr(), 2
Debug.Print "&H" & Hex(SaveArr(0))
Debug.Print "&H" & Hex(SaveArr(1))
End Sub
Sub SetSub(buf() As Byte)
SetData GamePid, &H4E53A9, buf(), 2
End Sub
好了,重新运行游戏,切出来,锁定按钮,回去花钱,呵呵,大功告成,点解除,再回去花,不错吧!不过有一个问题,这样做以后,矿车采的钱还会加的,怎么让它不加呢?(谁闲钱多啊!我也不闲,但是我们还有工作要做)温习一下刚才的过程吧,重复刚才的步骤,把花钱变为钱增长,也就是说用Find out what writes to this address来看看钱增加的时候关键代码是什么,用90大法击败它吧。
恩,到这里,我们的教程先告一段落,相信大家已经具备写外挂的基本能力了,下一节是给修改器定义一个热键,然后才是用自定义汇编语句来修改金钱,呵呵(不过啊,红色警戒五项属性修改器不是这个思路,是去读金钱内存指针,然后直接写入,悍啊,读了3个地址然后写了1次,偶会把它的实现方法研究明白然后告诉大家的~~~)。
嘿嘿,不包括界面弹出显示,这方面的问题偶不会,但是大家可以参照帖子里面的一些内容:
http://community.csdn.net/Expert/topic/4894/4894060.xml?temp=.8059656
好了,本节结束!
本文代码和成品一发送到本站下载区,可以到那里去下载
http://down.csdn.net/html/2006-08/30/158792.html