win xp经典扫雷的魔改(下)

上部分,给扫雷添加了一个无敌的选项,并在勾选后有可视化的反馈。
下部分,实现踩雷变插旗。

Tools

CE
OllyDbg

Environment

Windows 10专业版 64位 1803

先展示一下效果

我们需要把判断踩雷的函数与插旗函数找到。

1.寻找插旗函数

点了右键插旗之后左上角显示还剩多少个雷就会-1。
就据此来寻找插旗函数。
打开“上”修改后的版本。CE载入。
搜索左上角显示的雷数目。插旗之后再搜索雷的数目。

我们就会得到这样一个地址。01005194。明显这个地址就是保存的剩余雷的数目。
接下来我们od载入,在01005194上设置内存写入断点。

右键插旗,程序断在了0100346E。
通过右下角堆栈窗口回溯。。成功的找到了插旗的函数。

1
2
3
4
5
6
7
8
010037AA  |> \6A 0E         push 0xE
010037AC |. 5F pop edi ; 修改.010037B4
010037AD |. 6A FF push -0x1
010037AF |> E8 B6FCFFFF call 修改.0100346A ; 剩余雷数-1
010037B4 |> 57 push edi ; 传入参数 0xE
010037B5 |. FF7424 18 push dword ptr ss:[esp+0x18] ; 传入参数 插旗的行
010037B9 |. 53 push ebx ; 传入参数插旗的列
010037BA |. E8 ECF6FFFF call 修改.01002EAB ; 插旗

2.寻找踩雷函数

开始游戏后,右上角的计时框会增加同样可以通过CE找到这个值的地址,然后再通过这个地址找到踩雷函数。
这里介绍一个新的方法————消息断点。
点击上方w

在主窗体设置消息断点 202 WM_LBUTTONUP (左键弹起)

这时候要注意一个细节,,先右键单击激活扫雷的窗体,再左键单击游戏区域。
如果不右键单击激活窗体,直接左键单击那么程序就会断在激活窗体这个消息。

此时我们断在了01001BC9。
Ctrl+F9执行到返回。跟下去发现我们来到了系统域。
Alt+F9执行到用户代码。来到了01001FF9处。
此时我们按F8向下执行。注意游戏界面是在哪一个call更新的。

1
01002005   .  E8 D7170000   call 修改.010037E1

执行完这个call之后,游戏界面更新。说明踩雷的函数是在这个call内。取消所有断点。在01002005 F2下断,然后我们跟进去。
同样注意执行哪个call之后游戏的界面更新了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
010037E1  /$  A1 18510001   mov eax,dword ptr ds:[0x1005118]
010037E6 |. 85C0 test eax,eax
010037E8 |. 0F8E C8000000 jle 修改.010038B6
010037EE |. 8B0D 1C510001 mov ecx,dword ptr ds:[0x100511C]
010037F4 |. 85C9 test ecx,ecx
010037F6 |. 0F8E BA000000 jle 修改.010038B6
010037FC |. 3B05 34530001 cmp eax,dword ptr ds:[0x1005334]
01003802 |. 0F8F AE000000 jg 修改.010038B6
01003808 |. 3B0D 38530001 cmp ecx,dword ptr ds:[0x1005338]
0100380E |. 0F8F A2000000 jg 修改.010038B6
01003814 |. 53 push ebx
01003815 |. 33DB xor ebx,ebx
01003817 |. 43 inc ebx
01003818 |. 833D A4570001>cmp dword ptr ds:[0x10057A4],0x0
0100381F |. 75 4A jnz short 修改.0100386B
01003821 |. 833D 9C570001>cmp dword ptr ds:[0x100579C],0x0
01003828 |. 75 41 jnz short 修改.0100386B
0100382A |. 53 push ebx
0100382B |. E8 BD000000 call 修改.010038ED
01003830 |. FF05 9C570001 inc dword ptr ds:[0x100579C]
01003836 |. E8 7AF0FFFF call 修改.010028B5
0100383B |. 6A 00 push 0x0 ; /Timerproc = NULL
0100383D |. 68 E8030000 push 0x3E8 ; |Timeout = 1000. ms
01003842 |. 53 push ebx ; |TimerID = 0x1
01003843 |. FF35 245B0001 push dword ptr ds:[0x1005B24] ; |hWnd = 005414C2 ('扫雷',class='扫雷')
01003849 |. 891D 64510001 mov dword ptr ds:[0x1005164],ebx ; |
0100384F |. FF15 B4100001 call dword ptr ds:[<&USER32.SetTimer>] ; \SetTimer
01003855 |. 85C0 test eax,eax
01003857 |. 75 07 jnz short 修改.01003860
01003859 |. 6A 04 push 0x4
0100385B |. E8 F0000000 call 修改.01003950
01003860 |> A1 18510001 mov eax,dword ptr ds:[0x1005118]
01003865 |. 8B0D 1C510001 mov ecx,dword ptr ds:[0x100511C]
0100386B |> 841D 00500001 test byte ptr ds:[0x1005000],bl
01003871 |. 5B pop ebx ; 修改.0100200A
01003872 |. 75 10 jnz short 修改.01003884
01003874 |. 6A FE push -0x2
01003876 |. 59 pop ecx ; 修改.0100200A
01003877 |. 8BC1 mov eax,ecx
01003879 |. 890D 1C510001 mov dword ptr ds:[0x100511C],ecx
0100387F |. A3 18510001 mov dword ptr ds:[0x1005118],eax
01003884 |> 833D 44510001>cmp dword ptr ds:[0x1005144],0x0
0100388B |. 74 09 je short 修改.01003896
0100388D |. 51 push ecx
0100388E |. 50 push eax
0100388F |. E8 23FDFFFF call 修改.010035B7
01003894 |. EB 20 jmp short 修改.010038B6
01003896 |> 8BD1 mov edx,ecx
01003898 |. C1E2 05 shl edx,0x5
0100389B |. 8A9402 405300>mov dl,byte ptr ds:[edx+eax+0x1005340]
010038A2 |. F6C2 40 test dl,0x40
010038A5 |. 75 0F jnz short 修改.010038B6
010038A7 |. 80E2 1F and dl,0x1F
010038AA |. 80FA 0E cmp dl,0xE
010038AD |. 74 07 je short 修改.010038B6
010038AF |. 51 push ecx
010038B0 |. 50 push eax
010038B1 |. E8 5CFCFFFF call 修改.01003512
010038B6 |> FF35 60510001 push dword ptr ds:[0x1005160]
010038BC |. E8 52F0FFFF call 修改.01002913
010038C1 \. C3 retn

0100384F 执行了api,SetTimer这应该就是游戏右上角的计时器了。
而01003830这行,,0x0100579C就是那个储存时间的值,通过CE寻找就是找到的这个地址。
继续向下跑,直到010038B1这个call执行后,游戏界面更新。。重复刚刚的操作,继续向这个call跟进。
进入这个call发现,这里就是我们要寻找的判断是否踩雷的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
01003512  /$  8B4424 08     mov eax,dword ptr ss:[esp+0x8]
01003516 |. 53 push ebx
01003517 |. 55 push ebp
01003518 |. 56 push esi
01003519 |. 8B7424 10 mov esi,dword ptr ss:[esp+0x10] ; 取点击的列数
0100351D |. 8BC8 mov ecx,eax ; 取点击的行数
0100351F |. C1E1 05 shl ecx,0x5
01003522 |. 8D9431 405300>lea edx,dword ptr ds:[ecx+esi+0x1005340] ; 取点击处的地址
01003529 |. F602 80 test byte ptr ds:[edx],0x80 ; 把点击处的值与0x80比较
0100352C |. 57 push edi
0100352D |. 74 66 je short 修改.01003595 ; 如果不相等就跳转,
0100352F |. 833D A4570001>cmp dword ptr ds:[0x10057A4],0x0
01003536 |. 75 50 jnz short 修改.01003588 ; 判断已经点开的格子数目是否等于0,不等于0就跳
01003538 |. 8B2D 38530001 mov ebp,dword ptr ds:[0x1005338]
0100353E |. 33C0 xor eax,eax
01003540 |. 40 inc eax
01003541 |. 3BE8 cmp ebp,eax
01003543 |. 7E 6B jle short 修改.010035B0
01003545 |. 8B1D 34530001 mov ebx,dword ptr ds:[0x1005334]
0100354B |. BF 60530001 mov edi,修改.01005360
01003550 |> 33C9 /xor ecx,ecx
01003552 |. 41 |inc ecx
01003553 |. 3BD9 |cmp ebx,ecx
01003555 |. 7E 0B |jle short 修改.01003562
01003557 |> F6040F 80 |/test byte ptr ds:[edi+ecx],0x80
0100355B |. 74 0F ||je short 修改.0100356C
0100355D |. 41 ||inc ecx
0100355E |. 3BCB ||cmp ecx,ebx
01003560 |.^ 7C F5 |\jl short 修改.01003557
01003562 |> 40 |inc eax
01003563 |. 83C7 20 |add edi,0x20
01003566 |. 3BC5 |cmp eax,ebp
01003568 |.^ 7C E6 \jl short 修改.01003550
0100356A |. EB 44 jmp short 修改.010035B0
0100356C |> FF7424 18 push dword ptr ss:[esp+0x18] ; 修改.01001BC9
01003570 |. C1E0 05 shl eax,0x5
01003573 |. 8D8408 405300>lea eax,dword ptr ds:[eax+ecx+0x1005340]
0100357A |. C602 0F mov byte ptr ds:[edx],0xF
0100357D |. 8008 80 or byte ptr ds:[eax],0x80
01003580 |. 56 push esi
01003581 |. E8 FEFAFFFF call 修改.01003084
01003586 |. EB 28 jmp short 修改.010035B0
01003588 |> 6A 4C push 0x4C
0100358A |. 50 push eax
0100358B |. 56 push esi
0100358C |. E8 1AF9FFFF call 修改.01002EAB ; 炸雷
01003591 |. 6A 00 push 0x0
01003593 |. EB 16 jmp short 修改.010035AB
01003595 |> 50 push eax
01003596 |. 56 push esi
01003597 |. E8 E8FAFFFF call 修改.01003084
0100359C |. A1 A4570001 mov eax,dword ptr ds:[0x10057A4]
010035A1 |. 3B05 A0570001 cmp eax,dword ptr ds:[0x10057A0]
010035A7 |. 75 07 jnz short 修改.010035B0
010035A9 |. 6A 01 push 0x1
010035AB |> E8 CCFEFFFF call 修改.0100347C
010035B0 |> 5F pop edi ; 修改.010038B6
010035B1 |. 5E pop esi ; 修改.010038B6
010035B2 |. 5D pop ebp ; 修改.010038B6
010035B3 |. 5B pop ebx ; 修改.010038B6
010035B4 \. C2 0800 retn 0x8

下面来解释下为什么是和0x80比较来判断是否炸雷。
每个方格的内容是1个字节储存的。
字节的高4位是储存这个是否是雷 0不是雷,8是雷
字节的低4位是储存这个方格的状态的 F是还未点开,E是插旗,D是问号,0就已经点开。
所以,所以0x80就是已经点开雷,所以就执行炸雷;同样,在之前插旗函数的地方,传入的参数0xE就是插旗。

0x10057A4是记录已经点开的格子的个数。
所以代码01003538 - 01003581的执行条件是点到雷并且点开的格子数为0,条件等价于开始游戏第一步点到雷。
经分析01003538 - 01003581是执行重排,把点到的雷随机移动到一个不是雷的地方。
所以玩正常的扫雷第一步是永远不可能炸雷的,因为如果你点到雷就会把这个雷随机移动到另一个不是雷的地方。

说了一大堆废话,接下来我们来改代码。

0100352D是判断是否炸雷的关键跳,如果不跳,那么就要炸雷。
所以0100352F跳向我们的添加的区段后面执行我们添加的代码。

把0100352F改为

1
0100352F    - E9 60BB0100   jmp 修改.0101F094

0101F094这样写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
0101F094    833D A4570001 0>cmp dword ptr ds:[0x10057A4],0x0            ; 判断点开的格子数是否等于0
0101F09B - 0F84 9744FEFF je 修改.01003538 ; 等于0就跳转回去执行重排函数
0101F0A1 833D 00000201 0>cmp dword ptr ds:[0x1020000],0x0 ; 判断无敌选项是否勾选
0101F0A8 - 0F84 DA44FEFF je 修改.01003588 ; 未勾选就跳至正常的炸雷函数
0101F0AE 60 pushad ; 保护现场
0101F0AF 9C pushfd ; 保护现场
0101F0B0 51 push ecx
0101F0B1 56 push esi
0101F0B2 6A 0E push 0xE
0101F0B4 5F pop edi
0101F0B5 6A FF push -0x1
0101F0B7 E8 AE43FEFF call 修改.0100346A ; 剩余雷数-1
0101F0BC 5E pop esi
0101F0BD 59 pop ecx
0101F0BE C1E9 05 shr ecx,0x5
0101F0C1 6A 0E push 0xE
0101F0C3 51 push ecx
0101F0C4 56 push esi
0101F0C5 E8 E13DFEFF call 修改.01002EAB ; 插旗
0101F0CA 9D popfd ; 恢复现场
0101F0CB 61 popad ; 恢复现场
0101F0CC - E9 C444FEFF jmp 修改.01003595 ; 跳转回正常代码

注释都写到代码里面了。。

——

好了,现在改完了。。
最后总结一下一共改了两个地方。

1.

1
0100352F    - E9 60BB0100   jmp 修改.0101F094

2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
0101F094    833D A4570001 0>cmp dword ptr ds:[0x10057A4],0x0            ; 判断点开的格子数是否等于0
0101F09B - 0F84 9744FEFF je 修改.01003538 ; 等于0就跳转回去执行重排
0101F0A1 833D 00000201 0>cmp dword ptr ds:[0x1020000],0x0 ; 判断无敌选项是否勾选
0101F0A8 - 0F84 DA44FEFF je 修改.01003588 ; 未勾选就跳至正常的炸雷函数
0101F0AE 60 pushad ; 保护现场
0101F0AF 9C pushfd ; 保护现场
0101F0B0 51 push ecx
0101F0B1 56 push esi
0101F0B2 6A 0E push 0xE
0101F0B4 5F pop edi
0101F0B5 6A FF push -0x1
0101F0B7 E8 AE43FEFF call 修改.0100346A ; 剩余雷数-1
0101F0BC 5E pop esi
0101F0BD 59 pop ecx
0101F0BE C1E9 05 shr ecx,0x5
0101F0C1 6A 0E push 0xE
0101F0C3 51 push ecx
0101F0C4 56 push esi
0101F0C5 E8 E13DFEFF call 修改.01002EAB ; 插旗
0101F0CA 9D popfd ; 恢复现场
0101F0CB 61 popad ; 恢复现场
0101F0CC - E9 C444FEFF jmp 修改.01003595 ; 跳转回正常代码

因为博客空间有限,
附件里只有最后的修改版本
win xp经典扫雷的魔改(下).7z

文章目录
  1. 1. ——