一直对逆向感兴趣,就拿最简单的扫雷开始,对于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
炒鸡嗨客协管徐的博客《Win7扫雷逆向调试》:
http://blog.csdn.net/xfgryujk/article/details/52948705
这2篇文章里的思路都是通过对rand函数下断,分析找到雷存放的地址,每个格子里存放0或1,如果是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运行起来,启动该断点,扫雷断下
77C3C070 > E8 59E5FFFF call msvcrt.77C3A5CE ; rand 断点处
77C3C075 8B48 14 mov ecx,dword ptr ds:[eax+0x14]
77C3C078 69C9 FD430300 imul ecx,ecx,0x343FD
77C3C07E 81C1 C39E2600 add ecx,0x269EC3
77C3C084 8948 14 mov dword ptr ds:[eax+0x14],ecx
77C3C087 8BC1 mov eax,ecx
77C3C089 C1E8 10 shr eax,0x10
77C3C08C 25 FF7F0000 and eax,0x7FFF
77C3C091 C3 retn
一看便知该模块不是扫雷的模块,而是msvcrt。在堆栈窗口中看到,
在第1行处反汇编跟随(按Enter),到
0102D7F3 /$ 8BFF mov edi,edi ;调用rand的小函数
0102D7F5 |. 55 push ebp
0102D7F6 |. 8BEC mov ebp,esp
0102D7F8 |. FF15 B8160001 call dword ptr ds:[<&msvcrt.rand>] ; [rand
0102D7FE |. 8B4D 0C mov ecx,dword ptr ss:[ebp+0xC]
0102D801 |. 2B4D 08 sub ecx,dword ptr ss:[ebp+0x8]
0102D804 |. 99 cdq
0102D805 |. 41 inc ecx
0102D806 |. F7F9 idiv ecx
0102D808 |. 8BC2 mov eax,edx
0102D80A |. 0345 08 add eax,dword ptr ss:[ebp+0x8]
0102D80D |. 5D pop ebp
0102D80E \. C2 0800 retn 0x8
鼠标点到该函数第一行,OD在反汇编窗口下方会有提示
“本地调用来自 01020176, 01023169, 01023177, 01023BB3, 01027466, 010275B9, 01027E86, 01027E98”
对这8个call都进行下断,F9,会发现一直断在
01023BB3 |. E8 3B9C0000 call MineSwee.0102D7F3 ; 一直断,没用删除
删除这个断点,继续F9,回到游戏,不会中断了,开始游戏并点击一个格子,断在如下位置
01020176 |. E8 78D60000 |call MineSwee.0102D7F3
往上看,则如《Vista的扫雷》中一样,是根据第一次点击的格子产生序号的代码
010200BB /$ 8BFF mov edi,edi ; 布雷函数
……
010200C7 |. E8 4AD70000 call MineSwee.0102D816
010200CC |. FF76 2C push dword ptr ds:[esi+0x2C]
010200CF |. 8945 F8 mov dword ptr ss:[ebp-0x8],eax
010200D2 |. E8 4AD70000 call MineSwee.0102D821
010200D7 |. 6A 10 push 0x10
010200D9 |. E8 C9DB0000 call MineSwee.0102DCA7 ; 先申请4个DWORD
010200DE |. 59 pop ecx
010200DF |. 85C0 test eax,eax
010200E1 |. 74 0E je short MineSwee.010200F1
010200E3 |. 6A 10 push 0x10
010200E5 |. 8BC8 mov ecx,eax
010200E7 |. E8 6DF8FFFF call MineSwee.0101F959 ; 对上面申请的内存初始化为0 0 0x10 0
010200EC |. 8945 FC mov dword ptr ss:[ebp-0x4],eax
010200EF |. EB 04 jmp short MineSwee.010200F5
010200F1 |> 8365 FC 00 and dword ptr ss:[ebp-0x4],0x0
010200F5 |> 8B46 08 mov eax,dword ptr ds:[esi+0x8] ; 行数dwRow 9
010200F8 |. 0FAF46 0C imul eax,dword ptr ds:[esi+0xC] ;列数dwColumn和行数相乘,得到总方块数dwSum edx:eax
010200FC |. 33FF xor edi,edi ; i = 0 循环计数器
010200FE |. 85C0 test eax,eax
01020100 |. 7E 49 jle short MineSwee.0102014B
01020102 |> 8B4E 0C /mov ecx,dword ptr ds:[esi+0xC]
01020105 |. 8BC7 |mov eax,edi
01020107 |. 99 |cdq ; edx:eax 64位寄存器
01020108 |. F7F9 |idiv ecx ; i/dwColumn=eax行号....edx列号
0102010A |. 33C9 |xor ecx,ecx
0102010C |. 2B55 08 |sub edx,dword ptr ss:[ebp+0x8] ; i列号-鼠标点击列号(从0开始)
0102010F |. 2B45 0C |sub eax,dword ptr ss:[ebp+0xC] ; i行号-鼠标点击行号(从0开始)
01020112 |. 85D2 |test edx,edx
01020114 |. 0F9DC1 |setge cl ;判断列号差值正负,设置cl为1或0,从而取绝对值
01020117 |. 8D4C09 FF |lea ecx,dword ptr ds:[ecx+ecx-0x1]
0102011B |. 0FAFCA |imul ecx,edx
0102011E |. 83F9 01 |cmp ecx,0x1
01020121 |. 7F 13 |jg short MineSwee.01020136 ; 第一个差值绝对值<=1,判断第二个差值
01020123 |. 33C9 |xor ecx,ecx
01020125 |. 85C0 |test eax,eax ; 判断行号差值正负
01020127 |. 0F9DC1 |setge cl
0102012A |. 8D4C09 FF |lea ecx,dword ptr ds:[ecx+ecx-0x1]
0102012E |. 0FAFC8 |imul ecx,eax
01020131 |. 83F9 01 |cmp ecx,0x1
01020134 |. 7E 09 |jle short MineSwee.0102013F ; 第二个差值<=1,什么都不做
01020136 |> 8B4D FC |mov ecx,dword ptr ss:[ebp-0x4]
01020139 |. 57 |push edi ; 将序号传递进去
0102013A |. E8 12590100 |call MineSwee.01035A51 ; 将序号保存到realloc空间中去
0102013F |> 8B46 08 |mov eax,dword ptr ds:[esi+0x8] ; dwRow
01020142 |. 0FAF46 0C |imul eax,dword ptr ds:[esi+0xC] ; dwColumn
01020146 |. 47 |inc edi
01020147 |. 3BF8 |cmp edi,eax
01020149 |.^ 7C B7 \jl short MineSwee.01020102
0102014B |> 6A 10 push 0x10
0102014D |. E8 55DB0000 call MineSwee.0102DCA7 ; 再申请4个DWORD
01020152 |. 59 pop ecx
01020153 |. 85C0 test eax,eax
01020155 |. 74 0E je short MineSwee.01020165
01020157 |. FF76 04 push dword ptr ds:[esi+0x4]
0102015A |. 8BC8 mov ecx,eax
0102015C |. E8 F8F7FFFF call MineSwee.0101F959 ; 对上面申请的内存初始化为0 0 0x0A 0
01020161 |. 8BF8 mov edi,eax
01020163 |. EB 31 jmp short MineSwee.01020196
01020165 |> 33FF xor edi,edi
01020167 |. EB 2D jmp short MineSwee.01020196
01020169 |> 8B45 FC /mov eax,dword ptr ss:[ebp-0x4]
0102016C |. 8B00 |mov eax,dword ptr ds:[eax]
0102016E |. 85C0 |test eax,eax
01020170 |. 76 2B |jbe short MineSwee.0102019D
01020172 |. 48 |dec eax ;81个方块,有81-9=72(0x48)个编号,生成0~0x47之间的随机数
01020173 |. 50 |push eax
01020174 |. 6A 00 |push 0x0 ; Arg1=0
01020176 |. E8 78D60000 |call MineSwee.0102D7F3 ;第一次点击方块断在此处,调用rand,产生0~Arg2的随机数
0102017B |. 8BD8 |mov ebx,eax
0102017D |. 8B45 FC |mov eax,dword ptr ss:[ebp-0x4]
01020180 |. 8B40 0C |mov eax,dword ptr ds:[eax+0xC]
01020183 |. FF3498 |push dword ptr ds:[eax+ebx*4] ; 凭获得的随机数到上面数组中取值
01020186 |. 8BCF |mov ecx,edi
01020188 |. E8 C4580100 |call MineSwee.01035A51 ; 保存到一个新数组中
0102018D |. 8B4D FC |mov ecx,dword ptr ss:[ebp-0x4]
01020190 |. 53 |push ebx
01020191 |. E8 08EB0000 |call MineSwee.0102EC9E ;将已取出的数据从原数组中删除,以防重复
01020196 |> 8B07 mov eax,dword ptr ds:[edi]
01020198 |. 3B46 04 |cmp eax,dword ptr ds:[esi+0x4] ;如果小于需要产生的雷数[esi+4],继续循环
0102019B |.^ 75 CC \jnz short MineSwee.01020169
0102019D |> 33C9 xor ecx,ecx ; 循环i=0
0102019F |. 390F cmp dword ptr ds:[edi],ecx ; [edi]=产生的地雷总数
010201A1 |. 76 21 jbe short MineSwee.010201C4
010201A3 |> 8B47 0C /mov eax,dword ptr ds:[edi+0xC]
010201A6 |. 8B0488 |mov eax,dword ptr ds:[eax+ecx*4] ; 取第i个雷的位置
010201A9 |. 8B5E 0C |mov ebx,dword ptr ds:[esi+0xC] ; 列数
010201AC |. 99 |cdq
010201AD |. F7FB |idiv ebx ; i/dwColumn=eax行号....edx列号
010201AF |. 8B5E 44 |mov ebx,dword ptr ds:[esi+0x44]
010201B2 |. 8B5B 0C |mov ebx,dword ptr ds:[ebx+0xC] ;[[esi+0x44]+0xc]地址表,有dwColumn个地址
010201B5 |. 41 |inc ecx ; ++i
010201B6 |. 8B1493 |mov edx,dword ptr ds:[ebx+edx*4] ; 根据雷所在列号取第edx(列号)个地址
010201B9 |. 8B52 0C |mov edx,dword ptr ds:[edx+0xC] ; esi=[[minesweeper.exe+0x868B4]+0x10]
010201BC |. C60410 01 |mov byte ptr ds:[eax+edx],0x1 ;[[[[[esi+0x44]+0xc]+4*列号]+0xc]+行号],1表示有雷
010201C0 |. 3B0F |cmp ecx,dword ptr ds:[edi]
010201C2 |.^ 72 DF \jb short MineSwee.010201A3 ; 布雷完毕
按Ctrl+F9 2次执行到返回,或在堆栈窗口中找到第2个“返回到”,跟随到上上一层:
01026FB7 /$ 8BFF mov edi,edi ; 左键单击判断函数
01026FB9 |. 55 push ebp
01026FBA |. 8BEC mov ebp,esp
01026FBC |. A1 B4680801 mov eax,dword ptr ds:[0x10868B4] ;该地址就是我们要找的地址
01026FC1 |. 53 push ebx
01026FC2 |. 56 push esi
01026FC3 |. 8B75 08 mov esi,dword ptr ss:[ebp+0x8]
01026FC6 |. 57 push edi
01026FC7 |. C680 C5000000>mov byte ptr ds:[eax+0xC5],0x1
01026FCE |. FF76 1C push dword ptr ds:[esi+0x1C] ; Y坐标
01026FD1 |. 8BF9 mov edi,ecx
01026FD3 |. FF76 18 push dword ptr ds:[esi+0x18] ; X坐标
01026FD6 |. 8B0D B4680801 mov ecx,dword ptr ds:[0x10868B4] ; this指针
01026FDC |. 32DB xor bl,bl
01026FDE |. E8 35A4FFFF call MineSwee.01021418 ;判断踩下的方块是不是雷,参数有方块的X、Y坐标
01026FE3 |. 85C0 test eax,eax ;将返回到此处
01026FE5 |. 7F 0A jg short MineSwee.01026FF1
01026FE7 |. 89B7 9C000000 mov dword ptr ds:[edi+0x9C],esi
01026FED |. FEC3 inc bl
01026FEF |. EB 08 jmp short MineSwee.01026FF9
01026FF1 |> 50 push eax
01026FF2 |. 8BCF mov ecx,edi
01026FF4 |. E8 D4FBFFFF call MineSwee.01026BCD
01026FF9 |> C687 AC000000>mov byte ptr ds:[edi+0xAC],0x1
01027000 |. 5F pop edi
01027001 |. 5E pop esi
01027002 |. 8AC3 mov al,bl
01027004 |. 5B pop ebx
01027005 |. 5D pop ebp
01027006 \. C2 0400 retn 0x4
其中,call MineSwee.01021418中的代码及注释如下(看过的可以跳过)
01021418 $ 8BFF mov edi,edi
0102141A . 55 push ebp
0102141B . 8BEC mov ebp,esp
0102141D . 8B49 10 mov ecx,dword ptr ds:[ecx+0x10] ;this指针变成[[minesweeper.exe+0x868B4]+0x10]
01021420 . 5D pop ebp
01021421 .^ E9 2AF8FFFF jmp MineSwee.01020C50 ;跳转
此处有个跳转
01020C57 |. 8B5D 08 mov ebx,dword ptr ss:[ebp+0x8] ; ebx = X坐标
01020C5A |. 56 push esi
01020C5B |. 8BF1 mov esi,ecx ;esi ecx=[[minesweeper.exe+0x868B4]+0x10]
01020C5D |. 8B46 40 mov eax,dword ptr ds:[esi+0x40] ; this->某对象
01020C60 |. 8B40 0C mov eax,dword ptr ds:[eax+0xC] ; ->某对象
01020C63 |. 8B0498 mov eax,dword ptr ds:[eax+ebx*4] ; ->某对象
01020C66 |. 8B40 0C mov eax,dword ptr ds:[eax+0xC] ; eax = &某对象
01020C69 |. 57 push edi
01020C6A |. 8B7D 0C mov edi,dword ptr ss:[ebp+0xC] ; edi = Y坐标
01020C6D |. 8B04B8 mov eax,dword ptr ds:[eax+edi*4]
;方块数据=[[[[[[[minesweeper.exe+0x868B4]+0x10]+0x40]+0x0C]+4*X坐标]+0x0C]+4*Y坐标]
01020C70 |. 33C9 xor ecx,ecx
01020C72 |. 894D FC mov dword ptr ss:[ebp-0x4],ecx
01020C75 |. 83F8 09 cmp eax,0x9 ; 方块==9,该格子未开
01020C78 |. 74 1B je short MineSwee.01020C95
01020C7A |. 83F8 0B cmp eax,0xB ; 方块==11,该格子问号?标志
01020C7D |. 74 16 je short MineSwee.01020C95
01020C7F |. A1 B4680801 mov eax,dword ptr ds:[0x10868B4]
01020C84 |. 3848 18 cmp byte ptr ds:[eax+0x18],cl
01020C87 |. 74 5C je short MineSwee.01020CE5
01020C89 |. 51 push ecx
01020C8A |. 51 push ecx
01020C8B |. 51 push ecx
01020C8C |. E8 58F90000 call MineSwee.010305E9
01020C91 |. 33C9 xor ecx,ecx
01020C93 |. EB 50 jmp short MineSwee.01020CE5
01020C95 |> 394E 18 cmp dword ptr ds:[esi+0x18],ecx ; 判断是不是鼠标第一次点击
01020C98 |. 75 20 jnz short MineSwee.01020CBA ; 不是就跳走
01020C9A |. 57 push edi
01020C9B |. 53 push ebx
01020C9C |. 8BCE mov ecx,esi
01020C9E |. E8 18F4FFFF call MineSwee.010200BB ; 第一次点击时 地雷产生和布雷
01020CA3 |. 6A 00 push 0x0
01020CA5 |. 57 push edi
01020CA6 |. 53 push ebx
01020CA7 |. 6A 00 push 0x0
01020CA9 |. 57 push edi ; 压入Y
01020CAA |. 53 push ebx ; 压入X
01020CAB |. 8BCE mov ecx,esi
01020CAD |. E8 90FDFFFF call MineSwee.01020A42
01020CB2 |. 895E 24 mov dword ptr ds:[esi+0x24],ebx
01020CB5 |. 897E 28 mov dword ptr ds:[esi+0x28],edi
01020CB8 |. EB 23 jmp short MineSwee.01020CDD
01020CBA |> 8B46 44 mov eax,dword ptr ds:[esi+0x44] ; this->某对象
01020CBD |. 8B40 0C mov eax,dword ptr ds:[eax+0xC] ; ->某对象
01020CC0 |. 8B0498 mov eax,dword ptr ds:[eax+ebx*4] ; ->某对象
01020CC3 |. 8B40 0C mov eax,dword ptr ds:[eax+0xC] ; ->某对象
01020CC6 |. 380C07 cmp byte ptr ds:[edi+eax],cl
;雷地址=[[[[[[[minesweeper.exe+0x868B4]+0x10]+0x44]+0x0C]+4*X坐标]+0x0C]+Y坐标]
到此得到了地址0x10868b4,查看入口点代码
0102E08F > $ E8 B5050000 call MineSwee.0102E649
0102E094 .^ E9 4DFDFFFF jmp MineSwee.0102DDE6
扫雷模块地址为0x1000000,所以
方块数据=[[[[[[[minesweeper.exe+0x868B4]+0x10]+0x40]+0x0C]+4X坐标]+0x0C]+4Y坐标];分析一下取值有 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之前应该有如下类似操作:
push X坐标
push Y坐标
Call 0x……
在我们之前的分析中寻找,发现有2个地方可疑
第一处:
01026FC7 |. C680 C5000000>mov byte ptr ds:[eax+0xC5],0x1
01026FCE |. FF76 1C push dword ptr ds:[esi+0x1C] ; Y坐标
01026FD1 |. 8BF9 mov edi,ecx
01026FD3 |. FF76 18 push dword ptr ds:[esi+0x18] ; X坐标
01026FD6 |. 8B0D B4680801 mov ecx,dword ptr ds:[0x10868B4] ; this指针
01026FDC |. 32DB xor bl,bl
01026FDE |. E8 35A4FFFF call MineSwee.01021418 ;判断踩下的方块是不是雷,参数有方块的X、Y坐标
01026FE3 |. 85C0 test eax,eax ;将返回到此处
01026FE5 |. 7F 0A jg short MineSwee.01026FF1
01026FE7 |. 89B7 9C000000 mov dword ptr ds:[edi+0x9C],esi
01026FED |. FEC3 inc bl
01026FEF |. EB 08 jmp short MineSwee.01026FF9
01026FF1 |> 50 push eax
01026FF2 |. 8BCF mov ecx,edi
01026FF4 |. E8 D4FBFFFF call MineSwee.01026BCD
第二处:
01020CA9 |. 57 push edi ; 压入Y
01020CAA |. 53 push ebx ; 压入X
01020CAB |. 8BCE mov ecx,esi
01020CAD |. E8 90FDFFFF call MineSwee.01020A42
对于第二处,有个跳转,不是第一次点击格子的话会跳过,因此排除。怀疑第一处是我们要找的Call,用CE加载扫雷,在自动汇编中写入测试脚本:
alloc(myscript,1024)
define(adr,minesweeper.exe+868b4)
define(fun1,minesweeper.exe+21418)
define(fun2,minesweeper.exe+26bcd)
label(s0)
label(end0)
myscript:
push eax
push ecx
push ebx
mov eax,[adr]
mov [eax+0c5],1
mov ecx,[adr]
push 3
push 3
xor bl,bl
call fun1
test eax,eax
jg s0
jmp end0
s0:
push eax
call fun2
end0:
pop ebx
pop ecx
pop eax
ret
执行,得到myscript=某个地址,记住该地址,打开创建线程,输入该地址,确定,看扫雷打开了一大片,哈哈!这就是我们要找的左键单击打开一个格子的Call。现在便可以实现一键自动扫雷了,但是为了能够把雷标记出来,我们还可以找右键单击的Call。
右键单击格子会改变格子的状态,10为旗,11为问号,在第一颗方块数据处下硬件断点,即X=0 Y=0处,[[[[[[[minesweeper.exe+0x868B4]+0x10]+0x40]+0x0C]+40]+0x0C]+40],右键单击第一个格子,扫雷断下,进行回溯跟踪,在第二次返回后,找到了可疑的地方
01026BF7 /$ 8BFF mov edi,edi
01026BF9 |. 55 push ebp
01026BFA |. 8BEC mov ebp,esp
01026BFC |. 8B45 08 mov eax,dword ptr ss:[ebp+0x8]
01026BFF |. 56 push esi
01026C00 |. FF70 1C push dword ptr ds:[eax+0x1C] ; Y坐标,行数
01026C03 |. 8BF1 mov esi,ecx
01026C05 |. FF70 18 push dword ptr ds:[eax+0x18] ; X坐标,列数
01026C08 |. 8B4E 24 mov ecx,dword ptr ds:[esi+0x24] ; 找esi
01026C0B |. E8 9692FFFF call MineSwee.0101FEA6 ; 右键单击call
01026C10 |. C686 AC000000>mov byte ptr ds:[esi+0xAC],0x1
01026C17 |. 5E pop esi
01026C18 |. 5D pop ebp
01026C19 \. C2 0400 retn 0x4
和左键单击一样,也是压入Y坐标和X坐标为参数,然后调用一个Call,但是调用之前给ecx进行了赋值,来源于esi,又要找esi,但是在上层中寻找,并没有发现esi来源于什么地址。不如跟进该Call看看,
0101FEAC |. 8BF1 mov esi,ecx
0101FEAE |. 8B46 40 mov eax,dword ptr ds:[esi+0x40]
0101FEB1 |. 8B48 0C mov ecx,dword ptr ds:[eax+0xC]
0101FEB4 |. 8B45 08 mov eax,dword ptr ss:[ebp+0x8] ; X坐标
0101FEB7 |. 8B0C81 mov ecx,dword ptr ds:[ecx+eax*4]
0101FEBA |. 8B51 0C mov edx,dword ptr ds:[ecx+0xC]
0101FEBD |. 8B4D 0C mov ecx,dword ptr ss:[ebp+0xC]
0101FEC0 |. 8B148A mov edx,dword ptr ds:[edx+ecx*4]
是不是很熟悉,原来ecx就是之前的那个指针,即[[minesweeper.exe+0x868b4]+0x10],在CE中写脚本进行测试:
alloc(myscript,1024)
define(adr,minesweeper.exe+868b4)
define(fun,minesweeper.exe+1FEA6)
myscript:
mov ecx,[[adr]+10]
push 0 ;Y
push 0 ;X
call fun
ret
执行后,发现扫雷并没有什么反应,有点失望!但是在扫雷中其他格子右键单击一下,发现第一个格子也变成被标记状态了,哈哈,这就是我们要找的右键单击格子Call,分析终于完成了!花了2周多时间,最后还是有点小激动,\(o)/
最后进行编程实现:
界面如上,首先点击注入会获得共有几颗雷,并安装键盘钩子,按Home键进行一键扫雷,按F12进行全部标记。
最后,附一张扫雷自定义中难度最大时进行一键扫雷的截图,如下,24*30,共668颗雷。
主程序代码:
#include
#include "resource.h"
#include "..\GetProcessBase.h"
bool g_bGameTop = false;
typedef HHOOK(_stdcall *HookGameProc)();
BOOL CALLBACK MainProc(
HWND hwndDlg,
UINT UMsg,
WPARAM wParam,
LPARAM lParam
)
{
switch (UMsg)
{
case WM_COMMAND:
if (LOWORD(wParam) == IDC_INJECT)
{
DWORD dwESI, dwTotal, dwPid;
HWND hWnd = FindWindow(NULL, "扫雷");
if (!hWnd)
{
MessageBox(NULL, "扫雷未运行!", "提示Main", 0);
return 0;
}
GetWindowThreadProcessId(hWnd, &dwPid); // 获取进程Id
HMODULE hMine = GetProcessBase("MineSweeper.exe", dwPid);
if (NULL == hMine)
{
MessageBox(NULL, "无法找到模块MineSweeper!", "提示Main", 0);
return 0;
}
DWORD dwMineBaseAddr = (DWORD)hMine; // 模块句柄就是基址
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid);
if (!hProcess)
{
MessageBox(NULL, "打开扫雷进程失败!", "提示Main", 0);
return 0;
}
DWORD dwRead;
ReadProcessMemory(hProcess, (LPCVOID)(dwMineBaseAddr + 0x868b4), &dwESI, 4, &dwRead);
ReadProcessMemory(hProcess, (LPCVOID)(dwESI + 0x10), &dwESI, 4, &dwRead);
ReadProcessMemory(hProcess, (LPCVOID)(dwESI + 0x4), &dwTotal, 4, &dwRead);
char cTmp[256] = { 0 };
wsprintf(cTmp, "游戏中共发现 %d 颗雷", dwTotal);
SetWindowText(hwndDlg, cTmp);
CloseHandle(hProcess);
HookGameProc HookProc = 0;
HMODULE hDll = LoadLibrary("Win7MineCrackHook.dll");
if (hDll)
{
HookProc = (HookGameProc)GetProcAddress(hDll, "HookMine");
if (!HookProc)
{
MessageBox(hwndDlg, "加载注入函数失败!", "Error", MB_OK);
return 0;
}
}
else
{
MessageBox(hwndDlg, "加载Dll失败!", "Error", MB_OK);
return 0;
}
HHOOK hkGame = HookProc();
if(hkGame)
SetDlgItemText(hwndDlg, IDC_EDIT, "安装外挂成功,按F12全部标记,按Home秒杀!");
else
SetDlgItemText(hwndDlg, IDC_EDIT, "安装外挂失败!");
}
else if (LOWORD(wParam) == IDC_SETTOP)
{
HWND hWnd = FindWindow(NULL, "扫雷");
if (!hWnd)
{
MessageBox(NULL, "扫雷未运行!", "提示Main", 0);
return 0;
}
if (!g_bGameTop)
{
SetWindowPos(hWnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOSIZE | SWP_SHOWWINDOW);
SetDlgItemText(hwndDlg, IDC_EDIT, "设置游戏置顶成功!");
SetDlgItemText(hwndDlg, IDC_SETTOP, "取消游戏窗口置顶");
g_bGameTop = true;
}
else
{
SetWindowPos(hWnd, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOSIZE | SWP_SHOWWINDOW);
SetDlgItemText(hwndDlg, IDC_EDIT, "取消游戏置顶成功!");
SetDlgItemText(hwndDlg, IDC_SETTOP, "设置游戏窗口置顶");
g_bGameTop = false;
}
}
break;
case WM_CLOSE:
EndDialog(hwndDlg, 0);
break;
}
return 0;
}
int WINAPI WinMain(
HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow
)
{
DialogBox(hInstance, (LPCSTR)IDD_MAIN_DLG, NULL, MainProc);
}
GetProcessBase.h代码:
#pragma once
#include
HMODULE GetProcessBase(char* szModule, DWORD dwPid) // 根据进程id获取进程基址
{
HANDLE hSnapShot;
hSnapShot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwPid);
if (hSnapShot == INVALID_HANDLE_VALUE)
{
MessageBox(NULL, "无法创建快照", "提示Main", 0);
return 0;
}
MODULEENTRY32 me32;
me32.dwSize = sizeof(me32);
if (Module32First(hSnapShot, &me32))
{
do
{
if (lstrcmpi(me32.szModule, szModule) == 0)
return me32.hModule;
} while (Module32Next(hSnapShot, &me32));
}
CloseHandle(hSnapShot);
return 0;
}
该函数是获得扫雷模块地址的函数,相比《Vista的扫雷》中寻找程序入口点,该函数效率更高一些。
DLL钩子程序如下:
#include
#include "..\GetProcessBase.h"
HHOOK g_hHookMine = 0;
extern "C" LRESULT CALLBACK WinMineCrackProc(
int code,
WPARAM wParam,
LPARAM lParam
)
{
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);
_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;
}
}
如上注入后可直接操作内存,下面部分代码还是利用API读写内存,不建议这么用!
if ((wParam == VK_F12) && ((lParam&(1 << 31)) == 0) && HC_ACTION == code) // - F12键全部标记
{
DWORD dwPid;
HWND hWnd = FindWindow(NULL, "扫雷");
if (!hWnd)
{
MessageBox(NULL, "扫雷未运行!", "提示Main", 0);
return 0;
}
GetWindowThreadProcessId(hWnd, &dwPid); // 获取进程Id
HMODULE hMine = GetProcessBase("MineSweeper.exe", dwPid);
if (NULL == hMine)
{
MessageBox(NULL, "无法找到模块MineSweeper!", "提示Main", 0);
return 0;
}
DWORD dwMineBaseAddr = (DWORD)hMine; // 模块句柄就是基址
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid);
if (!hProcess)
{
MessageBox(NULL, "打开扫雷进程失败!", "提示Main", 0);
return 0;
}
DWORD vaCallLeft1 = dwMineBaseAddr + 0x26bcd;
DWORD vaCallLeft0 = dwMineBaseAddr + 0x21418;
DWORD vaCallRight = dwMineBaseAddr + 0x1fea6;
__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 sF12
jmp endF12
sF12:
push eax
mov eax, vaCallLeft1
call eax
endF12:
pop ebx
pop ecx
pop eax
}
DWORD dwESI, dwRow, dwColumn, dwRead, dwAddr, dwMine;
ReadProcessMemory(hProcess, (LPCVOID)(dwMineBaseAddr + 0x868b4), &dwESI, 4, &dwRead);
ReadProcessMemory(hProcess, (LPCVOID)(dwESI + 0x10), &dwESI, 4, &dwRead);
ReadProcessMemory(hProcess, (LPCVOID)(dwESI + 0x8), &dwRow, 4, &dwRead); // 行数
ReadProcessMemory(hProcess, (LPCVOID)(dwESI + 0xc), &dwColumn, 4, &dwRead); // 列数
ReadProcessMemory(hProcess, (LPCVOID)(dwESI + 0x44), &dwAddr, 4, &dwRead);
ReadProcessMemory(hProcess, (LPCVOID)(dwAddr + 0xc), &dwAddr, 4, &dwRead);
unsigned int i = 0, j = 0; // i行 j列
BYTE isMine;
BYTE bMine[30][24] = { 0 };
while (j < dwColumn)
{
ReadProcessMemory(hProcess, (LPCVOID)(dwAddr + 4 * j), &dwMine, 4, &dwRead);
ReadProcessMemory(hProcess, (LPCVOID)(dwMine + 0xc), &dwMine, 4, &dwRead);
i = 0;
while (i < dwRow)
{
ReadProcessMemory(hProcess, (LPCVOID)(dwMine + i), &isMine, 1, &dwRead);
bMine[j][i] = isMine;
++i;
}
++j;
}
CloseHandle(hProcess);
i = 0;
j = 0;
while (j < dwColumn)
{
i = 0;
while (i < dwRow)
{
if (0x01 == bMine[j][i])
{
__asm
{
push eax
mov eax, dwMineBaseAddr
mov eax, [eax + 0x868b4]
mov ecx, [eax + 0x10]
mov eax, i
push eax
mov eax, j
push eax
mov eax, vaCallRight
call eax
pop eax
}
}
++i;
}
++j;
}
}
return CallNextHookEx(g_hHookMine, code, wParam, lParam);
//return 0;
}
extern "C" _declspec(dllexport) HHOOK HookMine()
{
HWND hWnd = FindWindow(NULL, "扫雷");
if (NULL == hWnd)
{
MessageBox(NULL, "游戏未运行!", "提示", 0);
return 0;
}
DWORD tid;
tid = GetWindowThreadProcessId(hWnd, NULL);
g_hHookMine = SetWindowsHookEx(WH_KEYBOARD, WinMineCrackProc, GetModuleHandle("Win7MineCrackHook.dll"), tid);
return g_hHookMine;
}