找回密码
 立即注册→加入我们

QQ登录

只需一步,快速开始

搜索
热搜: 下载 VB C 实现 编写
查看: 5609|回复: 2

对xp扫雷程序的部分分析

[复制链接]
发表于 2014-3-23 17:37:48 | 显示全部楼层 |阅读模式

欢迎访问技术宅的结界,请注册或者登录吧。

您需要 登录 才可以下载或查看,没有账号?立即注册→加入我们

×
初学ollydbg,便出此文
将xp的winmine.exe拖到ollydbg2中,F9运行,出现窗口后Alt+W选择扫雷的主窗口,如图所示 pic1.jpg
由于是顶层窗口,因此Parent一栏为Topmost,右键选择000B047C,选择Conditional Breakpoint(条件断点),
输入[DWORD ESP+8] == WM_LBUTTONUP,
  问题1:为什么是 ESP+8?
  问题2:为什么选择WM_LBUTTONUP?

选中一块雷区点击,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--;
  }
}

2.jpg


我们从ollydbg的内存窗口取得1005340的数据,分析以后可以得到雷区数组

3.jpg


  问题3:为什么初始布置的雷区和实际的不一样?如上图,左上角的雷本是没有的,而[3][4]处本来应该有雷,为什么没有?
可以得知8F处的为雷,0F处的不是雷

这里仅对初级水平做了分析,其他的同学们自己研究!


问题1:为什么是 ESP+8?
这个问题式函数栈帧的结构问题,对函数下断点是断点总是断在入口处,而一般情况下(本例如此)进入该函数之前往往是:
  push Z
  push Y
  .......
  push B
  push A
  call func
  ??next??
(该函数原型为stdcall func(A,B,C,...,Z))

call是将下个指令地址压栈,因此进了func以后内存栈帧结构为这样
   低地址
       ??next?? <---ESP
       A        <---ESP+4
       B        <---ESP+8
       ...
       Y
       Z
    高地址

windows的窗口回调过程函数定义为:
    LRESULT CALLBACK WindowProc(  HWND hwnd,      // handle to window
                                 UINT uMsg,      // message identifier
                                 WPARAM wParam,  // first message parameter
                                 LPARAM lParam   // second message parameter);

数因此我们想截获uMsg == WM_LBUTTONUP消息,则判断[ESP+8]处即可

问题2:为什么选择WM_LBUTTONUP?
  这个你可以动手试试就知道了,你在雷区按下(设该点为A)鼠标不放,移动到别处(设该点为B),若B也在雷区,则B处鼠标释放后程序视为挖雷
而如果在外边则什么效果也没有。相反如果你在雷区之外A处按下鼠标不放,拖到雷区之内B处释放,则B处雷挖开。可见是否挖雷与LBUTTONDOWN的位置无关,而与LBUTTONUP位置有关

问题3:其实这2处在用户第一次点击雷区时被winmine偷偷替换掉了,也就是说用户第一次就点中了雷!但是这是不对的,第一次就中雷就没意思了
  所以winmine釜底抽薪,吧这个雷换到别的地方去了

xp版winmine变态的地方在于,点人脸的时候会布雷,而点第一次的时候你永远不会踩中雷,因为他在第一次的时候偷偷把他换掉
如果你第一次就点中了布好的雷上,他就把这个位置换到别的地方,让他不是雷,如果你没点到雷上自然啥都不做,所以第一次永远点不到雷上。。。
[1][1]-[9][9]之外的,都是0x10    [1][1]-[9][9]初始化为0x0F,然后每次你按人脸的时候,布9科雷 布雷就是发现arr[j]&0x80 == 0的时候进行
arr[j] |= 0x80 高位置1
点了第一次,他会判断你是否中了雷,中了,就把雷放到别的地方去,反正总是让你第一次不中雷,中了,他自然再去获取随机数,找到一个没有雷的位置,然后设置为雷,然后把你踩的地方设为非雷


file:///G:\Users\Administrator\AppData\Roaming\Tencent\Users\571652571\QQ\WinTemp\RichOle\]}O_2GAOJK1{3E1_)A{1H(9.jpg


回复

使用道具 举报

发表于 2014-3-26 17:12:27 | 显示全部楼层
{:soso_e179:}
回复 赞! 靠!

使用道具 举报

发表于 2014-3-27 20:20:00 | 显示全部楼层
顶一个。。。。。。。。。。。。。
回复

使用道具 举报

本版积分规则

QQ|Archiver|小黑屋|技术宅的结界 ( 滇ICP备16008837号 )|网站地图

GMT+8, 2024-12-26 21:43 , Processed in 0.044127 second(s), 27 queries , Gzip On.

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表