简单的扫雷内存挂
本帖最后由 别说见过我 于 2016-9-12 00:37 编辑这么多年来玩扫雷从未赢过,这次拿它来练手也算是圆了我多年来玩赢一盘的梦想。
动手之前,我们先来熟悉几个windows的API函数:
“OpenProcess”函数用来打开一个已存在的进程对象,并返回进程的句柄。
函数原型:
HANDLE OpenProcess(
DWORD dwDesiredAccess, //访问权限
BOOL bInheritHandle,//是否继承句柄
DWORD dwProcessId); //进程pid
“VirtualQueryEx”用来查询进程所使用的内存块的信息。
函数原型为:
DWORD VirtualQueryEx(
HANDLE hProcess, //进程句柄
LPCVOID lpAddress, //查询的地址
PMEMORY_BASIC_INFORMATION lpBuffer, //指向MEMORY_BASIC_INFORMATION结构的指针,用于在本进程中接收内存信息
DWORD dwLength); //MEMORY_BASIC_INFORMATION结构的大小
“ReadProcessMemory” 内存读取函数, 根据进程句柄和指定位置读取其内存数据。
函数原型为:
BOOL ReadProcessMemory(
HANDLE hProcess, //进程句柄
LPCVOID lpBaseAddress, //读取的地址
LPVOID lpBuffer, //本进程的内存地址指针,指向读取到的数据
DWORD nSize, //指定从地址处读出多少数据,单位字节
LPDWORD lpNumberOfBytesRead);//实际读出的空间大小,单位字节
“WriteProcessMemory” 写入某一进程的内存区域。入口区必须可以访问,否则操作将失败。
函数原型为:
BOOL WriteProcessMemory(
HANDLE hProcess,//进程句柄
LPVOID lpBaseAddress, //写入的起始地址
LPVOID lpBuffer, //要写入的数据的指针
DWORD nSize, //写入数据的大小,单位字节
LPDWORD lpNumberOfBytesWritten);//实际写入的数据大小,单位字节
我们所要用到的就是这四个函数,那么接下来我们来理一理大致思路:
扫雷每次的地雷分布是随机的,那么我们可以肯定它是进程执行过程中,在内存中生成了雷区信息数据,并非是写在执行文件中段内固定的。
我们可以首先用ReadProcessMemory来查找数据,找到这片雷区的信息,然后通过WriteProcessMemory把地雷的数据给覆盖掉,
或许能实现“不踩到雷,顺利过关”。我用的是“或许”这个词,因为有几点其实我们都是不确定的:
1.扫雷的获胜条件是什么?
2.将地雷信息给复写掉,会不会引发程序异常?
3.地雷怎样识别?如何避免错误识别?
前两条需要使用汇编知识来分析程序逻辑才能得知,我们先不考虑这些问题(完全无能为力,听天由命吧...);
但第三条不能忽略,没有雷区的特征,我们完全就无从下手,
好在扫雷游戏早就被众多高手分析过了,我找到了其中一篇文章中的描述:
文章出自:http://www.ghoffice.com/bbs/read-htm-tid-46053.html
在以上的信息中我们得到两个重要信息:
1.扫雷对于雷区的绘制是通过一个二维的BYTE数组,那么我们等下查内存数据的时候就知道了数据格式。
2.地雷是用0x8f;安全区是通过0x0f;墙壁边界是0x10;
有了以上知识做铺垫,那么我们可以细化先前的思路,如下:
最终实现代码如下:
#include <windows.h>
#include <stdio.h>
const unsigned char MINE = 0x8f; //地雷
const unsigned char SAFE_AREA = 0x0f; //安全位置
const unsigned char WALL = 0x10; //墙壁
static void removeMine(HANDLE hProcess, PVOID addr){
DWORD dwNumberOfBytesRead;
PBYTE pAddress = NULL;
/*
修改“地雷”的数据,两种可选方案:
1.用SAFE_AREA覆盖:只要先前将旗帜插完,随便点一下即过关
2.用WALL覆盖:在界面中有地雷的格子就会呈现出不可选的样子,然后在上面插旗、随便点哪也可以过关
*/
unsigned char replace = WALL;
if (WriteProcessMemory(hProcess, addr,&replace, sizeof(unsigned char),&dwNumberOfBytesRead) != 0){
printf("成功修改内存数据\n");
}else{
printf("修改内存数据失败\n");
}
}
static unsigned char getValue(HANDLE hProcess, PVOID pos){
DWORD pvBufferLocal;
DWORD dwNumberOfBytesRead;
if(ReadProcessMemory(hProcess, pos, &pvBufferLocal, sizeof(unsigned char), &dwNumberOfBytesRead) != 0){
return (unsigned char)pvBufferLocal;
}
return 0;
}
static void searchMine(HANDLE hProcess, PVOID addr, SIZE_T len){
if(len < sizeof(unsigned char))
return;
PVOID pos = addr;
while(TRUE){
if((SIZE_T)((PBYTE)pos - (PBYTE)addr) == len)
break;
unsigned char val = getValue(hProcess, pos);
if(MINE == (unsigned char)val){
//判断这个疑似地雷的字节旁边是什么数据
//如果旁边是 “地雷”、“安全位置”、“墙壁”,那么这个就“极有可能”真的是“地雷”
//有点撞运气的成分,听天由命吧 :)
//完美的方式是要判断当前这块区域是否是绘制雷区的二维数组,确定了数组边界后遍历数组修改数据。
//完整实现起来代码量比较大,不符合简单教学的目的;根据“够用就好”的原则来说,目前这样也能用,那就这么干吧。
unsigned char preVal = getValue(hProcess, (PBYTE)pos-1); //+1 -1都没有做范围判断,这么干是不行的,不过先就这样吧
unsigned char nextVal = getValue(hProcess, (PBYTE)pos+1);
if((MINE == preVal|| SAFE_AREA == preVal|| WALL == preVal) ||
(MINE == nextVal || SAFE_AREA == nextVal || WALL == nextVal)){
printf("找到疑似地雷: %p\n", pos);
removeMine(hProcess, pos);
}
}
pos = (PBYTE)pos + 1;
}
}
BOOL searchMem(DWORD pid){
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid); //以最大的权限打开进程句柄
if(NULL == hProcess)
return FALSE;
MEMORY_BASIC_INFORMATION mbi;
PBYTE pAddress = NULL;
SYSTEM_INFO siSysInfo;
GetSystemInfo(&siSysInfo);
while(pAddress < (DWORD)siSysInfo.lpMaximumApplicationAddress){
if(VirtualQueryEx(hProcess, pAddress, &mbi, sizeof(mbi)) != sizeof(mbi)){
break;
}
/*
检查内存块的保护类型是否为可读写(PAGE_READWRITE);
检查内存是否被使用(MEM_COMMIT),申请的空间如果没被使用,那么是不会产生缺页异常为其映射到物理页;
*/
if((mbi.Protect & PAGE_READWRITE) && (MEM_COMMIT == mbi.State)){
searchMine(hProcess, mbi.BaseAddress, mbi.RegionSize);
}
pAddress = ((PBYTE)mbi.BaseAddress + mbi.RegionSize);
}
CloseHandle(hProcess);
return TRUE;
}
int main(){
printf("先运行扫雷游戏,然后在进程管理器中找到winmine.exe的进程pid,将其输入到控制台,然后回车。\n");
printf("运行本程序只能作弊一次,想再次作弊,就再重新运行一次吧。\n");
printf("当程序运行结束后,记得插完所有的旗帜!位置随便,总之要把旗帜用光,否则不算赢!\n");
printf("插完旗帜后,随便点个格子就通关了游戏。\n");
printf("祝你装逼愉快 :)\n");
unsigned int pid = 0;
printf("你的扫雷游戏pid是?:");
scanf("%d", &pid);
if(NULL == pid || 0 == pid){
printf("错误的pid\n");
return 1;
}
searchMem(pid);
printf("已经完成了地雷清除工作。\n");
return 0;
}
运行结果:
使用SAFE_AREA覆盖效果:过关界面还是会显示雷没正确标记,也没有数字(数字应该是动态生成的,没了雷也就不生成了)
好在也算胜利了:
使用WALL覆盖效果:
这个外挂我是在xp 32位的系统上编译测试的,win7没有试过,不敢保证效果。
还有上面的判断雷的前后位置的时候指针+1 -1我都并没有做范围判断,这是比较危险的,理论上可能会导致非法访问,
还好我没遇到过,大家可以尝试自己动手来完善(判断边界);
还有判断雷区的逻辑也是,也有待完善(依靠特征识别二维数组,确定边界、遍历数组修改)。
希望这篇分享对大家有一定的帮助。
zhousai998 发表于 2016-9-11 10:11
怎么用.这东西.
先运行扫雷,然后不要动它,在任务管理器中找到扫雷的进程pid,
使用vc++ 贴入代码 然后编译运行,在运行之后会有提示,输入pid然后回车,
雷就被清理了,在扫雷游戏中使用完所有的旗帜(随便乱放就好了,游戏过关的要求,必须用完旗帜)。
最后随便点几一个格子,就过关了。
抱歉啊 程序我就没上传了,因为运行时360会报拦截,未免误会所以我就只贴了代码。
不是dev的标准。。。71 39 C:\Documents and Settings\Administrator\My Documents\saokei.cpp ISO C++ forbids comparison between pointer and integer [-fpermissive] 666扫雷挂,膜拜大神 MosterGay丶 发表于 2016-9-10 20:54
666扫雷挂,膜拜大神
大神不敢当,一起交流,共同进步 :) 扫雷还要开挂??? 现在还玩儿扫雷啊。。。。 表示扫雷手动到高级..
{:301_1001:}多谢分享最近在学C语言 很厉害的样子{:1_927:} 7楼的签名很亮 大婶 请收下我的膝盖{:1_918:}