前言
记得小学的时候,我玩网页游戏把家里的电脑弄坏了(网页游戏怎么会弄坏电脑呢?),然后我爸就拿去重装系统了,回来的时候电脑上多了两个新的单机游戏,植物大战僵尸和大鱼吃小鱼。
那时候,我身边的小学生都在玩《赛尔号》、《奥比岛》、《洛克王国》、《天书》、《龙之刃》、《梦幻西游》,玩《鼹鼠庄园》的小学生甚至还没上九年义务教育。《植物大战僵尸》虽然现在看来很老了,但它的游戏元素却催生了很多游戏音乐文化,比如 Billie Eilish 的《Bad Guy》,节奏就像植物大战僵尸里的灯、灯、灯、灯、灯。
当时因为被生存模式折磨的不行,所以在网上找了一个可以修改阳光点的修改器,虽然那时候我没学过计算机专业,但是对修改器的运行机制还是有一些猜测的,那时候我还在上小学,对内存什么的也不懂,所以就以为是修改文件里的数据。
这个猜测现在看起来好笑,但确实有道理(我快要吹毛求疵了)。有些游戏的数据可能先放在文件中,游戏启动后再把文件中的数据读入游戏的内存区域。所以我们需要先修改数据所在的文件内容,然后再启动游戏。但是很多游戏的数据文件都是加密的(一个大问题,Mount & Blade 就没加密),我们没法从文件中修改,所以我们需要先让游戏进程读入内存,再从内存中修改,然后退出游戏时游戏进程自动保存,然后这个数据才是留存下来的。
所以总结一下,我们可以发现修改对象有这么几种类型:
游戏数据本地没有加密,直接修改即可修改游戏数据
数据封装,通过修改内存来修改游戏数据
网络游戏中,数据在对方服务器,无法直接修改,可以通过模拟网络请求的形式来模拟获取游戏道具(取决于对方服务器的逻辑严密程度)
现在开始实战部分,因为不想再下载Mount and Blade,而且Mount and Blade官网上有很多关于如何修改文件的教程,所以就略过了。
1. 内存数据故障排除
游戏在我们的计算机中以进程的形式运行,因此要修改其数据,我们可以在内存中搜索和修改它。
以植物大战僵尸为例,我们想要修改阳光的量,假设我们现在的阳光量是75,那么我们需要在内存中找到75这个数字。
首先使用记忆工具打开植物大战僵尸流程。
然后搜索数字 25,下图中只搜索到一个,所以阳光数据存放在地址 21BF10C8,但很多游戏搜索到的地址可能不止一个,我们需要把查询到的道具(阳光)拿出来一点一点转化成其他数值再扫描一遍,才能准确定位。
修改该地址的值。
检查游戏中的阳光量。
是不是太简单了?!因为这个游戏本身的设计就已经与时代脱节了,如今的游戏厂商不仅要考虑内容,还要在安全性上与高阶玩家较量。
接下来就用另外一款游戏来练习一下,就是最近很火的老贼宫崎英高的作品《只狼》。
只狼:影逝二度
作为一名九轮下忍,这场游戏真正教会了我如何书写死亡这个词。
我先看一下我现在的金币,2323枚。
然后和平常一样选择Sekiro进程,搜索2323这个数据,会发现出现的次数很多。
我去杀了一只小怪,金币增加到2360。
再次搜索,发现还有5个地址,直接修改会发现没有任何变化。有猜测是它采用了类似病毒中多进程相互保护的方法来保持数据的一致,所以我们直接全部选中修改,会发现第三个并没有修改成功,因为这个数值并不是背包里的金币数量,而是战斗界面的数量。大家可以自己尝试一下。
成功添加了两万金币,本地其他数据也一样。你可以自己尝试一下,但是这种方法总是效率低下。我们可以用代码来自动化这个修改过程。
这里再补充一点,编码方式就是把数据的地址编码到代码里,如果因为游戏更新导致地址发生变化,代码也要同步修改,所以对于需要经常更新的游戏,很多修改器就会失效。
2.插件编程
我们首先来了解一些Windows API。
HWND FindWindow(LPCTSTR IpClassName,LPCTSTR IpWindowName);
通过类名或窗口名查找,返回窗口句柄
DWORD GetWindowThreadProcessId(HWND hWnd,LPDWORD lpdwProcessId);
得到窗口句柄后通过GetWindowThreadProcessId这个函数来获得窗口所属进程ID和线程ID
HANDLE OpenProcess(DWORD dwDesiredAccess,BOOL bInheritHandle,DWORD dwProcessId)
打开一个已存在的进程对象,并返回进程的句柄
bool WriteProcessMemory(HANDLE hProcess,LPVOID lpbaseAddress,LPVOID lpBuffer,DWORD nSize,LPDWORD lpNumberOfBytesWritten );
能写入某一进程的内存区域。入口区必须可以访问,否则操作将失败
附上代码:
#include
#include
int main() {
HWND h = ::FindWindow(NULL, "植物大战僵尸中文版"); // 寻找并打开进程
DWORD processid;
GetWindowThreadProcessId(h, &processid);
HANDLE hprocess = 0;
hprocess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, processid);
if (hprocess == 0) { // 对应处理
printf("打开进程失败!\n");
return 1;
} else {
printf("打开进程成功!\n");
DWORD hp = 3000; // 要修改的游戏数据最大值
LPCVOID addr = (LPVOID)0x21BF10C8; // 通过CE找到的游戏数据地址
DWORD res = WriteProcessMemory(hprocess, (LPVOID)addr, &hp, 4, 0); // 写入内存修改游戏数据
return 0;
}
}
这样的代码让我们可以随时调用它把日光改为2000。这个程序因为太过简单所以没有图形界面,所以仅仅当做一个入门demo而已。
我们已经直接通过地址修改了游戏的数据,那么能不能做一个游戏助手奥比岛开挂软件。,读取一些游戏数据来辅助玩家呢?CE工具虽然可以搜索读取,但是效率太差了。接下来我们介绍一个新的API,用于读取固定地址的数据。
HWND ReadProcessMemory(HANDLE hProcess, LPCVOID lpbaseAddress, LPVOID lpBuffer, DWORD nSize, LPDWORD lpNumberOfBytesRead);
根据进程句柄读入该进程的某个内存空间lpbaseAddress的nSize字节,并写入缓冲区lpBuffer,多次计算基址和偏移即可
尝试阅读:
附上代码实现:
#include
#include
int main() {
HWND h = ::FindWindow(NULL, "植物大战僵尸中文版"); // 寻找并打开进程
DWORD processid;
GetWindowThreadProcessId(h, &processid);
HANDLE processh = 0;
processh = OpenProcess(PROCESS_ALL_ACCESS,FALSE,processid);
if (processh == 0) { // 对应处理
printf("打开进程失败!\n");
return 1;
} else {
printf("打开进程成功!\n");
int sun; // 用于存放阳光数据
LPCVOID mbase = (LPCVOID)0x1E0CF020;
LPVOID mbuffer = (LPVOID)&sun;
::ReadProcessMemory(processh, mbase, mbuffer, 4, 0);
printf("您有阳光:%d\n", sun);
return 0;
}
3.自动插件挂机
以上都是修改数据的插件,另外还有一类自动操作修改器,比如自动玩连点游戏(雾),自动下棋(雾),自动拼图(笑)。
比如下面这个连点游戏,当你用CE观察内存的时候会发现,数据很神奇,是一个二维数组,而且不同的卡牌在内存中的值是不一样的,这里可以参考一下网上大佬lgx给出的解决办法。
想要自动消除连连看,首先要模拟鼠标操作,下面有一个API,可以模拟鼠标操作但是不会移动我们的物理鼠标(虚拟点击?)。
LRESULT SendMessage(HWND hWnd,UINT Msg,WPARAM wParam,LPARAM IParam);
其中Msg为WM_LBUTTONDOWN,WM_LBUTTONUP,表示对鼠标的软件模拟操作(虽然兼容性和安全性不如硬件模拟的mouse_event,但不改变移动鼠标指针)
另外,还可以利用BFS算法在二维数组中找出两张可以消除的牌。
void clearapair() { //找到可消去的两个点并点击
POINT p1, p2;
int x1, y1, x2, y2;
for (y1=0; y1<11; y1++)
for (x1=0; x1<19; x1++) {
if (!chessdata[y1][x1])
continue;
for (y2=0; y2<11; y2++)
for (x2=0; x2<19; x2++)
if (chessdata[y2][x2] && (chessdata[y1][x1]==chessdata[y2][x2]) && (x1!=x2 || y1!=y2) ) {
p1.x=x1; p1.y=y1;
p2.x=x2; p2.y=y2;
readchess();
if (llk_bfs(y1, x1, y2, x2) != -1) {
click2p(p1, p2);
return;
}
}
}
}
4. 网络游戏外挂
这是我从小学四年级就开始玩的游戏,由于很久没登录了,所以在这里积累了不少经验,大家可以先观察一下再领取。
捕获了表格。
初步分析发现,经验数据并没有直接指定,而是用其他数值代替奥比岛开挂软件。,经验获取类型也用其他数字代替,这里初步猜测是年龄。
嗯,可能是种类太多了,很难找到相同Age的。
但我换了方法,用经验包抓了两次宠物,抓到的两次经验包都叫fcode1f3....
Age 类型也是如此。
现在几分钟后,我们用完了第三个包。
南达?难道这个时代真的……(低语)
还是不确定,咱们就用爬虫模拟一下这个表单,然后炸掉(封号)!!!
经过N次测试...
成功!!!
需要注意的是,这个游戏服务器已经好几年没有更新了,上次公告也是十年前了。
如果你要用爬虫来模拟使用道具的话,有一个很关键的信息就是cookie,这个就是确认你登录账号的判断信息。
代码发布如下(我的 cookies 必须被删除)
import java.util.ArrayList;
import java.util.List;
import org.apache.http.Header;
import org.apache.http.HttpResponse;
import org.apache.http.NamevaluePair;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.BasicResponseHandler;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNamevaluePair;
import org.apache.http.protocol.HTTP;
public class Renren {
private static String renRenLoginURL = "http://resourcemop.l.imop.com/res/fc/fcde1f39034b164a0d5fe7e455b0c32e";
private HttpResponse response;
private CloseableHttpClient httpclient = HttpClients.createDefault();
private boolean login() {
HttpGet httpGet = new HttpGet(renRenLoginURL);
httpGet.setHeader("Accept-Ranges", "bytes");
httpGet.setHeader("Age", "74342");
httpGet.setHeader("Cache-Control", "");
httpGet.setHeader("Content-Length", "3266");
httpGet.setHeader("Content-Type", "text/plain");
httpGet.setHeader("Date", "Thu, 23 May 2019 08:41:58 GMT");
httpGet.setHeader("Expires", "Sun, 20 May 2029 08:41:58 GMT");
httpGet.setHeader("Last-Modified", "Wed, 22 May 2019 08:13:20 GMT");
httpGet.setHeader("Server", "Apache");
httpGet.setHeader("Via", "1.0 Static1.lzr.squid1:80 (squid/2.6.STABLE7)");
httpGet.setHeader("X-Cache", "HIT from Static1.lzr.squid1");
httpGet.setHeader("Referer", "http://s34.l.imop.com/");
httpGet.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36");
try {
response = httpclient.execute(httpGet);
} catch (Exception e) {
e.printStackTrace();
return false;
} finally {
httpGet.abort();
}
return true;
}
public static void main(String[] args) {
Renren renRen = new Renren();
renRen.login();
}
}
这其实是我两年前写的人人网爬虫,修改一下表单数据就可以用了。
篇幅有限,暂时就写这么多,后续可能会继续更新其他修改教程,不过Steam卡挂我一定会写。
编码就是爱,编码到世界充满爱!退场!
(超过)
Java 头