逆向扫雷笔记
好吧我承认放暑假我懒了。。。有一个多月没更新了。前几天看到久违的“纸牌”,想到了搞一下扫雷。其实三天前我就搞定了,但是紧接着我就往学校赶了,之后又感冒发烧拖到现在。现在我的体温还没有降下去,故后面的语言组织可能有些混乱。
之所以选择了扫雷首先是因为这是一个SDK编写程序,结构清晰、明了,可以很快的找到想要找的函数,而且游戏的运行模式也比较简单,就是在点下去的时候生 成一个“雷表”,之后获取鼠标的消息,根据雷表的信息和鼠标的消息来决定调用的函数。并且,分析一个SDK程序的汇编代码可以帮助我更好的理解 Windows程序运行的过程。
先看看程序运行的方式。运行扫雷,在下面按下鼠标左键只会使格子变成凹陷状,不会发生任何事。当鼠标左键弹起时,就会翻开相应格子。按下鼠标右键时会在相应格子出插上旗子。知道这些就足够了。
好了,知道了这些,可以开始动手了。首先想到的就是下消息断点。用OD载入扫雷,F9运行,用Spy++来获取窗口句柄,点上面的“W”看到相应句柄,右键下消息断点202 WM_LBOTTOMUP,结果弹出了这个对话框。。。
看来是不能直接下消息断点了。重新载入,停在这儿
01003E21 > $ 6A 70 push 70
01003E23 . 68 90130001 push 01001390
01003E28 . E8 DF010000 call 0100400C
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
01003E3D . 75 1F jnz short 01003E5E
没什么用往下拉看到这儿
01003F86 > \6A 0A push 0A
01003F88 . 58 pop eax
01003F89 > 50 push eax
01003F8A . 56 push esi
01003F8B . 53 push ebx
01003F8C . 53 push ebx
01003F8D . FFD7 call edi
01003F8F . 50 push eax ; |Arg1
01003F90 . E8 5BE2FFFF call 010021F0 ; \winmine.010021F0
在01003F90 . E8 5BE2FFFF call 010021F0 ; \winmine.010021F0
处跟进去。期间有个小插曲,我用笔记本可以顺利的F4到那儿,但是用台式机就会在01003F17 . FF15 8C110001 call dword ptr [<&msvcrt.__getmainarg>; msvcrt.__getmainargs
(好像)进入死循环。全程跟踪后发现进入了异常处理,然后返回的程序开始出,反复循环。但是用台式机的虚拟机却 又可以正常F4。至今不知为什么,希望有高手看到可以给予一点指示。小弟跪谢。后来注意到台式机载入后可以F9运行,于是尝试在01003F90 . E8 5BE2FFFF call 010021F0 ; \winmine.010021F0
处下断点,果然可以正常断下来。
跟进去一看就能发现是我们所熟悉的WinMain函数。往下拉找到窗口注册函数,因为我们要找的是消息处理函数,窗口注册函数调用了WNDCLASS结构的指针,而消息处理函数是WNDCLASS结果的成员。我们看到
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
得知WNDCLASS地址在eax里。获取eax内容得知WNDCLASS地址为0006FE98。因为消息处理函数的地址是WNDCLASS结构的第二 个成员所以db 0006FE98 + 4h,可以看到0006FE9C C9 1B 00 01 00 00 00 00 ?......
所以消息处理函数的地址为01001BC9。转到01001BC9,这就是我们的消息处理函数了~
接下来找到我们需要的API 202 WM_LBUTTONUP和204 WM_RBUTTONDOWN
01001FDF |> \33FF xor edi, edi ; Cases 202 (WM_LBUTTONUP),205 (WM_RBUTTONUP),208 (WM_MBUTTONUP) of switch 01001F5F
然后
0100200F |> \393D 48510001 cmp dword ptr [1005148], edi ; Case 204 (WM_RBUTTONDOWN) of switch 01001F5F
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.ReleaseCaptu>; [ReleaseCapture
01001FF9 |. 841D 00500001 test byte ptr [1005000], bl
01001FFF |. 0F84 B6000000 je 010020BB
01002005 |. E8 D7170000 call 010037E101002005 |. E8 D7170000 call 010037E1
,跟进去。之后反复跟踪,发现函数
010038B1 |. E8 5CFCFFFF call 01003512
是无论炸死与否都调用的函数。跟进去反复跟踪和F9,发现炸死时使用了跳转
01003536 |. /75 50 jnz short 01003588
接下来就是改变跳转位置让其不调用炸死函数。一开始我然他调到一个什么都不做返回的地方(主要堆栈平衡),运行后发现的却炸不死了,但是按过的雷看不出来,效果不好于是选择的跳到插旗函数。
0100200F |> \393D 48510001 cmp dword ptr [1005148], edi ; Case 204 (WM_RBUTTONDOWN) of switch 01001F5F
01002015 |.^ 0F85 69FFFFFF jnz 01001F84
0100201B |. 841D 00500001 test byte ptr [1005000], bl
01002021 |. 0F84 82010000 je 010021A9
01002027 |. 393D 40510001 cmp dword ptr [1005140], edi
0100202D |. 74 27 je short 01002056
0100202F |. 6A FD push -3 ; /Arg2 = FFFFFFFD
01002031 |. 6A FD push -3 ; |Arg1 = FFFFFFFD
01002033 |. E8 9C110000 call 010031D4 ; \winmine.010031D4
01002038 |. FF75 14 push dword ptr [ebp+14] ; /lParam
0100203B |. 891D 44510001 mov dword ptr [1005144], ebx ; |
反复跟踪后发现一定会使用跳转01002059 |. /0F84 09010000 je 01002168
跳转到
01002168 |> \393D 4C510001 cmp dword ptr [100514C], edi
0100216E |.^ 0F85 EAFAFFFF jnz 01001C5E
01002174 |. 8B45 14 mov eax, dword ptr [ebp+14]
01002177 |. C1E8 10 shr eax, 10
0100217A |. 83E8 27 sub eax, 27
0100217D |. C1F8 04 sar eax, 4
01002180 |. 50 push eax
01002181 |. 0FB745 14 movzx eax, word ptr [ebp+14]
01002185 |. 83C0 04 add eax, 4
01002188 |. C1F8 04 sar eax, 4
0100218B |. 50 push eax
0100218C |. E8 BE150000 call 0100374F
那么
01002174 |. 8B45 14 mov eax, dword ptr [ebp+14]
01002177 |. C1E8 10 shr eax, 10
0100217A |. 83E8 27 sub eax, 27
0100217D |. C1F8 04 sar eax, 4
01002180 |. 50 push eax
01002181 |. 0FB745 14 movzx eax, word ptr [ebp+14]
01002185 |. 83C0 04 add eax, 4
01002188 |. C1F8 04 sar eax, 4
0100218B |. 50 push eax
0100218C |. E8 BE150000 call 0100374F
就是调用插旗函数的过程。然后把前面找到的跳转01003536 |. /75 50 jnz short 01003588
更改成跳到一个空的地方然后平衡堆栈弄掉堆栈里多余的东西(我当时脑子发昏pop了8次,其实只要加ebp就好了)。然后jmp到 01002174 |. 8B45 14 、mov 、eax, dword ptr [ebp+14]
保存。收工。
接下来打开那个改过的扫雷后只需要一阵狂点可以创造记录哦(原则上不赞成)~