首页
社区
课程
招聘
[原创]利用OD通过Call调用实现扫雷(win7旗舰版32位)
发表于: 2017-7-18 18:57 12352

[原创]利用OD通过Call调用实现扫雷(win7旗舰版32位)

2017-7-18 18:57
12352

一直对逆向感兴趣,就拿最简单的扫雷开始,对于XP系统中的扫雷,雷的数目以及雷区的地址都是固定的,可以直接通过Cheat Engine搜索出来,然后在OD中直接下内存访问断点,找到扫雷整个区域,获得行数和列数,再顺藤摸瓜找到左键点击的处理函数和右键点击的处理函数,读取雷区数据,一一比较如果是雷就右键标记,如果不是可以直接左键点开,即可完成秒杀。

经过了大概半个月的时间,终于在查阅各种帖子和不断的摸索下,实现了Win7 32位系统扫雷的秒杀,哈哈,太开心了!

因为XP系统的扫雷中雷区的地址是固定的,可以通过CE进行搜索出来,但Win7系统的扫雷是用C++写的,雷区不再是固定的,而是在鼠标第一次点击之后再产生雷的位置。对win7扫雷一开始没有一点思路,后来看了很多牛人写的帖子,在他们的基础上才能慢慢的入手。可以参考:

Vista的扫雷》:http://bbs.pediy.com/thread-40295.htm

Win7自动扫雷》:http://bbs.pediy.com/thread-120152.htm

2篇文章里的思路都是通过对rand函数下断,分析找到雷存放的地址,每个格子里存放01,如果是1则是雷,是0则不是雷,发送消息模拟鼠标的单机,进行自动扫雷。其中,在《Vista的扫雷》提到了重定位的处理,解决方案2对于Win7 32位下的扫雷并不适用,这篇文章中提到:

“假设扫雷入口地址设为dwCodeAddress,则[3931B8] 这个地址得到的方法是: ( dwCodeAddress & 0xFFFF0000 ) + 0x531B8

但是在Win7 32位的扫雷中,并不是这样的,用PEiD打开扫雷

可以看到,扫雷的入口点是0x0002e08f,用OD打开扫雷,停在了入口点,代码是

001CE08F > $  E8 B5050000   call MineSwee.001CE649

001CE094   .^ E9 4DFDFFFF   jmp MineSwee.001CDDE6

所以扫雷中地址的计算方法是:扫雷模块地址+偏移

入口点地址0x1ce08f = 扫雷模块地址+0x2e08f,所以此次加载扫雷模块地址为

MineSweeper.exe = 0x1A0000

在此我找到了一种可以去除重定位的方法,PE头中的文件头的Characteristics字段指定了文件的类型,占用2字节,其第0比特为1,即表示文件中不存在重定位信息,用C32Asm打开扫雷,找到该字段

该字段为0x0102,其第0比特为0,即存在重定位信息,将其修改为1,将会给我们分析带来很大的方便(地址固定),即将该字段修改为0x0103,在下面的分析中我就用修改后的文件进行分析,该修改并不影响分析结果,同样适用于没修改的扫雷(因为偏移是固定的)。分析前面的过程在《Vista的扫雷》和《Win7自动扫雷》中讲得很详细,在此不做赘述,简单走一遍,看过的可以直接跳过。

首先,用OD加载扫雷,对rand函数下断:bp rand,然后先将该断点禁止,按F9运行起来,启动该断点,扫雷断下

一看便知该模块不是扫雷的模块,而是msvcrt。在堆栈窗口中看到,

在第1行处反汇编跟随(按Enter),到

鼠标点到该函数第一行,OD在反汇编窗口下方会有提示

“本地调用来自 01020176, 01023169, 01023177, 01023BB3, 01027466, 010275B9, 01027E86, 01027E98

对这8call都进行下断,F9,会发现一直断在

删除这个断点,继续F9,回到游戏,不会中断了,开始游戏并点击一个格子,断在如下位置

往上看,则如《Vista的扫雷》中一样,是根据第一次点击的格子产生序号并保存序号的代码,在此列举一下,看过的可以跳过

Ctrl+F9 2次执行到返回,或在堆栈窗口中找到第2个“返回到”,跟随到上上一层:

其中,call MineSwee.01021418中的代码及注释如下(看过的可以跳过)

到此得到了地址0x10868b4,查看入口点代码

扫雷模块地址为0x1000000,所以

方块数据=[[[[[[[minesweeper.exe+0x868B4]+0x10]+0x40]+0x0C]+4*X坐标]+0x0C]+4*Y坐标];分析一下取值有 1~8数字 9未开 10 11问号 12

雷地址  =[[[[[[[minesweeper.exe+0x868b4]+0x10]+0x44]+0x0c]+4*X坐标]+0x0c]+Y坐标]  1表示有雷 0表示没雷

esi=[[minesweeper.exe+0x868B4]+0x10]minesweeper.exe表示扫雷模块地址

[esi+0x4] => 雷数

[esi+0x8] => 行数

[esi+0xc] => 列数

[esi+0x18] => 鼠标左键单击次数

第一次点击  列号:[esi+0x24]

行号:[esi+0x28]

然后找左键单击打开格子Call,既然是左键单击,该Call中的参数应该是点击格子的X坐标和Y坐标,所以在该Call之前应该有如下类似操作:


[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

收藏
免费 2
支持
分享
打赏 + 2.00雪花
打赏次数 1 雪花 + 2.00
 
赞赏  CCkicker   +2.00 2017/07/20
最新回复 (15)
雪    币: 6978
活跃值: (4197)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
感谢分享
2017-7-18 19:23
0
雪    币: 12848
活跃值: (9142)
能力值: ( LV9,RANK:280 )
在线值:
发帖
回帖
粉丝
3
槽点太多,先说说槽点:
1.带重定位调试是基本功,手动把重定位去了是什么鬼
2.对rand下断这个上手思路明显就是有问题的,碰上/MT的程序找不到rand,或者人家自己写随机数生成的怎么办?这个上手思路不具备普适性,碰上稍微复杂一点的程序直接懵逼
2.都注入进去了还ReadProcessMemory,还有OpenProcess  PROCESS_ALL_ACCESS也是,完全没必要的操作+奇怪的api使用习惯
3.明明可以写成函数指针的非要内联asm
4.我记得64位系统的扫雷也是64位的,这个代码只有32位

再说说优点:思路记录的很完整,比我四年前记的扫雷笔记完整多了,虽然我是直接拖ida根本没调试(逃
2017-7-18 19:23
0
雪    币: 1573
活跃值: (198)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
4

谢谢提出不足!!欢迎讨论

2017-7-18 22:15
0
雪    币: 1573
活跃值: (198)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
5
hzqst 槽点太多,先说说槽点: 1.带重定位调试是基本功,手动把重定位去了是什么鬼 2.对rand下断这个上手思路明显就是有问题的,碰上/MT的程序找不到rand,或者人家自己写随机数生成的怎么办?这个上 ...
1.目前对这个程序,我觉得把重定位去了完全没影响,因为重定位就是给程序进程设定一个随机的地址,但是偏移不会变,因此去掉可以方便调试;再者重定位在程序编译的时候也就是设置一下随机基址,这也是一种调试方法,因为分析的结果对没有去重定位的程序依然适用。
2.可以查找该程序模块中都有什么函数,当然有rand,所以有点想当然;不过还可以通过程序结束的时候弹出对话框入手,在DialogBoxParamW处下断,逆向分析,也可以分析出结果;不过确实分析经验不够,还需多练习!!
3.这一点有点误导,其实注入按钮的功能是挂了个键盘钩子,忘了改掉了!我的错!!!OpenProcess的话,应该怎么写???
4.内联不是更直观,更方便么,前提是要懂点汇编,这点我没觉得函数指针就比内联好。。。
5.对,该程序只适合32位的扫雷,后面有时间再研究下64位的
谢谢提出不足!!!
2017-7-18 22:17
0
雪    币: 6079
活跃值: (3067)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
学习学习,感谢楼主分享!
2017-7-18 23:24
0
雪    币: 106
活跃值: (1988)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
7
ReadProcessMemory在注入后确实不必使用,直接操作内存即可,毕竟你既然已经注入进去了,完全可以直接操作内存,
如果使用ReadProcessMemory,则不必注入,直接远程修改即可,注入后再使用ReadProcessMemory,的确有点奇怪
2017-7-19 09:36
0
雪    币: 1573
活跃值: (198)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
8
hzqst 槽点太多,先说说槽点: 1.带重定位调试是基本功,手动把重定位去了是什么鬼 2.对rand下断这个上手思路明显就是有问题的,碰上/MT的程序找不到rand,或者人家自己写随机数生成的怎么办?这个上 ...

谢谢楼上大牛们的指点,现在才知道了Dll注入的强大,可以直接操作内存,OpenProcess,GetWindowThreadProcessId,以及自己定义的获得模块基址的函数GetProcessBase等等都完全就是多余!!!!!!!!!!!!!感谢大神们!

还有个问题,call可以直接用函数指针,但是call前要给ECX赋值,不知道如何处理,尝试了一下,不可行(见注释代码部分)

代码:

	if ((wParam == VK_HOME) && ((lParam&(1 << 31)) == 0) && HC_ACTION == code) // 按Home键秒杀
	{
		DWORD dwPid = GetCurrentProcessId();
		HMODULE hMine = GetModuleHandle(0);
		if (NULL == hMine)
		{
			MessageBox(NULL, "无法找到模块MineSweeper!", "提示Dll", 0);
			return 0;
		}
		DWORD dwMineBaseAddr = (DWORD)hMine; // 模块句柄就是基址
		HANDLE hProcess = GetCurrentProcess();
		if (!hProcess)
		{
			MessageBox(NULL, "获取扫雷进程句柄失败!", "提示Dll", 0);
			return 0;
		}
		DWORD vaCallLeft1 = dwMineBaseAddr + 0x26bcd;
		DWORD vaCallLeft0 = dwMineBaseAddr + 0x21418;
		DWORD* pBase = (DWORD*)(dwMineBaseAddr + 0x868b4);
		//typedef DWORD (*funCall0)(DWORD dwColumn, DWORD dwRow);
		//typedef DWORD(*funCall1)(DWORD dwEAX);
		//funCall0 fLeft0 =(funCall0)(dwMineBaseAddr + 0x21418);
		//funCall1 fLeft1 = (funCall1)(dwMineBaseAddr + 0x26bcd);
		//BYTE* pEAX = (BYTE*)(*pBase + 0xc5);
		//*pEAX = 1;
		//_asm
		//{
		//	mov ecx,dwMineBaseAddr
		//	mov ecx,[ecx + 0x868b4]
		//}
		//DWORD dwEAX = fLeft0(0, 0);
		//if (dwEAX > 0)
		//{
		//	fLeft1(dwEAX);
		//}
		_asm
		{
			push eax
			push ecx
			push ebx
			mov eax,dwMineBaseAddr
			mov eax,[eax+0x868b4]
			mov [eax+0xc5],1
			mov ecx,dwMineBaseAddr
			mov ecx,[ecx+0x868b4]
			push 0
			push 0
			xor bl,bl
			mov eax,vaCallLeft0
			call eax
			test eax,eax
			jg sHome
			jmp endHome
			sHome:		
			push eax
			mov eax,vaCallLeft1
			call eax
			endHome:	
			pop ebx
			pop ecx
			pop eax
		}
		DWORD* pESI = (DWORD*)(*pBase + 0x10);
		DWORD* pRow = (DWORD*)(*pESI + 0x8);
		DWORD* pColumn = (DWORD*)(*pESI + 0xc);
		DWORD* pAddr = (DWORD*)(*pESI + 0x44);
		pAddr = (DWORD*)(*pAddr + 0xc);
		BYTE bMine[30][24] = { 0 };
		DWORD* pMine = NULL;
		BYTE* pIsMine = NULL;
		unsigned int i = 0, j = 0; // i行 j列
		while (j < *pColumn)
		{
			pMine = (DWORD*)(*pAddr + 4 * j);
			pMine = (DWORD*)(*pMine + 0xc);
			i = 0;
			while (i < *pRow)
			{
				pIsMine = (BYTE*)(*pMine + i);
				bMine[j][i] = *pIsMine;
				++i;
			}
			++j;
		}
		i = 0;
		j = 0;
		while (j < *pColumn)
		{
			i = 0;
			while (i < *pRow)
			{
				if (0x00 == bMine[j][i])
				{
					__asm
					{
						push eax
						push ecx
						push ebx
						mov eax,dwMineBaseAddr
						mov eax,[eax + 0x868b4]
						mov [eax + 0xc5], 1
						mov ecx,dwMineBaseAddr
						mov ecx,[ecx + 0x868b4]
						mov eax,i
						push eax
						mov eax,j
						push eax
						xor bl, bl
						mov eax, vaCallLeft0
						call eax
						test eax,eax
						jg s
						jmp end
						s:
						push eax
						mov eax, vaCallLeft1
						call eax
						end:
						pop ebx
						pop ecx
						pop eax
					}
				}
				++i;
			}
			++j;
		}
	}

2017-7-21 10:47
0
雪    币: 739
活跃值: (3390)
能力值: ( LV7,RANK:140 )
在线值:
发帖
回帖
粉丝
9

可以看看fastcall函数调用规范,配合函数指针,强制转换

2017-7-21 12:45
0
雪    币: 1573
活跃值: (198)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
10
yeyeshun 可以看看fastcall函数调用规范,配合函数指针,强制转换
好的
2017-7-22 08:51
0
雪    币: 12848
活跃值: (9142)
能力值: ( LV9,RANK:280 )
在线值:
发帖
回帖
粉丝
11




Thvoifar

好的[em_69]

int  (__fastcall   *CXXXX::FunctionYYY)(void  *pthis,  int  dummy,  int  第一个参数,  int  第二个参数,...)
等价于int  (__thiscall   *CXXXX::FunctionYYY)(void  *pthis,  int  第一个参数,  int  第二个参数,...)
所以pthis->FunctionYYY(参数)就等价于fastcall的FunctionYYY(pthis, 0, 参数);

懂我的意思吧?

2017-7-23 23:35
0
雪    币: 1
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
12
我有一款软件,提示我的加密狗没有权限操作,我是做整木设计的,已经花了3800大洋买的,这个功能还限制,是非标板件设计。还要在花钱开通。有没有OD上一些好的破解思路,谢谢,这坑爹的软件供应商
2017-7-26 18:50
0
雪    币: 0
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
13
mark.
2018-10-12 21:43
0
雪    币: 33
能力值: (RANK:10 )
在线值:
发帖
回帖
粉丝
14
我有个想法 依次点每个雷 如果爆炸就从新开始这局 就能记录所有雷点  这样应该更简单暴力 哈哈
2018-10-12 22:56
0
雪    币: 37
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
ccj
15
楼主可以试一下64位的扫雷,X64下可没有方便的内联汇编。
2018-10-13 21:50
0
雪    币: 1573
活跃值: (198)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
16
ccj 楼主可以试一下64位的扫雷,X64下可没有方便的内联汇编。
好的,现在又有时间了!
2019-1-1 13:17
0
游客
登录 | 注册 方可回帖
返回
//