1.修复程序
打开的是一个启动器,加载广告,点击启动游戏会弹一个广告,去掉勾选框后点击继续,才会打开游戏本体
在任务管理器查看本体的名字叫做kyodai.exe 在文件夹中找到打开发现文件打不开。
在loadPe查看信息,发现本体文件的文件对齐为1000,但是一个普通exe的对齐是200,不知道是不是这里的问题。
去看看启动器对源文件做了些什么修改让他可以打开
打开启动器,在任务管理器查看进程
点击启动游戏,会弹出一个窗口,同时多了一个qqllk.ocx
点击继续就会启动游戏,并且多了一个kyodia.exe
那么,应该就是qqllk.ocx启动了游戏本体kyodia.exe它是怎么修改文件的呢?
将qqlk.ocx改为qqllk.exe,发现也可以运行。在od中打开它
首先qqllk创建了游戏的进程,那么一定会打开一个进程
在od中下一个createprocess断点,运行程序,果然在断点处断了下来。
在创建进程的参数中这个位置是挂起线程。那么应该就是在挂起线程后对内存进行了修改,修改内存会用到WriteProcessMemory。下个WriteProcessMemory断点接着运行
程序断在了这个位置,对参数分析
43817A是写入的位置,也就是游戏进程的位置,那么一会要去游戏进程看一下
484E70是写入的数据的缓冲区位置,数据窗口跟随查看数据,发现写入了00
想到之前在loadpe中查看的文件对齐大小是1000.会不会是方便修改
再打开一个od附加进程kyodai
CTRL+G去到刚刚找到的地址43817A,现在的数据是01
将启动器接着运行,然后再回来游戏进程,发现之前的01已经被修改成了00
游戏程序就可以运行了
那么把内存中43817A这个位置改变就可以正常的启动游戏本体。那么之前的文件对齐大小是1000,和内存对齐一致,那么对应文件的位置也是43817A,当然这个地址是一个VA地址,RVA地址就是3817A。
在010editor打开游戏文件,跳转到3817A的位置,将01修改为00
另存为一个新文件,改个名字。打开发现游戏可以正常运行了
2.分析道具
通过Cheat Engine 搜索道具个数。定位到道具数量地址是12AC5C
时间条的地址是12A748(在调试的时候锁定方便分析)
把数值修改,发现道具的数量也改变了
在0D中跳转到12AC5C这个位置发现了一些数据
刚验证了这个位置就是道具的数量位置,从上边的F0 09 F1 03推断F加一个数字是道具类型。根据推断改变下面的数据
果然道具的类型和数量发生了变化。
3.分析棋盘结构
通过Cheat engine 搜索棋盘第一个位置,不断刷新棋盘,对比第一个位置的数据进行搜索,对比是否发生变化
最后得到很多数据,但是只有第一个和之前的道具位置比较接近,合格应该是棋盘的位置,再次刷新棋盘,这里的数据发生变化。
在od中跳转到这个地方,对棋盘进行刷新,这里色数据都发生了变化,
棋盘最后面以00000结尾,多次刷新数据发现并没有固定的结束标记,所以从游戏入手,算出方块的数量是19*11=209个16进制是D1个
4.寻找关键CALL
##
4.1指南针
首先用CE修改道具的数量,并锁定。方便后面的调试
在地图的一个位置下一内存访问断点,当我们点击了指南针或者炸弹道具的话,肯定会来访问地图的数据寻找可以消除的两个位置
点击道具触发了断点,打开调用堆栈窗口,寻找是哪一个函数调用的
在这几个函数打上断点,在向上就是mfc库函数,只需要在本程序的函数上分析就可以了。
再次运行程序,使用指南针道具,断点在一个地方断了下来,分析一下栈内的参数,其中有一个F0,根据上面的分析,F0是指南针道具的标志
那我就换一个道具看看这里会不会变化。这次点击炸弹道具,这次F0变成F4,正好是炸弹的标志,那这个Call就可能是调用道具的CAll
在IDA中打开这个程序进行分析,来到我们刚刚看到的这个CALL的位置,并进入这个CAll的内部
分析一下这个CAll的流程图,发现这里有个switch选择,一共是8个分支,很可能是道具的种类选择,但是道具槽的位置只有7个,所以我就错过了这个位置,后来浪费了好多时间,等分析完一遍之后,再回到这个位置,感觉它在无情的嘲讽我,道具的位置有七个,但是也可能有八种
查看这个地址,去到OD中跳转到这里。
根据EAX的值来找到跳转的地址,在数据窗口跟随。
发现了八个地址,这八个地址其实就是switch的case,根据道具不同运行不同的代码。其实OD也有给提示caseF0到F7
接着,重新运行,点击指南针,触发断点,将程序运行到这个位置。跳到case F0也就是指南针的case里面,遇到第一个CALL
这里传进去了两个参数,这两个参数是两个局部变量的地址。在数据窗口跟随一下,现在应该是还没初始化。步过这个call
里面的数据已经变成了几个数字,棋盘式一个二维数组,这几个数字很可能是数组的下标,根据数值找到游戏的棋盘这两个位置
发现正好是两个可以消除的,那么这个call就是来获得两个可以消除的位置的call
4.2炸弹
重新运行,选择点击一个炸弹道具。断在case处
发现又是一个和之前的找两个位置一样的结构。验证确实是寻找了两个能消除的方块位置。
同样利用内存写入断点,使用炸弹道具,通过堆栈调用在call处打断点,分析call的参数,找到一个把可以消除的位置当参数传进去的地方
尝试了下手动点击两个可以消除的方块,发现也会经过这个地方。通过栈回溯找到上一层。并下断点
再次运行到这发现了这几个参数,到数据窗口跟随,确实是两个地址。还有地图的首地址。那这就是消除两个方块所所调用的call了
5.辅助工具的实现
5.1思路概述
单步消除:在道具中,有指南针还有炸弹,可以通过找到 指南针的call,分析指南针的算法,的道具给提示的时候肯定会算出哪两个方块可以消除,然后在屏幕点击对应的位置或者直接调用炸弹的call,来实现消除
一键消除:找到对应的剩余方块数量,除以2就是需要消除的次数,就调用几次单步消除就可以了
道具数量:我们已经有的道具的数量的地址,那么直接修改这个值就可以了
5.2代码实现
消除的功能的话。直接调用炸弹道具会比较方便,当然也可以用指南针的call获取到两个坐标,然后调用消除的call也可以实现,相对来说麻烦了一点。在实际情况中当然怎么简单怎么来。既然是学习,还是用复杂点的方法来让自己理解的透彻一点比较好。
5.2.1通过调用道具实现消除
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 | WNDPROC g_OldProc;
LRESULT
WINAPI
WindowProc(
_In_ HWND hWnd,
_In_ UINT Msg,
_In_ WPARAM wParam,
_In_ LPARAM lParam) {
if (Msg = = WM_K7)
{
OutputDebugString(L "检测到指南针" );
_asm {
mov ecx, 0x12A688
push 0xf0
push 0
push 0
mov eax, 0x41e691
call eax
}
}
/ / 调用道具实现
else if (Msg = = WM_K8) {
_asm {
mov ecx, 0x12A688
push 0xf4
push 0
push 0
mov eax, 0x41e691
call eax
}
return DefWindowProc(hWnd, Msg, wParam, lParam);
}
/ / 调用坐标加调用消除实现
else if (Msg = = WM_K9) {
POINT node1 = { 0 };
POINT node2 = { 0 };
int * p1 = ( int * )&node1.x;
int * p2 = ( int * )&node2.x;
CString str ;
str . Format (L "%d,%d--%d,%d" , node1.x, node1.y, node2.x, node2.y);
OutputDebugString( str );
_asm {
/ / 寻找两个可以消除的坐标
mov ecx, 0x45debc
mov ecx,[ecx]
lea ecx,DWORD PTR ds:[ecx + 0x494 ]
mov ecx,DWORD PTR ds:[ecx + 0x19f0 ]
push p1
push p2
mov eax, 0x42923f
call eax
}
str . Format (L "%d,%d--%d,%d" , node1.x, node1.y, node2.x, node2.y);
OutputDebugString( str );
_asm{
/ / / / 消除
mov ecx, 0x45debc
mov ecx,[ecx]
lea eax, DWORD PTR DS : [ecx + 0x494 ]
mov eax, DWORD PTR DS : [eax + 0x19f0 ]
add eax, 0x40
push 0x4
push eax
push p1
push p2
push 0x12bb50
push 0
mov eax, 0x41c68e
call eax
}
return DefWindowProc(hWnd, Msg, wParam, lParam);
}
return CallWindowProc(g_OldProc, hWnd, Msg, wParam, lParam);
}
unsigned __stdcall ThreadCall() {
/ / 弹出对话框
MyDlg mydlg;
mydlg.DoModal();
return 0 ;
}
BOOL CLinkLinkLookApp::InitInstance()
{
CWinApp::InitInstance();
/ / 查找窗口获取窗口句柄
m_hwnd = FindWindow(NULL, L "QQ连连看" );
if (m_hwnd = = NULL)
{
OutputDebugString(L "没有找到窗口" );
return FALSE;
}
OutputDebugString(L "打开成功" );
/ / 设置窗口回调函数
g_OldProc = (WNDPROC)SetWindowLong(m_hwnd, GWL_WNDPROC, ( LONG )WindowProc);
if (g_OldProc = = NULL) {
OutputDebugString(L "设置回调函数失败" );
}
OutputDebugString(L "设置回调函数成功" );
/ / 弹出对话框
_beginthreadex( 0 , 0 , (_beginthreadex_proc_type)ThreadCall, 0 , 0 , 0 );
return TRUE;
}
|
上面的代码是直接使用道具进行消除。因为游戏对消息机制有过滤,所以使用的是自定义消息。通过远程线程注入dll,在dll初始化时创建另一个线程打开一个窗口。点击按钮来向主窗口发送消息,通过消息来调用不同的功能
成功的背后是无数次的失败。尽管最后成功了,里面的原理还是有很多搞不清楚的。路还有很长。
[CTF入门培训]顶尖高校博士及硕士团队亲授《30小时教你玩转CTF》,视频+靶场+题目!助力进入CTF世界
最后于 2021-12-2 22:06
被wx_Van_Zovy编辑
,原因: