【工具】 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 eax, dword ptr ds:[edx-201] Switch (cases 201..212) ;在这句上右键菜单:列出switch的case,又打开一个窗口,在里面找到 202(WM_LBUTTONUP) 这一行,双击就来到下面,在下面这行设断点,F9运行,在游戏窗口点击鼠标左键就断在这里
01001FDF |> \33FF xor edi, edi ; 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 eax, dword ptr ds:[1005118] ; 列号,比如你点在第3列则让EAX=3
010037E6 |. 85C0 test eax, eax
010037E8 |. 0F8E C8000000 jle winmine.010038B6 ; EAX<=0则跳
010037EE |. 8B0D 1C510001 mov ecx, dword ptr ds:[100511C] ; 行号,比如你点在第9列则让EAX=9
010037F4 |. 85C9 test ecx, ecx ; ECX<=0则跳
010037F6 |. 0F8E BA000000 jle winmine.010038B6
010037FC |. 3B05 34530001 cmp eax, dword ptr ds:[<ColMax>] ; 最大列数,比如9列
01003802 |. 0F8F AE000000 jg winmine.010038B6 ; 你点击的列号大于最大列数则跳
01003808 |. 3B0D 38530001 cmp ecx, dword ptr ds:[<LineMax>] ; 你点击的行号大于最大行数则跳
0100380E |. 0F8F A2000000 jg winmine.010038B6
01003814 |. 53 push ebx
01003815 |. 33DB xor ebx, ebx ; 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 eax, eax
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 eax, dword ptr ds:[1005118]
01003865 |. 8B0D 1C510001 mov ecx, dword 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 eax, ecx
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 edx, ecx ; 行号
01003898 |. C1E2 05 shl edx, 5 ; EDX*=32
0100389B |. 8A9402 40530001 mov dl, byte 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 eax, dword ptr ds:[<设置的最大宽度>]
0100367F |. 8B0D A8560001 mov ecx, dword ptr ds:[<设置的最大高度>]
01003685 |. 53 push ebx
01003686 |. 56 push esi
01003687 |. 57 push edi
01003688 |. 33FF xor edi, edi
0100368A |. 3B05 34530001 cmp eax, dword ptr ds:[<ColMax>]
01003690 |. 893D 64510001 mov dword ptr ds:[1005164], edi
01003696 |. 75 0C jnz short winmine.010036A4
01003698 |. 3B0D 38530001 cmp ecx, dword 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 eax, dword 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 esi, eax ; 放雷位置列号的随机数
010036DA |. 46 inc esi
010036DB |. E8 60020000 call <winmine.产生随机数> ; rand(LineMax)
010036E0 |. 40 inc eax ; 放雷位置行号的随机数
010036E1 |. 8BC8 mov ecx, eax
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 eax, dword 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 ecx, dword ptr ds:[<LineMax>]
0100370B |. 0FAF0D 345300>imul ecx, dword ptr ds:[<ColMax>]
01003712 |. A1 A4560001 mov eax, dword ptr ds:[10056A4]
01003717 |. 2BC8 sub ecx, eax
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 flat, stdcall ;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