以下内容转载至本人QQ空间。如有雷同,纯属巧合。
前段时间,应魏小子之邀请开始学习编写外挂,后来他自己找到了按键精灵解决他的问题,于是我开始了自己的研究。最近研究了windows扫雷,QQ连连看两款游戏的外挂,下面先介绍扫雷外挂。
为什么先研究这两款呢?主要是网上能查得到相关信息,功力不够只能由浅入深,也算是依葫芦画瓢。
先看看扫雷效果:(由于游戏时间只需要1秒,因此可以称之为“秒杀”)
下面是编程方法:
一:可能需要的技能:
1.了解VC或其他编程语言(本例为VC)
2.熟悉CE(Cheat Engine)的使用方法
3.熟悉OD(Ollydbg)的使用方法
4.熟悉VC的spy++的使用方法
如果不了解VC,需要下很大的功夫;如果不了解CE和OD可以在网上搜索相关教程,别指望我能教会你。
如果不了解spy++和Findwindow等函数的用法也可以网上搜索。
二:参考资料
1) 豆丁网——基于CE和OD开发扫雷外挂详细步骤 http://www.docin.com/p-49471470.html
2)看雪学院——CARY写的【原创】打造扫雷终极外挂 http://bbs.pediy.com/showthread.php?t=48517
三:方法分析
1. 首先是CARY说的猜测这个游戏要用到随机函数rand,最后真的发现游戏使用了该函数,于是用OD进行反汇编调试,找到了游戏构建雷所需数组的基地址,通过读取基地址的信息判断各个区域是否有雷。(如果游戏没有使用rand而是使用了编程者自己写的随机函数,那么该方法就不行,就只有想其他的办法,简单点说外挂制作就是要猜测编程者的编程方法,然后逆向思维,看是否有机可趁)
2. 找雷就是要找出有雷和无雷时该数值的对应值,比如该游戏中有雷就是8F,无雷时是0F。
3.CARY里面介绍的秒杀方法是可行,但是却不完美,他是通过直接修改“雷区”内存地址实现扫雷。
这有两个缺点:1)就是很容易被杀毒软件杀掉,因为你要直接写内存很多杀毒软件不允许的。
2)由于游戏会记时还有英雄榜,直接写内存只能秒杀,不能让时间启动和停止,更不能让你登上英雄榜。除非找到写入英雄榜和停止时间的相关代码并进行信息传递,但是我一时也没找到。
4.本例采用模拟鼠标点击的方式,当读取内存发现有雷就右键单击,如果无雷就左键单击,速度很快,实现秒杀。
四:外挂代码
1. 主函数
DWORD addr = 0x1005361; //windowsXP下的雷区基址,如果是其他操作系统需要自己重新找
DWORD x = 0x10056A8;
DWORD y = 0x10056AC;
RECT r1;
POINT p;
bEnableDebugPriv(); //提升权限函数,由于OpenProcess需要管理员权限
//保存当前鼠标指针
//取得当前鼠标位置
GetCursorPos(&p);
HWND hwnd = ::FindWindow(NULL, _T("扫雷")); //查找应用程序窗口
DWORD hProcessId;
::GetWindowThreadProcessId(hwnd, &hProcessId); //得到窗口进程
::GetWindowRect(hwnd,&r1); //得到窗口大小
HANDLE Process = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, hProcessId); //打开进程
int b = 0 , s = 0, nx = 0, ny = 0;
::ReadProcessMemory(Process, (LPCVOID)x, &nx, 1, NULL); //获取横向方格长度
::ReadProcessMemory(Process, (LPCVOID)y, &ny, 1, NULL); //获取纵向方格长度
for(int i = 0; i < nx * 32; i += 32)
{
for(int j = 0; j < ny; j++)
{
::ReadProcessMemory(Process, (LPCVOID)(addr + i+j), &b, 1, NULL);
if (b == 0x8F || b == 0x8E) //判断是否有雷
{
SetCursorPos(22+j*16+r1.left,115+i/32*16+r1.top);
//移动鼠标位置,以下位置是很重要的,22和115是第一个雷相对应窗口的x,y坐标的大至距离,可以不是很精确,只要鼠标点击能实现就行,可以多根据抓图工具查看像素值,并且实践中多试一下。
mouse_event(MOUSEEVENTF_RIGHTDOWN,0,0,0,0);
mouse_event(MOUSEEVENTF_RIGHTUP,0,0,0,0);
}
else
{
SetCursorPos(22+j*16+r1.left,115+i/32*16+r1.top);
mouse_event(MOUSEEVENTF_LEFTDOWN,0,0,0,0);
mouse_event(MOUSEEVENTF_LEFTUP,0,0,0,0);
}
}
}
::InvalidateRect(hwnd, NULL, TRUE);
SetCursorPos(p.x,p.y);
::CloseHandle(Process);
2. 提升权限函数 bool bEnableDebugPriv()
HANDLE hToken;
LUID sedebugnameValue;
TOKEN_PRIVILEGES tkp;
if (!OpenProcessToken(GetCurrentProcess(),
TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken)) {
return false;
}
if (!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &sedebugnameValue)) {
CloseHandle(hToken);
return false;
}
tkp.PrivilegeCount = 1;
tkp.Privileges[0].Luid = sedebugnameValue;
tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
if (!AdjustTokenPrivileges(hToken, FALSE, &tkp, sizeof(tkp), NULL, NULL)) {
CloseHandle(hToken);
return false;
}
return true;
五、外挂缺陷
1. 由于该方法是读取基址,对于不同操作系统(比如win7和XP)的该游戏的基址是不同,因此需要对不同操作系统进行量身订做,以上代码适用于XP系统,如果是win7可能findwindow类名以及雷区基址不同。
2.模拟鼠标点击方式虽然不需要写内存,但是需要读内存,如果编程者注意到这个方面,在编写程序的时候不是全局分配而是临时分配则用OD跟踪的基址就不行,对于扫雷这种小游戏而言完全可以不用全局分配,就像我同事之前搞的一个水晶连连看就不是全局分配的。
3.模拟鼠标点击方式需要扫雷窗口完全不能被其他窗口遮挡,这里可以通过代码访问扫雷设置扫雷为始终最前,还可以将外挂窗体开机最小化并且通过设置windows系统热键RegisterHotKey相关函数实现快捷键。
4.可能在打开两个以上的外挂程序时出现失灵的状况。解决方法:可以通过互斥对象及OpenMutex等函数限制多个应用外挂程序同时运行。(可以网上查询)