【工具】 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.incinclude kernel32.incinclude user32.incinclude Comctl32.incinclude shell32.incincludelib kernel32.libincludelib user32.libincludelib Comctl32.libincludelib shell32.libinclude 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
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!
上传的附件: