逆向之制作扫雷外挂
本次扫雷外挂制作 涉及工具,VS2017、OD、Dbgview、CE。 至于扫雷程序,网上下的117kb 的那个。
外挂功能:一键通关、左上角窗口标题处显示雷区。
效果展示:
下面开始记录从头到尾的逆向与编写外挂过程
1.寻找关键数据基址
首先用CE打开扫雷进程,然后点击扫雷的自定义设置,找到宽、高、雷数量的界面。
然后用CE依次找到宽、高、雷的基址
发现有0x100533? 、0x10053A? 两组数据
分析思路是内存肯定要读取 宽、高、雷 的值,所以可以用OD打开扫雷,其次输入宽的地址,其次下硬件访问断点,找到访问 宽度 的代码块。
也可以利用CE直接查找访问地址,选中地址,点击访问此数据的代码。
点击笑脸,刷新一下扫雷,发现出现好几个访问地址。
优先选择汇编代码为 mov xxx,[0x???]的 代码,因为该变量将会被使用。
可以用OD一组一组的动态调试,我调试后发现0x100533?,相当于临时数据,0x10053A?相当于初始数据,都可以进行利用!
2.分析地图边界
根据上图,用OD跳转到0x1002EE4 地址
然后单步慢慢跟踪,发现一个0x1005340为首地址的数组,更改标签为基址Addr,这实际上是一个二维数组。一般的平面游戏地图用的都是二维数组。
开头就是一个循环,初始化基地址为0x1005340,大小为0x360个字节的空间为0F,查看数据基址内存如图
为了方便与OD的内存界面对齐,设置扫雷的宽度为14,高度为9。
继续单步跟踪,不难发现,下边的代码就是初始化扫雷区域的边界,其内存分布如下图:
如图,可以得知边界被赋值为0x10。因为边界额外占了两行和两列,所以操作这个数组的时候要特别注意。
该内存分布有点奇怪的是,每隔一行,都会有一行全是0F ,这个不用管它,根据你之前的代码,我们只要能逆向出遍历数组的代码就行了。遍历汇编重要代码如下:
3.分析扫雷区域各种数据对应的数组元素
同样的方法,通过CE找到地雷数量的基址,然后下访问断点,进入单步跟踪代码。
跟踪不了几步,就会发现一个 or [eax],0x80的地方,它会将eax逻辑或为 8F。
通过随时观看地图基址0x1005340的内存数据,发现他会在地图数组里面填充最大的地雷数量个8F。 猜测8F在地图数组里面就代表地雷。
为了验证猜想,先把OD中的断点都取消,然后让扫雷程序跑起来,之后对比雷区地图和内存数据。
只对比第一排,发现被点击后的地雷变为了8A, 有数字的地图被ASCII码填充了,至少说明没有找错方向,我们只需要关心8F代表没有被点击之前的地雷就行了。
3.消息响应与窗口回调分析
通过上面的分析,已经完全能写代码遍历地图数组,找到地雷都在什么位置了。
但是还有一个重点是,这是鼠标游戏,所以需要找到鼠标消息,以便用鼠标开挂。
这里需要才做鼠标的点击消息,LBUTTONDOWN和LBUTTONUP。
用spy++ 打开扫雷窗口,找到窗口进程(窗口回调)函数的地址。
既然是窗口回调函数,那在OD中就可以假设该地址的WndProc(1,2,3,4), 然后下LBUTTONDOWN的消息条件断点,直接用图片演示依次的操作。
下好消息条件断点,直接跑起来,然后鼠标点击一下雷区,就会断下,在这里我们主要的关注的该窗口的鼠标坐标,也就是WndProc()的第四个参数 lParam
可以看到传进来的坐标是50,62,实际上我们不用关心它的值是多少,只需要关心第四个参数,或者被第四个参数赋值的寄存器在哪被利用了。OD已经用arg.4 代表了这是第四个参数。
接下来F8单步跟踪,会发现有一个call调用了第四个参数,结果里面全是和界面绘图相关的API,因此跳过这个函数,继续往下走,就会找到真正的关键函数。
从上图知道,传进来的坐标,被进行了一系列的操作,然后计算出坐标所在雷区的那一个位置。我之前点击的是第一排的第三个格子。所以被程序转化成了(1,3)
找到了鼠标坐标是如何转化数组的序号索引,接下来就可以开始写外挂的。
在汇编代码我写的注释都是根据你单步跟踪慢慢分析出来的结果,x代表宽或x轴坐标,y代表高或y轴坐标。
有一点要注意的是,计算机上的y轴方向是向下的,所以最左上角的坐标应该是(0,0)
4.外挂编写
写外挂用MFC的动态库DLL ,创建一个MFC的DLL项目后,更改窗口回调,这类似于钩子。最难的逆向那一步都熬过来,DLL的代码就轻松多了。源码再此贴上。
// 唯一的 C扫雷DLLApp 对象
C扫雷DLLApp theApp;
HWND g_hWnd; //扫雷窗口的句柄
PDWORD g_Width = (PDWORD)0x10056ac; //宽
PDWORD g_High = (PDWORD)0x10056a8; //高
PDWORD g_Mine = (PDWORD)0x10056a4; //雷
PBYTE g_Addr = (PBYTE)0x1005340; //地图数组基址
WNDPROC g_WndProc;//原来的窗口回调
// 自己的窗口回调
LRESULT CALLBACK MyDefWindowProcW(
_In_ HWND hWnd,
_In_ UINT Msg,
_In_ WPARAM wParam,
_In_ LPARAM lParam)
{
// 按下F5键,一键通关
if (Msg == WM_KEYDOWN && wParam == VK_F5)
{
size_t nWidth = *g_Width;
size_t nHigh = *g_High;
size_t nMine = *g_Mine;
CString strShow;
strShow.Format(L"宽度:%d 高度:%d 雷数:%d", nWidth, nHigh, nMine);
OutputDebugString(strShow);
// 遍历每列
for (size_t j = 1; j < nHigh + 1; j++)
{
CString strAddr;
// 遍历每行
for (size_t i = 1; i < nWidth + 1; i++)
{
// (y + 2) * 32 + x + 1 + addr
BYTE bValue = *(PBYTE)((DWORD)g_Addr + j * 0x20 + i);
// 输出地图字节
CString strByte;
strByte.Format(L"%02X ", bValue);
strAddr += strByte;
// 如果不是雷区,就模拟点击
if (bValue != 0x8F)
{
int x, y;
x = (i << 4) - 4;
y = (j << 4) + 0x27;
SendMessage(hWnd, WM_LBUTTONDOWN, 0, MAKELPARAM(x, y));
SendMessage(hWnd, WM_LBUTTONUP, 0, MAKELPARAM(x, y));
}
}
OutputDebugString(strAddr);
}
}
// 标题栏上提示雷区
if (Msg == WM_MOUSEMOVE)
{
//OutputDebugStringA("移动了鼠标");
int x, y;
// 获取窗口中鼠标的x y 坐标
x = LOWORD(lParam);
y = HIWORD(lParam);
x = (x + 4) >> 4;
y = (y - 0x27) >> 4;
if (*(PBYTE)((DWORD)g_Addr + y * 0x20 + x) == 0x8F)
{
SetWindowText(hWnd, L"*** 此处有雷!***");
}
else
{
SetWindowText(hWnd, L"九阳道人");
}
}
// 将消息信息传递给扫雷窗口过程
return CallWindowProc(g_WndProc, g_hWnd, Msg, wParam, lParam);
}
BOOL C扫雷DLLApp::InitInstance()
{
CWinApp::InitInstance();
// 找到窗口句柄
g_hWnd = ::FindWindowA("扫雷", "扫雷");
if (!g_hWnd)
{
AfxMessageBox(L"FindWindowA!");
return FALSE;
}
// 设置窗口回调
g_WndProc = (WNDPROC)SetWindowLong(g_hWnd, GWL_WNDPROC, (LONG)MyDefWindowProcW);
if (!g_WndProc)
{
AfxMessageBox(L"SetWindowLong!");
return FALSE;
}
return TRUE;
}
5.扫雷程序及DLL和注入工具链接
可以自己编写注入代码,也可以用我的。我自己写了一个注入工具,点击注入,然后找到扫雷,然后前面四个注入方式都可以使用。
链接:https://pan.baidu.com/s/1fChp7lOVNCYtdqE1U5Q2FA
提取码:71fb