这是之前发布到某论坛的教程。当时是在xp的环境下改的。现在在win10重新修改下。
魔改的目的是给经典扫雷加一个无敌的选项,勾选后踩雷自动变成插旗。
教程分为上和下。上是实现UI的修改;下是实现踩雷自动变成插旗。
Tools
资源工具 Restorator
LordPE
OllyDbg
Environment
Windows 10专业版 64位 1803
给扫雷加一个无敌的选项
,并在勾选后有可视化的反馈(前面打上小勾勾)
,但是并没用实际的作用。
首先用Restorator打开扫雷,按F6打开编辑模式

修改部分已经标出。添加的选项后面是十进制的530,十六进制就是0x212。
这个530应该是一个id标识,程序内通过这个id来判断按下了哪个选项。
现在我们给它加上一个可视化反馈。
od载入,下断bp SetMenu(SetMenu是Windows初始化菜单栏的一个api),F9运行。
停下来后F2取消断点Alt+F9返回用户代码。停在了 01003D13
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| 01003CE5 /$ 8B4424 04 mov eax,dword ptr ss:[esp+0x4] 01003CE9 |. A3 C4560001 mov dword ptr ds:[0x10056C4],eax 01003CEE |. E8 23D8FFFF call 修改1.01001516 01003CF3 |. A1 C4560001 mov eax,dword ptr ds:[0x10056C4] 01003CF8 |. 24 01 and al,0x1 01003CFA |. F6D8 neg al 01003CFC |. 1BC0 sbb eax,eax 01003CFE |. F7D0 not eax 01003D00 |. 2305 945A0001 and eax,dword ptr ds:[0x1005A94] 01003D06 |. 50 push eax ; /hMenu = 00000001 01003D07 |. FF35 245B0001 push dword ptr ds:[0x1005B24] ; |hWnd = 000419A6 ('扫雷',class='扫雷') 01003D0D |. FF15 C4100001 call dword ptr ds:[<&USER32.SetMenu>] ; \SetMenu 01003D13 |. 6A 02 push 0x2 01003D15 |. E8 36DCFFFF call 修改1.01001950 01003D1A \. C2 0400 retn 0x4
|
我们进入第一个call 01001516看看。发现里面所有的call都指向了01003CC4,再看看push传入的参数。
转换成10进制是不是很熟悉。就是上图id标识。下面代码是已经打好注释的。
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
| 01001516 /$ 33C0 xor eax,eax 01001518 |. 66:3905 A0560>cmp word ptr ds:[0x10056A0],ax 0100151F |. 0f94c0 sete al 01001522 |. 50 push eax 01001523 |. 68 09020000 push 0x209 ; 初级 01001528 |. E8 97270000 call 修改1.01003CC4 0100152D |. 33C0 xor eax,eax 0100152F |. 66:833D A0560>cmp word ptr ds:[0x10056A0],0x1 01001537 |. 0f94c0 sete al 0100153A |. 50 push eax 0100153B |. 68 0A020000 push 0x20A ; 中级 01001540 |. E8 7F270000 call 修改1.01003CC4 01001545 |. 33C0 xor eax,eax 01001547 |. 66:833D A0560>cmp word ptr ds:[0x10056A0],0x2 0100154F |. 0f94c0 sete al 01001552 |. 50 push eax 01001553 |. 68 0B020000 push 0x20B ; 高级 01001558 |. E8 67270000 call 修改1.01003CC4 0100155D |. 33C0 xor eax,eax 0100155F |. 66:833D A0560>cmp word ptr ds:[0x10056A0],0x3 01001567 |. 0f94c0 sete al 0100156A |. 50 push eax 0100156B |. 68 0C020000 push 0x20C ; 自定义 01001570 |. E8 4F270000 call 修改1.01003CC4 01001575 |. FF35 C8560001 push dword ptr ds:[0x10056C8] 0100157B |. 68 11020000 push 0x211 ; 颜色 01001580 |. E8 3F270000 call 修改1.01003CC4 01001585 |. FF35 BC560001 push dword ptr ds:[0x10056BC] 0100158B |. 68 0F020000 push 0x20F ; 标记 01001590 |. E8 2F270000 call 修改1.01003CC4 01001595 |. FF35 B8560001 push dword ptr ds:[0x10056B8] 0100159B |. 68 0E020000 push 0x20E ; 声音 010015A0 |. E8 1F270000 call 修改1.01003CC4 010015A5 \. C3 retn
|
调用01003CC4这个call会push两个参数,一个id和一个地址。
push的地址我们可以大胆猜测这是用来标识这个选项的状态(勾选 or 未勾选)。
经过多次实验我们发现这个地址的值为0就是未勾选,为1就是勾选。
接下来就是寻找勾选这个动作在哪,也就是寻找是哪个地方更改了这个地址的值。
右键—》查找—》所有常量 随便选一个地址,0x10056BC。

全部下断

F9运行,菜单—》游戏—》标记。 ok停到了01001E13,再取消所有断点。
这是一个很标准的优化后的switch语句,至于是怎么优化的,如果感兴趣可以阅读《c++反汇编与逆向分析技术揭秘》。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| 01001DC0 |. B9 10020000 mov ecx,0x210 01001DC5 |. 3BC1 cmp eax,ecx 01001DC7 |. 0F8F 0F010000 jg 修改1.01001EDC 01001DCD |. 0F84 FF000000 je 修改1.01001ED2 01001DD3 |. 3D FE010000 cmp eax,0x1FE 01001DD8 |. 0F84 EA000000 je 修改1.01001EC8 01001DDE |. 3BC6 cmp eax,esi 01001DE0 |. 0F84 B7000000 je 修改1.01001E9D 01001DE6 |. 3D 08020000 cmp eax,0x208 ; Switch (cases 209..20F) 01001DEB |. 0F8E B8030000 jle 修改1.010021A9 01001DF1 |. 3D 0B020000 cmp eax,0x20B 01001DF6 |. 7E 61 jle short 修改1.01001E59 01001DF8 |. 3D 0C020000 cmp eax,0x20C 01001DFD |. 74 50 je short 修改1.01001E4F 01001DFF |. 3D 0E020000 cmp eax,0x20E 01001E04 |. 74 20 je short 修改1.01001E26 01001E06 |. 3D 0F020000 cmp eax,0x20F 01001E0B |. 0F85 98030000 jnz 修改1.010021A9 01001E11 |. 33C0 xor eax,eax ; Case 20F of switch 01001DE6 01001E13 |. 3905 BC560001 cmp dword ptr ds:[0x10056BC],eax 01001E19 |. 0f94c0 sete al 01001E1C |. A3 BC560001 mov dword ptr ds:[0x10056BC],eax 01001E21 |. E9 24010000 jmp 修改1.01001F4A
|
在01001DC0下断。F9运行,再随便点一下菜单栏内的选项。
断下后,看寄存器eax,就是菜单选项的id。
于是现在所有需要修改的地方我们都找到了,但是程序内并没有多余的空间给我们写代码。
所以添加区段。

我添加了两个区段,icode与idata。添加区段后一定要重建一下pe,这样程序才能正常运行。
od载入Ctrl+G来到01001DC0。我们分析一下这个switch语句。
1 2 3
| 01001DC0 |. B9 10020000 mov ecx,0x210 01001DC5 |. 3BC1 cmp eax,ecx 01001DC7 |. 0F8F 0F010000 jg 修改1_-_.01001EDC ; eax > 0x210 跳
|
而我们添加的选项是0x212,这个是一定要跳的,所以我们跟下去看看。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| 01001EDC |> \2D 11020000 sub eax,0x211 ; Switch (cases 211..251) 01001EE1 |. 74 36 je short 修改1_-_.01001F19 01001EE3 |. 83E8 3D sub eax,0x3D 01001EE6 |. 74 23 je short 修改1_-_.01001F0B 01001EE8 |. 48 dec eax 01001EE9 |. 74 1A je short 修改1_-_.01001F05 01001EEB |. 48 dec eax 01001EEC |. 74 11 je short 修改1_-_.01001EFF 01001EEE |. 48 dec eax 01001EEF |. 0F85 B4020000 jnz 修改1_-_.010021A9 01001EF5 |. E8 231E0000 call 修改1_-_.01003D1D ; Case 251 of switch 01001EDC 01001EFA |.^ E9 5FFDFFFF jmp 修改1_-_.01001C5E 01001EFF |> 6A 00 push 0x0 ; Case 250 of switch 01001EDC 01001F01 |. 6A 04 push 0x4 01001F03 |. EB 0A jmp short 修改1_-_.01001F0F 01001F05 |> 6A 02 push 0x2 ; Case 24F of switch 01001EDC 01001F07 |. 6A 01 push 0x1 01001F09 |. EB 04 jmp short 修改1_-_.01001F0F 01001F0B |> 6A 00 push 0x0 ; Case 24E of switch 01001EDC 01001F0D |. 6A 03 push 0x3 01001F0F |> E8 621E0000 call 修改1_-_.01003D76
|
01001EE1 是判断是颜色选项就跳,而01001EE3是下一个菜单了,所以我们要把代码插这中间。但是这中间没有空间写代码怎么办,这时候我们添加的区段就派上用场了。
把01001DC7改为
1
| 01001DC7 - 0F8F 33D20100 jg 修改1_-_.0101F000 ; 改为我们添加区段的首地址
|
0101F000这么写
1 2 3 4 5 6 7 8 9 10
| 0101F000 2D 11020000 sub eax,0x211 0101F005 - 0F84 0E2FFEFF je 修改1_-_.01001F19 ; 先把之前的两句抄过来 0101F00B 83F8 01 cmp eax,0x1 ; 因为这里我们的是0x212,减去0x211后就是0x1了,和0x1比较 0101F00E 74 05 je short 修改1_-_.0101F015 0101F010 - E9 CE2EFEFF jmp 修改1_-_.01001EE3 ; 不是0x212就跳回到之前的代码执行 0101F015 33C0 xor eax,eax 0101F017 3905 00000201 cmp dword ptr ds:[0x1020000],eax 0101F01D 0f94c0 sete al 0101F020 A3 00000201 mov dword ptr ds:[0x1020000],eax ; 这4句是给0x1020000值取反的,就是1变成0,0变成1 0101F025 - E9 202FFEFF jmp 修改1_-_.01001F4A
|
0x1020000是我们添加的idata段的首地址,这个地址就是记录我们添加的选项是勾选还是没勾选。
Ctrl+G来到01001516。
把0100155D改为
1
| 0100155D - E9 CEDA0100 jmp 修改1_-_.0101F030
|
0101F030这么写
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| 0101F030 33C0 xor eax,eax 0101F032 66:833D A056000>cmp word ptr ds:[0x10056A0],0x3 0101F03A 0f94c0 sete al 0101F03D 50 push eax 0101F03E 68 0C020000 push 0x20C 0101F043 E8 7C4CFEFF call 修改1_-_.01003CC4 0101F048 FF35 C8560001 push dword ptr ds:[0x10056C8] 0101F04E 68 11020000 push 0x211 0101F053 E8 6C4CFEFF call 修改1_-_.01003CC4 0101F058 FF35 BC560001 push dword ptr ds:[0x10056BC] 0101F05E 68 0F020000 push 0x20F 0101F063 E8 5C4CFEFF call 修改1_-_.01003CC4 0101F068 FF35 B8560001 push dword ptr ds:[0x10056B8] 0101F06E 68 0E020000 push 0x20E 0101F073 E8 4C4CFEFF call 修改1_-_.01003CC4 ; 这里之前都是把原来的抄过来 0101F078 FF35 00000201 push dword ptr ds:[0x1020000] ; 传入0x1020000地址 0101F07E 68 12020000 push 0x212 ; 传入选项id 0101F083 E8 3C4CFEFF call 修改1_-_.01003CC4 0101F088 - E9 1825FEFF jmp 修改1_-_.010015A5 ; 跳回刚刚call的返回处
|
到此已经改完了。运行一下没出错。勾选无敌后,前面也出现小勾勾了。
最后总结一下一共改了三个地方。
1.1
| 01001DC7 - 0F8F 33D20100 jg 修改1_-_.0101F000 ; 改为我们添加区段的首地址
|
2.1
| 0100155D - E9 CEDA0100 jmp 修改1_-_.0101F030
|
3.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
| 0101F000 2D 11020000 sub eax,0x211 0101F005 - 0F84 0E2FFEFF je 修改1_-_.01001F19 ; 先把之前的两句抄过来 0101F00B 83F8 01 cmp eax,0x1 ; 因为这里我们的是0x212,减去0x211后就是0x1了,和0x1比较 0101F00E 74 05 je short 修改1_-_.0101F015 0101F010 - E9 CE2EFEFF jmp 修改1_-_.01001EE3 ; 不是0x212就跳回到之前的代码执行 0101F015 33C0 xor eax,eax 0101F017 3905 00000201 cmp dword ptr ds:[0x1020000],eax 0101F01D 0f94c0 sete al 0101F020 A3 00000201 mov dword ptr ds:[0x1020000],eax ; 这4句是给0x1020000值取反的,就是1变成0,0变成1 0101F025 - E9 202FFEFF jmp 修改1_-_.01001F4A 0101F02A 0000 add byte ptr ds:[eax],al 0101F02C 0000 add byte ptr ds:[eax],al 0101F02E 0000 add byte ptr ds:[eax],al 0101F030 33C0 xor eax,eax 0101F032 66:833D A056000>cmp word ptr ds:[0x10056A0],0x3 0101F03A 0f94c0 sete al 0101F03D 50 push eax 0101F03E 68 0C020000 push 0x20C 0101F043 E8 7C4CFEFF call 修改1_-_.01003CC4 0101F048 FF35 C8560001 push dword ptr ds:[0x10056C8] 0101F04E 68 11020000 push 0x211 0101F053 E8 6C4CFEFF call 修改1_-_.01003CC4 0101F058 FF35 BC560001 push dword ptr ds:[0x10056BC] 0101F05E 68 0F020000 push 0x20F 0101F063 E8 5C4CFEFF call 修改1_-_.01003CC4 0101F068 FF35 B8560001 push dword ptr ds:[0x10056B8] 0101F06E 68 0E020000 push 0x20E 0101F073 E8 4C4CFEFF call 修改1_-_.01003CC4 ; 这里之前都是把原来的抄过来 0101F078 FF35 00000201 push dword ptr ds:[0x1020000] ; 传入0x1020000地址 0101F07E 68 12020000 push 0x212 ; 传入选项id 0101F083 E8 3C4CFEFF call 修改1_-_.01003CC4 0101F088 - E9 1825FEFF jmp 修改1_-_.010015A5 ; 跳回刚刚call的返回处
|
附件里包含原版和修改后的版本。
win xp经典扫雷的魔改(上).7z