反汇编逆向实战——扫雷辅助制作

news2024/11/19 3:46:36

一、编程前准备

刚开始是预备知识,如果熟悉的话,可以直接跳到第二部分阅读

在 Windows API 中,SetTimer 函数用于创建一个定时器,并在指定的时间间隔后触发一个定时器消息。以下是关于 SetTimer 函数的介绍:

功能:创建一个定时器,并在指定的时间间隔后触发定时器消息。

参数:

  • hWnd:指定接收定时器消息的窗口的句柄。
  • nIDEvent:指定定时器的标识符。在定时器消息中使用该标识符来区分不同的定时器。
  • uElapse:指定定时器触发的时间间隔,以毫秒为单位。
  • lpTimerFunc:指向定时器回调函数的指针。当定时器触发时,系统将调用该回调函数。
 case WM_PAINT:
        {
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hwnd, &ps);
            
            // 获取当前时间
            SYSTEMTIME sysTime;
            GetLocalTime(&sysTime);
            
            // 将时间转换为字符串
            char timeString[64];
            sprintf(timeString, "%02d:%02d:%02d", sysTime.wHour, sysTime.wMinute, sysTime.wSecond);
            
            // 绘制时间文本
            TextOut(hdc, 10, 10, timeString, strlen(timeString));
            
            EndPaint(hwnd, &ps);
            break;
        }

要在窗口中使用计时器(Timer)来定期绘制时间,可以按照以下步骤进行操作:

1、在窗口类中添加一个计时器标识符:在窗口类的定义中,添加一个计时器标识符作为类的成员变量,用于标识计时器的ID。

class WindowClass
{
private:
    static const UINT_PTR TIMER_ID = 1; // 计时器标识符
    // 其他成员变量和方法
};

2、在窗口的创建过程中启动计时器:在窗口的创建过程中,通过调用 SetTimer 函数来启动计时器。这个函数将设置一个定时器,每隔一定时间触发一个 WM_TIMER 消息。 

HWND hwnd = CreateWindow(/* 窗口参数 */);
SetTimer(hwnd, TIMER_ID, 1000, NULL); // 在这里启动计时器,每隔1秒触发一次 WM_TIMER 消息

3、处理 WM_TIMER 消息:在窗口的消息处理函数中,处理 WM_TIMER 消息,并在该消息中绘制时间。

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
        // 其他消息处理代码
        
        case WM_TIMER:
        {
            if (wParam == TIMER_ID) // 根据计时器标识符判断是哪个计时器触发的消息
            {
                // 获取当前时间
                SYSTEMTIME sysTime;
                GetLocalTime(&sysTime);
                
                // 将时间转换为字符串
                char timeString[64];
                sprintf(timeString, "%02d:%02d:%02d", sysTime.wHour, sysTime.wMinute, sysTime.wSecond);
                
                // 绘制时间文本
                HDC hdc = GetDC(hwnd);
                TextOut(hdc, 10, 10, timeString, strlen(timeString));
                ReleaseDC(hwnd, hdc);
            }
            
            break;
        }
        
        // 其他消息处理代码
        
        default:
            return DefWindowProc(hwnd, uMsg, wParam, lParam);
    }
    
    return 0;
}

另外,不要忘记在窗口销毁时停止计时器,可以在 WM_DESTROY 消息中调用 KillTimer 函数来停止计时器的触发。

case WM_DESTROY:
    KillTimer(hwnd, TIMER_ID); // 停止计时器
    PostQuitMessage(0);
    break;

二、扫雷找数据

1、获取雷区的数据

首先准备本次实验需要用到的工具:

如果大家需要扫雷的资源,可以私信我获取噢~😐😐😐

附加到扫雷的进程:

首次扫描未知的初始值,类型选择字节

点击首次扫描,发现有10W+的搜索结果,,,然后毫无疑问我们需要过滤掉一部分数据:

点击第一个格子,再次扫描变动的数据

点击旁边的格子,扫描不变的数据,再次扫描

重新开始,如果数字如果发生变化就选择变动的数值,再次扫描

 

然后继续点击几个格子,不是地雷的话就可以选择未变动的值,再次扫描

 

很快就找到了我们所需要的雷区的数据 

 

浏览这块区域所在的内存位置,观察一下里面都是什么数据 

可以手动测试一下:

 

 经过几轮尝试之后,不难发现:

"0x0f"表示不是地雷

"0x8f"表示是地雷

"0x0e"表示小旗子

"0x10"表示边界

"40"表示空格

"41-49"表示数字

2、获取雷区的高度和宽度

注意:这里说的高度和宽度是不计算无关数据的,就是真实的高、宽占多少个格子

比如对于初级模式就是9*9:

 

切换为中级、高级,分别搜索,很快就可以找到高度所在内存:

 

 

同理,还可以找到宽度和地雷数量 :

 

3、寻找存储时间的数据 

我们找存储时间内存的目的,就是观察谁修改了它,具体来说就是哪条指令把这块内存的数据inc了,这样我们通过把这句指令nop掉,就可以实现时间停止的功能,从而实现0秒扫雷😂😂😂

 、

其实搜索起来十分简单,只要一直追那个变化的数据就好了

 

 

这样我们就找到了inc时间的指令,位于0x1002ff5的位置,把他nop掉,时间就静止了~

 

但是我们一开始点击格子的时候,它还是会自动设置时间为1,所有我们还需要把这一条指令也nop掉,实现真正的0秒扫雷!!! 

我采用的思路是在左键弹起的地方设置一个硬件断点,然后一路f8下去,观察到时间发生变化,就在最近的那个call打个断点继续跟----->

 

 

它断下来的地方是整个消息处理的头部 !!!

 

看到修改全局的内存,高度怀疑是这里修改时间,经过测试果然如此:

 

跟进去发现果真调用了绘制函数!!!

所以我们最终确定,设置时间的地址位于0x1003830位置处!

三、编程思路梳理:

1、获取扫雷窗口句柄,然后获得pid

2、nop掉时间inc的函数,实现时间静止

3、读取雷区有效数据

4、模拟鼠标给窗口发消息

5、释放掉申请的内存和句柄

四、扫雷辅助源码

#include<iostream>
using namespace std;
#include<Windows.h>

#define increaseTimeAddr 0x01002FF5
#define setTimeAddr      0x01003830
#define mineStartAddr    0x01005340

int main() {

    //打开扫雷进程
    HWND hWnd = FindWindow(nullptr, L"扫雷");
    if (hWnd != nullptr)
    {
        DWORD dwProcessId;
        GetWindowThreadProcessId(hWnd, &dwProcessId);

        HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId);
        if (hProcess == nullptr)
        {
            cout << "fail to find the winmine process" << endl;
            CloseHandle(hProcess);
            return -1;
        }
        //测试是否正确打开扫雷进程,如果关闭了,就说明句柄是对的
        //TerminateProcess(hProcess, 0);

        //把时间增加的指令nop掉,实现0秒扫雷
        DWORD oldProtect = 0;
        SIZE_T nBytesWritten1 = 0;
        SIZE_T nBytesWritten2 = 0;
        bool bRet1, bRet2;
        BYTE replaceBuf[] = { 0x90,0x90,0x90,0x90,0x90,0x90 };
        VirtualProtectEx(hProcess, (LPVOID)increaseTimeAddr, 6, MEDIA_READ_WRITE, &oldProtect);
        bRet1=WriteProcessMemory(hProcess, (LPVOID)increaseTimeAddr, replaceBuf, sizeof(replaceBuf), &nBytesWritten1);
        bRet2=WriteProcessMemory(hProcess, (LPVOID)setTimeAddr, replaceBuf, sizeof(replaceBuf), &nBytesWritten2);
        VirtualProtectEx(hProcess, (LPVOID)increaseTimeAddr, 6, oldProtect, &oldProtect);

        if (bRet1 == FALSE || bRet2 == FALSE || nBytesWritten1 <= 0 || nBytesWritten2 <= 0) {
            cout << "修改时间失败!\n";
            CloseHandle(hProcess);
            return -1;
        }

        //读取整个雷区的数据
        //设置读取雷区的最大字节数
        DWORD maxSize = 832;
        char* pByte = NULL;
        SIZE_T nBytesRead = 0;
        pByte = (char*)malloc(sizeof(BYTE) * maxSize);
        bool bReadData = ReadProcessMemory(hProcess, (LPVOID)mineStartAddr, pByte, maxSize, &nBytesRead);
        if (!bReadData) {
            CloseHandle(hProcess);
            delete pByte;
            return -1;
        }
        
        //打印一下观察是否读取正确
        /*for (int i = 0; i < maxSize;i++) {
            printf("%x  ", pByte[i]);
        }*/

        //获取当前雷区的大小
        DWORD sizeAddr = 0x01005330;
        DWORD dwHeight = 0, dwWidth = 0;
        ReadProcessMemory(hProcess, (LPVOID)(sizeAddr + 4), &dwWidth, sizeof(DWORD),0);
        ReadProcessMemory(hProcess, (LPVOID)(sizeAddr + 8), &dwHeight, sizeof(DWORD), 0);

        //读取当前雷区的有效数据
        char* pCurByte = NULL;
        pCurByte = (char*)malloc(sizeof(BYTE) * dwHeight * dwWidth);

        int h = 0;
        int index = 0;
        while (1) {
            if (pByte[index] == 0x10 && pByte[index + 1] != 0x10 && pByte[index + dwWidth + 1] == 0x10) {
                for (int j = 0; j < dwWidth; j++) {
                    pCurByte[dwWidth * h + j] = pByte[index + 1];
                    index++;
                }
                h++;
                if (h >= dwHeight)
                    break;
            }
            index++;
        }
        //模拟鼠标点击格子
        for (int i = 0; i < dwWidth*dwHeight; i++) {
            if (i % dwWidth == 0) {
                cout << endl;
            }
            printf("%x ", *(pCurByte+i));
        }
        int x1 = 0, y1 = 0;
        int x = 0, y = 0;
        for (int i = 0; i < dwHeight * dwWidth; i++) {
            if (*(pCurByte+i) != 0xffffff8f) {
                x1 = i % dwWidth;
                y1 = i / dwWidth;
                x = x1 * 16 + 16;
                y = y1 * 16 + 61;
                SendMessage(hWnd, WM_LBUTTONDOWN, MK_LBUTTON, MAKELONG(x, y));
                SendMessage(hWnd, WM_LBUTTONUP, MK_LBUTTON, MAKELONG(x, y));
            }
           
        }
        free(pByte);
        free(pCurByte);
        pByte = NULL;
        pCurByte = NULL;
        CloseHandle(hProcess);
    }

    printf("???就这???\n");
    printf("???就这???\n");
    printf("???就这???\n");
    printf("???就这???\n");

    system("pause");

	return 0;
}

五、扫雷辅助效果

扫雷窗口最大是24*30

留下你的鼎鼎大名😂😂😂🤣

 

当然还要有适当的凡尔赛:

 

 好了,今天的逆向辅助实战就到这里了,如果大家喜欢的话,可以私信我,去更新你们想要的游戏噢~喜欢的话就多多点赞、收藏、关注吧!!!🧡🧡🧡🤞🤞

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/641468.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

接口文档设计注意事项

接口名称清晰 一般接口url要求能看得出接口的作用。比如说&#xff0c;查询用户信息&#xff08;queryUserInfo&#xff09;&#xff0c;就是一个不错的接口名称。 接口地址完整 接口的地址&#xff0c;也叫接口的URL地址。即别人调用你的接口&#xff0c;用的是什么URL。比…

【Unity Shader】入门到惊叹(1)基本概念:什么是网格?材质?Shader?

文章目录 一、什么是网格(Mesh)?二、什么是MeshFilter(网格过滤器)?三、什么是MeshRenderer(网格渲染器)?四、什么是材质(Material)?五、什么是Shader(着色器)?一、什么是网格(Mesh)? 如图,模型的三角形面就叫做网格(Mesh),它的本质是一堆顶点数据的规则排…

华为OD机试真题 JavaScript 实现【按身高和体重排队】【2022Q4 100分】,附详细解题思路

一、题目描述 某学校举行运动会&#xff0c;学生们按编号(1、2、3…n)进行标识&#xff0c;现需要按照身高由低到高排列&#xff0c;对身高相同的人&#xff0c;按体重由轻到重排列&#xff1b; 对于身高体重都相同的人&#xff0c;维持原有的编号顺序关系。请输出排列后的学生…

医械围城的觉醒时刻:从“乱世枭雄” 到“剩者为王”

我们现在看到医疗器械行业其实非常的热&#xff0c;不管是投资&#xff0c;还是创业&#xff0c;还有各种跨界进来打劫想分一杯羹的。 但是这个行业是不是一个围城&#xff1f; 真的是进来就可以捡钱吗&#xff1f; 在一片繁荣的景象下&#xff0c;企业的发展存在什么风险&a…

STM32FreeRTOS操作系统移植

移植好的FreeRTOS模板&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1_87VQAWXUl4jTqSCZ0MFjw?pwddw52 提取码&#xff1a;dw52 1.在工程中新建FreeRTOS文件夹 2.把源码source里面的文件全部粘贴进FreeRTOS文件夹中 3.在portable文件中只保留一下文件&#xff0c;…

【剑指offer刷题记录 java版】数组双指针 之 二分搜索

本系列文章记录labuladong的算法小抄中剑指offer题目 【剑指offer刷题记录 java版】数组双指针 之 二分搜索 剑指 Offer 53 - I. 在排序数组中查找数字 I剑指 Offer II 068. 查找插入位置剑指 Offer 04. 二维数组中的查找剑指 Offer II 069. 山峰数组的顶部剑指 Offer II 073. …

java周期性线程池newScheduledThreadPool介绍,多线程下载url文件(断点下载、进度展示、网速展示、剩余时间展示)

文章目录 一&#xff1a;newScheduledThreadPool&#xff08;周期性线程池&#xff09;1.1 特点1.2 核心线程数1.3 创建实例1.4 常用方法1.4.1 schedule方法1.4.2 scheduleAtFixedRate方法1.4.3 scheduleWithFixedDelay方法 二&#xff1a;多线程下载展示文件总大小、剩余时间、…

基于SpringBoot+vue的简历系统设计和实现

博主介绍&#xff1a; 大家好&#xff0c;我是一名在Java圈混迹十余年的程序员&#xff0c;精通Java编程语言&#xff0c;同时也熟练掌握微信小程序、Python和Android等技术&#xff0c;能够为大家提供全方位的技术支持和交流。 我擅长在JavaWeb、SSH、SSM、SpringBoot等框架下…

ESXi 7.0 U3m HPE (慧与) 定制版 OEM Custom Installer CD

VMware ESXi 7.0 Update 3m - 领先的裸机 Hypervisor (All OEM Customized Installer CDs) ESXi 7.0 U3m Standard (标准版) ESXi 7.0 U3m Dell (戴尔) 定制版 OEM Custom Installer CD ESXi 7.0 U3m HPE (慧与) 定制版 OEM Custom Installer CD ESXi 7.0 U3m Lenovo (联想) 定…

基于SSM的养老机构信息管理系统设计与实现

摘 要 随着我国老年人逐渐增加&#xff0c;老人们的子女数量减少&#xff0c;工作时间过长无暇照顾父母&#xff0c;导致养老院和护工需求量大幅上涨。伴随我国生活水平提高的同时对老年人的护工人员的要求也越来越高。根据以上要求关于养老院有很多的信息需要进行管理&#…

Zero-Shot, One-Shot, and Few-Shot Learning概念介绍

导语 本文将介绍零样本学习、一次样本学习和少样本学习的概念&#xff0c;它们使得机器学习模型能够在仅有有限数量的示例情况下对对象或模式进行分类和识别。 在机器学习中&#xff0c;我们通常需要大量的训练数据来训练模型&#xff0c;以便它能够准确地识别和分类新的输入…

数据挖掘知识与学习方向

数据挖掘是一门涉及多个学科的交叉学科&#xff0c;需要掌握的知识点也比较多。以下是一些数据挖掘的基础知识和学习方向&#xff1a; 数据库&#xff1a;需要掌握 SQL 语言和数据库设计&#xff0c;以便能够有效地管理和处理数据。 统计学&#xff1a;需要掌握基本的统计学知…

硬盘初始化后数据还能恢复吗?硬盘被初始化怎么恢复数据

现今热门的数据恢复话题之一便是硬盘被初始化后如何恢复数据。或许许多人都遭遇过这一问题&#xff0c;往往因为误操作或不小心&#xff0c;导致硬盘数据被不可逆地清除。所以&#xff0c;为帮助广大用户避免数据丢失的情况&#xff0c;本文将为大家介绍如何恢复被初始化的硬盘…

基于注解的Spring(IOC+AOP)

目录 这是基于黑马Spring的笔记 写再前面 开始 Component(valuebean的名称) componet衍生出的3个注解 Bean内部的属性进行注入 非自定义的Bean管理 使用配置类完全替代XML配置文件 配置类中的注解 spring中的其他注解&#xff08;偶尔会用到) Spring注解的解析原理 sp…

Java Supervisor RPC2 接口对接

1.引入xmlrpc-client 如果是C#语言&#xff0c;请参考《C#对接supervisor XML-RPC API 实现进程控制》 如何安装Supervisor&#xff0c;请参考《Linux进程守护—Supervisor&#xff08;ubuntu&#xff09;》 如果是Maven项目&#xff0c;则在pom.xml引入jar包 <dependenc…

详解CSS中的flex布局

详解CSS中的flex布局 1、概念2、容器属性2.1 flex-direction2.2 flex-wrap2.3 flew-flow2.4 justify-content2.5 align-items2.6 align-content 3、元素属性3.1 order3.2 flex-grow3.3 flex-shrink3.4 flex-basis3.5 flex3.6 align-self 1、概念 弹性盒子&#xff08;display: …

如何系列 JMeter如何录制脚本

文章目录 方式1. 手动编写2. JMeter自带录制功能3. Fiddler录制4. Badboy录制5. Blazemeter录制 总结和使用感受 方式 1. 手动编写 最原始的方式&#xff0c;在线程组中根据研发提供的接口文档和浏览器的Network请求一个个手动录入&#xff0c;它可以提供更大的灵活性和控制力…

PostgreSQL 中的虚拟文件描述符

由于每个操作系统限制了一个进程能打开的文件数&#xff08;例如&#xff1a;ubuntu 为1024&#xff09;&#xff0c;因此进程能获得的文件描述符是有限的。对于经常需要打开许多文件的数据库进程来说&#xff0c;很容易会超过操作系统对于文件描述符数量的限制。 为解决这个问…

如何写好一份解决方案

1、前言 我们在日常工作中会不可避免要去编写各种方案&#xff0c;如技术方案、建设方案、项目建议书、实施方案、规划方案、解决方案等。 我们去浏览华为、阿里、腾讯、IBM、海尔等公司的官网&#xff0c;可以看到在首页比较显眼的位置&#xff0c;都有解决方案的入口&#…

Postcat X APISIX 合作插件 :一键同步,轻松配置到 APISIX

近日&#xff0c;云流科技&#xff08;广州&#xff09;有限公司&#xff08;简称“Eolink”&#xff09;旗下的开源 API 管理工具 Postcat 和深圳支流科技有限公司&#xff08;简称“API7 支流科技”&#xff09;在各自擅长的领域携手合作&#xff0c;推出了 Postcat & Ap…