至个人年久失修的git博客搬运https://yusakul.github.io/
扫雷游戏插件的目标
- 当鼠标放在扫雷的方格中时,会显示是否有雷。
- 一键扫雷,快捷键是F5
扫雷游戏的分析
- 需要的技能
① 会编写DLL
②会写注入读取或者写内存的代码
③能够分析出扫雷程序中的信息
- 需要分析的数据
① 扫雷数组的大小
② 扫雷数组的宽度 、高度
③ 扫雷数组中的数量
- 需要分析的代码
① 找到遍历扫雷数组的代码,或者分析出如何遍历数组
② 找到屏幕坐标转换数组下的代码,或者分析出这个过程
③ 找到数组下标转换屏幕坐的代码,或者分析出这个过程
- 代码框架
接管扫雷窗口的回调函数,处理F5的按键消息
修改窗口属性SetWindowLong
扫雷游戏-分析数据
① 扫雷数组的大小
② 扫雷数组的宽度 、高度
③ 扫雷数组中的数量
根据宽度或者高的值变化,使用 CE 在内存中搜索数值,从而能找到宽度的地址
CE使用
-
修改程序数据的值,在CE中搜索
-
每次修改之后,再使用CE搜索,最后确定可能的值
注意:绿色的地址就是可以映射到文件偏移的地址,绿色地址称为基址
- 在可能的值上查找此地址数据的代码
再次修改数据,会有代码被记录
代码处:10036AC
高度:1005338
宽度:1005334
雷数:1005330
扫雷游戏-分析代码
- 从数值变化的代码处开始分析
从代码处: 10036 AC 开始跟踪分析
- 敏感 API 下断点,再进一步分析 下断点,再进一步分析
定时器创建和销毁: SetTimer,KillTimer
随机函数: rand
-
从窗口回调函数开始分析 窗口回调函数开始分析 ,分析鼠标按下消息
使用 spy++ 查找程序的窗口回调
地址:1001BC9
- 从数值变化的代码处开始分析
从代码处:10036AC开始跟踪分析
修改完数据之后,应该初始化扫雷数组、随机生成雷。
随机生成雷的代码中,是一个循环随机生成雷的x,y 坐标,然后写入到对应缓冲区
基地址:0x1005340
X坐标计算:随机数+1
Y坐标计算:(随机数+1)*32
根据内存情况可以分析出一些标记:
0F :初始化的值
8F :雷的标记
4X :周围有几颗雷就是4X, 41,42,43,44……
10 :边界
有了随机雷的生成,那么在雷生成前面的CALL,应该就是初始化雷区数组的代码,经过调试查看内存,发现真的是。
分析初始化雷区数组代码,发现为三步
-
初始化全部缓冲区为 0x0 F
-
初始化行标记 为 0x10
-
初始化列标记 位 0x 10
搭建代码框架
第一步 ,定义一些值和函数
WNDPROC g_OldProc = NULL;
HWND g_hWnd = NULL;
LRESULT WINAPI WindowProc (
_In_ HWND hWnd,
_In_ UINT Msg,
_In_ WPARAM wParam,
_In_ LPARAM lParam)
{
if (Msg == WM_KEYDOWN && wParam == VK_F5)
{
CString strString;
strString.Format(L"wParam = %p", wParam);
OutputDebugString(strString.GetBuffer());
return DefWindowProc(hWnd, Msg, wParam, lParam);
}
return CallWindowProc(g_OldProc, hWnd, Msg, wParam, lParam);
}
第二步,修改回调函数
// 修改扫雷窗口的回调函数
// 1. 找到扫雷窗口
g_hWnd = FindWindow(NULL, L"扫雷");
// 2. 修改窗口回调
g_OldProc = (WNDPROC)SetWindowLong(g_hWnd, GWL_WNDPROC, (LONG)WindowProc);
// 恢复窗口回调
SetWindowLong(g_hWnd, GWL_WNDPROC, (LONG)g_OldProc);
注意:写代码要记得释放和恢复,写代码要做好debug的准备
经过调整编写之后,遍历扫雷的数组
// 基地址:0x1005340
// 高度:1005338
// 宽度:1005334
// 雷数:1005330
byte** g_MineArray = (byte**)0x1005340;
int* g_nHeight = (int*)0x1005338;
int* g_nWidth = (int*)0x1005334;
int* g_nMineCount = (int*)0x1005330;
LRESULT WINAPI WindowProc (
_In_ HWND hWnd,
_In_ UINT Msg,
_In_ WPARAM wParam,
_In_ LPARAM lParam)
{
if (Msg == WM_KEYDOWN && wParam == VK_F5)
{
CString strString;
strString.Format(L"saolei wParam = %p", wParam);
OutputDebugString(strString.GetBuffer());
// 遍历扫雷数组
int nHeight = *g_nHeight; // 高度y
int nWidth = *g_nWidth; // 宽度x
int nCount = *g_nMineCount;
int nCurrentCount = 0;
for (int j = 1; j < nHeight+1; j++)
{
CString strString1;
strString1.Format(L"saolei 行:%d ", j);
// 行遍历时,需要注意去掉边界
for (int i = 1; i < nWidth + 2 - 1; i++)
{
byte byCode = *(byte*)((int)g_MineArray+i+j*32);
if (byCode == (byte)0x8F)
{
nCurrentCount += 1;
}
CString strCode;
strCode.Format(L" %02x ", byCode);
strString1 += strCode;
}
strString1 += L"\r\n";
OutputDebugString(strString1.GetBuffer());
}
CString strString2;
strString2.Format(L"saolei 雷数 = %d", nCurrentCount);
OutputDebugString(strString2.GetBuffer());
return DefWindowProc(hWnd, Msg, wParam, lParam);
}
return CallWindowProc(g_OldProc, hWnd, Msg, wParam, lParam);
}
分析 -屏幕坐标转数组下标
分析思路:
① 窗口回调下消息断点,分析鼠标按下弹起消息的流程
② 静态分析窗口回调,使用IDA找到对应的消息处理流程
窗口回调地址:1001BC9
在地址处下消息断点WM_LBUTTONDOWN
在这个消息的lParam参数里是x,y坐标
将汇编代码翻译为C代码
WORD y = HIWORD(lParam);
y = (y - 0x27) >> 4;
WORD x = LOWORD(lParam);
x = (x + 4) >> 4;
byte byCode = *(byte*)((int)g_MineArray + x + y * 32);
if (byCode == (byte)0x8F)
{
SetWindowText(hWnd,L"扫雷 - 友情提示:你踩到雷了!");
}
else {
SetWindowText(hWnd, L"扫雷");
}
分析-数组下标转屏幕坐标
将上面的代码反转
// 发送一个鼠标按下弹起的消息
WORD y = j;
y = (y << 4) + 0x27; //y 高16位
WORD x = i;
x = (x << 4) - 4; // x 低16位
SendMessage(hWnd, WM_LBUTTONDOWN, 0, MAKELPARAM(x,y));
SendMessage(hWnd, WM_LBUTTONUP, 0, MAKELPARAM(x, y));
DLL调试
-
如果有源码,可以使用VS进行调试,只需要设置启动exe即可
-
如果没有源码,使用OD调试,一般选择在调试的代码处加入特征汇编指令
比如:
Mov eax, eax
Mov eax, eax
在注入 DLL 之后,可以搜索指令 之后,可以搜索指令
选择 DLL 模块 (在模块列表中找到对应,右键跟随入口)
选择 DLL 模块的代码基址 (进入模块代码之后,拖到最前面)
使用 Ctrl +S, 搜索
- 在OD中注入DLL