若干个游戏辅助的分析手记(三)
----飞五棋牌平台幸运骰子自动投注
目标进程进程名(幸运骰子):Sicbo.exe
OK,开工。
进入游戏后,用OD附加,随后设断点:bp send,然后下注。没有断下,依次断WSASend等函数都断不下来。此路不通,祭出CE。
CE搜索投注额,最后定为到下面代码:
0040303A - 89 18 - mov [eax],ebx // 修改了自己在东风的押注数额
先走马观花逛一圈,一路回溯会先经过GamePubl.dll模块,最后会回溯到ProcMsg模块。在ProcMsg模块中是一个大的消息循环。这几个模块的相关CALL浪费了分析了整整一个通宵的时间,=_=!最后终于搞明白了它的大体框架,但是让我崩溃的是----随后得出的结论这几个CALL仅仅是根据消息更新界面(即相当于CE搜到的是界面的"Text"),所以得另寻方法。
哎,一晚上的体力活没想最后成了杨白劳。睡一觉起来再说。
zzZZZ.。o0O...
一觉起来顿觉神清气爽。昨晚运气不佳,浪费了一个通宵。这活不但要看体力,还有一部分运气也是非常重要的。好了,不灰心。继续开工。
整理下思路----经过昨天一个晚上的分析,既然知道了上面是根据消息来更新界面,那么得找到消息的源头。在OD中对Sicbo.exe的主窗口下WM_LBUTTONUP消息断点,断下来后,返回程序领空,来到下面代码:
10011AC0 >|$ 53 PUSH EBX 10011AC1 |. 55 PUSH EBP 10011AC2 |. 56 PUSH ESI 10011AC3 |. 8BF1 MOV ESI,ECX 10011AC5 |. 8B86 08020000 MOV EAX,DWORD PTR DS:[ESI+208] 10011ACB |. 33DB XOR EBX,EBX 10011ACD |. 85C0 TEST EAX,EAX 10011ACF |. 57 PUSH EDI 10011AD0 |. 0F8E 95000000 JLE GamePubl.10011B6B 10011AD6 |. 8B6C24 1C MOV EBP,DWORD PTR SS:[ESP+1C] 10011ADA |. 8D9B 00000000 LEA EBX,DWORD PTR DS:[EBX] 10011AE0 |> 85DB /TEST EBX,EBX 10011AE2 |. 0F8C 9E000000 |JL GamePubl.10011B86 10011AE8 |. 3B9E 08020000 |CMP EBX,DWORD PTR DS:[ESI+208] 10011AEE |. 0F8D 92000000 |JGE GamePubl.10011B86 10011AF4 |. 8B86 FC010000 |MOV EAX,DWORD PTR DS:[ESI+1FC] 10011AFA |. 8B0C98 |MOV ECX,DWORD PTR DS:[EAX+EBX*4] 10011AFD |. 8A41 56 |MOV AL,BYTE PTR DS:[ECX+56] 10011B00 |. 84C0 |TEST AL,AL 10011B02 |. 8D3C9D 000000>|LEA EDI,DWORD PTR DS:[EBX*4] 10011B09 |. 74 51 |JE SHORT GamePubl.10011B5C 10011B0B |. 3B9E 08020000 |CMP EBX,DWORD PTR DS:[ESI+208] 10011B11 |. 7D 73 |JGE SHORT GamePubl.10011B86 10011B13 |. 8B96 FC010000 |MOV EDX,DWORD PTR DS:[ESI+1FC] 10011B19 |. 8B043A |MOV EAX,DWORD PTR DS:[EDX+EDI] 10011B1C |. 8A48 55 |MOV CL,BYTE PTR DS:[EAX+55] 10011B1F |. 84C9 |TEST CL,CL 10011B21 |. 74 39 |JE SHORT GamePubl.10011B5C 10011B23 |. 3B9E 08020000 |CMP EBX,DWORD PTR DS:[ESI+208] 10011B29 |. 7D 5B |JGE SHORT GamePubl.10011B86 10011B2B |. 8BC2 |MOV EAX,EDX 10011B2D |. 8B0C38 |MOV ECX,DWORD PTR DS:[EAX+EDI] 10011B30 |. 03C7 |ADD EAX,EDI 10011B32 |. E8 39F7FEFF |CALL GamePubl.?GetVisible@CCardCtrl@@QAE> 10011B37 |. 84C0 |TEST AL,AL 10011B39 |. 74 21 |JE SHORT GamePubl.10011B5C 10011B3B |. 3B9E 08020000 |CMP EBX,DWORD PTR DS:[ESI+208] 10011B41 |. 7D 43 |JGE SHORT GamePubl.10011B86 10011B43 |. 8B4C24 18 |MOV ECX,DWORD PTR SS:[ESP+18] 10011B47 |. 8B5424 14 |MOV EDX,DWORD PTR SS:[ESP+14] 10011B4B |. 8B86 FC010000 |MOV EAX,DWORD PTR DS:[ESI+1FC] 10011B51 |. 55 |PUSH EBP 10011B52 |. 51 |PUSH ECX 10011B53 |. 8B0C38 |MOV ECX,DWORD PTR DS:[EAX+EDI] 10011B56 |. 52 |PUSH EDX 10011B57 |. E8 C426FFFF |CALL GamePubl.?OnLButtonUp@CCardCtrl@@QA> 10011B5C |> 8B86 08020000 |MOV EAX,DWORD PTR DS:[ESI+208] 10011B62 |. 43 |INC EBX 10011B63 |. 3BD8 |CMP EBX,EAX 10011B65 |.^ 0F8C 75FFFFFF \JL GamePubl.10011AE0 10011B6B |> 8BCE MOV ECX,ESI 10011B6D |. E8 CC430000 CALL <JMP.&MFC71.#1903_?Default@CWnd@@IAE> 10011B72 |. C686 28020000>MOV BYTE PTR DS:[ESI+228],0;返回程序领空后回到这里 10011B79 |. FF15 C4D80110 CALL DWORD PTR DS:[<&USER32.ReleaseCaptur> [ReleaseCapture 10011B7F |. 5F POP EDI 10011B80 |. 5E POP ESI 10011B81 |. 5D POP EBP 10011B82 |. 5B POP EBX 10011B83 |. C2 0C00 RETN 0C
回到程序领空的时候是停在了VA:10011B72,下面开始分析。
因为真正的投注(网络操作)跟用户界面更新是分开的,所以为了方便分析,要得找到网络操作跟用户界面更新的分界线,因为bp send行不通,所以换个方法。
打开虚拟机,在虚拟机中用另一个账号登陆游戏后申请坐庄,接着回到本机。
在上面的函数的开头F2下断,即下面这句代码下断:
10011AC0 >|$ 53 PUSH EBX
在本机投注后,程序断了下来,接着观察虚拟机中发现已经有了投注信息,说明网络操作的代码不在这个代码段中。OK,回溯到上一层,继续上面的思路。
下面代码是上一层的代码:
00409670 . 53 PUSH EBX 00409671 . 8B1D A4E04000 MOV EBX,DWORD PTR DS:[<&GDI32.PtInRegion>>; GDI32.PtInRegion 00409677 . 55 PUSH EBP 00409678 . 8B6C24 14 MOV EBP,DWORD PTR SS:[ESP+14] ; 第三个参数 Y 0040967C . 56 PUSH ESI 0040967D . 57 PUSH EDI 0040967E . 8B7C24 18 MOV EDI,DWORD PTR SS:[ESP+18] ; 第二个参数 X 00409682 . 8BF1 MOV ESI,ECX 00409684 . 8BCD MOV ECX,EBP 00409686 . 51 PUSH ECX ; /Y 00409687 . 8BC7 MOV EAX,EDI ; | 00409689 . 50 PUSH EAX ; |X 0040968A . 8B86 CC040000 MOV EAX,DWORD PTR DS:[ESI+4CC] ; | 00409690 . 50 PUSH EAX ; |hRegion 00409691 . FFD3 CALL EBX ; \PtInRegion 00409693 . 85C0 TEST EAX,EAX 00409695 . 0F84 9F000000 JE Sicbo.0040973A 0040969B . 80BE 02060000>CMP BYTE PTR DS:[ESI+602],1 004096A2 . 0F85 92000000 JNZ Sicbo.0040973A 004096A8 . 80BE 01060000>CMP BYTE PTR DS:[ESI+601],1 004096AF . 0F85 85000000 JNZ Sicbo.0040973A 004096B5 . 8BCD MOV ECX,EBP 004096B7 . 51 PUSH ECX ; /Y 004096B8 . 8B8E AC040000 MOV ECX,DWORD PTR DS:[ESI+4AC] ; | 004096BE . 8BC7 MOV EAX,EDI ; | 004096C0 . 50 PUSH EAX ; |X 004096C1 . 51 PUSH ECX ; |hRegion 004096C2 . FFD3 CALL EBX ; \PtInRegion 004096C4 . 85C0 TEST EAX,EAX 004096C6 . 74 04 JE SHORT Sicbo.004096CC 004096C8 . B3 01 MOV BL,1 004096CA . EB 3F JMP SHORT Sicbo.0040970B 004096CC > 8B96 B4040000 MOV EDX,DWORD PTR DS:[ESI+4B4] 004096D2 . 8BCD MOV ECX,EBP 004096D4 . 51 PUSH ECX 004096D5 . 8BC7 MOV EAX,EDI 004096D7 . 50 PUSH EAX 004096D8 . 52 PUSH EDX 004096D9 . FFD3 CALL EBX 004096DB . 85C0 TEST EAX,EAX 004096DD . 74 04 JE SHORT Sicbo.004096E3 004096DF . B3 02 MOV BL,2 004096E1 . EB 28 JMP SHORT Sicbo.0040970B 004096E3 > 55 PUSH EBP 004096E4 . 57 PUSH EDI 004096E5 . 8D8E B8040000 LEA ECX,DWORD PTR DS:[ESI+4B8] 004096EB . E8 C0D6FFFF CALL Sicbo.00406DB0 004096F0 . 85C0 TEST EAX,EAX 004096F2 . 74 04 JE SHORT Sicbo.004096F8 004096F4 . B3 03 MOV BL,3 004096F6 . EB 13 JMP SHORT Sicbo.0040970B 004096F8 > 55 PUSH EBP 004096F9 . 57 PUSH EDI 004096FA . 8D8E C0040000 LEA ECX,DWORD PTR DS:[ESI+4C0] 00409700 . E8 ABD6FFFF CALL Sicbo.00406DB0 00409705 . 85C0 TEST EAX,EAX 00409707 . 74 31 JE SHORT Sicbo.0040973A 00409709 . B3 04 MOV BL,4 0040970B > E8 12260000 CALL <JMP.&MFC71.#1091_?AfxGetThread@@YGP> 00409710 . 85C0 TEST EAX,EAX 00409712 . 74 09 JE SHORT Sicbo.0040971D 00409714 . 8B10 MOV EDX,DWORD PTR DS:[EAX] 00409716 . 8BC8 MOV ECX,EAX 00409718 . FF52 7C CALL DWORD PTR DS:[EDX+7C] 0040971B . EB 02 JMP SHORT Sicbo.0040971F 0040971D > 33C0 XOR EAX,EAX 0040971F > 0FB696 550800>MOVZX EDX,BYTE PTR DS:[ESI+855] 00409726 . 8B40 20 MOV EAX,DWORD PTR DS:[EAX+20] 00409729 . 0FB6CB MOVZX ECX,BL 0040972C . 51 PUSH ECX ; /lParam 0040972D . 52 PUSH EDX ; |wParam 0040972E . 68 C9040000 PUSH 4C9 ; |Message = MSG(4C9) 00409733 . 50 PUSH EAX ; |hWnd 00409734 . FF15 34E54000 CALL DWORD PTR DS:[<&USER32.SendMessageA>>; \这里是下注关键 0040973A > 8B4C24 14 MOV ECX,DWORD PTR SS:[ESP+14] 0040973E . 55 PUSH EBP 0040973F . 57 PUSH EDI 00409740 . 51 PUSH ECX 00409741 . 8BCE MOV ECX,ESI 00409743 . C686 02060000>MOV BYTE PTR DS:[ESI+602],0 0040974A . FF15 CCE04000 CALL DWORD PTR DS:[<&GamePublic.?OnLButto>; GamePubl.?OnLButtonUp@CGameFrameView@@QAEXIVCPoint@@@Z 00409750 . 5F POP EDI 00409751 . 5E POP ESI 00409752 . 5D POP EBP 00409753 . 5B POP EBX 00409754 . C2 0C00 RETN 0C
按照上面的思路,依然在函数头下断,投注后程序断了下来,观察虚拟机中此次并没有投注信息,很好!这段代码就是关键了!
重点分析此段代码。
首先分析该函数的参数,这个函数一共有3个参数,第一个参数是什么没去分析了这个不是重点。重点是第二个参数代表X坐标,第三个参数是Y坐标。
函数开头的代码是把XY的坐标传进PtInRegion来判断投注区。随后对XY坐标进行转化成相应投注区的索引。
继续往下看代码发现了这里:
0040972C . 51 PUSH ECX ; /lParam 0040972D . 52 PUSH EDX ; |wParam 0040972E . 68 C9040000 PUSH 4C9 ; |Message = MSG(4C9) 00409733 . 50 PUSH EAX ; |hWnd 00409734 . FF15 34E54000 CALL DWORD PTR DS:[<&USER32.SendMessageA>>; \这里是下注关键
这里的代码非常非常可疑,因为这里是一个SendMessage的调用,还记得刚才说要找界面更新的消息源头么?为了验证这个SendMessage,在 00409734 . FF15 34E54000 CALL DWORD PTR DS:[<&USER32.SendMessageA>> 这句代码下断,游戏投注断了下来,观察虚拟机中没有投注信息,回到本机单步执行该条语句,再次回到虚拟中发现了投注信息,说明这里就是下注的关键了!已经离成功很近了。
下面着重分析SendMessage的参数 首先是HWND,该句柄通过SPY++查找,发现该句柄就是游戏本身主窗口的句柄了, 接着是消息号是4C9,很明显这是一个自定义的消息。 WPARAM 是投注筹码的索引, LPARAM 是投注区域的索引。 OK了,参数分析完毕。如下:
SendMessage( HWND hWnd, // 游戏本身主窗口的句柄 UINT Msg, // 4C9,自定义的消息 WPARAM wParam, // 投注筹码的索引,2是1000,3是10000以此类推 LPARAM lParam // 投注区域的索引,1是东,2是南以此类推 );
那么游戏本身实现投注就是靠SendMessage来传递一个自定义的消息,而该自定义的消息处理函数内部进行了什么处理在这里不用去分析已经可以得出结论:该自定义消息处理函数内部进行投注的网络操作,网络操作完毕后,随后再次发送一个自定义消息来更新界面,这里就解释了第一天通宵的时候跟的那几个CALL是在一个消息循环里的原因了。 所以我们不必去分析该自定义的消息处理函数了,因为我们也可以直接向该窗口发送此消息就OK。因为SendMessage是跨进程的,这样做没有任何问题。 有了上面的参数分析,非常容易就写出投注的测试程序。这里提一下一点比较有意思的事情是,投注额的索引是从2开始,2代表1000.我们可以传递“1”。这样虽然在游戏中没有100元的筹码,但是我们可以投注100.呵呵,有意思。
至此,投注的功能已经搞定。
后记:投注功能写好后,就是根据客户提供的“赌徒算法”,进行挂机自动投注了。测试了几次,短时间(连续挂一两个小时)收益还不错。但是长时间比如每天睡觉的时候挂机,起床的时候,发现有赢有亏,看来“赌徒算法”有待改进,哈哈。