【文章标题】: 扫雷辅助工具完全制作
【文章作者】: fqucuo
【作者邮箱】: fqucuo@163.com
【作者QQ号】: 389990968
【软件名称】: UnWinmine
【下载地址】: 联系我
【加壳方式】: 无
【保护方式】: 无
【编写语言】: Win32ASM
【使用工具】: PEiD, OD, WinHex
【操作平台】: WinXp Sp2
【软件介绍】: 扫雷游戏的辅助工具,可以秒杀的哦!
【作者声明】: 初学反汇编,就拿这个小程序试手,估计很多搞定了吧,不过作为初学者还是要搞一下!
--------------------------------------------------------------------------------
【详细过程】
  闲话我会在后面说(因为没想好说什么),先来看过程吧!
  
  找到Winmine(扫雷游戏,别告诉我你不知道它在哪儿哦),PEiD一下,获得第一步信息,VC++7.0编写(VC++6.0以上的版本不是很清楚)。
  
  先谈下思路,打开Winmine.exe后,我们知道雷区是由X * Y组成的正方形区域,每次点击都会有改变其目标方块当前状态,那么势必就会调用GetDC,ReleaseDC,Bitblt等GDI函数,好,顺着这个思路继续。
  
  打开OD,载入Winmine,查找->当前模块中的名称(标签),找到Bitblt,bpx Bitblt,将所有调用都下断点,有人要问了,为什么就一定是Bitblt而不是其他的API呢?我只能说一般来说,使用GetDC直接画的不多,多数都是使用这种双缓冲方式绘图,能有效防止闪屏,所以我们直接就找Bitblt了。
  
  F9跑起来,OD这时候断到第一个Bitblt,上下翻翻。。。,呵呵 是不是另一个Bitblt也看到了(我是喜欢上下翻翻的),因为总共就2个Bitblt调用。
  
  
  对比我们可以看到,两个Bitblt上下有明显的区别,
  上面的Bitblt:
  01002646  /$  56            push    esi
  01002647  |.  FF35 245B0001 push    dword ptr [1005B24]              ; /hWnd = 00060598 ('扫雷',class='扫雷')
  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+1005340]  ; |
  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 = 00060598 ('扫雷',class='扫雷')
  0100269D  |.  FF15 28110001 call    dword ptr [<&USER32.ReleaseDC>]  ; \ReleaseDC
  010026A3  |.  5E            pop     esi
  010026A4  \.  C2 0800       retn    8
  
  下面的Bitblt:
  010026A7  /$  55            push    ebp
  010026A8  |.  8BEC          mov     ebp, esp
  010026AA  |.  83EC 0C       sub     esp, 0C
  010026AD  |.  33C0          xor     eax, eax
  010026AF  |.  40            inc     eax
  010026B0  |.  3905 38530001 cmp     dword ptr [1005338], eax
  010026B6  |.  C745 F8 37000>mov     dword ptr [ebp-8], 37
  010026BD  |.  8945 F4       mov     dword ptr [ebp-C], eax
  010026C0  |.  7C 68         jl      short 0100272A
  010026C2  |.  53            push    ebx
  010026C3  |.  56            push    esi
  010026C4  |.  BB 60530001   mov     ebx, 01005360
  010026C9  |>  33F6          /xor     esi, esi
  010026CB  |.  46            |inc     esi
  010026CC  |.  3935 34530001 |cmp     dword ptr [1005334], esi                 ; 比较1
  010026D2  |.  C745 FC 0C000>|mov     dword ptr [ebp-4], 0C
  010026D9  |.  7C 38         |jl      short 01002713                                       
  010026DB  |>  68 2000CC00   |/push    0CC0020                        ; /ROP = SRCCOPY
  010026E0  |.  33C0          ||xor     eax, eax                       ; |
  010026E2  |.  8A0433        ||mov     al, byte ptr [ebx+esi]         ; |
  010026E5  |.  6A 00         ||push    0                              ; |YSrc = 0
  010026E7  |.  6A 00         ||push    0                              ; |XSrc = 0
  010026E9  |.  83E0 1F       ||and     eax, 1F                        ; |
  010026EC  |.  FF3485 205A00>||push    dword ptr [eax*4+1005A20]      ; |hSrcDC
  010026F3  |.  6A 10         ||push    10                             ; |Height = 10 (16.)
  010026F5  |.  6A 10         ||push    10                             ; |Width = 10 (16.)
  010026F7  |.  FF75 F8       ||push    dword ptr [ebp-8]              ; |YDest
  010026FA  |.  FF75 FC       ||push    dword ptr [ebp-4]              ; |XDest
  010026FD  |.  FF75 08       ||push    dword ptr [ebp+8]              ; |hDestDC
  01002700  |.  FF15 5C100001 ||call    dword ptr [<&GDI32.BitBlt>]    ; \BitBlt // 第二个断点,先被断下的
  01002706  |.  8345 FC 10    ||add     dword ptr [ebp-4], 10
  0100270A  |.  46            ||inc     esi
  0100270B  |.  3B35 34530001 ||cmp     esi, dword ptr [1005334]                ; 比较2
  01002711  |.^ 7E C8         |\jle     short 010026DB                                       ; 跳转2
  01002713  |>  FF45 F4       |inc     dword ptr [ebp-C]
  01002716  |.  8B45 F4       |mov     eax, dword ptr [ebp-C]
  01002719  |.  8345 F8 10    |add     dword ptr [ebp-8], 10
  0100271D  |.  83C3 20       |add     ebx, 20
  01002720  |.  3B05 38530001 |cmp     eax, dword ptr [1005338]
  01002726  |.^ 7E A1         \jle     short 010026C9                                          ; 跳转1
  01002728  |.  5E            pop     esi
  01002729  |.  5B            pop     ebx
  0100272A  |>  C9            leave
  0100272B  \.  C2 0400       retn    4
  
  可以看到在先被断下来的Bitblt有明显的判断和跳转,我们可以这样理解:在一个X*Y的矩阵(雷区)中,分别处理每个元素的方式,常理是一个嵌套循环。好,为了验证这个道理,我们将带“循环嵌套”的Bitblt断点禁用,然后F9跑起来,然后在扫雷窗口中随便点一个方块,被断下,再跑,这个时候再去点扫雷窗口就不行了,当鼠标经过的时候就会断下,可以初步判断出这个Bitblt是用来重绘的,那么我们就取消这个Bitblt的断点,启用带“循环嵌套的”Bitblt断点,不过这个时候再跑将处于无限断下状态。o(∩_∩)o... 
  
  当时也挺郁闷的,不过看到这段反汇编不是很长,就决定尝试推一下源码,好,说干就干!
    010026AD  |.  33C0          xor     eax, eax
    010026AF  |.  40            inc     eax
    010026B0  |.  3905 38530001 cmp     dword ptr [1005338], eax
    
  在OD内存中,我们可以查到1005338地址内容为:09 00 00 00,暂时不知道干什么的,先不管它
    010026B6  |.  C745 F8 37000>mov     dword ptr [ebp-8], 37
    010026BD  |.  8945 F4       mov     dword ptr [ebp-C], eax
    010026C0  |.  7C 68         jl      short 0100272A
    
  [ebp - 8] = 37 [ebp - c] = eax = 1;  暂时也不清楚这两个变量是干什么的,继续
    010026C4  |.  BB 60530001   mov     ebx, 01005360
  ebx = 1005360,可以明显看出是个地址,先在内存窗口中看一下,发现了一些一大块的0F 10 8F 等组成的内存值
    01005330  0A 00 00 00 09 00 00 00 09 00 00 00 00 00 00 00  ................
    01005340  10 10 10 10 10 10 10 10 10 10 10 0F 0F 0F 0F 0F  
    01005350  0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F  
    01005360  10 8F 8F 0F 0F 0F 0F 0F 0F 0F 10 0F 0F 0F 0F 0F  
    01005370  0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F  
    01005380  10 0F 0F 0F 0F 0F 0F 0F 0F 0F 10 0F 0F 0F 0F 0F  
    01005390  0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F  
    010053A0  10 0F 0F 0F 8F 0F 0F 0F 0F 0F 10 0F 0F 0F 0F 0F  ?
    010053B0  0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F  
    010053C0  10 0F 0F 0F 0F 0F 8F 0F 0F 0F 10 0F 0F 0F 0F 0F  ?
    010053D0  0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F  
    010053E0  10 8F 0F 8F 8F 0F 0F 0F 0F 0F 10 0F 0F 0F 0F 0F  ?
    010053F0  0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F  
    01005400  10 0F 8F 0F 0F 0F 0F 0F 0F 0F 10 0F 0F 0F 0F 0F  ?
    01005410  0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F  
    01005420  10 0F 0F 0F 0F 0F 0F 0F 0F 0F 10 0F 0F 0F 0F 0F  
    01005430  0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F  
    01005440  10 0F 0F 8F 0F 0F 0F 0F 0F 0F 10 0F 0F 0F 0F 0F  ?
  .......
  (其实这就是双缓冲对应方块的内存,往下我们再来证明这一点)
  
    010026C9  |>  33F6          /xor     esi, esi
    010026CB  |.  46            |inc     esi
    010026CC  |.  3935 34530001 |cmp     dword ptr [1005334], esi                 ; 比较x
    010026D2  |.  C745 FC 0C000>|mov     dword ptr [ebp-4], 0C
    010026D9  |.  7C 38         |jl      short 01002713                                       
    010026DB  |>  68 2000CC00   |/push    0CC0020                        ; /ROP = SRCCOPY
    010026E0  |.  33C0          ||xor     eax, eax                       ; |
    010026E2  |.  8A0433        ||mov     al, byte ptr [ebx+esi]         ; |
    010026E5  |.  6A 00         ||push    0                              ; |YSrc = 0
    010026E7  |.  6A 00         ||push    0                              ; |XSrc = 0
    010026E9  |.  83E0 1F       ||and     eax, 1F                        ; |
    010026EC  |.  FF3485 205A00>||push    dword ptr [eax*4+1005A20]      ; |hSrcDC
    010026F3  |.  6A 10         ||push    10                             ; |Height = 10 (16.)
    010026F5  |.  6A 10         ||push    10                             ; |Width = 10 (16.)
    010026F7  |.  FF75 F8       ||push    dword ptr [ebp-8]              ; |YDest
    010026FA  |.  FF75 FC       ||push    dword ptr [ebp-4]              ; |XDest
    010026FD  |.  FF75 08       ||push    dword ptr [ebp+8]              ; |hDestDC
    01002700  |.  FF15 5C100001 ||call    dword ptr [<&GDI32.BitBlt>]    ; \BitBlt // 第二个断点
    01002706  |.  8345 FC 10    ||add     dword ptr [ebp-4], 10
    0100270A  |.  46            ||inc     esi
    0100270B  |.  3B35 34530001 ||cmp     esi, dword ptr [1005334]                ; 比较x
    01002711  |.^ 7E C8         |\jle     short 010026DB                                       ; 跳转x
    01002713  |>  FF45 F4       |inc     dword ptr [ebp-C]
    01002716  |.  8B45 F4       |mov     eax, dword ptr [ebp-C]
    01002719  |.  8345 F8 10    |add     dword ptr [ebp-8], 10
    0100271D  |.  83C3 20       |add     ebx, 20
    01002720  |.  3B05 38530001 |cmp     eax, dword ptr [1005338]                     ; 比较y
    01002726  |.^ 7E A1         \jle     short 010026C9                                           ; 跳转y
  
  通过OD的跳转标记我们可以明显的看到循环嵌套的过程,并且根据矩阵X * Y的理论,我们可以得知,内层循环是一个x轴(列坐标),外层循环是个y轴(行坐标),具体是使用了for循环或者是while循环我们不必关心,就当他是for循环好了,实现功能为主,我们不妨定义两个变量x 和 y,并且通过
    010026C9  |>  33F6          /xor     esi, esi
    010026CB  |.  46            |inc     esi                                                                    ;x = 1,x++
    
    010026AD  |.  33C0          xor     eax, eax
    010026AF  |.  40            inc     eax                                                                       ;y = 1
    ......
    01002713  |>  FF45 F4       |inc     dword ptr [ebp-C]                                      ;y++
    01002716  |.  8B45 F4       |mov     eax, dword ptr [ebp-C]
    
    01002720  |.  3B05 38530001 |cmp     eax, dword ptr [1005338]                     ; 比较y
    01002726  |.^ 7E A1         \jle     short 010026C9                                           ; 跳转y
    
    0100270B  |.  3B35 34530001 ||cmp     esi, dword ptr [1005334]                  ; 比较x
    01002711  |.^ 7E C8         |\jle     short 010026DB                                         ; 跳转x
  我们得知两个循环都是从1开始,步长为1,jle 推出比较运算符为 <=, 内存窗口查找[1005338] 与[1005334] 的值,得到
    [1005338]  = 9 ; y轴
    [1005334]  = 9 ; x轴
  可以知道矩阵的y轴界限为9,x轴界限为9,也知道了这两个内存地址是用来存放矩阵的高和宽的值,那么我们就有(开始我是用Win32MASM环境下写的,为了直观我用C语法表示)
  定义变量int nHeight = [1005338], nWidth = [1005334];
    int nHeight, nWidth; // 这里不赋初值是因为现在只是反汇编, 后面写工具的时候会从内存中读初值
    for(int y = 1; y <= nHeight; y++)
    {
      for(int x = 1; x <= nWidth; x++)
      {
        ;
      }
    }
  
  我们继续分析循环体内部的代码
    010026DB  |>  68 2000CC00   |/push    0CC0020                        ; /ROP = SRCCOPY
    010026E0  |.  33C0          ||xor     eax, eax                       ; |
    010026E2  |.  8A0433        ||mov     al, byte ptr [ebx+esi]         ; |
    010026E5  |.  6A 00         ||push    0                              ; |YSrc = 0
    010026E7  |.  6A 00         ||push    0                              ; |XSrc = 0
    010026E9  |.  83E0 1F       ||and     eax, 1F                        ; |
    010026EC  |.  FF3485 205A00>||push    dword ptr [eax*4+1005A20]      ; |hSrcDC
    010026F3  |.  6A 10         ||push    10                             ; |Height = 10 (16.)
    010026F5  |.  6A 10         ||push    10                             ; |Width = 10 (16.)
    010026F7  |.  FF75 F8       ||push    dword ptr [ebp-8]              ; |YDest
    010026FA  |.  FF75 FC       ||push    dword ptr [ebp-4]              ; |XDest
    010026FD  |.  FF75 08       ||push    dword ptr [ebp+8]              ; |hDestDC
    01002700  |.  FF15 5C100001 ||call    dword ptr [<&GDI32.BitBlt>]    ; \BitBlt // 第二个断点
    01002706  |.  8345 FC 10    ||add     dword ptr [ebp-4], 10
  这是内层循环的代码
  
  可以看到主要就是调用了一个Bitblt,现在我们还原Bitblt,通过查看MSDN,可以得知其声明方式
    BOOL BitBlt(
    HDC hdcDest, // handle to destination DC
    int nXDest,  // x-coord of destination upper-left corner
    int nYDest,  // y-coord of destination upper-left corner
    int nWidth,  // width of destination rectangle
    int nHeight, // height of destination rectangle
    HDC hdcSrc,  // handle to source DC
    int nXSrc,   // x-coordinate of source upper-left corner
    int nYSrc,   // y-coordinate of source upper-left corner
    DWORD dwRop  // raster operation code
  );
  
  那么我们就有:
    Bitblt(hDestDC,//目的DC
               XDest, // 目的x坐标
               YDest, // 目的y坐标
               10, // 
               10, // 重绘区域的高和宽
               hSrcDC, // 源DC
               0,
               0,
               SRCCOPY);// 指定操作方式
    
  这里我们可以得到[ebp - 4] = XDest, [ebp - 8] = YDest,那么我们可以解释
    010026D2  |.  C745 FC 0C000>|mov     dword ptr [ebp-4], 0C
    010026B6  |.  C745 F8 37000>mov     dword ptr [ebp-8], 37
    
  这两句估计是用来确定有效写入区域的x轴与y轴坐标的,(因为要算上边框和显示时间和排雷数区域的高度)
  
  这时我们在定义变量XDest和YDest
    int XDest = 0, YDest = 0x37;  
    int nHeight, nWidth; 
    for(int y = 1; y <= nHeight; y++)
    {
      XDest  = 0x0C;
      for(int x = 1; x <= nWidth; x++)
      {
        Bitblt(hDestDC, XDest, YDest, 0x10, 0x10, hSrcDC, 0, 0, SRCCOPY);
        // 01002706  |.  8345 FC 10    ||add     dword ptr [ebp-4], 10
        XDest += 0x10;
      }
      //  01002719  |.  8345 F8 10    |add     dword ptr [ebp-8], 10
      YDest += 0x10;
    }
    
  基本上就这些,下面我们来看下关键的ebx
  上面我们知道ebx用来存储缓冲区地址首地址,其实整个矩阵首地址并不是1005360,而是1005340,我们可以试想一下,矩阵内部是有效数据,但是对于一个矩阵游戏来说,其外层会有一圈定义值限制其范围,通过内存我们可以得知其定义为0x10,这个并不是重点,知道即可.
  
  那么ebx到底是做什么的呢?
    010026E0  |.  33C0          ||xor     eax, eax                       ; |
    010026E2  |.  8A0433        ||mov     al, byte ptr [ebx+esi]         ; |
    010026E5  |.  6A 00         ||push    0                              ; |YSrc = 0
    010026E7  |.  6A 00         ||push    0                              ; |XSrc = 0
    010026E9  |.  83E0 1F       ||and     eax, 1F                        ; |
    010026EC  |.  FF3485 205A00>||push    dword ptr [eax*4+1005A20]      ; |hSrcDC
    
  通过反汇编我们可以看到它是在做一个位与查表的方式获取源资源DC句柄,也就得到了我们平时玩游戏是提示数字、插旗、?号等等,这里我们不去深究,知道即可(把地雷DC换到数字DC里也是挺有意思的...)
  有:
    0100271D  |.  83C3 20       |add     ebx, 20
     010026E2  |.  8A0433        ||mov     al, byte ptr [ebx+esi]         ; |
  我们得知每次地址+0x20,那么我们就定义地址变量dwBaseAddress = 1005360 = ebx, 内层循环每次 +x(+esi),外层循环每次+0x20
    int XDest = 0, YDest = 0;  
    int nHeight, nWidth; 
    DWORD dwBaseAddress = 0x1005360;// 这里我们就用一个地址常量了
    DWORD dwTempBaseAddress = 0; //临时变量
    for(int y = 1; y <= nHeight; y++)
    {
      XDest  = 0x0C;
      dwTempBaseAddress = dwBaseAddress;
      for(int x = 1; x <= nWidth; x++)
      {
        dwTempBaseAddress += x;
        ...// 其源资源DC查表过程与本例无关,所以省去
        Bitblt(hDestDC, XDest, YDest, 0x10, 0x10, hSrcDC, 0, 0, SRCCOPY);
        // 01002706  |.  8345 FC 10    ||add     dword ptr [ebp-4], 10
        XDest += 0x10;
      }
      // 01002719  |.  8345 F8 10    |add     dword ptr [ebp-8], 10
      YDest += 0x10;
      dwBaseAddress  += 0x20;
    }
  呼。。。反汇编分析基本上就这些了
  
  这个时候我们可以新开一个Winmine,打开WinHex查看内存,找到地址1005360,可以看到很多10 0F 8F等组成的数值,这时候我们每点一个方块,就看一次内存,我们可以得到
    10 为边框
    8F 为有雷且并未点取的
    0F 为无雷且并未点取的
    4X 为点取了无雷区域,x为1-9的数值,也就是游戏中的提示数字
    8E 为插旗并有雷 0E为插旗但无雷
    8D 为问号并有雷 0D为问号但无雷
    ....
  
  
  好了,知道这些就够了,我们现在就可以做插旗和秒杀的功能了,具体的实现代码偶就不多说了
  
--------------------------------------------------------------------------------
【经验总结】
  惭愧啊,学逆向2个月了,在看雪偶还是第一次发帖,无奈本人水平太菜,还望高手多多指教啊~,千万别扔鸡蛋哦!
  希望高手、和我一样的菜菜一起进步,我的QQ:389990968 
  在实现代码中我用的是模拟鼠标操作,不知道有没有更好的办法,过程中一定会有不正确的地方,还望高手指点!
  感谢看雪提供这么好的平台,希望越办越好!没了。。。
  
--------------------------------------------------------------------------------
【版权声明】: 本文原创于看雪技术论坛, 转载请注明作者并保持文章的完整, 谢谢!

                                                       2007年12月03日 22:50:33