逆向分析-扫雷

it2025-11-28  9

例如:系统环境:win10 64位

工具:吾爱专用OD、CE、代码注入器

1.分别以两种方式写辅助工具,MFC的辅助工具,DLL注入补丁模式的辅助工具。 2.功能:暂停时间、无限插旗数量、根据鼠标坐标得知是否有雷、一键获胜、一键镖旗、更改初级难度、中级难度、高级难度。

2.具体分析过程 2.1 暂停时间 2.1.1 CE找基址 先用CE找基址,点击一块区域发现时间会变动,用CE找精确的4字节数值,找到之后发现是绿色的基址。 、

时间基址:0x0100579C

CE右键->找出是什么改写了这个地址

->双击这条汇编指令

这个指令是让时间自增的,记住0x002FF5这个地址。 然后写代码把这里nop掉。

2.1.2 找到时间基址后写代码 以下是MFC内部button的代码

//改变时间的代码的基址 2. DWORD dwTime = 0x01002FF5; 3. DWORD dwRealSize = 0; 4. DWORD pid = 0; 5. //获取游戏窗口句柄 6. HWND hWnd = ::FindWindow(NULL, L"扫雷"); 7. if (hWnd == NULL) 8. { 9. MessageBox(L"没有找到扫雷游戏进程", L"错误", 0); 10. return; 11. } 12. //通过窗口句柄得到进程ID 13. DWORD dwThreadPid = GetWindowThreadProcessId(hWnd, &pid); 14. //通过进程ID得到进程句柄 15. HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, 0, pid); 16. BYTE* nopCode = new BYTE[6]; 17. for (size_t i = 0; i < 6; i++) 18. { 19. nopCode[i] = 0x90; 20. } 21. WriteProcessMemory(hProcess, (LPVOID)dwTime, nopCode, 6, &dwRealSize); 22. CloseHandle(hProcess);

2.2 镖旗次数无限 跟修改时间一样,用CE找到基址,再把修改镖旗数的代码修改。

2.3 切换难度初级、中级、高级 首先把扫雷拖进OD,跑起来,点击上方一排H小按钮,右键刷新来到这里

选择父窗口是Topmost,标题是扫雷的这栏,右键跟随ClassProc 来到这里

这里就是窗口的回调函数 关键在于我知道这是SDK程序 把扫雷拖进PEID分析,而且这个程序很小,所以判断是一个SDK程序 窗口回调函数原型是

//窗口消息回调函数 2. LRESULT CALLBACK WindowProc ( 3. HWND hwnd, 4. UINT uMsg, 5. WPARAM wParam, 6. LPARAM IParam 7. );

当窗口点击菜单的时候回发送 uMsg == WM_COMMAND消息,wParam是对应的菜单ID 图下,在这里下一个条件断点, ebx == WM_COMMAND 右键->断点->条件断点 ebx== WM_COMMAND

跑起来,切换一下难度试一下 断下来了 ALT+K看一下堆栈的参数

哇哦,参数都被我看光光了哎 WM_COMMAND消息,窗口句柄也看到,菜单ID也看到了,但是这里显示的是10进制的 16进制应该是0x20B

我写了如下代码,尝试用注入器,注入一下试试,看能不能改变难度

//以下是汇编指令 扫雷调用窗口消息的函数 2. //菜单触发WM_COMMAND消息等会再回头看 3. push 0 //0 IParam 4. push 0x209 //菜单消息宏 wParam 5. push 0x111 //宏 uMsg 6. push 0x00D07AC //窗口句柄 hwnd 7. call 0x01001BC9 //函数基址 LRESULT CALLBACK WindowProc

成功~ 写MFC代码吧,我就贴部分代码

1. HWND hWnd = ::FindWindow(NULL, L"扫雷"); 2. if (hWnd == NULL) 3. { 4. MessageBox(L"没有找到扫雷游戏进程", L"错误", 0); 5. return; 6. } 7. ::SendMessage(hWnd, WM_COMMAND, 0x20B, 0); 8. return;

2.4 一键获胜 得先找到雷区的基址,用CE找呗,也不知道是什么类型,先找字节类型呗 字节类型,为知的初始值,变动的数值,不变动的数值,一直搜索 找到结果如下

那我就去0x01005361这个地址去看一下,前两个都是栈里的值,先不管 而且0x01005361是个绿颜色的是基址,哟,在OD里果然是雷区数组的基址

什么OF、8F、10 测试得到 8F是雷、0F是可以点击的、10是墙壁

写代码之前先把雷区的高宽得到,切换难度然后用CE找 宽基地址:0x01005334 高基地址:0x01005338

我要发送鼠标点击,先用spy++获取到x、y的坐标 监视->日志消息->选中窗口->消息->WM_LBUTTONDOWN 点击第一个方块

X坐标:23 Y坐标:60

然后测量出方块的宽度为16

写代码:

1. DWORD pid = 0; 2. //获取游戏窗口句柄 3. HWND hWnd = ::FindWindow(NULL, L"扫雷"); 4. if (hWnd == NULL) 5. { 6. MessageBox(L"没有找到扫雷游戏进程", L"错误", 0); 7. return; 8. } 9. //通过窗口句柄得到进程ID 10. DWORD dwThreadPid = GetWindowThreadProcessId(hWnd, &pid); 11. //通过进程ID得到进程句柄 12. HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, 0, pid); 13. //雷区数据基地址 0x01005361 14. //宽 基地址 0x01005334 15. //高 基地址 0x01005338 16. //每个雷格距离 为16 17. //0x8F是雷 18. unsigned char gamedata[24][32] = { 0 }; 19. 20. BOOL bRead_If_Success = 21. ReadProcessMemory(hProcess, 22. (LPVOID)0x01005361, 23. &gamedata, 24. 24 * 32, 25. &pid 26. ); 27. 28. if (bRead_If_Success == 0) 29. { 30. MessageBox(L"读取扫雷游戏内存失败", 0, 0); 31. return; 32. } 33. 34. //数据处理 35. DWORD dwHight = 0; 36. 37. bRead_If_Success = 38. ReadProcessMemory(hProcess, 39. (LPVOID)0x01005338, 40. &dwHight, 41. sizeof(dwHight), 42. &pid 43. ); 44. //数据处理 45. DWORD dwWidth = 0; 46. 47. bRead_If_Success = 48. ReadProcessMemory(hProcess, 49. (LPVOID)0x01005334, 50. &dwWidth, 51. sizeof(dwWidth), 52. &pid 53. ); 54. 55. 56. if (bRead_If_Success == 0) 57. { 58. MessageBox(L"读取扫雷游戏内存失败", 0, 0); 59. return; 60. } 61. 62. 63. short gamex = 20; 64. short gamey = 60; 65. unsigned short xypos[2] = { 0 }; 66. 67. for (size_t i = 0; i < dwHight; i++) 68. { 69. for (size_t j = 0; j < dwWidth; j++) 70. { 71. if (0x10 == gamedata[i][j]) 72. { 73. break; 74. } 75. xypos[0] = gamex + j * 16; 76. xypos[1] = gamey + i * 16 ; 77. if (0x8F != gamedata[i][j]) 78. { 79. ::PostMessage(hWnd, WM_LBUTTONDOWN, MK_LBUTTON, *(int*)xypos); 80. ::PostMessage(hWnd, WM_LBUTTONUP, 0, *(int*)xypos); 81. } 82. } 83. } 84. CloseHandle(hProcess);

2.5 一键镖旗 方法1:把一键获胜的代码改一下,是雷就发送右键按下、弹起消息,就好了。 方法2:把雷区的内存写入0x8E,但是写完之后要把扫雷最小化,最大化,才能看见。

2.6 根据鼠标坐标判断是不是雷 这里就必须要用到DLL注入的技术了 写一个MFC的静态DLL 在初始化函数BOOL CSaoLeiDLLApp::InitInstance()里面写东西

在我的回调函数里面写我的东西 比如 if (Msg == WM_MOUSEMOVE) 原理就是不断获取鼠标消息,得到鼠标坐标,计算变成雷区数组的下标,是雷就把标题改成有雷 如下图:

代码如下:

1. 扫雷新的回调函数 2. LRESULT CALLBACK MyWindowProc( 3. _In_ HWND hWnd, 4. _In_ UINT Msg, 5. _In_ WPARAM wParam, 6. _In_ LPARAM lParam) 7. { 8. //一键秒杀 9. if (Msg == WM_KEYDOWN && wParam == VK_F5) 10. { 11. DWORD width = *g_pWidth; 12. DWORD height = *g_pHeight; 13. for (size_t i = 0; i < height; i++) 14. { 15. for (size_t j = 0; j < width; j++) 16. { 17. BYTE code = *(BYTE*)(g_GameArray + i * 0x20 + j); 18. if (code == 0x10) 19. { 20. break; 21. } 22. if (code != 0x8F ) 23. { 24. WORD x = 20 + j * 16; 25. WORD y = 60 + i * 16 ; 26. DWORD dwPos = MAKELONG(x, y); 27. PostMessage(g_hWnd, WM_LBUTTONDOWN, MK_LBUTTON, (LPARAM)dwPos); 28. PostMessage(g_hWnd, WM_LBUTTONUP, MK_LBUTTON, (LPARAM)dwPos); 29. } 30. } 31. } 32. } 33. 34. //鼠标坐标 35. else if (Msg == WM_MOUSEMOVE) 36. { 37. //首先获取x和y轴的坐标 38. WORD xPos = GET_X_LPARAM(lParam); 39. WORD yPos = GET_Y_LPARAM(lParam); 40. xPos = (xPos - 12) / 16; 41. yPos = (yPos - 55) / 16; 42. if (xPos + yPos <= width + height) 43. { 44. CString titleText; 45. if (*(g_GameArray + xPos + yPos * 0x20) == 0x8F ) 46. { 47. titleText.Format(L"扫雷 x:%d,y:%d,有雷 F5:一键秒杀", xPos + 1, yPos + 1); 48. } 49. else 50. { 51. titleText.Format(L"扫雷 x:%d,y:%d,没雷 F5:一键秒杀", xPos + 1, yPos + 1); 52. } 53. ::SetWindowText(g_hWnd, titleText); 54. } 55. else 56. { 57. CString titleText; 58. titleText.Format(L"扫雷 辅助工具by 博客:简单起个名字"); 59. ::SetWindowText(g_hWnd, titleText); 60. } 61. } 62. return CallWindowProc(g_OdlProc, hWnd, Msg, wParam, lParam); 63. }

3.总结 我自己写了一个MFC的辅助工具,然后看官方视频写了一个注入的DLL 总共是两个工程。 一个小小的扫雷游戏,麻雀虽小但五脏俱全,练手的第一个项目。要比较熟练的会使用OD、CE工具,还要懂得Windows编程的API,还要会注入技术,要会读写内存的技术。

最新回复(0)