• 标 题: 扫雷游戏作弊逆向菜文
  • 作 者:qINGfENG
  • 时 间:2005-01-14 21:10

【目标程序】: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