[toc]
功能
- 鼠标悬停在扫雷数组上可以判断是否是雷,并作出提示
- 一键扫雷
分析工具
- 动态调试工具 Ollydbg
- 搜索数据 Cheat Engine
- 寻找窗口回调函数 Spy++
- 查壳,查编译环境 PEiD/exeinfo
- 开发工具 VS2017
需要分析的数据和代码
- 扫雷数组的宽度、高度和雷的数量
- 扫雷数组的基址
- 鼠标位置
- 鼠标位置转换成扫雷数组下标的代码
- 扫雷数组下标转换成鼠标位置的代码(便于发消息)
扫雷游戏分析
获取扫雷数组的数据
先查看这个游戏的编写工具
链接器版本是7.0,故可推测是使用 VC2003 编写的
查看 API,可看到第一个运行时库 msvcrt.dll,有这个库的程序一般是 SDK 程序,且扫雷游戏大小只有 117KB,只可能是 SDK,若是静态编译的MFC程序则大小在 1M 以上
使用 CE 工具查找扫雷数组的宽度、高度和雷的数量,得到这些数据的地址
OD中分析数据
先在 CE 中查找访问高度、宽度、雷数的地址数据的代码,应该是先取出地址再去访问,故其中只有3处符合,分别在 OD 中查找这3处的地址
设置扫雷的界面便于我们之后在 OD 中观察内存,高度设为10,宽度为14,雷数为10,在 OD 的数据窗口中一行为16个字节,2列的边界加14列的宽度刚好填满一行
在 OD 中附加扫雷,依次进入这3处地址 0x1002EEA,0x1003705,0x10019EB,简单地看一下汇编代码,发现第一个地址是初始化雷区的代码,后两处地址代码是和界面有关
接下来需要详细地分析第一处的汇编代码,其中数组基地址是 0x1005340,高度是 0x1005338,宽度是 0x1005334,雷数是 0x1005330
第一次运行,雷区边界的初始化:
之后在扫雷界面点击一下:
发现雷区里面有数据填充,对比三个界面,可知:
0x10 代表边界,0x0F 代表初始值,0x40 代表周围没有雷,0x41 代表周围有1个类,依次类推,0x48 代表周围有8个类,0x8F 代表雷,数组中每一行下面都会有一行 0x0F 隔开
坐标转换
首先使用 Spy++ 查找鼠标点击的消息,先清除一些干扰消息,如 WM_NCHITTEST
,WM_SETCURSOR
,
WM_SETFOCUS
,WM_TIMECHANGE
,WM_TIMER
等,便于查找消息
在扫雷界面点击,出现了2个消息,WM_LBUTTONDOWN
和 WM_LBUTTONUP
,参数中有坐标点信息,接下来从这2个消息着手分析
再看到 Window Proc:01001BC9
,打开 OD 进行分析
在 OD 中的 0x1001BC9 地址处假定参数,设置消息断点
运行,程序断下
37=0x25,63=0x3F,可见 ECX 高16位保存 y 坐标,低16位保存 x 坐标
因为要寻找将屏幕坐标转换为数组下标的代码,需跟踪 ARG.4,而 ECX 保存了 x,y 坐标,故之后单步往下走,看代码有没有对 ARG.4 和 ECX 的再次访问,直到找到 0x1002009 地址处
点击扫雷界面的红框位置,接着运行程序,发现程序会将鼠标点击位置处的屏幕坐标转换为扫雷界面的数组下标,x=3,y=2
#注入程序DLL编写
VS2017 中新建 MFC DLL,部分代码如下:
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
| LRESULT CALLBACK My_DefWindowProcW( _In_ HWND hWnd, _In_ UINT Msg, _In_ WPARAM wParam, _In_ LPARAM lParam) { if (Msg == WM_KEYDOWN && wParam == VK_F5) { OutputDebugString(L"F5"); int nWidth = *g_pWidth; int nHeight = *g_pHeight; int nCounter = *g_pCounter; CString strString; strString.Format(L"宽度:%d, 高度:%d, 雷数:%d", nWidth, nHeight, nCounter); OutputDebugString(strString.GetBuffer());
int nMineCount = 0; for (size_t y = 1; y < nHeight + 1; y++) { CString strLine; for (size_t x = 1; x < nWidth + 1; x++) { BYTE byCode = *(PBYTE)((DWORD)g_pBase + y * 32 + x); if (byCode == MINE) nMineCount++; else { int xPos, yPos; xPos = (x << 4) - 4; yPos = (y << 4) + 0x27; SendMessage(hWnd, WM_LBUTTONDOWN, 0, MAKELPARAM(xPos, yPos)); SendMessage(hWnd, WM_LBUTTONUP, 0, MAKELPARAM(xPos, yPos)); } CString strCode; strCode.Format(L"%02x ", byCode); strLine += strCode; } OutputDebugString(strLine.GetBuffer()); } CString strCount; strCount.Format(L"找到的雷数:%d", nMineCount); OutputDebugString(strCount.GetBuffer()); } else if (Msg == WM_MOUSEMOVE) { int x, y; x = LOWORD(lParam); y = HIWORD(lParam); x = (x + 4) >> 4; y = (y - 0x27) >> 4;
BYTE byCode = *(PBYTE)((DWORD)g_pBase + y * 32 + x); if (byCode == MINE) SetWindowText(hWnd, L"此处有雷"); else SetWindowText(hWnd, L" "); } return CallWindowProc(g_OldProc, hWnd, Msg, wParam, lParam); } BOOL CMFCsaoleiApp::InitInstance() { CWinApp::InitInstance(); g_Wnd = ::FindWindow(L"扫雷", L"扫雷"); if (g_Wnd == NULL) { OutputDebugString(L"找不到扫雷窗口"); return FALSE; } g_OldProc = (WNDPROC)SetWindowLong(g_Wnd, GWL_WNDPROC, (LONG)My_DefWindowProcW); if (g_OldProc == NULL) { OutputDebugString(L"设置窗口回调函数失败"); return FALSE; } return TRUE; }
|