本文为看雪论优秀文章
看雪论坛作者ID:三一米田
本人师从于15PB,以下分析的是一个小作业。分析过程中,遇到不懂的问题,参考了15PB薛老师录制的视频。可能有很多更加便捷快速的方法可以制作辅助,直接通关游戏,但是这里主要目的是为了分析程序的功能和函数,目的还是学习。
打开qqllk.exe。查看进程列表:发现点击开始游戏之后会新建一个qqllk.ocx的进程,点击继续之后,该进程结束,并且打开了游戏程序:点击继续之后:qqllk.ocx进程结束了,kyodai.exe进程被打开,也就是游戏程序。
也就是说qqllk.ocx的功能就是打开游戏程序。但是为什么我们自己却不能打开程序?
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的空间中。这里猜测可能是使用了挂起的方式创建进程,然后修改内存,将游戏的内存修改为正确的值之后,再恢复进程的执行。writeprocessmemory下断点之后,点击qqllk窗口中的继续,断下来了。注意参数:使用010打开该程序,搜索地址43817a-400000=3817a。
一、寻找突破口
1.1直接分析新文件,OD打开,点击练习,会随机生成新地图,猜测使用了随机函数rand。搜索rand函数,下断点,点击练习按钮,看看是否会断下来。结果证明确实断了下来。:1.2点击K,进入栈回溯窗口,查看调用堆栈,发现我们自己的程序0041A080调用了0041CAF2,0041CAF2调用了rand()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.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.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地址是一块程序没有使用到的空间,在这里我利用这段空间作为一个跳板,执行原代码。由于插入之后,会覆盖原本的代码,所以需要先备份一下原本的代码。在OD中修改完成之后:选中修改的代码,复制到可执行文件。这样会生成一个新的文件。我测试的时候一共生成了两次文件,第一次是在44AF44处添加原本代码的时候,选中修改的代码,生成了新的文件。执行完之后,双击打开exe,效果图:点击"练习按钮"整个游戏就剩下一对相同的图案了:
以上的就是一个简单的破解,直接修改原程序,在原程序中插入我们自己的代码,然后让程序继续正常跑起来。这种方法比较无脑简单,也没有太多的技术性,有点类似爆破。接下来我们继续分析程序的算法,编写一个辅助工具。
我们发现,在游戏中可以使用指南针道具,直接找到两个相同的图案,那么尝试一下通过这个道具入手,达到自动无限次使用道具。二、寻找指南针道具函数
2.1 首先,指南针也不可能预先知道哪里有相同的图案,也是需要访问地图才能查找到相同的图案,那么思路有了,程序运行起来之后,在内存中的地图下一个内存访问断点,然后使用指南针道具,看看程序是在哪里调用了指南针功能的函数:2.2 断下来之后,点击K查看调用堆栈,一共有5个调用,全部下断点,断下来之后再分析哪个函数真正调用了指南针函数。
2.3 第五层执行了多次,指南针函数应该不会调用多次,逻辑上应该是使用一次指南针,调用一次指南针函数,取消断点。2.4 当点击游戏图案时,第一层和第二层会断下来,肯定不是调用指南针的,取消断点。从这里开始,分析函数的功能,首先看看这个函数有几个参数,具体是什么。查看函数CALL之前,PUSH了几个参数,以及函数RET时返回了多少个字节,有些时候有可能有一些参数很早就PUSH了,中间又调用了其他函数,这样就容易混淆到底哪个才是参数,所以通过返回值和PUSH的个数一起确认参数是最靠谱的。一直往下翻,这个函数特别长,翻得我怀疑人生....最终总算找到了,返回0xC的字节,说明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,ECX很可能是某个对象中的成员,也就是说是某个对象中的成员对象,所以可以尝试一下,理解为ECX=基址+0x494,基址就是ESI,值为0x18A1F4。2.11 打开CE,附加游戏程序,搜索十六进制0x18A1F4,搜索发现4个常量基址,这4个基址是不会改变的,很可能就是这四个,首先排除77D84278,这里是系统高2GB的内核空间,不可能被3环程序访问0045DCF8:双击进去发现跳转到0041D153,排除2.13 0047FDE0:同样是有不少引用,尝试在赋值给ECX处下断点,但是两个基址都没断下来,那么就一个一个尝试吧。mov eax,[45DEBC] lea ebx,[eax+0x494] ecx=ebx+19F0(指南针函数使用的this指针)现在我们有了这些参数,就可以知道下一个可以消除的图案坐标在哪里了,只需要调用这个函数,并且传参:(0,0,F0)即可。2.15 通过测试,得知这个函数其实是使用道具的函数三、寻找消除图案的函数
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 考虑从其他方法找突破口:首先看内存,这里面存的是一个地址,看看上下文,发现一个很眼熟的数字:18A6883.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;
}
点击游戏中空白的区域,发现成功了,在坐标为(5,5)和(6,6)的地方消除了图案。验证了我们上面的猜测。现在只需要在调用消除函数之前,调用指南针函数,将坐标获取出来就可以完成破解了。有了以上信息,我们就可以为所欲为了~~编写辅助程序,使用以下功能:四、编写辅助工具
看雪ID:三一米田
https://bbs.pediy.com/user-home-881392.htm
*本文由看雪论坛 三一米田 原创,转载请注明来自看雪社区。
* 看雪《安卓高研班》线下班和网课(12月班)正在火热招生中!满10人即可开班!抓紧报名,升职加薪不是梦 !!