例如:系统环境:win10 64位
工具:吾爱专用OD、CE、代码注入器
1.分别以两种方式写辅助工具,MFC的辅助工具,DLL注入补丁模式的辅助工具。
2.功能:暂停时间、无限插旗数量、根据鼠标坐标得知是否有雷、一键获胜、一键镖旗、更改初级难度、中级难度、高级难度。
2.具体分析过程
2.1 暂停时间
2.1.1 CE找基址
先用CE找基址,点击一块区域发现时间会变动,用CE找精确的4字节数值,找到之后发现是绿色的基址。
、
时间基址:0x0100579C
->双击这条汇编指令
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找呗,也不知道是什么类型,先找字节类型呗
字节类型,为知的初始值,变动的数值,不变动的数值,一直搜索
找到结果如下
什么OF、8F、10
测试得到
8F是雷、0F是可以点击的、10是墙壁
我要发送鼠标点击,先用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 CSDN博客:简单起个名字");
59. ::SetWindowText(g_hWnd, titleText);
60. }
61. }
62. return CallWindowProc(g_OdlProc, hWnd, Msg, wParam, lParam);
63. }
3.总结
我自己写了一个MFC的辅助工具,然后看官方视频写了一个注入的DLL
总共是两个工程。
一个小小的扫雷游戏,麻雀虽小但五脏俱全,练手的第一个项目。要比较熟练的会使用OD、CE工具,还要懂得Windows编程的API,还要会注入技术,要会读写内存的技术。
因篇幅问题不能全部显示,请点此查看更多更全内容