查看原文
其他

编写单机游戏连连看辅助的全过程

三一米田 看雪学苑 2022-07-01

本文为看雪论优秀文章

看雪论坛作者ID:三一米田


本人师从于15PB,以下分析的是一个小作业。分析过程中,遇到不懂的问题,参考了15PB薛老师录制的视频。

可能有很多更加便捷快速的方法可以制作辅助,直接通关游戏,但是这里主要目的是为了分析程序的功能和函数,目的还是学习。




去除广告

打开游戏:
打开qqllk.exe。查看进程列表:发现点击开始游戏之后会新建一个qqllk.ocx的进程,点击继续之后,该进程结束,并且打开了游戏程序:
点击继续之后:qqllk.ocx进程结束了,kyodai.exe进程被打开,也就是游戏程序。

也就是说qqllk.ocx的功能就是打开游戏程序。但是为什么我们自己却不能打开程序?

介绍下面的API:

STARTUPINFO ie_si = {0};PROCESS_INFORMATION ie_pi;ie_si.cb = sizeof(ie_si);CreateProcess(NULL,szBuffer,NULL,NULL,FALSE, CREATE_SUSPENDED,NULL,NULL,&ie_si,&ie_pi); //参数:CREATE_SUSPENDED//恢复执行ResumeThread(ie_pi.hThread);

以挂起的形式创建进程,此时创建出来的是一个4GB的虚拟空间,程序还没有跑起来,在这期间可以对这4GB的内存空间做一些操作。比如:修改内存,或者直接卸载这4GB的内存,将另一个程序的内存放入这4GB的空间中。

这里猜测可能是使用了挂起的方式创建进程,然后修改内存,将游戏的内存修改为正确的值之后,再恢复进程的执行。

CrateProcessA下断点:
writeprocessmemory下断点之后,点击qqllk窗口中的继续,断下来了。注意参数:
使用010打开该程序,搜索地址43817a-400000=3817a。
修改为程序要求的字节:00,另存为一个新的文件:
双击打开程序:直接运行游戏了。




分析源程序


一、寻找突破口


1.1直接分析新文件,OD打开,点击练习,会随机生成新地图,猜测使用了随机函数rand。

搜索rand函数,下断点,点击练习按钮,看看是否会断下来。结果证明确实断了下来。:
1.2点击K,进入栈回溯窗口,查看调用堆栈,发现我们自己的程序0041A080调用了0041CAF2,0041CAF2调用了rand()
1.3双击进入,在这两个地方设置断点
1.4再次点击"练习按钮",断在了第一次调用的函数,发现这是一个thiscall,ECX时this指针,是一个C++对象的调用。单步F7进入查看
1.5发现了一个文件,在本地磁盘中打开该文件试听一下,发现这是点击练习之后会播放的音乐。继续往下走:
1.6单步没有能发现什么能认识的东西,直到CALL了第二个调用,并且注释说明,调用了函数:memset(void*dest , const void* src ,  int n)。

这几行代码的意思,猜测一下:首先调用随机数函数rand(),然后调用memcpy,将src的内容拷贝给dest,一共拷贝0xDC个字节的内容。
1.7接下来F8单步,然后查看内存,验证一下:
1.8调用前EAX的内存情况:              
调用前EDX的内存情况:
调用后EAX的内存情况:
1.9首先一看到这样的布局,比较容易联想到这是一张地图。多走几次,发现前8字节都是固定不变的,那么先不看前8字节,看一下能否与游戏地图对接上。

地址栏CTRL+G跳转:EAX+8,直接运行起来,发现这就是游戏地图。应该是每一种图案都对应一个数字。并且在游戏中消除了方块之后,地图对应的位置也会清零。

那么联想上面的操作:初始化地图,01就代表这个位置有物品,00代表空地,然后后面会对01位置进行赋值。

1.10继续往下走,发现紧跟着的一个函数调用就是对01位置的赋值操作,因为CALL了之后,01的位置全部都变化了:

1.11由此可以确认EAX就是地图,但是前面还有8个字节的内容不知道是什么,但是不管是随机了多少次,前8个字节都是固定不变的,并且又是以偏移的形式得到的地图,那么前8字节很可能就是地图的基址。基址+偏移得到地图。
1.12看一下上图中的EAX是从哪里来的,一直向上查找,会发现ESI就是ECX赋值过来的,也就说ESI就是this指针
1.13继续分析,有一个疑问,上面分析得出,memcpy函数将地图从EDX中copy到EAX上,那么EDX又是从哪里来了?

查看的方法是下内存断点,继续点击练习,让它断下来,然后内存中跳转到EDX+8,选中一段地图,右键,断点,内存写入,然后重新点击练习按钮:


1.14断下来之后,点击K查看栈回溯
1.15 逐个双击进去查看,发现又回到刚刚分析的地方了,说明在调用memcpy函数之前,程序就预先初始化了一张地图,这张地图里面存储的是0和1。
1.16 但是我发现,这个初始化之后的地图全都是固定不变的,不管初始化多少次,地图仍然是不会变,那么后面的地图为什么又会变呢?跟进去看看
1.17 发现使用了一个文件,在本地磁盘中打开看看,发现这个正是一个地图文件。所以程序应该是预先设置好了地图,然后随机数应该是将位置为1的设置为随机图案。
1.18 再看之前的记录:调用rand()随机数,执行CDQ指令,获取偏移EDX(随机值),加上基址,获取地图,再赋值给EAX。

1.19 到此为止,我们得到了内存中的地图位置,并且知道了游戏是在什么时候初始化地图的,什么时候随机地图的,可以直接修改地图内存,写一个简单的破解了:
只需要在游戏初始化地图之后,将地图设置为0000即可消除所有方块,这里我设置为如下:
在设置两个图案进去:
运行起来,整个游戏就只剩下两个图案了,直接点击就可以完成游戏了。


在OD里面测试:以下代码其实可以优化,直接写JMP会比较简单,但是写完后才想起,就懒得改了。

在地图赋值完成之后,插入一段代码,将地图的第一个位置和第二个位置设置为图案,其他的全部设置为00,设置完之后,jcc跳转,跳转到0044af44处执行原本的代码,执行完原本代码之后,再跳转回41cb4a处继续执行代码。

44AF44地址是一块程序没有使用到的空间,在这里我利用这段空间作为一个跳板,执行原代码。由于插入之后,会覆盖原本的代码,所以需要先备份一下原本的代码。
在44AF44处添加原本的代码:
在OD中修改完成之后:选中修改的代码,复制到可执行文件。这样会生成一个新的文件。我测试的时候一共生成了两次文件,第一次是在44AF44处添加原本代码的时候,选中修改的代码,生成了新的文件。

第二次是在41CB25处,添加代码时生成的:
执行完之后,双击打开exe,效果图:点击"练习按钮"整个游戏就剩下一对相同的图案了:


以上的就是一个简单的破解,直接修改原程序,在原程序中插入我们自己的代码,然后让程序继续正常跑起来。这种方法比较无脑简单,也没有太多的技术性,有点类似爆破。接下来我们继续分析程序的算法,编写一个辅助工具。

我们发现,在游戏中可以使用指南针道具,直接找到两个相同的图案,那么尝试一下通过这个道具入手,达到自动无限次使用道具。

二、寻找指南针道具函数


2.1 首先,指南针也不可能预先知道哪里有相同的图案,也是需要访问地图才能查找到相同的图案,那么思路有了,程序运行起来之后,在内存中的地图下一个内存访问断点,然后使用指南针道具,看看程序是在哪里调用了指南针功能的函数

2.2 断下来之后,点击K查看调用堆栈,一共有5个调用,全部下断点,断下来之后再分析哪个函数真正调用了指南针函数。

2.3 第五层执行了多次,指南针函数应该不会调用多次,逻辑上应该是使用一次指南针,调用一次指南针函数,取消断点。
2.4 当点击游戏图案时,第一层和第二层会断下来,肯定不是调用指南针的,取消断点。

从这里开始,分析函数的功能,首先看看这个函数有几个参数,具体是什么。查看函数CALL之前,PUSH了几个参数,以及函数RET时返回了多少个字节,有些时候有可能有一些参数很早就PUSH了,中间又调用了其他函数,这样就容易混淆到底哪个才是参数,所以通过返回值和PUSH的个数一起确认参数是最靠谱的。

一直往下翻,这个函数特别长,翻得我怀疑人生....最终总算找到了,返回0xC的字节,说明PUSH了3个参数进栈,查看堆栈。

调用之前PUSH了3个参数:
2.5 返回时,RET了12字节。可以确定函数的参数就是这三个外加一个this指针。
2.6 查看参数:通过测试,并且查看这三个参数,发现这三个参数都是固定的,不会动态变化。
2.7 也就是说,我们只要调用这个函数,并且传三个参数(0 , 0 , 0xF0),就可以模拟程序调用这个函数了,只要可以随心所欲的调用这个函数,就可以随心所欲的使用指南针道具了。

继续向下分析,分析第四层:分析函数的关键:①查看返回值变化②查看参数变化③查看自身变化。

看参数,0x189D84,发现这样的形式特别像地址,在数据窗口查看,F8运行完之后,发现这两个参数被修改为08 01和02 01:
2.8 与游戏对比:发现这两个数字正好是相同图案的坐标:
X=[8] ,Y=[1]      X=[1],Y=[2]

这里留意一下,this的值是:传进来的this指针+0x19F0
传进来的this指针又是上层函数传进来的this指针+0x494

2.9 目前已有信息:知道哪个函数会可以使用指南针功能参数为(this,0,0,F0),调用它即可获取两个形同图案的坐标。

现在还差一个this指针,返回第三层查找this指针是哪里来的。

逐步向上分析:
第三层
ECX=ESI+0x494
ESI=ECX

第二层
ECX=ESI
ESI=ECX

第一层
ECX=ESI
ESI=ECX

再上一层
ECX=EDI
EDI=ECX
ECX=EBP-4
........太难找了调用堆栈太多,放弃从调用堆栈找的方法了

使用CE查找:
ECX=ESI+0x494,ECX很可能是某个对象中的成员,也就是说是某个对象中的成员对象,所以可以尝试一下,理解为ECX=基址+0x494,基址就是ESI,值为0x18A1F4。
2.11 打开CE,附加游戏程序,搜索十六进制0x18A1F4,搜索发现4个常量基址,这4个基址是不会改变的,很可能就是这四个,首先排除77D84278,这里是系统高2GB的内核空间,不可能被3环程序访问
2.12 剩下3个,右键查找所有常量
0045DCF8:双击进去发现跳转到0041D153,排除
0045DEBC:继续看剩下的是什么情况再判断
2.13 0047FDE0:同样是有不少引用,尝试在赋值给ECX处下断点,但是两个基址都没断下来,那么就一个一个尝试吧。
暂时先尝试使用0045DEBC,如果不对再换。

2.14 结合以上得知this指针ecx为:
mov eax,[45DEBC]    lea ebx,[eax+0x494]     ecx=ebx+19F0(指南针函数使用的this指针)

现在我们有了这些参数,就可以知道下一个可以消除的图案坐标在哪里了,只需要调用这个函数,并且传参:(0,0,F0)即可。

2.15 通过测试,得知这个函数其实是使用道具的函数

当参数为(0,0,F0)时,是使用指南针
当参数为(0,0,F4)时,是使用炸弹
当参数为(0,0,F1)时,是使用重列

三、寻找消除图案的函数


3.1 那么现在可以信息都有了:知道哪个函数会可以使用指南针功能参数为(this,0,0,F0),调用它即可获取两个形同图案的坐标。

我们想更加完善功能:让程序自动调用消除图案的函数,也就是说帮我们自动点击相同的图案,完成自动消除图案。

那么消除图案功能应该也是一个函数的调用,并且需要传递两个相同图案的X和Y坐标,调用之后需要将地图修改为00,由此,在内存中的地图下内存写入断点,然后在游戏中消除图案。

在下断点的过程中,消除图案但是,发现并没有断下来,可能是由于内存跨页了,而我只设置了某一页的断点,所以我选中所有地图,下内存写入断点
3.2 此时断下来了,查看调用堆栈,有一个地址是72A63B7D,系统空间的地址,直接pass。
3.3 与之前一样,全部下断点,逐个排除,分析,目的是找到哪里调用了消除图案的那个CALL,同理,排除后剩下3个CALL存在可能性。

接下来,从这三个函数的参数作为突破口,分析这些函数的参数与消除图案的函数功能是否相匹配。确定参数个数的时候,查看RET配合PUSH可以准确的确定函数的参数
3.4 接下来与之前的分析一致,分析函数,看参数,返回值,自身是否改变,变成什么。

首先看第3层,传了4个参数,但是没有特别有意义的地址,有一个是X和Y坐标,但是有些时候这个地址中的坐标的偏移会发生改变,现在还不能确定,但是可能性应该不会很大,先看后面的。
3.5 第4层,在内存地址中,发现这一层的参数中有坐标,多次测试后,参数3、4每一次与游戏中的地图图案都对应上了,那么基本上可以断定就是这个函数调用了消除图案的功能了。但是参数5比较容易混淆:


3.6 参数5很疑惑,看着像是一个76字节的地图数组中的两个坐标,并且这个坐标是我们点击的第一个图案的坐标。
3.7 回溯上层函数查看:是局部变量,发现追踪起来特别费劲,很容易跟丢。
3.8 考虑从其他方法找突破口:首先看内存,这里面存的是一个地址,看看上下文,发现一个很眼熟的数字:18A688
3.8 这个地址正是之前2.14中使用的  ecx+0x494==18A688  18A688+0x19F0又是指南针道具函数的this指针,我们不妨进去指南针this指针看看里面的内容:
3.9 这里面存储了一堆地址,我们发现,第一个成员加上0x30的值就等于参数5的值。

因为不同的电脑,基址有可能不同,即0233有可能不同,但是BC10这个是偏移,偏移是不会变的,所以我们不能直接使用0233BC10这个地址,而是选择在程序中动态获取。
3.10 查找参数6的值,参数6是LOCAL.3是上层函数的参数3,回到上层函数,发现参数3是EDI,溯源EDI,找到EDI是一个函数返回值赋值的:

进入函数,发现EAX:

MOV ECX,DWORD PTR DS:[ESI+0x1E84]
MOV EAX,DWORD PTR DS:[ECX+0x50]

3.11 此时,已经找到了消除图案功能的函数,以及堆栈中的参数实际值,假设通过2.14得到了偏移坐标,编写代码模仿调用这个函数:

struct XY{ int x; int y;}struct XXYY{ XY XY1; XY XY2;}


3.12 在OD里面测试代码


点击游戏中空白的区域,发现成功了,在坐标为(5,5)和(6,6)的地方消除了图案。验证了我们上面的猜测。现在只需要在调用消除函数之前,调用指南针函数,将坐标获取出来就可以完成破解了。


3.13 致此,已经获取的信息:
①道具函数的地址、道具函数的参数
②消除函数的地址、消除函数的参数

有了以上信息,我们就可以为所欲为了~~编写辅助程序,使用以下功能:
①单次消除
②消除所有
③使用各种道具

四、编写辅助工具  

添加Dialg对话框:
全部消除功能截图:



- End -



看雪ID:三一米田

https://bbs.pediy.com/user-home-881392.htm

  *本文由看雪论坛 三一米田 原创,转载请注明来自看雪社区。


* 看雪《安卓高研班线下班和网课(12月班)正在火热招生中!满10人即可开班!抓紧报名,升职加薪不是梦 !!



推荐文章++++

* Linux Kernel Pwn_1_Double fetch

* Linux Kernel Pwn_0_kernel ROP与驱动调试

* 使用Frida分析动态注册jni函数绑定流程

* OneFuzz踩坑教程

* 最右sign-v2签名算法追踪及逆向还原







公众号ID:ikanxue
官方微博:看雪安全
商务合作:wsc@kanxue.com



求分享

求点赞

求在看


“阅读原文”一起来充电吧!

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存