扫雷小游戏逆向分析

1样本概况

1.1 应用程序信息

应用程序名称:扫雷

MD5值:16A4FD569A3EB5CEBEB3DA99EF1D17E1

SHA1值:31A1A89BA067EA95F117754818429D6D8E8E59CF

1.2 分析环境及工具

系统环境:win7 32位

工具:Ollydbg、Cheat Engine、Spy++、PEiD、Vistual Studio

1.3 分析目标

1、鼠标悬停在棋盘上可以查出雷所在的位置

2、一键扫雷

要想实现功能,需要分析以下数据及代码:

鼠标位置

扫雷数组的高度、宽度、雷数、基地址

鼠标位置转成扫雷数组下标的代码

扫雷数组下标转成鼠标位置的代码(便于发消息)

需要使用的技术:

    1、MFC DLL(新建-->MFC动态链接库-->DLL类型:静态链接)

    使用MFC DLL,不需要自己写DLLMain的case,直接写在Initstance函数中即可,且调试时使用Cstring比较方便

    2、SetWindowLong

    修改窗口回调函数,在自己的窗口回调函数中处理快捷键响应。

    3、CallWindowProc

调用指定窗口回调函数

边分析边测试:

先找到必要的数据:扫雷数组的相关数据,宽度、高度、雷数等

测试数据的可靠性:写代码测试

找到扫雷数组初始化的代码:将其转为自己的代码,写在DLL中,进行测试

寻找屏幕坐标转为扫雷数组下标的代码:找到之后,写在DLL中,测试

 整合代码,完成功能。

2.具体分析过程

2.1 分析过程

2.1.1 使用PEID找出程序的版本信息

将程序拖入PEiD,发现其链接器版本是7.0,对应的编译器是VC2003

    查看其输入表,发现有msvctr.dll,是VC运行的库,应该是SDK程序,再看其大小,只有100多K,如果是MFC静态编译的话会比较大,至少1M,所以判断其是SDK程序

2.1.2 使用Cheat Engine找到必要数据

首先使用Cheat Engine,找到必要的数据:宽度、高度、雷数

找到几组数据,可以写代码进行验证。代码生成后,将DLL注入到exe中,进行操作后,DebugView会有以下结果:

根据结果,可以确定宽度的地址是:0x1005334,高度的地址:0x1005338,雷数地址是:0x1005330

2.1.3 找到扫雷数组初始化的代码

根据已经得到的宽度、高度数据,选中地址右键,查看是哪些访问了这个地址,点击笑脸刷新之后,会记录一些指令:

因为宽度、高度等这些数据,会被用到,所以我们查看用到这些数据的操作:

找到0x01002EE4这个地址,使用OD打开扫雷,Ctrl+G,输入该地址进行查看,并对这一段代码进行分析。该代码块,主要有几个循环的操作,先是对雷区进行了初始化,初始化为0F:

然后将雷区的四周边界都填充为10(只是边界,不在游戏的点击范围内):

同时也可以知道雷区的基地址为:0x01005340,游戏界面可以点击到的左上角地址为:0x01005361

观察发现,虽然高度是10,但是每行中间都隔一行。运行之后,再点击雷区,对比OD中数据的变化,可以发现雷区中元素的标识。雷区中的空白,在数据中对应是0,雷对应8F,1、2、3、4分别对应41、42、43、44。

我们可以写出代码,同样的进行注入,使用DebugView,看结果进行测试验证:

由结果可以看出游戏中实际雷的位置和DebugView中的位置相对应,说明分析正确。

2.1.4 找到屏幕坐标转为扫雷数组坐标的代码

使用Spy++,查看回调函数的地址:

在OD中搜索该地址,右键->分析,假定参数:

然后右键->断点,在WinProc上消息断点

选择设置断点的消息:WM_LBUTTONDOWN

断点设置好之后,鼠标左键点击游戏雷区,就会中断。我们在OD的堆栈区进行查看:

可以看到第四个参数是鼠标的X、Y坐标,4D是77,32是50,所以高十六位是Y,低十六位是X。再看ARG.4是EBP+0x14,所以ARG.4是第四个参数,X、Y坐标。

下面开始F8单步走,看哪些操作对ECX和ARG.4进行了访问。这里用到了ARG.4,进入函数,查看:

这些操作和界面有关,和数组无关,应该是没有什么用。跳过,继续往下跟踪:

发现关键位置,该段代码,将ARG.4赋值给EAX(004D0032),EAX右移0x10位,即:去掉低16位,保留高16位(y),然后y-0x27,再右移四位得到扫雷数组的y坐标,将EAX压入栈。再得到低16位(x),将x+4再右移四位得到扫雷数组的x坐标。点击的是:第二行的第三列,屏幕坐标是:X=50,Y=77,转化后的扫雷数组坐标是:x=3,y=2。

实现一键扫雷的话,需要模拟鼠标点击的操作。按下一键扫雷的时候,开始遍历雷区数组,如果不是雷,就将该坐标的x,y,转化为屏幕的xPos,yPos,然后发送消息,自动排雷。

2.1.5 功能演示

功能一:鼠标移动,提示对应位置是否有雷

功能二:F5一键扫雷

2.2 测试代码(如有代码验证贴出关键代码)

// 唯一的 CMFCLeiPlugApp 对象

CMFCLeiPlugApp theApp;

HWND g_Wnd;

WNDPROC g_OldProc;

//宽度、高度、雷数保存地址

PDWORD g_pWidth = (PDWORD)0x1005334;

PDWORD g_pHeight = (PDWORD)0x1005338;

PDWORD g_pCount = (PDWORD)0x1005330;

//雷区的基地址

PBYTE g_pBase = (PBYTE)0x01005340;

//雷区中元素的标识:空白:0x40、雷:0x8F、1/2/3/4: 41/42/43/44

#define LEI 0x8F

 

//回调函数

LRESULT CALLBACK WindowProc(_In_ HWND hWnd, _In_ UINT Msg, _In_ WPARAM wParam, _In_ LPARAM lParam)

{

    //一键扫雷。思路:需要遍历扫雷数组,判断是否是雷,如果不是雷,模拟点击的操作。

    if (Msg == WM_KEYDOWN && wParam == VK_F5)

    {

        //? 验证:宽度、高度、雷数

        OutputDebugString(L"F5");

        int nHeight = *g_pHeight;

        int nWidth = *g_pWidth;

        int nCount = *g_pCount;

        CString strString;

        strString.Format(L"宽度:%d,高度:%d,雷数:%d", nWidth, nHeight, nCount);

        OutputDebugString(strString.GetBuffer());

        //? 对雷区进行遍历(不包括四周边界)

        int nLeiCount = 0;

        for (int y = 1; y < nHeight + 1; y++)

        {

            CString strLine;

            for (int x = 1; x < nWidth + 1; x++)

            {

                BYTE Code = *(PBYTE)((DWORD)g_pBase + y * 32 + x);

                if (Code == LEI)

                {

                    nLeiCount++;

                }

                else

                {

                    int xPos;

                    int yPos;

                    // x = (x + 4) >> 4;

                    xPos = (x << 4) - 4;

                    // y = (y - 0x27) >> 4;

                    yPos = (y << 4) + 0x27;

                    SendMessage(hWnd, WM_LBUTTONDOWN, 0, MAKELPARAM(xPos, yPos));

                    SendMessage(hWnd, WM_LBUTTONUP, 0, MAKELPARAM(xPos, yPos));

                }

                CString strCode;

                strCode.Format(L"%02x ", Code);

                strLine += strCode;

            }

            OutputDebugString(strLine.GetBuffer());

        }

        CString strCount;

        strCount.Format(L"找到的雷数是:%d ", nLeiCount);

        OutputDebugString(strCount.GetBuffer());

    }

    //鼠标移动:移动鼠标时,判断鼠标对应位置是否是雷,如果是雷,在标题中进行提示。

    else if (Msg == WM_MOUSEMOVE)

    {

        int x = 0;

        int y = 0;

        x = LOWORD(lParam);

        y = HIWORD(lParam);

        //将屏幕坐标转为雷区数组坐标

        x = (x + 4) >> 4;

        y = (y - 0x27) >> 4;

        BYTE Code = *(PBYTE)((DWORD)g_pBase + y * 32 + x);

        if (Code == LEI)

        {

            SetWindowText(hWnd, L"提示:此处有雷");

        }

        else

        {

            SetWindowText(hWnd, L"扫雷");

        }

    }

    return CallWindowProc(g_OldProc, hWnd, Msg, wParam, lParam);

}

// CMFCLeiPlugApp 初始化

BOOL CMFCLeiPlugApp::InitInstance()

{

    CWinApp::InitInstance();

    //1、查找窗口,获取窗口句柄

    g_Wnd = ::FindWindow(L"扫雷", L"扫雷");

    if (NULL == g_Wnd)

    {

        OutputDebugString(L"无法找到扫雷窗口");

        return FALSE;

    }

    //2、设置窗口回调函数

    g_OldProc = (WNDPROC)SetWindowLong(g_Wnd, GWL_WNDPROC, (LONG)WindowProc);

    //CString temp;

    //temp.Format(L"窗口回调函数地址:%p", g_OldProc);

    //OutputDebugString(temp.GetBuffer());

    if (NULL==g_OldProc)

    {

        OutputDebugString(L"设置窗口回调函数失败");

        return FALSE;

    }

    return TRUE;

}

3.总结

虽然本次分析扫雷游戏,并写出辅助插件是一个比较小的逆向项目,但整体来说还是比较复杂的。有的地方需要我们一步步进行跟踪分析,有的需要我们写出对应功能的代码进行测试,还需要运用一些工具以及各种技术进行分析。通过本次项目的分析,感觉逆向是一个复杂的工作,要想做好逆向分析,需要学好多方面的技术知识,并不断地练习。

  • 1
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值