修改扫雷程序实现悬停显雷
2020-01-20 20:49:18 Author: bbs.pediy.com(查看原文) 阅读量:188 收藏

[原创]修改扫雷程序实现悬停显雷

1小时前 81

本人最近刚刚开始学习逆向分析,突发奇想想弄一个Windows XP扫雷的辅助。发帖前,在论坛上一搜,好多人都做过这个了,还有人做过Windows7 32位,64位的,突然感觉自己好低级……于是我又认真地把windowsXP扫雷的几乎所有汇编代码都分析了一遍,最后做了一个修改扫雷内部代码而实现悬停显示雷的操作。

效果如下:

进入正题,这是我的预计任务

1.     找到回调函数

2.     找到并分析雷表

        找到存放时间的位置

3.     分析绘图代码

首先,用OllyDbg打开windows xp的扫雷程序。

一开始如下图所示

01003E21 > $  6A 70         push    70
01003E23   .  68 90130001   push    01001390
01003E28   .  E8 DF010000   call    0100400C                         ;  SEH安装
01003E2D   .  33DB          xor     ebx, ebx
01003E2F   .  53            push    ebx                              ; /pModule => NULL
01003E30   .  8B3D 8C100001 mov     edi, dword ptr [<&KERNEL32.GetMo>; |KERNEL32.GetModuleHandleA
01003E36   .  FFD7          call    edi                              ; \GetModuleHandleA
01003E38   .  66:8138 4D5A  cmp     word ptr [eax], 5A4D             ;  MZ头确认
01003E3D   .  75 1F         jnz     short 01003E5E
01003E3F   .  8B48 3C       mov     ecx, dword ptr [eax+3C]          ;  ecx=elfanew
01003E42   .  03C8          add     ecx, eax
01003E44   .  8139 50450000 cmp     dword ptr [ecx], 4550            ;  PE头确认
01003E4A   .  75 12         jnz     short 01003E5E
01003E4C   .  0FB741 18     movzx   eax, word ptr [ecx+18]           ;  eax=OptionalHeader
01003E50   .  3D 0B010000   cmp     eax, 10B                         ;  Magic

显然,是属于程序的入口部分代码,包含有初始化SHE,获取ImageBase,测试PE文件的有效性等等等等。


一:找窗口回调函数

为了实现找到回调函数这一个目标,首先需要知道获取来源。

首先想到WNDCLASSA(或WNDCLASSW)和WNDCLASSEX这两个结构体,WNDCLASSW定义如下

typedef struct tagWNDCLASSW {
    UINT        style;
    WNDPROC     lpfnWndProc;
    int         cbClsExtra;
    int         cbWndExtra;
    HINSTANCE   hInstance;
    HICON       hIcon;
    HCURSOR     hCursor;
    HBRUSH      hbrBackground;
    LPCWSTR     lpszMenuName;
    LPCWSTR     lpszClassName;
} WNDCLASSW, *PWNDCLASSW, NEAR *NPWNDCLASSW, FAR *LPWNDCLASSW;

这两个结构体都是用来初始化窗口类的,在RegisterClass中被引用,根据该结构体的定义,在此结构的(+0x04)位置处,就是窗口回调函数的地址。所以,从设置WNDCLASS成员时指定的地址或者从RegisterClass调用时的栈窗口,可以找到WNDCLASS的地址,进而找到lpfnWndProc的值。


RegisterClass一般在主线程的WinMain中被调用,我向下跟踪,来到此处

01003F8F   .  50            push    eax
01003F90   .  E8 5BE2FFFF   call    010021F0
01003F95   .  8BF0          mov     esi, eax
01003F97   .  8975 84       mov     dword ptr [ebp-7C], esi
01003F9A   .  395D E4       cmp     dword ptr [ebp-1C], ebx
01003F9D   .  75 07         jnz     short 01003FA6
01003F9F   .  56            push    esi                              ; /status
01003FA0   .  FF15 94110001 call    dword ptr [<&msvcrt.exit>]       ; \exit
01003FA6   >  FF15 9C110001 call    dword ptr [<&msvcrt._cexit>]     ; [msvcrt._cexit

目前窗口并没有创建,而接下来的第二个函数是exit的调用,标志着程序的退出,因此可以确认,下面的第一个函数,就是程序的WinMain函数。

跟踪进去

010021F0  /$  55            push    ebp
010021F1  |.  8BEC          mov     ebp, esp
010021F3  |.  83EC 4C       sub     esp, 4C
010021F6  |.  8B45 08       mov     eax, dword ptr [ebp+8]
010021F9  |.  53            push    ebx
010021FA  |.  56            push    esi
010021FB  |.  57            push    edi
010021FC  |.  A3 305B0001   mov     dword ptr [1005B30], eax
01002201  |.  E8 AA180000   call    01003AB0                         ;  设置随机数种子
01002206  |.  33DB          xor     ebx, ebx
01002208  |.  43            inc     ebx
01002209  |.  33FF          xor     edi, edi
0100220B  |.  837D 14 07    cmp     dword ptr [ebp+14], 7
0100220F  |.  74 0C         je      short 0100221D
01002211  |.  837D 14 02    cmp     dword ptr [ebp+14], 2
01002215  |.  893D 385B0001 mov     dword ptr [1005B38], edi
0100221B  |.  75 06         jnz     short 01002223
0100221D  |>  891D 385B0001 mov     dword ptr [1005B38], ebx
01002223  |>  8D45 F8       lea     eax, dword ptr [ebp-8]
01002226  |.  50            push    eax                              ; /pInitEx
01002227  |.  C745 F8 08000>mov     dword ptr [ebp-8], 8             ; |
0100222E  |.  C745 FC FD160>mov     dword ptr [ebp-4], 16FD          ; |
01002235  |.  FF15 1C100001 call    dword ptr [<&COMCTL32.InitCommon>; \InitCommonControlsEx
0100223B  |.  6A 64         push    64                               ; /RsrcName = 100.
0100223D  |.  FF35 305B0001 push    dword ptr [1005B30]              ; |hInst = NULL
01002243  |.  FF15 AC100001 call    dword ptr [<&USER32.LoadIconW>]  ; \LoadIconW
01002249  |.  8B0D 305B0001 mov     ecx, dword ptr [1005B30]
0100224F  |.  68 007F0000   push    7F00                             ; /RsrcName = IDC_ARROW
01002254  |.  57            push    edi                              ; |hInst
01002255  |.  A3 285B0001   mov     dword ptr [1005B28], eax         ; |
0100225A  |.  897D B4       mov     dword ptr [ebp-4C], edi          ; |
0100225D  |.  C745 B8 C91B0>mov     dword ptr [ebp-48], <lpfnWndProc>; |
01002264  |.  897D BC       mov     dword ptr [ebp-44], edi          ; |
01002267  |.  897D C0       mov     dword ptr [ebp-40], edi          ; |
0100226A  |.  894D C4       mov     dword ptr [ebp-3C], ecx          ; |
0100226D  |.  8945 C8       mov     dword ptr [ebp-38], eax          ; |
01002270  |.  FF15 BC100001 call    dword ptr [<&USER32.LoadCursorW>>; \LoadCursorW
01002276  |.  53            push    ebx                              ; /ObjType
01002277  |.  8945 CC       mov     dword ptr [ebp-34], eax          ; |
0100227A  |.  FF15 60100001 call    dword ptr [<&GDI32.GetStockObjec>; \GetStockObject
01002280  |.  8945 D0       mov     dword ptr [ebp-30], eax
01002283  |.  8D45 B4       lea     eax, dword ptr [ebp-4C]
01002286  |.  BE A05A0001   mov     esi, 01005AA0
0100228B  |.  50            push    eax                              ; /pWndClass
0100228C  |.  897D D4       mov     dword ptr [ebp-2C], edi          ; |
0100228F  |.  8975 D8       mov     dword ptr [ebp-28], esi          ; |
01002292  |.  FF15 CC100001 call    dword ptr [<&USER32.RegisterClas>; \RegisterClassW

在第一个CALL处我进行了查看,发现是设置随机数种子,接下来明显地看到调用了LoadIconW,LoadCursorW,和GetStockObject三个API函数对WNDCLASSW进行设置,往下翻,立刻可以看见RegisterClassW。事实上,hIcon的位置在lpfnWndProc(+0x10)的位置,从LoadIcon的返回值的设置位置(2个地方)

01002255  |.  A3 285B0001   mov     dword ptr [1005B28], eax         ; |

一开始我以为是这里,结果发现1005B18没有写入的代码,接着向下看:

0100226D  |.  8945 C8       mov     dword ptr [ebp-38], eax          ; |

可知ebp-38-10=ebp-48就是回调函数的地址,接下来看到

0100225D  |.  C745 B8 C91B0>mov     dword ptr [ebp-48], 01001BC9     ; |

可以确认,01001BC9就是回调函数的地址了,添加标签为lpfnWndProc

二:

进入窗口回调函数,马上看到一个switch

01001C05   .  48            dec     eax                              ;  Switch (cases 2..47)

根据对回调函数的整体浏览,发现对WM_LBUTTONDOWN的处理部分,有将(记住这个地址)1005144处的数据设为0这估计是为了实现左键按下时显示格子0的效果,添加标签为bLButtonUp(Down的非是Up (`へ´*)ノ)

接着对WM_LBUTTONDOWN和WM_LBUTTONUP这两个消息的处理部分下断

01001FA6   > \393D 48510001 cmp     dword ptr [1005148], edi         ;  Case 201 (WM_LBUTTONDOWN) of switch 01001F5F
01001FAC   .^ 75 D6         jnz     short 01001F84
01001FAE   .  FF75 14       push    dword ptr [ebp+14]
01001FB1   .  E8 56F4FFFF   call    0100140C
01001FB6   .  85C0          test    eax, eax
01001FB8   .^ 0F85 A0FCFFFF jnz     01001C5E
01001FBE   .  841D 00500001 test    byte ptr [1005000], bl
01001FC4   .  0F84 DF010000 je      010021A9
01001FCA   .  8B45 10       mov     eax, dword ptr [ebp+10]
01001FCD   .  24 06         and     al, 6
01001FCF   .  F6D8          neg     al
01001FD1   .  1BC0          sbb     eax, eax
01001FD3   .  F7D8          neg     eax
01001FD5   .  A3 44510001   mov     dword ptr [<bLButtonUp>], eax
01001FDA   .  E9 80000000   jmp     0100205F
01001FDF   >  33FF          xor     edi, edi                         ;  Cases 202 (WM_LBUTTONUP),205 (WM_RBUTTONUP),208 (WM_MBUTTONUP) of switch 01001F5F
01001FE1   .  393D 40510001 cmp     dword ptr [1005140], edi
01001FE7   .  0F84 BC010000 je      010021A9
01001FED   >  893D 40510001 mov     dword ptr [1005140], edi
01001FF3   .  FF15 D8100001 call    dword ptr [<&USER32.ReleaseCapture>]; ReleaseCapture
01001FF9   .  841D 00500001 test    byte ptr [1005000], bl
01001FFF   .  0F84 B6000000 je      010020BB
01002005   .  E8 D7170000   call    010037E1
0100200A   .  E9 9A010000   jmp     010021A9

在扫雷点击一个格子,在WM_LBUTTONDOWN断下,继续运行,WM_LBUTTONUP自然不会断下,而游戏界面无变化,说明WM_LBUTTONDOWN不进行点击格子的操作

现在大概可以判断,是左键弹起表示了对一个块的点击。

取消WM_LBUTTONDOWN的断点,继续点击,跟踪WM_LBUTTONUP,来到此处

010037E1  /$  A1 18510001   mov     eax, dword ptr [1005118]        ;  左键弹起消息的处理函数
010037E6  |.  85C0          test    eax, eax
010037E8  |.  0F8E C8000000 jle     010038B6
010037EE  |.  8B0D 1C510001 mov     ecx, dword ptr [100511C]
010037F4  |.  85C9          test    ecx, ecx
010037F6  |.  0F8E BA000000 jle     010038B6
010037FC  |.  3B05 34530001 cmp     eax, dword ptr [1005334]
01003802  |.  0F8F AE000000 jg      010038B6
01003808  |.  3B0D 38530001 cmp     ecx, dword ptr [1005338]
0100380E  |.  0F8F A2000000 jg      010038B6

开始是四个判断,根据对数据的分析,1005118应该保存一个POINT结构体,是当前点击的单位坐标,而1005334保存了扫雷区域的单位长度,1005338则是单位宽度。于是添加标签g_xPos,g_yPos,Length,Height

那么此处的作用应该是,判断点击坐标是否在范围之内,否则函数跳到末尾。


接下来则是这部分

01003818  |.  833D A4570001>cmp     dword ptr [10057A4], 0
0100381F  |.  75 4A         jnz     short 0100386B
01003821  |.  833D 9C570001>cmp     dword ptr [100579C], 0
01003828  |.  75 41         jnz     short 0100386B
0100382A  |.  53            push    ebx
0100382B  |.  E8 BD000000   call    010038ED
01003830  |.  FF05 9C570001 inc     dword ptr [100579C]
01003836  |.  E8 7AF0FFFF   call    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 [1005B24]              ; |hWnd = NULL
01003849  |.  891D 64510001 mov     dword ptr [1005164], ebx         ; |
0100384F  |.  FF15 B4100001 call    dword ptr [<&USER32.SetTimer>]   ; \SetTimer
01003855  |.  85C0          test    eax, eax
01003857  |.  75 07         jnz     short 01003860
01003859  |.  6A 04         push    4
0100385B  |.  E8 F0000000   call    01003950
01003860  |>  A1 18510001   mov     eax, dword ptr [<g_xPos>]
01003865  |.  8B0D 1C510001 mov     ecx, dword ptr [<g_yPos>]
0100386B  |>  841D 00500001 test    byte ptr [1005000], bl

一开始,判断了两个位置的数据,不为0则设置间隔为1s的定时器,于是我大胆猜测,这是第一次点击时会调用的代码,设置定时器,进行计时。那么,10057A4和100579C就可能:一个是时间的数据,一个是游戏是否开始的BOOL型数据。

然后让扫雷运行,再分别对10057A4和100579C下硬件写入断点,发现10057A4是每点开一个格子都会断下,并且计数+1,100579C是每秒都会断下,计数+1。那么,10057A4应该是翻开的格子数目,标签其为PressNum,10057C9应该是时间,标签其为Time


之后是设置值, eax=x,ecx=y

接下来的部分如图所示

0100386B  |> \841D 00500001 test    byte ptr [1005000], bl
01003871  |.  5B            pop     ebx
01003872  |.  75 10         jnz     short 01003884
01003874  |.  6A FE         push    -2
01003876  |.  59            pop     ecx
01003877  |.  8BC1          mov     eax, ecx
01003879  |.  890D 1C510001 mov     dword ptr [<g_yPos>], ecx
0100387F  |.  A3 18510001   mov     dword ptr [<g_xPos>], eax
01003884  |>  833D 44510001>cmp     dword ptr [<bLButtonUp>], 0
0100388B  |.  74 09         je      short 01003896

1003872的跳转一般不会实现,100388B的跳转一般一定实现,第一个跳转是判断1005000处的数据是否为零,通过对此地址设置硬件断点,可以发现,当“新游戏”时,此处的值被设置为1,而游戏结束后,此处的值被设置为16。

第二个跳转发现访问了bLButtonDown这个地址,为0则跳转。这个地址前面见到过,在左键按下的时候被设置成了0,而左键弹起前一定会有按下操作,因此,这个地方一般也会跳过。(也可以写监测程序查看值的变化)


然后就是

01003896  |> \8BD1          mov     edx, ecx
01003898  |.  C1E2 05       shl     edx, 5                           ;  edx=ecx*32
0100389B  |.  8A9402 405300>mov     dl, byte ptr [edx+eax+1005340]  ;  读取雷表(1维数组)数据
010038A2  |.  F6C2 40       test    dl, 40
010038A5  |.  75 0F         jnz     short 010038B6                   ;  已翻开,不处理
010038A7  |.  80E2 1F       and     dl, 1F
010038AA  |.  80FA 0E       cmp     dl, 0E
010038AD  |.  74 07         je      short 010038B6                   ;  是旗子,不处理
010038AF  |.  51            push    ecx
010038B0  |.  50            push    eax
010038B1  |.  E8 5CFCFFFF   call    <PressThisBlock>                 ;  处理

从前三行可以知道读取雷表的方法

例如有雷表BYTE arMine[..],需要读取第r行第c列

第一行将ecx:也就是r,赋给edx

shl相当于<<,第二行是将edx左移五位,五个二进制位就是乘以2^5(=32)

第三行读取

那么可以知道,1005340是雷表的头地址,标签其为arMine,读取方式为arMine[r*32+c]

通过不断地对数据进行更新和检查,可以知道储存规律:

雷表是一个以1个字节为元素长度的数组,0x10为游戏的边界

雷表元素的低四位:

i.              未翻开时,是0xF

ii.             插旗子时,是0xE

iii.            打问号时,是0xD

iv.            翻开时(非雷),是它的数字,是雷则为0xA(不是点击到的)

v.             左键按下时,是0x0

vi.            边界为0x1

雷表元素的高四位:

i.              未翻开时,是雷则为0x8,否则为0x0

ii.             翻开时(非雷),是0x4,是雷则为0x8(不是点击到的)

iii.            边界为0x0

雷表元素是点击到的雷,数据则为0xCC


三.

上面的基本说的都没用(众所周知),接下来才是重头戏,如何实现,把鼠标悬停在一个格子上,如果那个格子有雷,就把雷显示出来(透视的感觉有木有)

首先,需要寻找一个可以安心写代码并能保存到文件的位置,将代码向下翻,找到1004A60的位置,下面都是0数据,可以使用,不过要先判断这个区域是否在扫雷文件内(不然保存不到文件里),RVA=4A60,转为文件FileOffset=3E60,而文件中.text节区是到4000,那么把它标签为InjectCode,于是在此处就可以放心地写入代码了。

首先我在扫雷窗口生成后对WM_PAINT下断,然后在扫雷点开一格,并没有断下,说明重绘函数不会处理左键单击,格子变化之类的游戏进行中的小调整,在这里写入代码就没有用了(我一开始莽,直接在这里写,然并卵)。为了知道贴图的怎么变化的,我在BitBlt下了断,点击一下,来到此处:

01002646 >/$  56            push    esi
01002647  |.  FF35 245B0001 push    dword ptr [1005B24]              ; /hWnd = NULL
0100264D  |.  FF15 2C110001 call    dword ptr [<&USER32.GetDC>]      ; \GetDC
01002653  |.  8B4C24 0C     mov     ecx, dword ptr [esp+C]
01002657  |.  68 2000CC00   push    0CC0020                          ; /ROP = SRCCOPY
0100265C  |.  8BF0          mov     esi, eax                         ; |
0100265E  |.  8B4424 0C     mov     eax, dword ptr [esp+C]           ; |
01002662  |.  8BD1          mov     edx, ecx                         ; |
01002664  |.  6A 00         push    0                                ; |YSrc = 0
01002666  |.  C1E2 05       shl     edx, 5                           ; |
01002669  |.  0FBE9402 4053>movsx   edx, byte ptr [edx+eax+<arMine>] ; |
01002671  |.  6A 00         push    0                                ; |XSrc = 0
01002673  |.  83E2 1F       and     edx, 1F                          ; |
01002676  |.  FF3495 205A00>push    dword ptr [edx*4+1005A20]        ; |hSrcDC
0100267D  |.  C1E1 04       shl     ecx, 4                           ; |
01002680  |.  6A 10         push    10                               ; |Height = 10 (16.)
01002682  |.  6A 10         push    10                               ; |Width = 10 (16.)
01002684  |.  83C1 27       add     ecx, 27                          ; |
01002687  |.  C1E0 04       shl     eax, 4                           ; |
0100268A  |.  51            push    ecx                              ; |YDest
0100268B  |.  83E8 04       sub     eax, 4                           ; |
0100268E  |.  50            push    eax                              ; |XDest
0100268F  |.  56            push    esi                              ; |hDestDC
01002690  |.  FF15 5C100001 call    dword ptr [<&GDI32.BitBlt>]      ; \BitBlt
01002696  |.  56            push    esi                              ; /hDC
01002697  |.  FF35 245B0001 push    dword ptr [1005B24]              ; |hWnd = NULL
0100269D  |.  FF15 28110001 call    dword ptr [<&USER32.ReleaseDC>]  ; \ReleaseDC
010026A3  |.  5E            pop     esi
010026A4  \.  C2 0800       retn    8

发现两个参数分别是我点下的x,y坐标,那么这个函数就是根据一个格的数值来绘图。标签其为UpdateBlockPaint,观察栈,发现:把雷表中的相应元素&0x1F后,作为8字节为元素长度的数组1005A20的索引值,就可以得到相应贴图的HDC,那么,显示的雷是0x8A,&0x1F得0xA,于是位置就是4*0xA+0x1005A20=1005A48,这个地址之后BitBlt中会用到哦~

这个函数的调用最终是起源于回调函数中的:

01002085  |> \393D 40510001 cmp     dword ptr [1005140], edi         ;  edi:0x00000000
0100208B  |.  74 34         je      short 010020C1                   ;  左键没按下
0100208D  |.  841D 00500001 test    byte ptr [1005000], bl           ;  bl:0x01
01002093  |.^ 0F84 54FFFFFF je      01001FED                         ;  游戏没开始
01002099  |.  8B45 14       mov     eax, dword ptr [ebp+14]          ;  lParam
0100209C  |.  C1E8 10       shr     eax, 10                          ;  eax=HIWORD(lParam):yPos
0100209F  |.  83E8 27       sub     eax, 27                          ;  减去边框后+16
010020A2  |.  C1F8 04       sar     eax, 4                           ;  eax/=16
010020A5  |.  50            push    eax                              ;  参数2:y
010020A6  |.  0FB745 14     movzx   eax, word ptr [ebp+14]           ;  eax=LOWORD(lParam):xPos
010020AA  |.  83C0 04       add     eax, 4                           ;  减去边框后+16
010020AD  |.  C1F8 04       sar     eax, 4                           ;  eax/=16
010020B0  |.  50            push    eax                              ;  参数1:x
010020B1  |>  E8 1E110000   call    010031D4

触发WM_MOUSEMOVE一定来到此处,左中右键按下也可能来到此处,一开始就判断左键是否按下,关于1005140的值,我用监测程序判断了一下

PROCESSENTRY32 pe32;
HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
BOOL bRet = Process32First(hSnap, &pe32);
DWORD dwPid = 0;
while (bRet)
{
	if (lstrcmp(pe32.szExeFile, "saolei.exe") == 0){
		dwPid = pe32.th32ProcessID;
		break;
	}
	bRet = Process32Next(hSnap, &pe32);
}
if (dwPid == 0)return 0;
CloseHandle(hSnap);
g_hProcess = OpenProcess(PROCESS_ALL_ACCESS, 1, dwPid);
int value;
int last = 0;
while (ReadProcessMemory(g_hProcess, (LPVOID)0x01005140, &value, 4, 0)){
	Sleep(10);
	if (value != last){
		char*szMsg = new char[100];
		wsprintf(szMsg, "Turn to %d", value);
		printf("%s\n",szMsg);
	}
	last = value;
}

左键按下为1,弹起后为0

接下来是10031D4函数的部分

010031D4  /$  55            push    ebp                              ;  edx=x,edi=y(新的) eax=x,ecx=y(旧的)
010031D5  |.  8BEC          mov     ebp, esp
010031D7  |.  83EC 20       sub     esp, 20
010031DA  |.  8B55 08       mov     edx, dword ptr [ebp+8]           ;  参数1:x
010031DD  |.  A1 18510001   mov     eax, dword ptr [<g_xPos>]
010031E2  |.  3BD0          cmp     edx, eax
010031E4  |.  8B0D 1C510001 mov     ecx, dword ptr [<g_yPos>]
010031EA  |.  57            push    edi
010031EB  |.  8B7D 0C       mov     edi, dword ptr [ebp+C]           ;  参数2:y
010031EE  |.  75 08         jnz     short 010031F8                   ;  有变动,即刻更改g_xPos和g_yPos
010031F0  |.  3BF9          cmp     edi, ecx
010031F2  |.  0F84 1F020000 je      01003417                         ;  没变动,不需要更新数据与画面
010031F8  |>  833D 44510001>cmp     dword ptr [<bLButtonUp>], 0
010031FF  |.  53            push    ebx
01003200  |.  56            push    esi
01003201  |.  8BD8          mov     ebx, eax
01003203  |.  8BF1          mov     esi, ecx
01003205  |.  8915 18510001 mov     dword ptr [<g_xPos>], edx        ;  更新
0100320B  |.  893D 1C510001 mov     dword ptr [<g_yPos>], edi
01003211  |.  0F84 80010000 je      01003397                         ;  当前情况下,一定跳转
…………
01003397  |> \85DB          test    ebx, ebx                         ;  (ebx,esi)上次移动时所在的格子 (edx,edi)这次移动时...
01003399  |.  7E 34         jle     short 010033CF
0100339B  |.  85F6          test    esi, esi                         ;  上次点越界的判断
0100339D  |.  7E 30         jle     short 010033CF
0100339F  |.  3B1D 34530001 cmp     ebx, dword ptr [<Length>]
010033A5  |.  7F 28         jg      short 010033CF
010033A7  |.  3B35 38530001 cmp     esi, dword ptr [<Height>]
010033AD  |.  7F 20         jg      short 010033CF
010033AF  |.  8BC6          mov     eax, esi
010033B1  |.  C1E0 05       shl     eax, 5
010033B4  |.  F68418 405300>test    byte ptr [eax+ebx+<arMine>], 40
010033BC  |.  75 11         jnz     short 010033CF
010033BE  |.  56            push    esi
010033BF  |.  53            push    ebx
010033C0  |.  E8 DBFDFFFF   call    <PressOff>                       ;  解除上一次点的按下
010033C5  |.  56            push    esi
010033C6  |.  53            push    ebx
010033C7  |.  E8 7AF2FFFF   call    <UpdateBlockPaint>               ;  处理目标位置的贴图
010033CC  |.  8B55 08       mov     edx, dword ptr [ebp+8]
010033CF  |>  85D2          test    edx, edx                         ;  当前点越界的判断
010033D1  |.  7E 42         jle     short 01003415
010033D3  |.  85FF          test    edi, edi
010033D5  |.  7E 3E         jle     short 01003415
010033D7  |.  3B15 34530001 cmp     edx, dword ptr [<Length>]
010033DD  |.  7F 36         jg      short 01003415
010033DF  |.  3B3D 38530001 cmp     edi, dword ptr [<Height>]
010033E5  |.  7F 2E         jg      short 01003415
010033E7  |.  C1E7 05       shl     edi, 5                           ;  点击是否有效的判断(必须是未翻开格)
010033EA  |.  8A8417 405300>mov     al, byte ptr [edi+edx+<arMine>]
010033F1  |.  A8 40         test    al, 40
010033F3  |.  75 20         jnz     short 01003415
010033F5  |.  24 1F         and     al, 1F
010033F7  |.  3C 0E         cmp     al, 0E
010033F9  |.  74 1A         je      short 01003415
010033FB  |.  8B3D 1C510001 mov     edi, dword ptr [<g_yPos>]
01003401  |.  8B35 18510001 mov     esi, dword ptr [<g_xPos>]
01003407  |.  57            push    edi
01003408  |.  56            push    esi
01003409  |.  E8 5DFDFFFF   call    <PressOn>                        ;  这一次点的按下
0100340E  |.  57            push    edi
0100340F  |.  56            push    esi
01003410  |.  E8 31F2FFFF   call    <UpdateBlockPaint>
01003415  |>  5E            pop     esi
01003416  |.  5B            pop     ebx
01003417  |>  5F            pop     edi
01003418  |.  C9            leave
01003419  \.  C2 0800       retn    8

是为了实现,格子按下的效果,如果说左键按下情况下这次悬停在的格子和上次不同,就更改上次格子为不按下,这次格子为按下,关于PressOn和PressOff两个函数,还原后代码如下(我对IDA结果的类型表示不满意):

BYTE WINAPI PressOn(int x, int y){
	BYTE *pElement;
	BYTE Value;
	BYTE ret;

	pElement = (BYTE*)&arMine[y * 32 + x];
	Value = *pElement & 0x1F;
	if (Value == 0x0D){
		Value = 0x09;
	}
	else if (Value == 0x0F){
		Value = 0;
	}
	ret = Value | *pElement & 0xE0;
	*pElement = ret;
	return ret;
}

BYTE WINAPI PressOff(int x, int y){
	BYTE *pElement;
	BYTE Value;
	BYTE ret;

	pElement = (BYTE*)&arMine[y * 32 + x];
	Value = *pElement & 0x1F;
	if (Value == 0x09){
		Value = 0x0D;
	}
	if ((*pElement & 0x1F) == 0){
		Value = 0x0F;
	}
	ret = Value | *pElement & 0xE0;
	*pElement = ret;
	return ret;
}

测试一下即知效果。

在回调函数中,WM_MOUSEMOVE时左键没按下,跳到此处

010020C1  |> \A1 54510001   mov     eax, dword ptr [1005154]
010020C6 >|.  3BC7          cmp     eax, edi

刚好可以在这里修改代码(字节刚好为5,强迫症福利),把前面写代码的位置标签为InjectCode,把10020C1处改为

010020C1     /E9 9A290000   jmp     <InjectCode>
010020C6 >|. |3BC7          cmp     eax, edi

并把10020C6标签为CodeReturn

接下来是InjectCode处代码

01004A60   > \8B45 14       mov     eax, dword ptr [ebp+14]          ;  这里其实是读取lParam
01004A63   .  C1E8 10       shr     eax, 10                          ;  复制于 1002099
01004A66   .  83E8 27       sub     eax, 27
01004A69   .  C1F8 04       sar     eax, 4
01004A6C   .  50            push    eax
01004A6D   .  0FB745 14     movzx   eax, word ptr [ebp+14]
01004A71   .  83C0 04       add     eax, 4
01004A74   .  C1F8 04       sar     eax, 4
01004A77   .  50            push    eax
01004A78   .  58            pop     eax                              ;  接着它来
01004A79   .  59            pop     ecx                              ;  eax=x,ecx=y
01004A7A   .  60            pushad                                   ;  保存寄存器环境
01004A7B   .  85C9          test    ecx, ecx                         ;  越界判断
01004A7D   .  0F8E A9000000 jle     01004B2C
01004A83   .  85C0          test    eax, eax
01004A85   .  0F8E A1000000 jle     01004B2C
01004A8B   .  3B05 34530001 cmp     eax, dword ptr [<Length>]
01004A91   .  0F8F 95000000 jg      01004B2C
01004A97   .  3B0D 38530001 cmp     ecx, dword ptr [<Height>]
01004A9D   .  0F8F 89000000 jg      01004B2C
01004AA3   .  3B05 18510001 cmp     eax, dword ptr [<g_xPos>]
01004AA9   .  75 08         jnz     short 01004AB3
01004AAB   .  3B0D 1C510001 cmp     ecx, dword ptr [<g_yPos>]
01004AB1   .  74 20         je      short 01004AD3
01004AB3   >  50            push    eax                              ;  悬停的格子改变
01004AB4   .  51            push    ecx
01004AB5   .  FF35 1C510001 push    dword ptr [<g_yPos>]
01004ABB   .  FF35 18510001 push    dword ptr [<g_xPos>]
01004AC1   .  E8 80DBFFFF   call    <UpdateBlockPaint>               ;  覆盖上一次的绘制
01004AC6   .  59            pop     ecx
01004AC7   .  58            pop     eax
01004AC8   .  A3 18510001   mov     dword ptr [<g_xPos>], eax        ;  更新值
01004ACD   .  890D 1C510001 mov     dword ptr [<g_yPos>], ecx
01004AD3   >  8BD1          mov     edx, ecx
01004AD5   .  C1E2 05       shl     edx, 5
01004AD8   .  0FBE9410 4053>movsx   edx, byte ptr [eax+edx+<arMine>]
01004AE0   .  80FA 8F       cmp     dl, 8F
01004AE3   .  75 47         jnz     short 01004B2C                   ;  不是雷就跳走
01004AE5   .  51            push    ecx
01004AE6   .  50            push    eax
01004AE7   .  FF35 245B0001 push    dword ptr [1005B24]              ; /hWnd = NULL
01004AED   .  FF15 2C110001 call    dword ptr [<&USER32.GetDC>]      ; \GetDC
01004AF3   .  8BF0          mov     esi, eax
01004AF5   .  58            pop     eax
01004AF6   .  59            pop     ecx
01004AF7   .  C1E1 04       shl     ecx, 4                           ;  BitBlt参数设置
01004AFA   .  83C1 27       add     ecx, 27                          ;  语法参考UpdateBlockPaint
01004AFD   .  C1E0 04       shl     eax, 4
01004B00   .  83E8 04       sub     eax, 4
01004B03   .  68 2000CC00   push    0CC0020                          ; /ROP = SRCCOPY
01004B08   .  6A 00         push    0                                ; |YSrc = 0
01004B0A   .  6A 00         push    0                                ; |XSrc = 0
01004B0C   .  FF35 485A0001 push    dword ptr [1005A48]              ; |hSrcDC = NULL
01004B12   .  6A 10         push    10                               ; |Height = 10 (16.)
01004B14   .  6A 10         push    10                               ; |Width = 10 (16.)
01004B16   .  51            push    ecx                              ; |YDest
01004B17   .  50            push    eax                              ; |XDest
01004B18   .  56            push    esi                              ; |hDestDC
01004B19   .  FF15 5C100001 call    dword ptr [<&GDI32.BitBlt>]      ; \绘制!
01004B1F   .  56            push    esi                              ; /hDC
01004B20   .  FF35 245B0001 push    dword ptr [1005B24]              ; |hWnd = NULL
01004B26   .  FF15 28110001 call    dword ptr [<&USER32.ReleaseDC>]  ; \ReleaseDC
01004B2C   >  61            popad
01004B2D   .  A1 54510001   mov     eax, dword ptr [1005154]         ;  执行覆盖的代码
01004B32   .^ E9 8FD5FFFF   jmp     <CodeReturn>

然后其中UpdateBlockPaint中没有越界判断(其实是我不想写了,这段代码费了我4个小时,Ollydbg打汇编代码就是自闭啊,写到后面由于长度,要把一些短跳改成长跳,awsl)

所以,要把g_xPos和g_yPos直接初始化为0,就没有问题了

完成!

至于代码中UpdateBlockPaint的越界判断,以及执行前对游戏是否开始的判断,可以自行添加


[2020元旦礼物]《看雪论坛精华17》发布!(补齐之前所有遗漏版本)!


文章来源: https://bbs.pediy.com/thread-257328.htm
如有侵权请联系:admin#unsafe.sh