对xp扫雷程序的部分分析
初学ollydbg,便出此文将xp的winmine.exe拖到ollydbg2中,F9运行,出现窗口后Alt+W选择扫雷的主窗口,如图所示
由于是顶层窗口,因此Parent一栏为Topmost,右键选择000B047C,选择Conditional Breakpoint(条件断点),
输入 == 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:
01001BD2|.8B4D 14 MOV ECX,DWORD PTR SS:
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, ; 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:
01001F75|.FF2485 C22100 JMP DWORD PTR DS:
跳几次,就到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:,EDI
01001FE7|.0F84 BC010000 JE 010021A9
01001FED|> /893D 40510001 MOV DWORD PTR DS:,EDI
01001FF3|. |FF15 D8100001 CALL DWORD PTR DS:[<&USER32.ReleaseCaptu ; [USER32.ReleaseCapture
01001FF9|. |841D 00500001 TEST BYTE PTR DS:,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:,EDI ; Case 204 (WM_RBUTTONDOWN) of switch winmine.1001F5F
由于下面已经是WM_RBUTTONDOWN的判断了,因此CALL 010021A9是处理WM_LBUTTONUP的函数,我们F7跟进
010037E1/$A1 18510001 MOV EAX,DWORD PTR DS: ; winmine.010037E1(guessed void)
010037E6|.85C0 TEST EAX,EAX
010037E8|.0F8E C8000000 JLE 010038B6
010037EE|.8B0D 1C510001 MOV ECX,DWORD PTR DS:
010037F4|.85C9 TEST ECX,ECX
010037F6|.0F8E BA000000 JLE 010038B6
010037FC|.3B05 34530001 CMP EAX,DWORD PTR DS:
01003802|.0F8F AE000000 JG 010038B6
01003808|.3B0D 38530001 CMP ECX,DWORD PTR DS:
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:,0 ; 次数?
0100381F|.75 4A JNE SHORT 0100386B
01003821|.833D 9C570001 CMP DWORD PTR DS:,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:
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: ; |hWnd = 000B047C, class = 扫雷, text = 扫雷
01003849|.891D 64510001 MOV DWORD PTR DS:,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:
01003865|.8B0D 1C510001 MOV ECX,DWORD PTR DS:
0100386B|>841D 00500001 TEST BYTE PTR DS:,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:,ECX
0100387F|.A3 18510001 MOV DWORD PTR DS:,EAX
01003884|>833D 44510001 CMP DWORD PTR DS:,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:
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: ; /Arg1 = 0
010038BC|.E8 52F0FFFF CALL 01002913 ; \winmine.01002913
010038C1\.C3 RETN
由于雷区数据不应该随函数的退出而消失,可以判断数据存放在静态或者全局变量区,所以着重关注DS:这种地方,多试几次可知
10057A4记录了用户挖雷次数,而100579C处记录着已用时间,真正的布雷函数显然在那几个call中。你在这些汇编代码中发现了SetTimer这个函数,你在游戏时可能已经发现,在用户挖第一个雷时才会计数,因此call 010038ED和call 010028B5处一定有猫腻
第一个点进去以后是这样:
010038ED/$833D B8560001 CMP DWORD PTR DS:,3 ; winmine.010038ED(guessed Arg1)
010038F4|.75 47 JNE SHORT 0100393D
010038F6|.8B4424 04 MOV EAX,DWORD PTR SS:
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:
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:
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:
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:
0100394B|.8BC2 MOV EAX,EDX
0100394D\.C2 0400 RETN 4
这个函数显然是用来取rand()%N的
运行到winmine代码中,
会发现一个循环,很重要
010036C7|> /FF35 34530001 /PUSH DWORD PTR DS: ; /Arg1 = 9
010036CD|. |E8 6E020000 |CALL 01003940 ; \winmine.01003940
010036D2|. |FF35 38530001 |PUSH DWORD PTR DS: ; /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:,80
010036EE|.^\75 D7 |JNZ SHORT 010036C7
010036F0|. |C1E0 05 |SHL EAX,5
010036F3|. |8D8430 405300 |LEA EAX,
010036FA|. |8008 80 |OR BYTE PTR DS:,80
010036FD|. |FF0D 30530001 |DEC DWORD PTR DS:
01003703|.^\75 C2 \JNZ SHORT 010036C7
这段代码经过逆向分析可以得到伪代码:
处存放棋盘大小SIZE
处存放余下要摆放的雷数MineToSet
存放棋盘数据BYTE DATA 我们初级模式9*9只用到-的数据
while(MineToSet)
{
int line=rand%SIZE+1,column=rand%SIZE+1;
if((DATA&0x80) == 0)
{//如果不是雷
DATA |= 0x80;//设置为雷
MineToSet--;
}
}
我们从ollydbg的内存窗口取得1005340的数据,分析以后可以得到雷区数组
问题3:为什么初始布置的雷区和实际的不一样?如上图,左上角的雷本是没有的,而处本来应该有雷,为什么没有?
可以得知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消息,则判断处即可
问题2:为什么选择WM_LBUTTONUP?
这个你可以动手试试就知道了,你在雷区按下(设该点为A)鼠标不放,移动到别处(设该点为B),若B也在雷区,则B处鼠标释放后程序视为挖雷
而如果在外边则什么效果也没有。相反如果你在雷区之外A处按下鼠标不放,拖到雷区之内B处释放,则B处雷挖开。可见是否挖雷与LBUTTONDOWN的位置无关,而与LBUTTONUP位置有关
问题3:其实这2处在用户第一次点击雷区时被winmine偷偷替换掉了,也就是说用户第一次就点中了雷!但是这是不对的,第一次就中雷就没意思了
所以winmine釜底抽薪,吧这个雷换到别的地方去了
xp版winmine变态的地方在于,点人脸的时候会布雷,而点第一次的时候你永远不会踩中雷,因为他在第一次的时候偷偷把他换掉
如果你第一次就点中了布好的雷上,他就把这个位置换到别的地方,让他不是雷,如果你没点到雷上自然啥都不做,所以第一次永远点不到雷上。。。
-之外的,都是0x10 -初始化为0x0F,然后每次你按人脸的时候,布9科雷 布雷就是发现arr&0x80 == 0的时候进行
arr |= 0x80 高位置1
点了第一次,他会判断你是否中了雷,中了,就把雷放到别的地方去,反正总是让你第一次不中雷,中了,他自然再去获取随机数,找到一个没有雷的位置,然后设置为雷,然后把你踩的地方设为非雷
file:///G:\Users\Administrator\AppData\Roaming\Tencent\Users\571652571\QQ\WinTemp\RichOle\]}O_2GAOJK1{3E1_)A{1H(9.jpg
{:soso_e179:} 顶一个。。。。。。。。。。。。。
页:
[1]