首页
社区
课程
招聘
[下载][原创]小白也能逆向分析扫雷游戏
2020-10-7 08:29 17641

[下载][原创]小白也能逆向分析扫雷游戏

2020-10-7 08:29
17641

1.样本概况具体分析过程

1.1 应用程序信息

      

应用程序名称:winmine.exe(扫雷)

说明:一款基础的扫雷游戏


1.2 分析环境及工具

      

系统环境:Win7 32位

工具:

       OllyDbg(动态调试代码)

       Cheat Engine(搜索数据)

       Spy++(寻找窗口回调函数)

       PEID/exeinfo(查壳,查编译环境)

       Visual Studio2019(开发程序)

       MFCInject(注入工具)


1.3 分析目标


1)标注某处是否有雷

2)一键通关

2.具体分析过程

2.1 已知与推测

2.1.1判断开发游戏的语言


首先将游戏拖拽到PEID上会发现是由C++编写的程序


 


当我们打开游戏时是一个窗口,而不是一个黑框框,猜测是SDK或MFC编写的


 


猜测是SDK编写的,这是由于

       1)点击子系统箭头,点击导入表箭头,会发现列表中有msvrt.dll,这是微软的运行时库,故多半是SDK编写


       2)列表中没有MFC特有的库

       3)查看该游戏大小只有100kb,若是MFC静态编译则会有1M左右


若游戏是由SDK编写的,这意味着会有一些相关的函数可供我们查找与调用

如   

       CreateWindowExW/CreateWindowExA(创建窗口)

       GetWindowTextA/GetWindowTextW(将指定窗口的标题拷贝到一个缓冲区内)

       SetWindowTextA/SetWindowTextW(改变控件的文本内容,窗口也是一个控件)

       rand(产生随机数函数)


2.1.2可探究路线

1)动态调试,即从API函数入手

       由于是由SDK编写的代码,那么必定含有windows的api函数,那么就可以用OllyDbg进行调试,来探究这款游戏内部的逻辑,得到更多的信息

2)数据分析,即从变化的数据入手

       由于扫雷是一款游戏,必定有一些数据来代表着游戏的内容,诸如雷的数量,时间等信息。像雷的分布,可能会有一个数组来标注,那么我们可以通过获取数组的地址,来得到雷的分布数据。对于以上这些信息,通过Cheat Engine不仅可以获得,还可以进行修改,通过修改,可以验证我们的一些猜想。


2.2数据分析

2.2.1寻找重要数值

通过观察,左上角有自定义游戏选项,可自定义高度,宽度,雷的数量,右上角的时间计时与左上角的已判断雷数计数

其中游戏通关与否与时间和待判定雷数无关,高度,宽度可用于表示坐标,雷数是用于判断的重要指标并与多项游戏机制相关,所以主要寻找高度,宽度,雷的数量这三项数据即可

 

              

 

2.2.2寻找数值地址


Cheat Engine添加扫雷进程

 


通过自定义选项改变游戏的高,宽,雷数,不断搜索这些数值,筛选到最后,剩下绿色标记的就是基地址(不会因为重新打开游戏而变化的地址),最终得到6条地址信息


 

通过更改地址,得到访问数据的地址信息


  

 


用OllyDbg查找响应地址,数据窗口跟随,发现初始化雷数组的汇编代码区域

宽的地址是0x1005334;

高的地址是0x1005338;

雷数的地址是0x1005330;

雷数组的地址是1005340,边界的值是10,雷的值是8F


  


3.游戏反汇编代码调试与注入软件编写


用Visual Studio2019 创建项目


   

     

用spy++获取窗口类名,知道窗口的地址为1001BC9


 

  

许多代码已被编写,直接写在instance函数中编写即可,SetWindowLong将游戏的回调函数替换为自己的回调函数,判断若返回值为NULL,说明没找到      窗口,用于提示报错

  

BOOL CMFCGamePlugApp::InitInstance()
{
	CWinApp::InitInstance();

	//1.通过查找窗口,获取窗口句柄
	g_Wnd=::FindWindow(L"扫雷", L"扫雷");
	if (g_Wnd==NULL)
	{
		OutputDebugString(L"无法找到 扫雷窗口");
		return FALSE;
	}

	//2.设置窗口回调函数
	g_OldProc = (WNDPROC)SetWindowLong(g_Wnd,GWL_WNDPROC, (LONG)WindowProc);
	if (g_OldProc == NULL)
	{
		OutputDebugString(L"设置窗口回调函数 失败");
		return FALSE;
	}

	return TRUE;
}


回调函数编写,如果按下F5,则进行接下来的行为(启动注入)


LRESULT CALLBACK WindowProc(
	_In_ HWND hWnd,
	_In_ UINT Msg,
	_In_ WPARAM wParam,
	_In_ LPARAM lParam)
{
	if (Msg == WM_KEYDOWN && wParam == VK_F5)
	{
		
	}

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


编写代码,用MFCInject注入,按下F5后用DebugView查看是否得到想要的内容


int nWidth = *g_pWidth;
int nHeight = *g_pHeight;
int nMineCount = *g_pMineCount;
CString strString;
strString.Format(L"宽度: %d,高度: %d,雷数:%d ", nWidth, nHeight,nMineCount);
OutputDebugString(strString.GetBuffer());


        


        


        输入窗口的地址,在OllyDbg中找到窗口回调汇编代码,分析参数坐标和我们需要输出内容的数学计算关系

        


        编写代码,根据鼠标移动获取坐标,用Spy++查看窗口输出的消息中是否有想要的坐标,最后在窗口标题处显示是否有雷


        

  

else if (Msg == WM_MOUSEMOVE)
	{
		//鼠标移动
		int x, y;
		x = LOWORD(lParam);
		y = HIWORD(lParam);
		x = (x + 4) >> 4;
		y = (y-0x27)>>4;
		BYTE byCode = *(PBYTE)((DWORD)g_pBase + x + y * 32);
		if (byCode == MINE)
		{
			SetWindowText(hWnd,L"此处有雷");
		}
		else
		{
			SetWindowText(hWnd, L"无事");
		}
	}


        在回调函数处设置条件断点,当点击时断下,分析反汇编代码,得到x,y坐标

         


         

        


        通过模拟点击事件把所有非雷区域点击,实现一键通关


if (Msg == WM_KEYDOWN && wParam == VK_F5)
	{
		//一键通关
		OutputDebugString(L"F5");
		int nWidth = *g_pWidth;
		int nHeight = *g_pHeight;
		int nMineCount = *g_pMineCount;
		CString strString;
		strString.Format(L"宽度: %d,高度: %d,雷数:%d ", nWidth, nHeight,nMineCount);
		OutputDebugString(strString.GetBuffer());
		int nFindCount = 0;
		for (size_t y = 1;y<nHeight+1;y++)
		{
			CString strLine;
			for (size_t x = 1; x < nWidth + 1; x++)
			{
				//数组基地址+(y+1)*32+x+1(y=0到高度)
				BYTE byCode = *(PBYTE)((DWORD)g_pBase + x + y * 32);
				if (byCode == MINE)
				{
					nFindCount++;
				}
				else
				{
					int xPos, yPos;
					xPos = (x << 4) - 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 ", byCode);
				strLine += strCode;
			}
			OutputDebugString(strLine.GetBuffer());
		}
		CString strCode;
		strCode.Format(L"找到的雷数 %d ", nFindCount);
		OutputDebugString(strCode.GetBuffer());
	}


4.最终效果与附件下载


            


           附件中包含:

                  注入的dll

                  注入工具MFCInject

                  游戏本体

                  dll源码(用 Visual Studio2019编写)


           本文记录平日自己写的项目(其中用到的知识来自于15pb,遇到不解处多谢老师指导)


[培训]二进制漏洞攻防(第3期);满10人开班;模糊测试与工具使用二次开发;网络协议漏洞挖掘;Linux内核漏洞挖掘与利用;AOSP漏洞挖掘与利用;代码审计。

最后于 2020-11-27 09:52 被哦哈哈哈哈编辑 ,原因:
上传的附件:
收藏
点赞6
打赏
分享
最新回复 (16)
雪    币: 344
活跃值: (922)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
BIX 2020-10-8 23:22
2
0
学习学习
雪    币: 630
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
文生 2020-10-10 09:12
3
0
学习学习
雪    币: 0
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
Palmer0801 2020-11-6 16:06
4
0
想问一下这是什么语言啊?看起来是C语言?或者说是什么库呢?小白想了解一下这方面的编程知识
雪    币: 1820
活跃值: (2405)
能力值: ( LV5,RANK:65 )
在线值:
发帖
回帖
粉丝
哦哈哈哈哈 2020-11-6 19:41
5
1
Palmer0801 想问一下这是什么语言啊?看起来是C语言?或者说是什么库呢?小白想了解一下这方面的编程知识
黑色背景中的代码是MFC与C++,其余的(各种工具中显示的,如OD,CE)代码是汇编语言
雪    币: 0
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
Palmer0801 2020-11-7 13:28
6
0
感谢楼主哈哈哈,这么细心汇编语言都解释了。主要问一下那个库呜呜呜,还没接触过这种win32编程?呜呜呜
雪    币: 1820
活跃值: (2405)
能力值: ( LV5,RANK:65 )
在线值:
发帖
回帖
粉丝
哦哈哈哈哈 2020-11-14 16:04
7
0
Palmer0801 感谢楼主哈哈哈,这么细心汇编语言都解释了。主要问一下那个库呜呜呜,还没接触过这种win32编程?呜呜呜

以上两张图片是我的vs2019安装的详细信息(点击visual studio installer可修改)


以上两张图片是我写的dll的导入表(用PEID可以查看)

雪    币: 110
活跃值: (299)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
公章你又胖了 2020-11-17 18:00
8
0
好厉害。努力学习中
雪    币: 0
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
Palmer0801 2020-11-20 00:35
9
0
哦哈哈哈哈 以上两张图片是我的vs2019安装的详细信息(点击visual studio installer可修改)以上两张图片是我写的dll的导入表(用PEID可以查看)
爱了爱了,
雪    币: 190
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
wx_一口田不点 2021-6-4 10:13
10
0
非常详细啊,谢谢大神
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
lambda_wall 2021-6-7 01:37
11
0
BYTE byCode = *(PBYTE)((DWORD)g_pBase + x + y * 32); 中的32是什么意思
雪    币: 1857
活跃值: (1643)
能力值: ( LV6,RANK:80 )
在线值:
发帖
回帖
粉丝
HLuKT 1 2021-11-29 09:55
12
0
lambda_wall BYTE byCode = *(PBYTE)((DWORD)g_pBase + x + y * 32); 中的32是什么意思

BYTE byCode = *(PBYTE)((DWORD)g_pBase + x + y * 32); 中的32

可以看汇编代码中,C1E6 05部分 为:SHL ESI,0x5 意为左移5位,就是y*32,即2的5次方,为32

最后于 2021-11-29 09:58 被HLuKT编辑 ,原因:
雪    币: 4448
活跃值: (3481)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
木志本柯 2021-11-29 10:47
13
0
若游戏是由SDK编写的,这意味着会有一些相关的函数可供我们查找与调用

如   

       CreateWindowExW/CreateWindowExA(创建窗口)

       GetWindowTextA/GetWindowTextW(将指定窗口的标题拷贝到一个缓冲区内)

       SetWindowTextA/SetWindowTextW(改变控件的文本内容,窗口也是一个控件)

       rand(产生随机数函数)

如果不用SDK编写的话 会用什么来创建窗口呢。。。
雪    币: 4448
活跃值: (3481)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
木志本柯 2021-11-29 10:49
14
0

哪怕是用汇编编写 创建窗口也得是用api  创建窗口的实现在Ring0 你一个ring3程序汇编在怎么写 如何可以创建一个窗口出来

最后于 2021-11-29 10:50 被木志本柯编辑 ,原因:
雪    币: 1857
活跃值: (1643)
能力值: ( LV6,RANK:80 )
在线值:
发帖
回帖
粉丝
HLuKT 1 2021-11-30 09:17
15
0
木志本柯 若游戏是由SDK编写的,这意味着会有一些相关的函数可供我们查找与调用 如 CreateWindowExW/CreateWindowExA(创建窗口) ...
我看了下这里写的意思应该只是为了判断是使用的SDK还是MFC吧,毕竟他们的api不一样
雪    币: 560
活跃值: (751)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
wx_Van_Zovy 2021-12-1 09:34
16
0
向大佬学习
雪    币: 244
活跃值: (311)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
hygzs 2021-12-1 10:13
17
0
向大佬学习
游客
登录 | 注册 方可回帖
返回