【目标程序】:Win2000的 Winmine.exe
【作者声明】:只是感兴趣,没有其他目的。失误之处敬请诸位大侠赐教!
【调试环境】:Win2000、Ollydbg1.10、ExeScope
【逆向原因】:我有一舍友,十分精于扫雷游戏,闲暇之时与其比试,总是败多胜少,遂逆
向一热键作弊(本文改成了菜单)功能,后常胜 ^_^ 。今日无事,逆向菜
文一篇,希望能对刚入门的菜鸟朋友们有些许帮助。
【逆向过程】:
――――――――――――――――――――――――――――――――――――――――
一、分析
1、弄清程序的流程,将自己的处理代码,挂在消息响应流程中。
// 用DefWindowProcW下断,然后向上翻看,即可看到消息响应流程
// 点击菜单后,会来到如下代码处
010019AD > \8B4D 10 mov ecx, dword ptr ss:[ebp+10] ; 点击的菜单ID
010019B0 . 0FB7C1 movzx eax, cx
010019B3 . 3D 0B020000 cmp eax, 20B
010019B8 . 7F 76 jg short winmine.01001A30 ; 大于 0x20B 跳到后边
010019BA . 3D 09020000 cmp eax, 209
010019BF . 7D 2D jge short winmine.010019EE
010019C1 . 2D FE010000 sub eax, 1FE
010019C6 . 74 1C je short winmine.010019E4
010019C8 . 48 dec eax
010019C9 . 48 dec eax
010019CA . 0F85 7C030000 jnz winmine.01001D4C
010019D0 . 33FF xor edi, edi ; Case 200 of switch 010019B3
010019D2 . 57 push edi ; /ShowState => SW_HIDE
010019D3 . FF35 A8520001 push dword ptr ds:[10052A8] ; |hWnd = NULL
010019D9 . FF15 EC100001 call dword ptr ds:[<&USER32.ShowWi>; \ShowWindow
010019DF . E9 A2000000 jmp winmine.01001A86
010019E4 > E8 26190000 call winmine.0100330F ; Case 1FE of switch 010019B3
010019E9 . E9 5E030000 jmp winmine.01001D4C
010019EE > 8D81 F7FDFFFF lea eax, dword ptr ds:[ecx-209] ; Cases 209,20A,20B of switch 010019B3
010019F4 . 66:A3 C0520001 mov word ptr ds:[10052C0], ax
010019FA . 0FB7C0 movzx eax, ax
010019FD . 8D0440 lea eax, dword ptr ds:[eax+eax*2]
01001A00 . C1E0 02 shl eax, 2
01001A03 . 8B88 28500001 mov ecx, dword ptr ds:[eax+100502>
01001A09 . 890D C4520001 mov dword ptr ds:[10052C4], ecx
01001A0F . 8B88 2C500001 mov ecx, dword ptr ds:[eax+100502>
01001A15 . 8B80 30500001 mov eax, dword ptr ds:[eax+100503>
01001A1B . 890D C8520001 mov dword ptr ds:[10052C8], ecx
01001A21 . A3 CC520001 mov dword ptr ds:[10052CC], eax
01001A26 . E8 E4180000 call winmine.0100330F
01001A30 > \B9 4E020000 mov ecx, 24E ;大于 0x20B 跳到这里
01001A35 > 3BC1 cmp eax, ecx
01001A37 . 0F8F D3000000 jg winmine.01001B10
01001A3D . 0F84 C7000000 je winmine.01001B0A
01001A43 . 2D 0C020000 sub eax, 20C
根据上边的代码,可知,只要从01001A30处(其它地方也可以)跳到我们添加的代码中,就可以对消息进行响应了。
2、如何实现作弊?
// 开局,点击一下,到如下代码处,(第一次用PlaySoundW下断,很容易就能来到这里)
01003772 |. 833D F4560001 00 cmp dword ptr ds:[10056F4], 0 ; //开始扫雷了么?
01003779 |. 6A 01 push 1
0100377B |. 5B pop ebx
0100377C |. 75 4A jnz short winmine.010037C8 ; 第一点击?
0100377E |. 833D F0560001 00 cmp dword ptr ds:[10056F0], 0
01003785 |. 75 41 jnz short winmine.010037C8
01003787 |. 53 push ebx
01003788 |. E8 ED050000 call winmine.01003D7A
0100378D |. FF05 F0560001 inc dword ptr ds:[10056F0]
01003793 |. E8 1FF4FFFF call winmine.01002BB7
01003798 |. 6A 00 push 0 ; /Timerproc = NULL
0100379A |. 68 E8030000 push 3E8 ; |Timeout = 1000. ms
0100379F |. 53 push ebx ; |TimerID
010037A0 |. 891D 84510001 mov dword ptr ds:[1005184], ebx ; |
010037A6 |. FF35 A8520001 push dword ptr ds:[10052A8] ; |hWnd = NULL
010037AC |. FF15 6C110001 call dword ptr ds:[<&USER32.SetTim>; \ 设置计时间隔1秒,修改这里进行时间作弊
// 通过跟踪发现,对相应行、列对应的内存中的一个字节进行变换,变换后的值高位为1(0x80),
// 表示该位置是雷,GAME OVER, 如果不是雷,则显示该位置的数字
01003144 /$ 8B4424 08 mov eax, dword ptr ss:[esp+8] ; 行
01003148 |. 53 push ebx
01003149 |. 55 push ebp
0100314A |. 8BC8 mov ecx, eax
0100314C |. 56 push esi
0100314D |. 8B7424 10 mov esi, dword ptr ss:[esp+10] ; 列
01003151 |. C1E1 05 shl ecx, 5
01003154 |. F68431 00570001 80 test byte ptr ds:[ecx+esi+1005700], 80 ; 是雷么
0100315C |. 8D9431 00570001 lea edx, dword ptr ds:[ecx+esi+1005700]
01003163 |. 57 push edi
01003164 |. 74 6B je short winmine.010031D1 ; 不是雷,跳
01003166 |. 833D F4560001 00 cmp dword ptr ds:[10056F4], 0
0100316D |. 75 55 jnz short winmine.010031C4
0100316F |. 8B2D 685A0001 mov ebp, dword ptr ds:[1005A68]
01003175 |. 6A 01 push 1
01003177 |. 58 pop eax
01003178 |. 3BE8 cmp ebp, eax
0100317A |. 7E 70 jle short winmine.010031EC
0100317C |. 8B1D F8560001 mov ebx, dword ptr ds:[10056F8]
01003182 |. BF 20570001 mov edi, winmine.01005720
01003187 |> 6A 01 /push 1
01003189 |. 59 |pop ecx
0100318A |. 3BD9 |cmp ebx, ecx
0100318C |. 7E 0B |jle short winmine.01003199
0100318E |> F60439 80 |/test byte ptr ds:[ecx+edi], 80
01003192 |. 74 0F ||je short winmine.010031A3
01003194 |. 41 ||inc ecx
01003195 |. 3BCB ||cmp ecx, ebx
01003197 |.^ 7C F5 |\jl short winmine.0100318E
01003199 |> 40 |inc eax
0100319A |. 83C7 20 |add edi, 20
0100319D |. 3BC5 |cmp eax, ebp
0100319F |.^ 7C E6 \jl short winmine.01003187
010031A1 |. EB 49 jmp short winmine.010031EC
010031A3 |> FF7424 18 push dword ptr ss:[esp+18] ; /Arg2
010031A7 |. C602 0F mov byte ptr ds:[edx], 0F ; |
010031AA |. C1E0 05 shl eax, 5 ; |
010031AD |. 56 push esi ; |Arg1
010031AE |. 808C08 00570001 80 or byte ptr ds:[eax+ecx+1005700], 80 ; |
010031B6 |. 8D8408 00570001 lea eax, dword ptr ds:[eax+ecx+1005700] ; |
010031BD |. E8 EDFEFFFF call winmine.010030AF ; \winmine.010030AF
010031C2 |. EB 28 jmp short winmine.010031EC
010031C4 |> 6A 4C push 4C
010031C6 |. 50 push eax
010031C7 |. 56 push esi
010031C8 |. E8 53FCFFFF call winmine.01002E20
010031CD |. 6A 00 push 0
010031CF |. EB 16 jmp short winmine.010031E7
// 不是雷跳到这里
010031D1 |> 50 push eax ; 行
010031D2 |. 56 push esi ; 列
010031D3 |. E8 D7FEFFFF call winmine.010030AF ; 显示该位置的数字
010031D8 |. A1 F4560001 mov eax, dword ptr ds:[10056F4] ;
010031DD |. 3B05 FC560001 cmp eax, dword ptr ds:[10056FC]
010031E3 |. 75 07 jnz short winmine.010031EC ; 雷扫完了么
010031E5 |. 6A 01 push 1
010031E7 |> E8 86FDFFFF call winmine.01002F72 ; 游戏成功结束
010031EC |> 5F pop edi ; 没扫完,继续
010031ED |. 5E pop esi
010031EE |. 5D pop ebp
010031EF |. 5B pop ebx
010031F0 \. C2 0800 retn 8
//根据行、列,调用下面过程计算,内存中的值如果高位是1(0x80)就是雷
010033E9 /$ 8B4424 08 mov eax, dword ptr ss:[esp+8] //行
010033ED |. 8B4C24 04 mov ecx, dword ptr ss:[esp+4] //列
010033F1 |. C1E0 05 shl eax, 5
010033F4 |. 8D9408 00570001 lea edx, dword ptr ds:[eax+ecx+1005700] //位置
010033FB |. 8A8408 00570001 mov al, byte ptr ds:[eax+ecx+1005700]
01003402 |. 8AC8 mov cl, al
01003404 |. 83E1 1F and ecx, 1F
01003407 |. 83F9 0D cmp ecx, 0D
0100340A |. 75 05 jnz short winmine.01003411
0100340C |. 6A 09 push 9
0100340E |. 59 pop ecx
0100340F |. EB 07 jmp short winmine.01003418
01003411 |> 83F9 0F cmp ecx, 0F
01003414 |. 75 02 jnz short winmine.01003418
01003416 |. 33C9 xor ecx, ecx
01003418 |> 24 E0 and al, 0E0
0100341A |. 0AC1 or al, cl
0100341C |. 8802 mov byte ptr ds:[edx], al
0100341E \. C2 0800 retn 8
//根据行、列,将上面过程变换后内存中的值恢复
01003421 /$ 8B4424 08 mov eax, dword ptr ss:[esp+8]
01003425 |. 8B4C24 04 mov ecx, dword ptr ss:[esp+4]
01003429 |. C1E0 05 shl eax, 5
0100342C |. 8D9408 00570001 lea edx, dword ptr ds:[eax+ecx+1005700]
01003433 |. 8A8408 00570001 mov al, byte ptr ds:[eax+ecx+1005700]
0100343A |. 8AC8 mov cl, al
0100343C |. 83E1 1F and ecx, 1F
0100343F |. 83F9 09 cmp ecx, 9
01003442 |. 75 04 jnz short winmine.01003448
01003444 |. 6A 0D push 0D
01003446 |. EB 06 jmp short winmine.0100344E
01003448 |> 85C9 test ecx, ecx
0100344A |. 75 03 jnz short winmine.0100344F
0100344C |. 6A 0F push 0F
0100344E |> 59 pop ecx
0100344F |> 24 E0 and al, 0E0
01003451 |. 0AC1 or al, cl
01003453 |. 8802 mov byte ptr ds:[edx], al
01003455 \. C2 0800 retn 8
――――――――――――――――――――――――――――――――――――――――
二、逆向
1、用ExeScope为Winmine.exe添加一个菜单项:ID=0x259=601(大于0x20B),Name="过关"。
2、在Winmine.exe的消息循环中作如下修改,使自己的添加的菜单选项能够被响应。
01001A30 > \B9 4E020000 mov ecx, 24E
01001A35 . 3BC1 cmp eax, ecx
==〉 修改成
01001A30 /E9 4B2F0000 jmp winmine.01004980 //跳转到自己的处理代码处
01001A35 . |3BC1 cmp eax, ecx
3、在01004980处写入如下代码:
下面的代码是在Winmine.exe中找了一段全为零的地方写入的,也可以添加一个节后写入节中。
01004980 3D 58020000 cmp eax, 258 ;
01004985 7F 0A jg short winmine.01004991 ; > 0x258
01004987 B9 4E020000 mov ecx, 24E ; 恢复原来的代码
0100498C ^ E9 A4D0FFFF jmp winmine.01001A35 ; 跳回
01004991 3D 59020000 cmp eax, 259 ; 是否选择了“过关”
01004996 7F 40 jg short winmine.010049D8 ; > 0x259 ?跳 继续处理其它
// 下面是过关代码
01004998 B8 01000000 mov eax, 1 ; 双重循环遍历;
0100499D B9 01000000 mov ecx, 1
010049A2 50 push eax
010049A3 51 push ecx
010049A4 50 push eax
010049A5 51 push ecx
010049A6 50 push eax
010049A7 51 push ecx
010049A8 E8 3CEAFFFF call winmine.010033E9 ; 变换
010049AD A8 80 test al, 80 ; 是否为雷
010049AF 75 07 jnz short winmine.010049B8 ;
010049B1 E8 F9E6FFFF call winmine.010030AF ; 不是雷,点开
010049B6 EB 05 jmp short winmine.010049BD
010049B8 E8 64EAFFFF call winmine.01003421 ; 是雷,恢复
010049BD 59 pop ecx
010049BE 58 pop eax
010049BF 41 inc ecx ; 列数增1
010049C0 3B0D CC520001 cmp ecx, dword ptr ds:[10052CC] ; 遍历完该行的所有列?
010049C6 ^ 7E DA jle short winmine.010049A2
010049C8 40 inc eax ; 行数增1
010049C9 3B05 C8520001 cmp eax, dword ptr ds:[10052C8] ; 遍历完所有行?
010049CF ^ 7E CC jle short winmine.0100499D
010049D1 6A 01 push 1
010049D3 E8 9AE5FFFF call winmine.01002F72 ; 游戏过关
010049D8 ^ E9 58D3FFFF jmp winmine.01001D35 ; 返回到原来的消息循环结束处
在OD中写入代码后,复制到可执行文件,保存即可!
4、运行Winmine.exe,选择菜单中的“过关”即可!
――――――――――――――――――――――――――――――――――――――――
【完】
qINGfENG
2005-01-14
附件:winmine.rar
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)