论坛上已经有不少这个小游戏的分析了,我也试着用了差不多两个周日的时间来分析它(萌新,分析的很慢TAT),有了一些不同的思路。
先说一下大概的思路吧,辅助工具的编写其实很简单,甚至都不用去看反汇编,利用游戏里的道具(炸弹)去实现就可以了。通过CE找出道具栏的位置,炸弹的值,发送消息模拟点击炸弹就行。
实现后完成,又感觉太投机取巧了,就又自虐了一下,把它判断如何链接的算法给逆了出来。
游戏版本:百度qq连连看单机版第一个
在分析游戏之前,我们先去一个广告。
不对,应该是多个广告
打开qqllk后直接会弹出第一个广告,然后第二个第三个,最后才会进入游戏。
通过进程名的查看,真正游戏的进程名是kyodai.exe,我们直接去打开kyodai,会发现直接是打开不了的。
观察后发现,游戏启动的过程中一共会有三个进程出现,一个就是一开始的qqllk.exe,然后会开启一个qqllk.ocx的进程,最后才会启动kyodai.exe
也就是说kyodai.exe是qqllk.ocx这个进程启动的,而它肯定对游戏进程进行了解密,通过API断点,可以定位到解密的点
仅仅是在0x43817A处修改了一个字节,qqllk.ocx 在此之前已经创建了kyodai.exe进程,不过是以挂起的方式创建的,解密完成后再恢复运行。
最后通过16进制编辑器手动修复一个字节就可以了,当然也可以脱壳后dump出来。去广告就到这结束了,具体细节也不细说了。
接下来,进入主题
从目标入手,想要实现这个小游戏的辅助。
最直接的方法就是找到各个数据的基址(前提是它不会变)
好吧,我们先找找看,假定它是不变的,至少这游戏没有随机基址
首次搜索,再次搜索......
等等,我们要找啥
基址
地图基址 和 道具基址
假定空地就是0,其他有具体的值,然后搜几次,发现,没有符合要求的地址
这里默认的四字节是搜索不到的,切换到单字节就能找到。
几番确认后,我们能够确定地图数组的基地址为0x0012BB58,道具栏的基地址为0x0012AC5E
有了基址了不起啊?
你们知道我要说啥了吧
下一步,我们需要找到炸弹
观察道具基址的数值,我们猜测12AC5E处的值是道具(指南针)数量,下面6E处是第二个道具数量,7E,8E等还没有道具(初始会有3个指南针和3个重置道具)
然后通过多次修改数据验证猜想,得出最后结论
5E,6E,7E,8E,9E,AE,BE处为道具栏各个道具数量,
每行末尾有一个标志位,通过F1,F2,F3...来标志下一格道具的种类,炸弹为F4(0表示下一个没有道具)
还有一些其他的标志位,比如默认显示什么的,不过根我们这里没啥关系了
我们已经找到了道具栏的位置,也找到了炸弹的标志
接下来,就可以去写外挂 了,实现起来也十分简单,将特定道具格子改成炸弹,然后模拟鼠标点击事件
实现的方式也有很多种,我们在这里可以直接使用SetWindowLong来修改游戏本身的消息回调,来实现类似于HOOK的功能
不过在测试的时候,发现即使已经修改了消息回调,也没法响应我们的键盘消息,可能是这游戏程序里本身就有钩子。
不过好在,鼠标点击事件是能够获取到的,不然我们就得去找其他方法了。
我们直接在道具栏指定位置点击,得到真实点击事件得wParam(1)与lParam(高位和地位对应着鼠标坐标)。
至此,基本就完成了,所有功能都围绕这个来实现就可以了。
修改道具栏道具后不会立马显示道具,需要鼠标移动到附近,所以也要模拟一个鼠标移动得消息
(将道具栏指定位置修改为炸弹(我是开启得时候直接将道具栏得道具顺序都给固定了),模拟鼠标点击炸弹)
[C++] 纯文本查看 复制代码 BYTE old_count[1];
LPARAM lParam = (LPARAM)0x00c802d5;
ReadProcessMemory(INVALID_HANDLE_VALUE, (LPVOID)0x12AC9E, &old_count[0], 1, &dwSize);
WriteProcessMemory(INVALID_HANDLE_VALUE,(LPVOID)0x12AC9E, "\xFF", 1, &dwSize);
::SendMessage(g_hwnd,WM_LBUTTONDOWN, 1, (LPARAM)lParam);
WriteProcessMemory(INVALID_HANDLE_VALUE, (LPVOID)0x12AC9E, &old_count[0], 1, &dwSize);
::SendMessage(g_hwnd, WM_MOUSEMOVE, 0, (LPARAM)lParam);
爽过之后,进入
自虐模式
虽然这只是一个小游戏,并没有涉及很多数据结构和算法,但要去逆它得的关键算法对于我这种小白来说,还是很不友好的。
含泪打开OD,
第一步,定位
之前我们拿道具开刀,现在我们仍旧从道具下手,这次的主角是指南针,因为这个道具里面肯定会包含链接的算法,去判断两个点能否链接。
有了前期的信息,定位其实很快。直接在指南针地址上下断,通过栈回溯去找调用的函数。
待分析函数:
该函数传入了两个参数,用于接受返回的两个点坐标
该函数实现:
它的内部实际上是两层循环,逻辑其实很简单(但还是看了好久),从地图数组的第一个点开始取,依次比较两个点类型(若是空地则跳过,若类型相同,则去比较判断能否链接)
在下面一层的函数有判断类型的,检测类型的,还有判断两个点能否链接的,而最后一个其实就是整个游戏核心的算法,在这个函数内部仍有4个函数调用,不过后两个也都是基于前两个基础之上的
判断能否链接的函数:
首先它判断了两个点的y坐标是否相等,如果相等就进入到这个函数(其实其他情况也会进入这个函数,不过参数不一样),这种情况里面会判断两点中间有没有其他障碍物。
接着返回上一层判断x坐标是否相等,然后进入另一个类似的函数。
如果这两次判断都无效,则会进入到第三个算法,若第三个也无法判断正确,会进入到第四个算法
其实后面两个算法也都是基于前两个上面的。
前两个一个是在横向的判断,一个是纵向
整理后的源码大概如下:
[C++] 纯文本查看 复制代码 BOOL IsXCheck(DWORD y, DWORD x1, DWORD x2, BOOL by1, BOOL by2)
{
DWORD dwSmallx, dwBigx;
dwSmallx = x2 < x1 ? (x2 + by2) : (x1 + by1);
dwBigx = x2 < x1 ? (x1 - by1) : (x2 - by2);
while (dwSmallx <= dwBigx)
{
if ((dwSmallx & 0x80000000) || dwSmallx >= 0x13 || y < 0 || y >= 0xB) return FALSE;
BYTE dwAddr = *(PBYTE)((DWORD)dwAddrBase + y * 0x13 + dwSmallx);
if (dwAddr > 0)return FALSE;
++dwSmallx;
}
return TRUE;
}
BOOL IsYCheck(DWORD x,DWORD y1,DWORD y2,BOOL b1,BOOL b2)
{
DWORD dwSmally, dwBigy;
dwSmally = y2 < y1 ? (y2 + b2) : (y1 + b1);
dwBigy = y2 < y1 ? (y1 - b1) : (y2 - b2);
if (dwSmally > dwBigy)return TRUE;
DWORD temp = dwSmally;
dwSmally *= 0x13;
do
{
if ((x & 0x80000000) || x >= 0x13 ||
(dwSmally & 0x80000000) || dwSmally >= 0xD1)
return FALSE;
BYTE dwAddr = *(PBYTE)((DWORD)dwAddrBase + dwSmally + x);
if (dwAddr > 0)return FALSE;
dwSmally += 0x13; ++temp;
} while (temp <= dwBigy);
return TRUE;
}
后两个其实只是在调用前两个:
[C++] 纯文本查看 复制代码 BOOL AlgorithmA(POINT a, POINT b)
{
for (DWORD i = 0; i < 0xB; i++)
{
BOOL loc1 = (i == a.y) ? TRUE : FALSE;
BOOL loc2 = (i == b.y) ? TRUE : FALSE;
if (!IsXCheck(i, a.x, b.x, loc1, loc2)||
!IsYCheck(a.x, a.y, i, 1, loc1))
continue;
if (IsYCheck(b.x, b.y, i, 1, loc2))
return TRUE;
}
return FALSE;
}
BOOL AlgorithmB(POINT a, POINT b)
{
for (DWORD i = 0; i < 0x13; i++)
{
BOOL loc1 = (i == a.x) ? TRUE : FALSE;
BOOL loc2 = (i == b.x) ? TRUE : FALSE;
if (!IsYCheck(i, a.y, b.y, loc1, loc2) ||
!IsXCheck(a.y, a.x, i, 1, loc1))
continue;
if (IsXCheck(b.y, b.x, i, 1, loc2))
return TRUE;
}
return FALSE;
}
四个算法都分析出来后,再往上一层判断能否链接
[C++] 纯文本查看 复制代码 BOOL IsRemove(POINT a, POINT b)
{
if (a.y == b.y&&IsXCheck(a.y, a.x, b.x, 1, 1)) return TRUE;
if (a.x == b.x&&IsYCheck(a.x, a.y, b.y, 1, 1))return TRUE;
if (AlgorithmA(a,b))return TRUE;
if (AlgorithmB(a,b))return TRUE;
return FALSE;
}
再往上就是那个循环遍历了
[C++] 纯文本查看 复制代码 BOOL GetPoint(POINT&a, POINT&b)
{
for (DWORD i = 1 ,j=1; i < 0xD1; i++)
{
j = i;
obj.a1.x = (i - 1) % 0x13;
obj.a1.y = (i - 1) / 0x13;
do
{
obj.b1.x = j % 0x13;
obj.b1.y = j / 0x13;
DWORD style1 = CheckStyle(obj.a1.x, obj.a1.y);
DWORD style2 = CheckStyle(obj.b1.x, obj.b1.y);
if (style1 == 0xff || style1==0|| style1==0x1)
break;
if (style2 == 0xff || style2 == 0 || style1 == 0x1)
{
j++;
continue;
}
if (style1 == style2 && IsRemove(obj.a1, obj.b1))
{
a = obj.a1;
b = obj.b1;
return TRUE;
}
j++;
} while (j<0xD1);
}
return FALSE;
}
主要还原的代码都在按照反汇编看的,应该还有很多地方可以优化。
最终测试结果,与游戏本身指南针找到的是同样的两个点。
|