【工具】 ollice, PEiD
【平台】 WinXP SP2
【软件】 XP自带的扫雷游戏“winmine.exe”
【编程】 masm9.0+Radasm2.209
【简介】 分析游戏代码,编写出了地雷位置查看和自动快速扫雷功能的asm代码

  前几天在看雪论坛看到有人把XP的“红心大战”游戏爽爽地玩了一通,我也凑了一阵热闹,觉得还不过瘾,就打算拿扫雷游戏来练练。这个程序相对较简单,适合新手练习。

一.游戏代码分析

  用PEiD看一下为:Microsoft Visual C++ 7.0 Method2 [Debug],无壳,直接用OD调试“winmine.exe”。
  本游戏要用鼠标左、右键来操作,所以跟踪鼠标消息可以找到关键点,而跟踪鼠标消息的方法有许多,这里说说我的方法:
  用OD载入,Ctrl+A分析代码后,右键菜单:查找->所有分支,打开“已识别的分支”窗口
    已识别的分支位于 winmine:.text
    地址       反汇编                                    注释
    010015AC   sub     eax, 53                           Switch (cases 53..111) ;里面有 WM_INITDIALOG 消息
    010015D5   dec     eax                               Switch (cases 1..6D)
    01001700   sub     eax, 53                           Switch (cases 53..111)
    0100182B   sub     eax, 110                          Switch (cases 110..111)
    01001919   sub     eax, 0                            Switch (cases 0..1)
    01001C05   dec     eax                               Switch (cases 2..47)
    01001C9D   sub     eax, 10                           Switch (cases 10..75)
    01001D5B   sub     eax, 111                          Switch (cases 111..113)
    01001DE6   cmp     eax, 208                          Switch (cases 209..20F)
    01001EDC   sub     eax, 211                          Switch (cases 211..251)
    01001F5F   lea     eaxdword ptr ds:[edx-201]       Switch (cases 201..212) ;在这句上右键菜单:列出switch的case,又打开一个窗口,在里面找到 202(WM_LBUTTONUP) 这一行,双击就来到下面,在下面这行设断点,F9运行,在游戏窗口点击鼠标左键就断在这里

01001FDF  |> \33FF          xor     ediedi                                    ;  Cases 202 (WM_LBUTTONUP),205 (WM_RBUTTONUP),208 (WM_MBUTTONUP) of switch 01001F5F
01001FE1  |.  393D 40510001 cmp     dword ptr ds:[1005140], edi
01001FE7  |.  0F84 BC010000 je      winmine.010021A9
01001FED  |>  893D 40510001 mov     dword ptr ds:[1005140], edi
01001FF3  |.  FF15 D8100001 call    near dword ptr ds:[<&USER32.ReleaseCapture>>; [ReleaseCapture
01001FF9  |.  841D 00500001 test    byte ptr ds:[<游戏是否开始的标志?>], bl
01001FFF  |.  0F84 B6000000 je      winmine.010020BB
01002005  |.  E8 D7170000   call    <winmine.处理你的左键点击>                  ;跟进去就好了^O^
0100200A  |.  E9 9A010000   jmp     winmine.010021A9

处理你的左键点击:
010037E1   /$  A1 18510001        mov     eaxdword ptr ds:[1005118]              ;  列号,比如你点在第3列则让EAX=3
010037E6   |.  85C0               test    eaxeax
010037E8   |.  0F8E C8000000      jle     winmine.010038B6                         ;  EAX<=0则跳
010037EE   |.  8B0D 1C510001      mov     ecxdword ptr ds:[100511C]              ;  行号,比如你点在第9列则让EAX=9
010037F4   |.  85C9               test    ecxecx                                 ;  ECX<=0则跳
010037F6   |.  0F8E BA000000      jle     winmine.010038B6
010037FC   |.  3B05 34530001      cmp     eaxdword ptr ds:[<ColMax>]             ;  最大列数,比如9列
01003802   |.  0F8F AE000000      jg      winmine.010038B6                         ;  你点击的列号大于最大列数则跳
01003808   |.  3B0D 38530001      cmp     ecxdword ptr ds:[<LineMax>]            ;  你点击的行号大于最大行数则跳
0100380E   |.  0F8F A2000000      jg      winmine.010038B6
01003814   |.  53                 push    ebx
01003815   |.  33DB               xor     ebxebx                                 ;  EBX=0
01003817   |.  43                 inc     ebx                                      ;  EBX=1
01003818   |.  833D A4570001 00   cmp     dword ptr ds:[10057A4], 0                ;  此处为已经探明的空格数
0100381F   |.  75 4A              jnz     short winmine.0100386B
01003821   |.  833D 9C570001 00   cmp     dword ptr ds:[100579C], 0                ;  游戏是否开始的标志
01003828   |.  75 41              jnz     short winmine.0100386B
0100382A   |.  53                 push    ebx
0100382B   |.  E8 BD000000        call    winmine.010038ED
01003830   |.  FF05 9C570001      inc     dword ptr ds:[100579C]
01003836   |.  E8 7AF0FFFF        call    winmine.010028B5                         ;  绘图相关调用
0100383B   |.  6A 00              push    0                                        ; /Timerproc = NULL
0100383D   |.  68 E8030000        push    3E8                                      ; |Timeout = 1000. ms
01003842   |.  53                 push    ebx                                      ; |TimerID
01003843   |.  FF35 245B0001      push    dword ptr ds:[1005B24]                   ; |hWnd = 0014014C ('扫雷',class='扫雷')
01003849   |.  891D 64510001      mov     dword ptr ds:[1005164], ebx              ; |TimerID
0100384F   |.  FF15 B4100001      call    near dword ptr ds:[<&USER32.SetTimer>]   ; \SetTimer
01003855   |.  85C0               test    eaxeax
01003857   |.  75 07              jnz     short winmine.01003860
01003859   |.  6A 04              push    4                                        ; /Arg1 = 00000004
0100385B   |.  E8 F0000000        call    winmine.01003950                         ; \winmine.01003950
01003860   |>  A1 18510001        mov     eaxdword ptr ds:[1005118]
01003865   |.  8B0D 1C510001      mov     ecxdword ptr ds:[100511C]
0100386B   |>  841D 00500001      test    byte ptr ds:[<什么标志?>], bl
01003871   |.  5B                 pop     ebx
01003872   |.  75 10              jnz     short winmine.01003884
01003874   |.  6A FE              push    -2
01003876   |.  59                 pop     ecx
01003877   |.  8BC1               mov     eaxecx
01003879   |.  890D 1C510001      mov     dword ptr ds:[100511C], ecx
0100387F   |.  A3 18510001        mov     dword ptr ds:[1005118], eax
01003884   |>  833D 44510001 00   cmp     dword ptr ds:[<雷数?>], 0
0100388B   |.  74 09              je      short winmine.01003896
0100388D   |.  51                 push    ecx
0100388E   |.  50                 push    eax
0100388F   |.  E8 23FDFFFF        call    winmine.010035B7
01003894   |.  EB 20              jmp     short winmine.010038B6
01003896   |>  8BD1               mov     edxecx                                 ;  行号
01003898   |.  C1E2 05            shl     edx, 5                                   ;  EDX*=32
0100389B   |.  8A9402 40530001    mov     dlbyte ptr ds:[edx+eax+<byteITEM>]     ;  item[line*32+col]
010038A2   |.  F6C2 40            test    dl, 40
010038A5   |.  75 0F              jnz     short winmine.010038B6
010038A7   |.  80E2 1F            and     dl, 1F
010038AA   |.  80FA 0E            cmp     dl, 0E
010038AD   |.  74 07              je      short winmine.010038B6
010038AF   |.  51                 push    ecx
010038B0   |.  50                 push    eax
010038B1   |.  E8 5CFCFFFF        call    winmine.01003512
010038B6   |>  FF35 60510001      push    dword ptr ds:[1005160]
010038BC   |.  E8 52F0FFFF        call    winmine.01002913
010038C1   \.  C3                 retn

 同样用跟踪 WM_INITDIALOG 消息的方法可以找到游戏初始化的相关代码,具体方法同上,就不多说了。我这里就直接给出代码:
0100367A >/$  A1 AC560001   mov     eaxdword ptr ds:[<设置的最大宽度>]
0100367F  |.  8B0D A8560001 mov     ecxdword ptr ds:[<设置的最大高度>]
01003685  |.  53            push    ebx
01003686  |.  56            push    esi
01003687  |.  57            push    edi
01003688  |.  33FF          xor     ediedi
0100368A  |.  3B05 34530001 cmp     eaxdword ptr ds:[<ColMax>]
01003690  |.  893D 64510001 mov     dword ptr ds:[1005164], edi
01003696  |.  75 0C         jnz     short winmine.010036A4
01003698  |.  3B0D 38530001 cmp     ecxdword ptr ds:[<LineMax>]
0100369E  |.  75 04         jnz     short winmine.010036A4
010036A0  |.  6A 04         push    4
010036A2  |.  EB 02         jmp     short winmine.010036A6
010036A4  |>  6A 06         push    6
010036A6  |>  5B            pop     ebx
010036A7  |.  A3 34530001   mov     dword ptr ds:[<ColMax>], eax
010036AC  |.  890D 38530001 mov     dword ptr ds:[<LineMax>], ecx
010036B2  |.  E8 1EF8FFFF   call    winmine.01002ED5
010036B7  |.  A1 A4560001   mov     eaxdword ptr ds:[10056A4]
010036BC  |.  893D 60510001 mov     dword ptr ds:[1005160], edi
010036C2  |.  A3 30530001   mov     dword ptr ds:[1005330], eax
010036C7  |>  FF35 34530001 push    dword ptr ds:[<ColMax>]
010036CD  |.  E8 6E020000   call    <winmine.产生随机数>                        ;  rand(ColMax)
010036D2  |.  FF35 38530001 push    dword ptr ds:[<LineMax>]
010036D8  |.  8BF0          mov     esieax                                    ;  放雷位置列号的随机数
010036DA  |.  46            inc     esi
010036DB  |.  E8 60020000   call    <winmine.产生随机数>                        ;  rand(LineMax)
010036E0  |.  40            inc     eax                                         ;  放雷位置行号的随机数
010036E1  |.  8BC8          mov     ecxeax
010036E3  |.  C1E1 05       shl     ecx, 5                                      ;  line*32
010036E6  |.  F68431 405300>test    byte ptr ds:[ecx+esi+<byteITEM>], 80        ;  该点放有雷了吗?
010036EE  |.^ 75 D7         jnz     short winmine.010036C7                      ;  有雷则去重新产生行、列号
010036F0  |.  C1E0 05       shl     eax, 5                                      ;  line*32
010036F3  |.  8D8430 405300>lea     eaxdword ptr ds:[eax+esi+<byteITEM>]      ;  byteITEM[line*32+col]
010036FA  |.  8008 80       or      byte ptr ds:[eax], 80                       ;  在该点置雷
010036FD  |.  FF0D 30530001 dec     dword ptr ds:[1005330]                      ;  还未产生的雷的枚数
01003703  |.^ 75 C2         jnz     short winmine.010036C7
01003705  |.  8B0D 38530001 mov     ecxdword ptr ds:[<LineMax>]
0100370B  |.  0FAF0D 345300>imul    ecxdword ptr ds:[<ColMax>]
01003712  |.  A1 A4560001   mov     eaxdword ptr ds:[10056A4]
01003717  |.  2BC8          sub     ecxeax
01003719  |.  57            push    edi
0100371A  |.  893D 9C570001 mov     dword ptr ds:[100579C], edi
01003720  |.  A3 30530001   mov     dword ptr ds:[1005330], eax
01003725  |.  A3 94510001   mov     dword ptr ds:[1005194], eax
0100372A  |.  893D A4570001 mov     dword ptr ds:[10057A4], edi
01003730  |.  890D A0570001 mov     dword ptr ds:[10057A0], ecx
01003736  |.  C705 00500001>mov     dword ptr ds:[<什么标志?>], 1
01003740  |.  E8 25FDFFFF   call    winmine.0100346A
01003745  |.  53            push    ebx                                         ; /Arg1
01003746  |.  E8 05E2FFFF   call    winmine.01001950                            ; \winmine.01001950
0100374B  |.  5F            pop     edi
0100374C  |.  5E            pop     esi
0100374D  |.  5B            pop     ebx
0100374E  \.  C3            retn

从内存读取到的游戏数组,以9×9的为例:
          游戏未开始时数据              |                 游戏后数据
0F 0F 0F 0F 0F 0F 0F 0F 0F      |      40 40 40 40 40 40 40 40 40
                                        |      
0F 0F 0F 0F 0F 0F 0F 0F 0F      |      40 40 40 40 40 40 40 40 40
                                        |      
0F 0F 0F 0F 0F 0F 0F 0F 0F      |      40 41 41 41 40 40 40 40 40
                                        |      
0F 0F 8F 0F 0F 0F 0F 0F 0F      |      40 41 8E 41 40 40 41 42 42
                                        |      
0F 0F 0F 0F 0F 0F 0F 8F 8F      |      40 42 42 43 41 41 41 8E 8E
                                        |      
0F 0F 8F 0F 8F 0F 0F 0F 8F      |      41 42 8E 42 8E 41 41 44 8E
                                        |      
8F 0F 0F 0F 0F 0F 0F 0F 8F      |      8E 43 42 43 41 41 40 42 8D
                                        |      
0F 0F 8F 0F 0F 0F 0F 0F 0F      |      42 43 8F 41 40 40 40 41 41
                                        |      
0F 8F 0F 0F 0F 0F 0F 0F 0F      |      41 8E 42 41 40 40 40 40 40
可以看出 8F、8E、8D 就是有地雷格子的三种状态,4X 为已探明的地方,其中 X 就是在游戏时所看到的1、2、3......
0F为未探明的地方。

总结一下有用的东西:
  放雷数组的地址为:byteITEM=1005340
  数组大小固定为:32×32
  当前游戏最大行数存放在:LineMax=1005338
  当前游戏最大列数存放在:ColMax=1005334
 if(byteITEM[Line,Col]&0x80)有雷^O^

二.我的扫雷程序Asm源码
  思路:找游戏窗口->取窗口进程信息->读取数据->依次判断每一byte的数据,是否是地雷,是则发送鼠标右击消息,否则发送鼠标左击消息,完成扫雷。

  1.“扫雷专家.asm”文件
.386
.model flatstdcall  ;32 bit memory model
option casemap :none  ;case sensitive

include 扫雷专家.inc

.code

start:

        invoke InitCommonControls
    invoke GetModuleHandle,NULL
    invoke DialogBoxParam,eax,IDD_DIALOG,NULL,addr DlgProc,NULL
    invoke ExitProcess,0

;########################################################################
_Read2Do proc  hWin:HWND,doit:DWORD
         LOCAL  @hWnd:HWND
         LOCAL  @dbItem[32*32]:byte
         LOCAL  @dwProcessId,@hProcess,@LineMax,@ColMax
         LOCAL  @hDC,@i

         invoke FindWindow,NULL,addr lpstrFind
         .if  eax==NULL
              invoke MessageBox,hWin,addr lpstrMsg1,addr lpstrErr,MB_ICONERROR
              jmp  @F
         .endif
         mov  @hWnd,eax
         lea edx,@dwProcessId
         invoke GetWindowThreadProcessId,eax,edx
         invoke OpenProcess,PROCESS_ALL_ACCESS,FALSE,@dwProcessId
         .if  eax==NULL
              invoke MessageBox,hWin,addr lpstrMsg2,addr lpstrErr,MB_ICONERROR
              jmp  @F
         .endif
        mov  @hProcess,eax

        lea edx,@LineMax
        invoke ReadProcessMemory,eax,01005338h,edx,4,NULL
        lea edx,@ColMax
        invoke ReadProcessMemory,@hProcess,01005334h,edx,4,NULL
        lea edx,@dbItem
        invoke ReadProcessMemory,@hProcess,01005340h,edx,32*32,NULL
        invoke CloseHandle,@hProcess       
        
        invoke GetDC,@hWnd
        mov    @hDC,eax
        invoke SetBkMode,eax,TRANSPARENT
        .while @LineMax>0
               push  @ColMax
               pop   @i
               .while @i>0
                      mov eax,@LineMax
                      shl eax,5
                      add eax,@i
                      .if byte ptr @dbItem[eax]&080h  ;此处有雷
                             mov eax,@i
                             shl eax,4
                             sub eax,2
                             mov edx,@LineMax
                             shl edx,4
                             add edx,38
                             .if doit  ;按右键扫雷
                                 add eax,8
                                 add edx,6
                                 shl edx,16 ;计算坐标
                                 add edx,eax
                                 push edx
                                 invoke SendMessage,@hWnd,WM_RBUTTONDOWN,MK_RBUTTON,edx
                                 pop edx
                                 invoke SendMessage,@hWnd,WM_RBUTTONUP,MK_RBUTTON,edx
                             .else
                             invoke TextOut,@hDC,eax,edx,addr lpC,1  ;标记雷位
                             .endif
                      .elseif doit  ;按左键探空地
                             mov eax,@i
                             shl eax,4
                             add eax,6
                             mov edx,@LineMax
                             shl edx,4
                             add edx,44
                             shl edx,16 ;计算坐标
                             add edx,eax
                             push edx
                             invoke SendMessage,@hWnd,WM_LBUTTONDOWN,MK_LBUTTON,edx
                             pop edx
                             invoke SendMessage,@hWnd,WM_LBUTTONUP,MK_LBUTTON,edx
                      .endif
                      dec @i
               .endw
               dec @LineMax
        .endw
        invoke ReleaseDC,@hWnd,@hDC
@@:
        ret
         
_Read2Do endp

DlgProc proc hWin:HWND,uMsg:UINT,wParam:WPARAM,lParam:LPARAM

    mov        eax,uMsg
    .if eax==WM_INITDIALOG
                invoke WinExec,addr lpWinMine,SW_SHOW
    .elseif eax==WM_COMMAND
        mov    eax,wParam 
            .if lParam!=0 
                   mov edx,eax
                   shr edx,16 
                    .if dx==BN_CLICKED ;按钮消息处理
                    xor ecx,ecx
                        .if ax==IDC_DO ;快速扫雷
                            inc ecx
                        .endif
                    invoke _Read2Do,hWin,ecx
                    .endif
                .endif
    .elseif eax==WM_CLOSE
        invoke EndDialog,hWin,0
    .else
        mov        eax,FALSE
        jmp             @F
    .endif
    mov        eax,TRUE
@@:    ret

DlgProc endp

end start
***********************************************************************************************
  2.“扫雷专家.Inc”文件

include windows.inc
include kernel32.inc
include user32.inc
include Comctl32.inc
include shell32.inc

includelib kernel32.lib
includelib user32.lib
includelib Comctl32.lib
includelib shell32.lib

include gdi32.inc 
includelib gdi32.lib 
DlgProc            PROTO    :HWND,:UINT,:WPARAM,:LPARAM

.const

IDD_DIALOG            equ 101
IDC_LOOK            equ 1001
IDC_DO                equ 1002

;#########################################################################

.data
lpstrFind                      db '扫雷',0
lpstrMsg1                      db '扫雷游戏没有运行',0
lpstrMsg2                      db '打开进程失败',0
lpstrErr                       db '错误',0
lpC                            db 'Q',0
lpWinMine                      db 'winmine.exe',0