一.注入代码 1.ollydbg打开扫雷,多按几次f9,然后打开window视窗(可以在view–windows打开,也可以直接点那个w)
2.之后右键->Follow ClassProc。这个就是窗口的处理函数,这样就定位到了窗口的处理函数,此时就是断在了该函数的起始位置:01001BC9,如图:
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 插个基础知识:windows的窗口处理函数:WindowProc(): LRESULT CALLBACK WindowProc ( HWND hWnd, UINT uMsg, WPARAM wParam, LParam); 参数的意义如下: HWND hWnd : 事件引起消息发生的那个窗口。(窗口的句柄) UINT message: 消息的ID,它是32位值,指明了消息类型。(消息id) WPARAM wParam : 32位值,包含附加信息,决定于消息的种类。例如键盘的哪个键代码。 LPARAM lParam: 32位值,同上。例如,前16位=重复数 接着8位:扫描码(决定于厂家) 第24位:为1时表示扩展键。 第25到28位:保留区 第29位为1时=alt按下,否则为0。 第30位为1时=消息前按下,否则为0。 第31位为1时=正在被释放,否则为0。 当点击菜单的时候,WindowProc会被系统调用, uMsg 赋值为:WM_COMMAND wParam 赋值为:对应菜单的id(我们要分析的数据) 再插一句:这个WM_COMMAND就是当用户从菜单中选择命令项、控件将通知消息发送到其父窗口或翻译快捷键击时发送的参数。
3.接下来看点菜单栏里:“初级”“中级”“高级”时菜单的id 是什么,(即要找第三个参数,第二个入栈)
在WindowProc函数处下条件断点:uMsg==WM_COMMAND时
在[arg.4]参数处右键->breakpoint->conditional(条件断点),条件填写:edx==WM_COMMAND。因为第二个参数就是把参数传给了edx,所以左边是edx。
这个就是对运行到这里是不一定会断,只有满足条件也可以断下来,进行了筛选
试验:在切换初级,中级,高级时候成功在断点处断下(只有在点击菜单时才是WM_COMMAND消息),而运行其他消息指令是不会断的
此时可以分析出点击菜单时,传入函数的四个参数的值:
在栈窗口观察,利用ebp偏移看,右键地址->address->relative to ebp
初级菜单id:0x209
可以看到第二个参数值是0x111,连着的这四个就是传入的四个参数
同理观察到:中级菜单id:0x20A , 高级菜单id:0x20B
4.注入代码:
模拟函数调用,传递四个参数:
1 2 3 4 5 push 0 push 0x20b push 0x111 push 230526 call 01001bc9
如图:
这个效果就是可以不断变换第二个参数的值来操作扫雷的菜单
将这个过程利用mfc实现:
先添加给button,代码也简单,如下:
1 2 3 4 5 6 7 8 9 10 11 void C扫雷Dlg::OnBnClickedButton1 () { HWND hWnd = ::FindWindow (NULL , _T("扫雷" )); if (hWnd == NULL ) { ::MessageBox (NULL , _T("扫雷游戏未打开" ), _T("错误" ), MB_OK); return ; } ::SendMessage (hWnd, WM_COMMAND, 0x209 , 0 ); }
二.分析游戏基地址 1.基地址的概念
全局变量,字符常量的地址一般都是基地址,查找和验证基地址
1.首次先让要查找的数据稳定在一个范围或者精确的值(使用CE的首次搜索)
2.改变要查找的数据,根据变化再筛选出数据的地址
使用OD验证是否是基地址,通过内存断点的方式来验证
内存断点:如果检测到对该内存有读或写的操作就会断下来
add dowrd ptr[1005194] eax 这种立即数寻址一般就是基地址
三.使用CE扫描具体实现 现在目标是查找雷区的内存
猜想:雷区在内存中以一个二维数组形式存放
先选”未知的初始值”,首次扫描,接着不断变换雷区的首元素,(注意,这里不可以搜索“确定的值”,因为即使显示“1”,在内存中的存储可不一定,卡了我10分钟) 按照”未变动”或者是”变动”不断缩小范围,最后可以锁定雷区的首元素地址(01005361),右键(browse this memory region)查看该地址对应内存
各数据意义 随便再点点,发现41对应1,42对应2……8f对应雷,雷区每行以01结尾
此时就可以达到作弊效果了,但是没有实现自动化
四.实现自动扫雷(利用mfc实现) 准备工作 因为有初级,中级等不同模式,所以为了做出一个通用的…,要找雷区的宽和高所在地址,继续使用CE查找,步骤同上。
1 2 3 雷区数据基地址 0x1005361 宽的基地址 0x1005334 高的基地址 0x1005338
在实现自动化之前,先来一个小目标练练手:
读取当前的雷数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 void C扫雷Dlg::OnBnClickedButton5 () { DWORD pid; HWND hWnd = ::FindWindow (NULL , _T("扫雷" )); if (hWnd == NULL ) { ::MessageBox (NULL , _T("扫雷游戏未打开" ), _T("错误" ), MB_OK); return ; } GetWindowThreadProcessId (hWnd, &pid); HANDLE hProcess = OpenProcess (PROCESS_ALL_ACCESS, FALSE, pid); ReadProcessMemory (hProcess, (LPCVOID)0x1005194 , &m_editbase, sizeof (m_editbase), &pid); UpdateData (FALSE); }
解释:核心函数是ReadProcessMemory(),传入的参数:进程句柄,要读取数据的起始地址,存放数据的缓存区地址,要读取的字节数,实际读取数存放地址。
为了找到进程句柄,需要OpenProcess()这个函数,它通过进程id来找
为了找到进程id,需要GetWindowThreadProcessId(),它通过窗口句柄找
为了找到窗口句柄,需要FindWindow()函数,它通过窗口名称来找
窗口名称每次运行都不会变,就是“扫雷”嘛
效果如图:
正式实现 接下来,试着把雷区打印出来:
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 void C扫雷Dlg::OnBnClickedButton6 () { DWORD pid; HWND hWnd = ::FindWindow (NULL , _T("扫雷" )); if (hWnd == NULL ) { ::MessageBox (NULL , _T("扫雷游戏未打开" ), _T("错误" ), MB_OK); return ; } GetWindowThreadProcessId (hWnd, &pid); HANDLE hProcess = OpenProcess (PROCESS_ALL_ACCESS, FALSE, pid); unsigned char gamedata[24 ][32 ] = { 0 }; if (!ReadProcessMemory (hProcess, (LPCVOID)0x01005361 , &gamedata, 32 * 24 , &pid)) { ::MessageBox (NULL , _T("读取扫雷游戏进程失败" ), _T("错误" ), MB_OK); return ; } DWORD dwHight = 0 ; m_strshowdata.Empty (); if (!ReadProcessMemory (hProcess, (LPCVOID)0x01005338 , &dwHight, sizeof (dwHight), &pid)) { ::MessageBox (NULL , _T("读取扫雷游戏进程失败" ), _T("错误" ), MB_OK); return ; } CString strTemp = _T("" ); short gamex = 0x13 ; short gamey = 0x59 ; for (int i = 0 ; i < dwHight; ++i) { for (int j = 0 ; j < 32 ; ++j) { if (0x10 == gamedata[i][j]) { break ; } strTemp.Format (_T("%02X " ), gamedata[i][j]); m_strshowdata += strTemp; } m_strshowdata += _T("\r\n" ); } UpdateData (FALSE); }
效果如图:
其中8F对应的就是雷
在此基础上就可以实现自动化了,原理就是模拟鼠标点击消息发送给扫雷程序
增加的代码:
1 2 3 4 5 6 7 8 9 10 unsigned short xypos[2 ] = { 0 }; xypos[0 ] = gamex + j * 0x18 ; xypos[1 ] = gamey + i * 0x18 ;if (0x8F != gamedata[i][j]) { ::PostMessage (hWnd, WM_LBUTTONDOWN,MK_LBUTTON,*(int *)xypos); ::PostMessage (hWnd, WM_LBUTTONUP,0 , *(int *)xypos); }
附上最后的完整代码:
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 void C扫雷Dlg::OnBnClickedButton6 () { DWORD pid; HWND hWnd = ::FindWindow (NULL , _T("扫雷" )); if (hWnd == NULL ) { ::MessageBox (NULL , _T("扫雷游戏未打开" ), _T("错误" ), MB_OK); return ; } GetWindowThreadProcessId (hWnd, &pid); HANDLE hProcess = OpenProcess (PROCESS_ALL_ACCESS, FALSE, pid); unsigned char gamedata[24 ][32 ] = { 0 }; if (!ReadProcessMemory (hProcess, (LPCVOID)0x01005361 , &gamedata, 32 * 24 , &pid)) { ::MessageBox (NULL , _T("读取扫雷游戏进程失败" ), _T("错误" ), MB_OK); return ; } DWORD dwHight = 0 ; m_strshowdata.Empty (); if (!ReadProcessMemory (hProcess, (LPCVOID)0x01005338 , &dwHight, sizeof (dwHight), &pid)) { ::MessageBox (NULL , _T("读取扫雷游戏进程失败" ), _T("错误" ), MB_OK); return ; } CString strTemp = _T("" ); short gamex = 0x13 ; short gamey = 0x59 ; unsigned short xypos[2 ] = { 0 }; for (int i = 0 ; i < dwHight; ++i) { for (int j = 0 ; j < 32 ; ++j) { if (0x10 == gamedata[i][j]) { break ; } xypos[0 ] = gamex + j * 0x18 ; xypos[1 ] = gamey + i * 0x18 ; if (0x8F != gamedata[i][j]) { } strTemp.Format (_T("%02X " ), gamedata[i][j]); m_strshowdata += strTemp; } m_strshowdata += _T("\r\n" ); } UpdateData (FALSE); }
效果展示:
完毕!