别说见过我 发表于 2016-9-10 20:47

简单的扫雷内存挂

本帖最后由 别说见过我 于 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我都并没有做范围判断,这是比较危险的,理论上可能会导致非法访问,
还好我没遇到过,大家可以尝试自己动手来完善(判断边界);
还有判断雷区的逻辑也是,也有待完善(依靠特征识别二维数组,确定边界、遍历数组修改)。

希望这篇分享对大家有一定的帮助。





别说见过我 发表于 2016-9-11 12:27

zhousai998 发表于 2016-9-11 10:11
怎么用.这东西.

先运行扫雷,然后不要动它,在任务管理器中找到扫雷的进程pid,
使用vc++ 贴入代码 然后编译运行,在运行之后会有提示,输入pid然后回车,
雷就被清理了,在扫雷游戏中使用完所有的旗帜(随便乱放就好了,游戏过关的要求,必须用完旗帜)。
最后随便点几一个格子,就过关了。
抱歉啊 程序我就没上传了,因为运行时360会报拦截,未免误会所以我就只贴了代码。

MAXtoDEATH 发表于 2016-9-23 11:49

不是dev的标准。。。71        39        C:\Documents and Settings\Administrator\My Documents\saokei.cpp        ISO C++ forbids comparison between pointer and integer [-fpermissive]

MosterGay丶 发表于 2016-9-10 20:54

666扫雷挂,膜拜大神

别说见过我 发表于 2016-9-10 21:03

MosterGay丶 发表于 2016-9-10 20:54
666扫雷挂,膜拜大神

大神不敢当,一起交流,共同进步 :)

冰楓丶殘瀷 发表于 2016-9-10 21:05

扫雷还要开挂???

SoVieTing 发表于 2016-9-10 21:07

现在还玩儿扫雷啊。。。。

woaidky 发表于 2016-9-10 21:07

表示扫雷手动到高级..

黑龍 发表于 2016-9-10 21:12

{:301_1001:}多谢分享最近在学C语言

大.闻道 发表于 2016-9-10 21:14

很厉害的样子{:1_927:}

吾爱-宿命 发表于 2016-9-10 21:15

7楼的签名很亮

在你不远的距离 发表于 2016-9-10 21:30

大婶 请收下我的膝盖{:1_918:}
页: [1] 2 3 4 5 6
查看完整版本: 简单的扫雷内存挂