在上一篇教程中,我们已经找到雷的基址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);
}

我们打开扫雷,然后设置成初级,并打开我们的软件,点获取数据看看效果,如下图,

可以看到根据数据显示,第二排的第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));
			}
		}
	}
}

我们编译 运行,然后打开扫雷 高级的,点秒杀试试

是不是很酷~

源码链接: http://pan.baidu.com/s/1hsjz5ha 密码: yw4q

PS:由于我们逆的是老版本的扫雷,下图的新版本扫雷不能用我们的挂,有兴趣的朋友可以自己逆,压缩包里面包含了我从xp复制出来的扫雷

用鼠标模拟也可实现QQ连连看的外挂,大家可以自己先试试,下一篇我们将实现QQ连连看

打赏