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

一、编程前准备

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

在 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、获取雷区的数据

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

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

反汇编逆向实战——扫雷辅助制作_第1张图片

附加到扫雷的进程:

反汇编逆向实战——扫雷辅助制作_第2张图片

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

反汇编逆向实战——扫雷辅助制作_第3张图片

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

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

反汇编逆向实战——扫雷辅助制作_第4张图片

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

反汇编逆向实战——扫雷辅助制作_第5张图片

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

反汇编逆向实战——扫雷辅助制作_第6张图片 

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

反汇编逆向实战——扫雷辅助制作_第7张图片 

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

反汇编逆向实战——扫雷辅助制作_第8张图片 

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

反汇编逆向实战——扫雷辅助制作_第9张图片

可以手动测试一下:

反汇编逆向实战——扫雷辅助制作_第10张图片

 反汇编逆向实战——扫雷辅助制作_第11张图片

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

"0x0f"表示不是地雷

"0x8f"表示是地雷

"0x0e"表示小旗子

"0x10"表示边界

"40"表示空格

"41-49"表示数字

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

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

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

反汇编逆向实战——扫雷辅助制作_第12张图片 

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

反汇编逆向实战——扫雷辅助制作_第13张图片 

反汇编逆向实战——扫雷辅助制作_第14张图片 

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

反汇编逆向实战——扫雷辅助制作_第15张图片 

3、寻找存储时间的数据 

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

反汇编逆向实战——扫雷辅助制作_第16张图片 、

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

反汇编逆向实战——扫雷辅助制作_第17张图片 

反汇编逆向实战——扫雷辅助制作_第18张图片 

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

反汇编逆向实战——扫雷辅助制作_第19张图片 

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

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

反汇编逆向实战——扫雷辅助制作_第20张图片 

反汇编逆向实战——扫雷辅助制作_第21张图片 

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

反汇编逆向实战——扫雷辅助制作_第22张图片 

反汇编逆向实战——扫雷辅助制作_第23张图片

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

反汇编逆向实战——扫雷辅助制作_第24张图片 

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

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

三、编程思路梳理:

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

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

3、读取雷区有效数据

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

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

四、扫雷辅助源码

#include
using namespace std;
#include

#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

反汇编逆向实战——扫雷辅助制作_第25张图片

留下你的鼎鼎大名

 反汇编逆向实战——扫雷辅助制作_第26张图片

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

反汇编逆向实战——扫雷辅助制作_第27张图片 

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

你可能感兴趣的:(逆向学习,windows,c++,c语言,汇编)