在上一篇教程中,我们已经找到雷的基址0x1005361 这次,我们编程实现秒杀的功能
文章末尾有源码下载
我们先创建一个MFC对话框工程,界面如下所示 edit控件绑定了一个cstring的变量m_strData 还有两个按钮 用来获取数据和秒杀的,获取数据是为了测试基址
我们双击获取数据按钮 来编写获取数据的函数
我们获取数据之前,得用OpenProcess打开扫雷这个进程(并不是双击运行扫雷,在这之前应该已经双击运行了)这样才能得到进程句柄,然后通过ReadProcessMemory函数来读取0x1005361 的数据 但OpenProcess函数需要进程的ID,所以我们得FindWindow得到句柄,然后GetWindowThreadProcessId得到进程ID,
FindWindow函数接受2个参数 窗口类名 和 窗口标题 随便传哪个都行,不传的填0或者null ,返回值就是窗口的句柄 ;
比如 我们要找扫雷 HWND hWnd = ::FindWindow(NULL, L”扫雷”);
GetWindowThreadProcessId接受2个参数 第一个是窗口句柄,第二个是DWORD类型的指针,用来保存该窗口对应的进程ID,返回值是线程ID
比如,我们获取扫雷的PIDDWORD dwPid;
GetWindowThreadProcessId(hWnd, &dwPid);
OpenProcess函数接受3个参数第一个参数是打开的权限,我们传PROCESS_ALL_ACCESS表示所有权限,第二个参数表示是否继承,第三个参数是进程ID,返回值是进程句柄,具体的参数含义和宏请查阅MSDN
比如,我们获取扫雷的进程句柄 HANDLE hWinMine = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid);
ReadProcessMemory有5个参数,第一个参数是进程句柄,第二个参数是要读的内存地址,也就是我们的基址,第三个参数是读出来存哪里,也就是缓冲区,第四个参数是读多少个字节,第五个参数是实际读到多少个字节
前面几个函数都没问题,最后一个函数,我们不知道读多少个字节,我们先把扫雷调成高级,看看有多少格子
我们发现是16*30个格子,那么我们就读byte data[16][32] = { 0 };个字节吧
byte data[16][32] = { 0 };
ReadProcessMemory(hWinMine, (LPVOID)0x1005361, data, sizeof(data), NULL);
我们再把读出来的显示到edit上面去
按钮获取数据的完整代码如下
void CslDlg::OnBnClickedBtngetdata()
{
HWND hWnd = ::FindWindow(NULL, L"扫雷");
if (!hWnd)
{
MessageBox(L"游戏没有运行");
return;
}
DWORD dwPid;
GetWindowThreadProcessId(hWnd, &dwPid);
HANDLE hWinMine = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid);
if (!hWinMine)
{
MessageBox(L"打开进程失败");
return;
}
byte data[16][30] = { 0 };
if (!ReadProcessMemory(hWinMine, (LPVOID)0x1005361, data, sizeof(data), NULL))
{
MessageBox(L"读内存失败");
return;
}
for (int i = 0; i < 16; i++)
{
for (int j = 0; j < 30; j++)
{
char szTmp[10] = { 0 };
sprintf_s(szTmp, 10, "%02x", data[i][j]);
m_strData += szTmp;
m_strData += " ";
}
m_strData += "\r\n";
}
UpdateData(FALSE);
}
我们打开扫雷,然后设置成初级,并打开我们的软件,点获取数据看看效果,如下图,
可以看到根据数据显示,(若想了解更多,请关注我的博客http://www点dbgpro点com/)第二排的第4个是雷,我们点开却发现 不是雷,我们在附近找找看有没有雷
我们发现,雷在第二个位置 而不是第四个,这是怎么回事
看看完整的数据,我们发现最后一列的后面 和最后一行的后面都是10 也就是说游戏数组里面存了边框,那也就是说 游戏数据应该是[16][32]的数组,
我们把代码里面30改32
…………
byte data[16][32] = { 0 };
if (!ReadProcessMemory(hWinMine, (LPVOID)0x1005361, data, sizeof(data), NULL))
{
MessageBox(L”读内存失败”);
return;
}
for (int i = 0; i < 16; i++)
{
for (int j = 0; j < 32; j++)
…………
然后再编译看看,我们再获取数据,由于游戏没关,获取到的是上次的数据,我们看到 红色背景的雷(我们点爆的)是cc ,其他的雷都变8a了,但是位置都对了
重新开始了一盘发现也是对的;
那么我们就可以开始写秒杀的功能了,思路比较简单,我们通过判断数组里面的值是不是0x8f 不是的话就用 PostMessage发送鼠标左键消息
找到所有的雷的行列坐标以后怎么实现一键扫雷呢??
有一个思路是把所有不是雷的地方都点击一下,这样游戏就胜利了。
SendMessage(hwnd, WM_LBUTTONUP, MK_LBUTTON, MAKELONG(x, y)); x y如何转化为窗口的x,y。
我的做法是找来了QQ截图软件,观察出像素点信息为,二维数组的棋盘起始偏移为(5,50) 方块之间的空格为4,方块为12*12.则 (row, col)对应的窗体偏移为
int x = (col + 1) * 16 + 5;
int y = (row + 1) * 16 + 50;
(由于都是从0开始 所以+1)
那么 秒杀那个按钮的实现代码如下
void CslDlg::OnBnClickedBtnkill()
{
//先要获取数据
HWND hWnd = ::FindWindow(NULL, L"扫雷");
if (!hWnd)
{
MessageBox(L"游戏没有运行");
return;
}
DWORD dwPid;
GetWindowThreadProcessId(hWnd, &dwPid);
HANDLE hWinMine = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid);
if (!hWinMine)
{
MessageBox(L"打开进程失败");
return;
}
byte data[16][32] = { 0 };
if (!ReadProcessMemory(hWinMine, (LPVOID)0x1005361, data, sizeof(data), NULL))
{
MessageBox(L"读内存失败");
return;
}
//秒杀逻辑
for (int i = 0; i < 17; i++)
{
for (int j = 0; j < 32; j++)
{
if (data[i][j] != 0x8f)
{
int x = (j + 1) * 16 + 5;
int y = (i + 1) * 16 + 50;
::PostMessage(hWnd, WM_LBUTTONDOWN, 0, MAKELPARAM(x, y));
::PostMessage(hWnd, WM_LBUTTONUP, 0, MAKELPARAM(x, y));
}
}
}
}
我们编译 运行,然后打开扫雷 高级的,点秒杀试试
是不是很酷~
PS:由于我们逆的是老版本的扫雷,下图的新版本扫雷不能用我们的挂,有兴趣的朋友可以自己逆,压缩包里面包含了我从xp复制出来的扫雷
用鼠标模拟也可实现QQ连连看的外挂,大家可以自己先试试,下一篇我们将实现QQ连连看