xp扫雷程序的部分分析


layout: post
title: xp扫雷程序的部分分析
categories: Reverse_Engineering
description: xp扫雷程序的部分分析
keywords: Reverse_Engineering
url: https://lichao890427.github.io/ https://github.com/lichao890427/


简介

  xp扫雷很受欢迎,那么里面有什么古怪呢?让我们进行逆向分析。初学ollydbg,便出此文。

分析布雷过程

  使用调试器附加扫雷程序,选中一块雷区点击,ollydbg便停在入口处(由于程序内部释放焦点,因此我们由winmine切入到ollydbg窗口时)

01001BC9  /.  55            PUSH EBP
01001BCA  |.  8BEC          MOV EBP,ESP
01001BCC  |.  83EC 40       SUB ESP,40
01001BCF  |.  8B55 0C       MOV EDX,DWORD PTR SS:[ARG.2]
01001BD2  |.  8B4D 14       MOV ECX,DWORD PTR SS:[ARG.4]
01001BD5  |.  53            PUSH EBX
01001BD6  |.  56            PUSH ESI
01001BD7  |.  33DB          XOR EBX,EBX
01001BD9  |.  57            PUSH EDI
01001BDA  |.  BE 00020000   MOV ESI,200
01001BDF  |.  43            INC EBX
01001BE0  |.  33FF          XOR EDI,EDI
01001BE2  |.  3BD6          CMP EDX,ESI
01001BE4  |.  0F87 75030000 JA 01001F5F


01001F5F  |> \8D82 FFFDFFFF LEA EAX,[EDX-201]                        ; Switch (messages 201..212, 7 exits)
01001F65  |.  83F8 11       CMP EAX,11
01001F68  |.  0F87 3B020000 JA 010021A9
01001F6E  |.  0FB680 DE2100 MOVZX EAX,BYTE PTR DS:[EAX+10021DE]
01001F75  |.  FF2485 C22100 JMP DWORD PTR DS:[EAX*4+10021C2]

跳几次,就到WM_LBUTTONUP的位置了

01001FDF  |> \33FF          XOR EDI,EDI                              ; Cases 202 (WM_LBUTTONUP), 205 (WM_RBUTTONUP), 208 (WM_MBUTTONUP) of switch winmine.1001F5F
01001FE1  |.  393D 40510001 CMP DWORD PTR DS:[1005140],EDI
01001FE7  |.  0F84 BC010000 JE 010021A9

01001FED  |> /893D 40510001 MOV DWORD PTR DS:[1005140],EDI
01001FF3  |. |FF15 D8100001 CALL DWORD PTR DS:[<&USER32.ReleaseCaptu ; [USER32.ReleaseCapture
01001FF9  |. |841D 00500001 TEST BYTE PTR DS:[1005000],BL
01001FFF  |. |0F84 B6000000 JZ 010020BB
01002005  |. |E8 D7170000   CALL 010037E1                            ; [winmine.010037E1
0100200A  |. |E9 9A010000   JMP 010021A9
0100200F  |> |393D 48510001 CMP DWORD PTR DS:[1005148],EDI           ; Case 204 (WM_RBUTTONDOWN) of switch winmine.1001F5F

  由于下面已经是WM_RBUTTONDOWN的判断了,因此CALL 010021A9是处理WM_LBUTTONUP的函数,我们F7跟进

010037E1  /$  A1 18510001   MOV EAX,DWORD PTR DS:[1005118]           ; winmine.010037E1(guessed void)
010037E6  |.  85C0          TEST EAX,EAX
010037E8  |.  0F8E C8000000 JLE 010038B6
010037EE  |.  8B0D 1C510001 MOV ECX,DWORD PTR DS:[100511C]
010037F4  |.  85C9          TEST ECX,ECX
010037F6  |.  0F8E BA000000 JLE 010038B6
010037FC  |.  3B05 34530001 CMP EAX,DWORD PTR DS:[1005334]
01003802  |.  0F8F AE000000 JG 010038B6
01003808  |.  3B0D 38530001 CMP ECX,DWORD PTR DS:[1005338]
0100380E  |.  0F8F A2000000 JG 010038B6
01003814  |.  53            PUSH EBX
01003815  |.  33DB          XOR EBX,EBX
01003817  |.  43            INC EBX
01003818  |.  833D A4570001 CMP DWORD PTR DS:[10057A4],0             ; 次数?
0100381F  |.  75 4A         JNE SHORT 0100386B
01003821  |.  833D 9C570001 CMP DWORD PTR DS:[100579C],0             ; 时间?
01003828  |.  75 41         JNE SHORT 0100386B
0100382A  |.  53            PUSH EBX                                 ; /Arg1 => 1
0100382B  |.  E8 BD000000   CALL 010038ED                            ; \winmine.010038ED
01003830  |.  FF05 9C570001 INC DWORD PTR DS:[100579C]
01003836  |.  E8 7AF0FFFF   CALL 010028B5                            ; [winmine.010028B5
0100383B  |.  6A 00         PUSH 0                                   ; /TimerFunc = 00000000
0100383D  |.  68 E8030000   PUSH 3E8                                 ; |Timeout = 1000. ms
01003842  |.  53            PUSH EBX                                 ; |TimerID
01003843  |.  FF35 245B0001 PUSH DWORD PTR DS:[1005B24]              ; |hWnd = 000B047C, class = 扫雷, text = 扫雷
01003849  |.  891D 64510001 MOV DWORD PTR DS:[1005164],EBX           ; |
0100384F  |.  FF15 B4100001 CALL DWORD PTR DS:[<&USER32.SetTimer>]   ; \USER32.SetTimer
01003855  |.  85C0          TEST EAX,EAX
01003857  |.  75 07         JNZ SHORT 01003860
01003859  |.  6A 04         PUSH 4                                   ; /Arg1 = 4
0100385B  |.  E8 F0000000   CALL 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:[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 DS:[100511C],ECX
0100387F  |.  A3 18510001   MOV DWORD PTR DS:[1005118],EAX
01003884  |>  833D 44510001 CMP DWORD PTR DS:[1005144],0
0100388B  |.  74 09         JE SHORT 01003896
0100388D  |.  51            PUSH ECX                                 ; /Arg2
0100388E  |.  50            PUSH EAX                                 ; |Arg1
0100388F  |.  E8 23FDFFFF   CALL 010035B7                            ; \winmine.010035B7
01003894  |.  EB 20         JMP SHORT 010038B6
01003896  |>  8BD1          MOV EDX,ECX
01003898  |.  C1E2 05       SHL EDX,5
0100389B  |.  8A9402 405300 MOV DL,BYTE PTR DS:[EAX+EDX+1005340]
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                                 ; /Arg2
010038B0  |.  50            PUSH EAX                                 ; |Arg1
010038B1  |.  E8 5CFCFFFF   CALL 01003512                            ; \winmine.01003512
010038B6  |>  FF35 60510001 PUSH DWORD PTR DS:[1005160]              ; /Arg1 = 0
010038BC  |.  E8 52F0FFFF   CALL 01002913                            ; \winmine.01002913
010038C1  \.  C3            RETN

  由于雷区数据不应该随函数的退出而消失,可以判断数据存放在静态或者全局变量区,所以着重关注DS:[100??]这种地方,多试几次可知
10057A4记录了用户挖雷次数,而100579C处记录着已用时间,真正的布雷函数显然在那几个call中。你在这些汇编代码中发现了SetTimer这个函数,你在游戏时可能已经发现,在用户挖第一个雷时才会计数,因此call 010038ED和call 010028B5处一定有猫腻,第一个点进去以后是这样:

010038ED  /$  833D B8560001 CMP DWORD PTR DS:[10056B8],3             ; winmine.010038ED(guessed Arg1)
010038F4  |.  75 47         JNE SHORT 0100393D
010038F6  |.  8B4424 04     MOV EAX,DWORD PTR SS:[ARG.1]
010038FA  |.  48            DEC EAX                                  ; Switch (cases 1..3, 4 exits)
010038FB  |.  74 2A         JZ SHORT 01003927
010038FD  |.  48            DEC EAX
010038FE  |.  74 15         JZ SHORT 01003915
01003900  |.  48            DEC EAX
01003901  |.  75 3A         JNZ SHORT 0100393D
01003903  |.  68 05000400   PUSH 40005                               ; Case 3 of switch winmine.10038FA
01003908  |.  FF35 305B0001 PUSH DWORD PTR DS:[1005B30]
0100390E  |.  68 B2010000   PUSH 1B2
01003913  |.  EB 22         JMP SHORT 01003937
01003915  |>  68 05000400   PUSH 40005                               ; Case 2 of switch winmine.10038FA
0100391A  |.  FF35 305B0001 PUSH DWORD PTR DS:[1005B30]
01003920  |.  68 B1010000   PUSH 1B1
01003925  |.  EB 10         JMP SHORT 01003937
01003927  |>  68 05000400   PUSH 40005                               ; Case 1 of switch winmine.10038FA
0100392C  |.  FF35 305B0001 PUSH DWORD PTR DS:[1005B30]
01003932  |.  68 B0010000   PUSH 1B0
01003937  |>  FF15 68110001 CALL DWORD PTR DS:[<&WINMM.PlaySoundW>]  ; \WINMM.PlaySoundW
0100393D  \>  C2 0400       RETN 4                                   ; Default case of switch winmine.10038FA

  可以看出这个函数就是在你点击了雷以后发出声音的,没什么价值,但是此时你注意到下面的一个函数用到了rand,对啊,这个随机数在初始化雷阵的时候肯定用得到啊,先下个断点再说。开始新游戏,然后扫雷就断在rand函数处:

01003940  /$  FF15 B0110001 CALL DWORD PTR DS:[<&msvcrt.rand>]       ; [MSVCRT.rand
01003946  |.  99            CDQ
01003947  |.  F77C24 04     IDIV DWORD PTR SS:[ARG.1]
0100394B  |.  8BC2          MOV EAX,EDX
0100394D  \.  C2 0400       RETN 4

  这个函数显然是用来取rand()%N的,运行到winmine代码中,会发现一个循环,很重要:

010036C7  |> /FF35 34530001 /PUSH DWORD PTR DS:[1005334]             ; /Arg1 = 9
010036CD  |. |E8 6E020000   |CALL 01003940                           ; \winmine.01003940
010036D2  |. |FF35 38530001 |PUSH DWORD PTR DS:[1005338]             ; /Arg1 = 9
010036D8  |. |8BF0          |MOV ESI,EAX                             ; |
010036DA  |. |46            |INC ESI                                 ; |
010036DB  |. |E8 60020000   |CALL 01003940                           ; \winmine.01003940
010036E0  |. |40            |INC EAX
010036E1  |. |8BC8          |MOV ECX,EAX
010036E3  |. |C1E1 05       |SHL ECX,5
010036E6  |. |F68431 405300 |TEST BYTE PTR DS:[ESI+ECX+1005340],80
010036EE  |.^\75 D7         |JNZ SHORT 010036C7
010036F0  |. |C1E0 05       |SHL EAX,5
010036F3  |. |8D8430 405300 |LEA EAX,[ESI+EAX+1005340]
010036FA  |. |8008 80       |OR BYTE PTR DS:[EAX],80
010036FD  |. |FF0D 30530001 |DEC DWORD PTR DS:[1005330]
01003703  |.^\75 C2         \JNZ SHORT 010036C7

  这段代码经过逆向分析可以得到伪代码:

  • [1005338]处存放棋盘大小 SIZE
  • [1005330]处存放余下要摆放的雷数 MineToSet
  • [1005340]存放棋盘数据 BYTE DATA[32][32] 我们初级模式9*9只用到[1][1]-[9][9]的数据
while(MineToSet)
{
  int line=rand%SIZE+1,column=rand%SIZE+1;
  if((DATA[line][column]&0x80) == 0)
  {//如果不是雷
    DATA[line][column] |= 0x80;//设置为雷
    MineToSet--;
  }
}

  我们从ollydbg的内存窗口取得1005340的数据,分析以后可以得到雷区数组,可以得知8F处的为雷,0F处的不是雷
[图片上传失败...(image-bb4539-1516376132761)]

问题思考

  • 为什么选择WM_LBUTTONUP?
      这个你可以动手试试就知道了,你在雷区按下(设该点为A)鼠标不放,移动到别处(设该点为B),若B也在雷区,则B处鼠标释放后程序视为挖雷
    而如果在外边则什么效果也没有。相反如果你在雷区之外A处按下鼠标不放,拖到雷区之内B处释放,则B处雷挖开。可见是否挖雷与LBUTTONDOWN的位置无关,而与LBUTTONUP位置有关
  • 为什么初始布置的雷区和实际的不一样?如上图,左上角的雷本是没有的,而[3][4]处本来应该有雷,为什么没有?
      其实这2处在用户第一次点击雷区时被winmine偷偷替换掉了,也就是说用户第一次就点中了雷!但是这是不对的,第一次就中雷就没意思了。所以winmine釜底抽薪,吧这个雷换到别的地方去了。xp版winmine变态的地方在于,点人脸的时候会布雷,而点第一次的时候你永远不会踩中雷,因为他在第一次的时候偷偷把他换掉。如果你第一次就点中了布好的雷上,他就把这个位置换到别的地方,让他不是雷,如果你没点到雷上自然啥都不做,所以第一次永远点不到雷上。。。
      [1][1]-[9][9]之外的,都是0x10 [1][1]-[9][9]初始化为0x0F,然后每次你按人脸的时候,布9科雷 布雷就是发现arr[j]&0x80 == 0的时候进行arr[j] |= 0x80 高位置1,点了第一次,他会判断你是否中了雷,中了,就把雷放到别的地方去,反正总是让你第一次不中雷,中了,他自然再去获取随机数,找到一个没有雷的位置,然后设置为雷,然后把你踩的地方设为非雷

扫雷内挂分析

  先介绍一下xp扫雷内挂用法:

  • 1.打开扫雷界面
  • 2.输入X Y Z Z Y
  • 3.按一下右下角的shift键

  这时,鼠标放在雷区活动,你会看到屏幕左上角有个小光点在一闪一闪。(很小很小的,不容易看。最好桌面是深色的,要不看不清),小光点出现,说明鼠标停在的格子不是雷,没有小光点,就是雷区!

static int index=0;
static WCHAR str[]=L"XYZZY";
static int xCur=-1,yCur=-1;
static xBoxMax,yBoxMax;//雷区大小,初级为9*9,存储在注册表中
static BYTE data[32][32];//雷区数据
switch(uMsg)
{
    case WM_KEYDOWN:
    {
        switch(wParam)
        {
            case VK_SHIFT:
                if(index >= 5)
                        index ^= 0x14;//如果前5字符正确,后面只要index>=5即可
                return DefWindowProc();
            break;
            
            case VK_F4:        
            case VK_F5:
            case VK_F6:
            ...无关代码
            break;
        
            default:
                if(index < 5)
                {
                    if(str[index] == wParam)
                            index++;
                    else
                            index=0;
                }
            }
        }
        break;
    
        case WM_MOUSEMOVE:
        {        
            ......
            if(index)
            {
                if((index == 5 && wParam & MK_CONTROL) || index > 5)
                {
                    int x=LOWORD(lParam);
                    int y=HIWORD(lParam);
                    xCur=x/16;//这个16显然可以推测为一个雷格子的大小了。。。
                    yCur=(y-39)/16;//39同样可以推测为窗口顶部到雷区上边缘长度
                    if(xCur>0 && yCur>0 && xCur<xBoxMax && yCur<yBoxMax)
                    {
                        HDC hdc=GetDC(NULL);
                        //注意下面data[yCur][xCur]是对的,yCur代表行,然而在绘图中却是竖直方向
                        if(data[yCur][xCur]&0x80)//如果是雷
                                SetPixel(hdc,0,0,RGB(0,0,0_);//显示为黑
                        else
                                SetPixel(hdc,0,0,RGB(255,255,255);                                        
                        ReleaseDC(NULL,hdc);
                    }
                }
            }
        }
        break;
    }
    return DefWindowProc();
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容