|
原文链接:网络安全编程:C语言逆向之扫雷游戏辅助工具
扫雷游戏是Windows系统自带的小游戏,貌似从Win95开始就已经存在了,看来非常经典和受欢迎。扫雷游戏的辅助工具,网上有很多,而且它本身就存在后门,可以让玩家判断当前是否是雷。本文针对扫雷游戏来做一个简单的能够读取雷分布的工具。
1. 扫雷的简单分析
Windows对消息的处理是在窗口过程中完成的。一般情况下,通过分析对API函数的调用都可以找到窗口过程的地址。通过分析RegisterClassEx()、DialogBoxParam()等函数,都可以找到窗口过程的地址。
扫雷程序是由VC7进行开发的,使用OD加载扫雷程序,其完整路径为C:\Windows\Sys tem32\Winmine.exe。用OD载入扫雷程序以后,入口点停留在01003E21地址处,停留的位置处于启动代码处,而不是真正的主程序处。VC开发的程序基本都可以通过exit()函数往上找到第1个CALL,如图1所示。
图1 通过exit()函数找主函数
直接按F4键运行到01003F90地址处对主函数调用的CALL指令上,然后按F7键单步步入至主函数的反汇编代码处。在主函数的入口处向下可以看到对RegisterClassW()函数的调用。这里注册了窗口类,需要找到对窗口过程的地址。在调用RegisterClassW()函数前是大量的赋值语句。在这些语句中,只有0100225D地址处的MOV指令的源操作数看起来是个地址。猜测这里是窗口过程的地址,如图2所示。
图2 调用RegisterClassW()函数
直接按Ctrl+G组合键到01001BC9地址处,可以看到这里的函数非常大。需要找到WM_LBUTTONDOWN和WM_LBUTTONUP两个消息。这两个消息是用来处理鼠标左键按下和鼠标左键抬起的事件。分别在WM_LBUTTON DOWN和WM_LBUTTONUP消息上下断点,如图3所示。
图3 对鼠标左键消息设置断点
按下 F9 键让扫雷运行起来,在扫雷游戏中的雷区单击鼠标左键,此时 OD 的 WM_ LBUTTONDOWN地址处的断点会被断下。继续按F9键运行,回到扫雷游戏界面,发现刚才单击的位置没有发生任何改变,且WM_LBUTTONUP消息未被断下。可以猜测改变雷区格子状态的处理代码在WM_LBUTTO NUP消息中,而并非在WM_LBUTTONDOWN消息中,因此取消WM_LBUTTONDOWN处的断点。
取消WM_LBUTTONDOWN消息后,再次回到扫雷游戏中的雷区单击鼠标左键,OD再次被断下,这次被断下的位置在WM_LBUTTONUP消息的地址处。按F8键单步至01002005地址处的call 010037E1指令处,然后按F7键进入该函数,继续按F8键单步跟踪至0100389B地址处,如图4所示。
图4 雷区分布地址
按Ctrl + G组合键在数据窗口查看01005340地址处的内容,如图5所示。
图5 雷区分布
从图5中可以看到,数据窗口中非常整齐且看似有序地排列着一些数据。这里就是雷区的分布。雷区的分布从01005340地址处开始,从雷区的起始地址处往前16字节是雷的数量、雷区的矩阵的宽和高。在图5中,01005330地址处保存着雷的数量63(十进制的99),宽是1E(十进制的30),高是10(十进制的16)。从地址01005340开始的数据进行复制,可以看到01005340地址处的数据是连续的10。从这里开始,一直复制到一串连续的10地址处。这里从01005340地址处一直复制到0100557F地址处,将复制出来的数据粘贴在记事本中,并进行整理,结果如图6所示。
图6 雷区分布及相关地址
从图6中可以看出,雷区的分布是一个矩阵,在所有数据的最外围全部是10,这表示雷区周围的墙。切换到扫雷游戏,从左上角开始单击鼠标左键,然后观察内存中的值,依次进行测试,得出结果为:10 代表墙,0F代表空白,8F代表雷,8E代表旗。由此可知,只要将雷区中的8F的位置全部找到,即可找到全部的雷。
至此,对扫雷的分析结束。接下来通过编写程序找出扫雷游戏中全部的雷。
2. 辅助工具编写
扫雷游戏中雷的布局是随机的。通过多年玩扫雷的经验发现,每局的第1次都不会因为扫到雷而爆掉。因此,推断雷的布局是在每1局的第1次单击后进行随机分布的。那么,每次使用程序时,应该先在扫雷游戏中单击1次鼠标左键。
由于对扫雷游戏进行了分析,下面直接来看代码:
- #include <Windows.h>
- #include <stdio.h>
- int main(int argc, char* argv[])
- {
- // 找到扫雷游戏对应的窗口句柄和进程 ID
- HWND hWinmine = FindWindow(NULL, "扫雷");
- DWORD dwPid = 0;
- GetWindowThreadProcessId(hWinmine, &dwPid);
- // 打开扫雷游戏获取其句柄
- HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid);
- PBYTE pByte = NULL;
- DWORD dwHight = 0, dwWidth = 0;
- DWORD dwAddr = 0x01005330;
- DWORD dwNum = 0;
- DWORD dwRead = 0;
- // 读取雷的数量级
- // 读取雷区的宽和高
- ReadProcessMemory(hProcess, (LPVOID)(dwAddr),
- &dwNum, sizeof(DWORD), &dwRead);
- ReadProcessMemory(hProcess, (LPVOID)(dwAddr + 4),
- &dwWidth, sizeof(DWORD), &dwRead);
- ReadProcessMemory(hProcess, (LPVOID)(dwAddr + 8),
- &dwHight, sizeof(DWORD), &dwRead);
- // 本代码只针对扫雷的高级级别
- // 因此需要判断一下高和宽
- if ( dwWidth != 30 || dwHight != 16 )
- {
- return 0;
- }
- DWORD dwBoomAddr = 0x01005340;
- // dwWidth * dwHight = 游戏格子的数量
- // dwWidth * 2 = 上下墙
- // dwHight * 2 = 左右墙
- // 4 = 4 个角度墙
- DWORD dwSize = dwWidth * dwHight + dwWidth * 2 + dwHight * 2 + 4;
- pByte = (PBYTE)malloc(dwSize);
- // 读取整个雷区的数据
- ReadProcessMemory(hProcess, (LPVOID)dwBoomAddr, pByte, dwSize, &dwRead);
- BYTE bClear = 0x8E;
- int i = 0;
- int n = dwNum;
- while( i < dwSize )
- {
- if ( pByte[i] == 0x8F )
- {
- DWORD dwAddr1 = 0x01005340 + i;
- WriteProcessMemory(hProcess, (LPVOID)dwAddr1,
- &bClear, sizeof(BYTE), &dwRead);
- n --;
- }
- i ++;
- }
- // 刷新扫雷的客户区
- RECT rt;
- GetClientRect(hWinmine, &rt);
- InvalidateRect(hWinmine, &rt, TRUE);
- free(pByte);
- printf("%d \r\n", n);
- CloseHandle(hProcess);
- return 0;
- }
复制代码
以上就是整个扫雷的辅助工具的源代码,效果如图7所示。
图7 雷的分布
该辅助工具无法完成扫雷的工作,虽然雷的分布都已经找到,但是时间仍然在继续,剩下的工作交给大家自行分析。
|
|