连连看
exe
VS2017
、OD
、CE
、EXEINFOPE
、Spy++
、010Editor
、PCHunter
(可用任务管理器替代)Win7
虚拟机exe
CreateProcessA/W
下断分析API
上下断点(下面是些常用的,当然还有,这里就不列举了),通过栈回溯分析DialogBoxA/W
对话框弹出API
CreateWindowExA/W
创建窗口API
WinExec
启动一个可执行文件API
CreateProcessA/W
创建进程ShellExecuteA/W
创建进程CALL
,实现一键秒杀CALL
CALL
MFC DLL
使用MFC DLL
,方便之处在于不需要自己写DLLMain
的case
了,直接写在InitInstance
函数中即可,且调试时使用Cstring
比较方便。
SetWindowLong
修改窗口回调函数,在自己的窗口回调函数中处理快捷键响应
CallWindowProc
调用指定窗口回调函数
多线程
_beginthreadex
创建线程便于弹窗
1.我们先运行程序,会弹出下面的对话框
2.用PCHunter
查看运行中的进程,此时只有一个进程,就是我们打开的exe
3.点击开始游戏,又会弹出下面的对话框,再用PCHunter
查看运行中的进程,此时多了一个新的进程qqllk.ocx
4.先不管它,我们点击继续,终于来到我们游戏主体,再用PCHunter
查看,发现qqllk.ocx
退出,多出一个kyodai.exe
,
5.由于最终运行的是kyodai.exe
,我们可以假设kyodai.exe
就是我们要找的游戏原进程,我们回去双击kyodai.exe
,发现点击它又运行不了。但是通过其它程序又能运行它,由此猜想其它程序在打开kyodai.exe
之时先对kyodai.exe
的内存进行了修改,然后使其能正常运行。那是哪个程序修改的它呢,前面分析有一个进程qqllk.ocx
在kyodai.exe
运行后就退出了,我们大胆假设就是这个打开的我们游戏主体进程,并对其进行修改
,下面用OD
对其进行分析
6.重新打开游戏,到这一步,通过上面我们知道,此时我们产生了一个新进程qqllk.ocx
,现在我们用OD
附加这个进程,猜想它可能使用了CreateProcessA/W
函数打开进程,由于不知道到底是A版
还是W
版,我们在两个函数上面都设置上断点
7.运行之后发现在CreateProcessA
函数上断下,并且kyodai.exe
程序被它以挂起的方式打开了,很多恶意程序都是这样的套路,那么就容易猜想了,挂起了程序之后就可能修改内存数据,之后恢复程序,那么我们就在修改内存数据函数WriteProcessMemory
下断点,运行。
8.发现果然断在了我们下的WriteProcessMemory
函数上,并且观察堆栈,会发现该函数是在kyodai.exe
程序中的0x43817A
地址处修改了1个字节的数据。数据窗口跟随可以看出是修改后的数据是0
。
9.然后在唤醒线程函数ResumeThread
下断点,运行之后果然走到了这里。
10.那么我们现在就已经分析处理其运行机制,它首先运行qqllk.ocx
,然后再由qqllk.ocx
执行CreateProcessA
以挂起的方式打开kyodai.exe
,再执行WriteProcessMemory
修改kyodai.exe
内的0x43817A
地址处的一个字节,将其改为0
,再执行ResumeThread
恢复kyodai.exe
进程。
11.那么我们现在可以直接使用010Editor
打开kyodai.exe
直接找到3817A
(因为默认加载基址0x400000
,需要减去它得到文件偏移)地址,把里面的数据改为00
,如果文件是只读的情况,改不了,可以将文件复制一份,修改复制后的文件,保存,运行复制后的文件。
12.运行我们修改后的kyodai.exe
,发现直接就来到了游戏主体,到此,我们找到了游戏的原程序并且去掉了广告。接下来就是分析游戏关键功能了
13.点击练习的时候地图会随机刷新,那么肯定会用到rand
这个随机函数,我们用OD
附加我们原程序,再在rand
函数下断点,点击游戏中的练习按钮,就会断在我们rand
函数处,然后在点击OD
工具栏上的K
进行栈回溯分析。
14.观察发现有2个上层调用(带程序名字的2个),它们的关系是0x41A085
处的函数调用0x41CAF2
的函数,0x41CAF2
再调用rand
函数。在K
中越靠下就越是外层的函数。
15.双击分别进入上面两个地址,并在它们上面设置断点,然后把之前rand
的断点删除
16.F9
运行后再次在游戏中点击练习会在第一个断点0x41A085
处断下,这是一个函数CALL
,要重点关注一下它上面一行代码MOV ECA,EDI
,由于这是C++
所写的程序,它都会使用ECX
这个寄存器传递this
指针也就是传递一个对象。之后进入函数要留意一下ECX
寄存器。按F7
进入这个函数,先Ctrl+A
分析一下该模块,发现一开始就把ECX
的值传给了ESI
,先不管继续单步,没几步就看见了字符串start.wav
,结合下面的 %sSound\%s
分析这可能是个音乐文件,我们在音乐文件夹中去找找看,发现真的是,这里就差不多可以证明这个函数就是初始化游戏的哈桑函数。
17.然后再快速单步很快就能发现刚才所下的第二个断点,发现rand
下面有一个memcpy
拷贝内存的函数,走一遍注意观察OD
中右上角寄存器的变化,我们刚刚看到这个函数里面,是将ECX
传递给了ESI
,而0x41CAFC
地址处对ESI
进行了访问,走一步之后我们发现EAX
的值是0x12BB50
,那么选中它,右键->数据窗口跟随 ,查看0x13BB50
地址里面有些什么数据。
18.然后单步走到memcpy
下一步处,观察0x12BB50
地址里的数据,多来几次,发现除了前8个字节没变,后面都被填充成某种规律的010100...猜测0x12BB50
可能是游戏的地图数组基地址,先不管,继续单步,当走到0x41CB15
时,0x12BB50
处数据又被刷新了。所以0x12BB50
极有可能是我们要找的地图数组基地址。
19.为了验证是否正确,按F9
让游戏运行起来,观察地图与该内存数据是否有联系,多来几次,发现地图和OD
中的内存非常相似。
20.为了再次确认,我们把游戏中的物品点击消除掉在观察内存发现被清0了,那么我们就可以确定0x12BB50
就是地图数组基地址了,并且得到了0x12BB58
就是地图数据的起始位置了。
21.现在,我们来捋一捋思路,先将地图数组变成有规律的0与1,然后再将1上的位置随机分配各种代表图案的数值,而前一步并没有循环随机函数,只随机了一次,大胆假设它应该存在一个保存各个地图基本形状的数组,随机函数是随机使用那个地图,经过分析我在rand
函数上面最近的一个CALL
证实了我这个假设
22.localkyodaimap.map
就保存着我们所有地图的基本形状(有图案还是没有图案),至于是什么图案,是在0x41CB15
地址处的CALL
里面随机分配的,这里就不细说了
23.接下来就是分析道具了,先从指南针开始。指南针道具是帮助玩家找到可以消除的2个相同图案,而要实现其功能必须要访问地图数组,所以我们可以在0x12BB58
地址处下一个内存访问断点。
24.先在OD
中按ALT+B
来到断点模块,把其它断点禁止或者删掉,然后运行游戏,点击指南针,程序就会停下来,点击K
进行栈回溯分析。发现有5个上层调用,在5个位置处都设置上断点,把刚刚设置的内存访问断点删除掉。
25.再按F9
运行OD
发现运行多次都停在0x4292A5
处,那么这肯定不是我们要找的函数,去掉其断点。再运行,发现只要点击游戏界面,就会在0x40CACA
处断下,所以这个也不是我们要找的,去掉其断点,继续运行,同样的0x41AF11
只要点击游戏界面,就会在该地址处断下,pass
掉,再按F9
运行起来。
剩下的0x41E76C
与0x41DE5C
处的函数可能是我们要找的关键函数,现在想要写代码完成指南针的功能,只需要找到这两处地址的函数调用时所需要的参数,那就可以模拟出指南针的功能。
重新运行起来,点击指南针,会在0x41DE5C
处断下,选中这一行按Enter
键进入这个函数一直往下拉找到末尾RETN
观察发现RETN 0xC
也就是说0x41DE5C
处的函数调用需要3个参数(当然这里不考虑通过ECX
寄存器传递的this
指针),通过观察堆栈得出参数是0
,0
,F0
。
26.为了确认这3个参数是不是可变的,多操作几次,发现参数没有改变,那现在就只要确认ECX
的值,就可以用这个函数模拟指南针功能了。暂时先不管它,那么我们再次运行,使其段在0x41E76C
处,按照上面分析方法,这个函数有2个参数,是两个地址,数据窗口跟随,然后单步,看看两处地址的数据有啥变化。
27.指南针的作用是找出两个可以相消除的图案,也就是要找到这两个图案的坐标,通过观察发现这个CALL
有可能是获取两个可以相消除的图案的坐标。然后我们多次将内存中的数值与找到的两个图案相比较,证实了此猜想成立。
28.所以现在只剩下我们刚刚找到的处于0x41DE5C
的CALL
,只要找到ECX
的值就可以了。往上找ECX
的值,需要往上层函数分析,这里我们换一种方法分析,使用CE
附加程序,搜索ESI
的值,发现有几个绿色的基址。
29.我们把这几个基址在OD
反汇编窗口右键->查找->所有常量,排除后还剩下0x45DEBC
与0x7793CDD8
,这里先用0x45DEBC
试试。
30.相同的原理我们找出其它道具的调用CALL
,这里就不细说了。我们发现,都会调用0x41DE5C
处的CALL
,只是参数不同,对比下面参数
调用指南针:0
,0
,F0
调用炸弹:0
,0
,F4
调用重列:0
,0
,F1
得出结论第3个参数是道具类型,而0x41DE5C
处的函数功能应该是使用道具。
31.找消除CALL
,要消除,肯定要对地图数组改写,所以我们可以在相应位置设置内存写入断点,然后点击这两个位置消除,OD
就会断下,取消内存写入断点,然后通过栈回溯分析。
32.通过工具栏上的K
跳转到栈回溯窗口,发现有5个,按照之前的套路进行分析。最后还有3个CALL
有可能是我们要找的,通过分析,发现第一个CALL
有7个参数,有点多,而且其中有参数的值不确定,pass
,第三个CALL
没有传坐标,应该不是,也pass
,那么我们就只剩下第2个CALL
了,它有6个参数,经过分析,第一个参数为0,第二个参数为地图数组基址(这个前面已经找到),第3,4个参数为两个坐标,我们可以通过上面指南针中找到的获取两个可以消除的点的函数获得。
33.往上找到(这里需要往上一层找,第5,6参数是上层函数的参数),第5,6个参数的值是在这赋值的,ECX
是ESI+0x1E84
的值,而ESI
和上面我们找到的ESI
一样,这就不说了。
下面就是编写辅助了,单消只需要调用获取两个能相消的坐标的函数,再调用消除CALL
就可以了,当然使用炸弹道具也可以完成,秒杀就是循环使用单消就行,这里有点,就是如果没有能够相消除的两个点的话,是会返回两个(0,0)坐标,所以,可以根据此退出循环。
1.用VS2017
首先创建一个MFC
的DLL
程序,选择在静态库中使用MFC
,这样兼容性更好,这个选项也可以在属性->常规->MFC
的使用里面更改。
2.通过Spy++
获得游戏的窗口名与类名,用于FindWindow
函数获取游戏窗口句柄。
3.使用__asm
内联汇编调用我们找到的关键函数,说明下,消息采用自定义消息。
LRESULT CALLBACK WindowProc( _In_ HWND hWnd, _In_ UINT Msg, _In_ WPARAM wParam, _In_ LPARAM lParam) { if (Msg == WM_DATA1) { //指南针 OutputDebugString(L"指南针"); __asm { mov ecx,0x45DEBC mov ecx,[ecx] lea ecx,dword ptr ds:[ecx+0x494] //获取this指针 push 0xF0 //道具类型,指南针 push 0 //参数2 push 0 //参数1 mov eax,0x41E691 call eax //调用道具函数 } return DefWindowProc(hWnd, Msg, wParam, lParam); } else if (Msg == WM_DATA2) { //炸弹 OutputDebugString(L"炸弹"); __asm { mov ecx, 0x45DEBC mov ecx, [ecx] lea ecx, dword ptr ds : [ecx + 0x494] //获取this指针 push 0xF4 //道具类型,炸弹 push 0 //参数2 push 0 //参数1 mov eax, 0x41E691 call eax //调用道具函数 } return DefWindowProc(hWnd, Msg, wParam, lParam); } else if (Msg == WM_DATA3) { //重列 OutputDebugString(L"重列"); __asm { mov ecx, 0x45DEBC mov ecx, [ecx] lea ecx, dword ptr ds : [ecx + 0x494] push 0xF1 push 0 push 0 mov eax, 0x41E691 call eax } return DefWindowProc(hWnd, Msg, wParam, lParam); } else if (Msg == WM_DATA4) { //秒杀 OutputDebugString(L"单消"); //1.获取可以连接的两个点 POINT pt1 = { 0 }; POINT pt2 = { 0 }; __asm { mov ecx,0x45DEBC mov ecx,[ecx] lea ecx, dword ptr ds : [ecx + 0x494] mov ecx, dword ptr ds : [ecx+0x19F0] lea eax,pt1.x push eax lea eax,pt2.x push eax mov eax,0x42923F call eax } if (pt1.x == 0 && pt1.x == pt1.y) { return -1; } //2.调用消除CALL __asm { mov ecx, 0x45DEBC mov ecx, [ecx] mov eax, dword ptr ds : [ecx + 0x1E84] mov eax, dword ptr ds : [eax + 0x50] push eax mov eax, dword ptr ds : [ecx + 0x1E84] lea eax, dword ptr ds : [eax + 0x30] push eax lea eax, pt1.x push eax lea eax,pt2.x push eax lea eax, dword ptr ds : [ecx + 0x494] mov eax, dword ptr ds : [eax + 0x19F0] mov eax,dword ptr ds:[eax+0x4] push eax push 0 mov eax,0x41C68E call eax } return DefWindowProc(hWnd, Msg, wParam, lParam); } return CallWindowProc(g_oldProc, hWnd, Msg, wParam, lParam); }
4.最后上图
游戏链接:
链接:https://pan.baidu.com/s/1kP4-mthdA8-0LIa7Oo0CVw
提取码:h2ea