逆向扫雷小游戏

一、寻找并分析关键函数

猜测构成扫雷雷区的是一个二维数组,直接在.data段定位到关键函数
函数名叫Generate_minefield,也很合实意
0
 
经过逆向分析发现,这个二维数组可以适应不同的模式,对于9 X 9、16 X16、16 X 30 三种模式,二维数组均以0x10为边界包围雷区与非雷区,0xf代表非雷,0x8f代表雷。我发现,第一布永远不会踩雷,就算本来这一步所在的二维数组对应的值为0x8f,程序也会改变这个雷或这些雷的位置
雷区地址为 0x1005340,无地址随机化机制
0
扫雷程序采用了MapWindowPoints函数实现了程序窗口到屏幕总窗口的映射。
下面的代码告诉了我们每一块雷区相对于扫雷.exe的位置
0

二、外挂编写思路

读取扫雷EXE进程中的二维数组,遍历二维数组,遇到非雷区时调用sendMessage API 发送鼠标左击事件,以此代替手动点击
/********************************************/ /*编译器: vs2022 */ /*编译者: tlsn */ /*编译时间: 2022/8/15 */ /********************************************/ #include <stdio.h> #include <Windows.h> #include <TlHelp32.h> #define mine_field 576 int TIMES = 0; int Nor_Point = 0; void Error_Event(const char * str) { printf("~~~~~~~~~~~~~~~~~~~~~"); printf(strcat((char *)str ," error, error code is %d\n"), GetLastError()); getchar(); } bool click_it(int m,int n,HWND hand_game) { int sleeptimes = TIMES * 1000 / Nor_Point; Sleep(sleeptimes); POINT p; p.x = 16 * n + 24; p.y = 16 * m + 67; //printf("m : %d n : %d", m, n); //这里我们发送消息的时候注意,不要发送相对于屏幕的点击事件,而要发送相对于扫雷.exe的点击事件,因为扫雷.exe自己进行了点击事件相对于屏幕的转换,不需要我们自己转换// SendMessage(hand_game, WM_LBUTTONDOWN, 0, MAKELPARAM(p.x, p.y)); SendMessage(hand_game, WM_LBUTTONUP, 0, MAKELPARAM(p.x, p.y)); return 1; } HANDLE Get_mine_Handle(const TCHAR* name) { PROCESSENTRY32 entry; entry.dwSize = sizeof(PROCESSENTRY32); HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL); if (Process32First(snapshot, &entry) == TRUE) { while (Process32Next(snapshot, &entry) == TRUE) { //wprintf(L"%s\n", entry.szExeFile); if (lstrcmpi(entry.szExeFile, name) == 0) { HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, entry.th32ProcessID); return hProcess; } } } } bool mine_Game_cheat(HANDLE hProcess) { bool ret; DWORD mine_nums_val; DWORD mine_nums_addr = 0x1005330; DWORD lpNumberOfBytesRead; DWORD longth, width; ret = ReadProcessMemory(hProcess, (LPCVOID)mine_nums_addr, (LPVOID)&mine_nums_val, 4,&lpNumberOfBytesRead); if (ret == 0) { Error_Event("ReadProcessMemory"); return 0; } if(mine_nums_val == 10){ longth = 9; width = 9; } else if (mine_nums_val == 40) { longth = 16; width = 16; } else if (mine_nums_val == 99) { longth = 30; width = 16; } else if (mine_nums_val != 40 && mine_nums_val != 10 && mine_nums_val != 99) { Error_Event("ReadProcessMemory patermater error"); return 0; } DWORD mine_nums_arr_addr = 0x1005340; LPVOID map = malloc(576); ret = ReadProcessMemory(hProcess, (LPCVOID)mine_nums_arr_addr, map, mine_field, &lpNumberOfBytesRead); if (ret == 0) { Error_Event("ReadProcessMemory"); return 0; } Nor_Point = width * longth - mine_nums_val; int sum = 0; for (int i = 1; i <= width; i++) { for (int j = 1; j <= longth; j++) { sum += 1; DWORD val = ((BYTE*)map)[32 * i + j ]; if (val != 0x8f) { HWND hand_game = FindWindow(NULL, L"扫雷"); if (hand_game == NULL) { Error_Event("FindWindow"); return 0; } click_it(i-1,j-1, hand_game); //getchar(); } } printf("\n"); } return 1; } int main() { while (1) { printf("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"); printf("输入你想几秒完成(计时不太精确...)\n"); printf("按回车开始操作\n"); scanf("%d", &TIMES); if (TIMES == 0) { goto ttt; } TIMES -= 1; ttt: getchar(); HANDLE hProcess = Get_mine_Handle(L"winmine.exe"); if (hProcess == NULL) { Error_Event("Get_mine_Handle"); return 1; } if (mine_Game_cheat(hProcess) == 0) { Error_Event("mine_Game_cheat"); return 1; } CloseHandle(hProcess); } return 0; }

三、效果

0
 

__EOF__

本文作者_TLSN
本文链接https://www.cnblogs.com/lordtianqiyi/articles/16588656.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   TLSN  阅读(88)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 25岁的心里话
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示