摩爾莊園這個遊戲想必很多人都聽過,這是一款兒童網頁遊戲,我從初中就開始玩了,通過這個遊戲我也結識了不少朋友,給我留下了難以忘懷的美好記憶。
當時這個遊戲非常火熱,經常是幾十個伺服器爆滿的狀態。
我記得當時我每天都會打開摩爾莊園看看摩爾時報,喂喂拉姆,種種地,釣釣魚,完成日常任務。
但後來同類型的遊戲(如洛克王國、奧比島、奧拉星等)層出不窮,英雄聯盟這類大型遊戲又火熱起來,或許是因為這些,摩爾莊園的玩家逐漸減少。
終於,摩爾莊園停止了更新,摩爾時報停在了2015年2月6日,標題是回憶莊園。
現在,摩爾莊園的伺服器從最多時候的一千多個變成了30個(實際上只有一個伺服器,IP是123.206.131.236)且非常不穩定。
我想衝個超拉祭奠一下我的回憶,可誰知充值的網頁都打不開了。
再這樣下去的話估計離關閉伺服器不遠了,於是我寫下這篇文章,算是和摩爾莊園做一個告別儀式,同時也希望相關遊戲研發人員看到能做出相應改進。
關於網頁遊戲
網頁遊戲一般值無需下載,打開網頁就能玩的遊戲。
網頁遊戲的作弊一般都是模擬操作或者分析封包協議模擬封包的,因為代碼是在虛擬機中運行的,內存修改很難找到基址。
目前的網頁遊戲的框架一般是Flash,也有Unity3D的,由於Unity3D需要安裝客戶端或者插件,所以佔極少數,也有個別網頁遊戲是HTML5代碼寫的,這種暫不討論。
摩爾莊園則是一款Flash遊戲,網頁屬於客戶端,與伺服器通信就會產生封包,我們只需要構建出封包數據,就可以實現遊戲內的所有動作,通過修改封包數據,還可以實現很多客戶端本身沒有的功能(如刷金幣刷道具等)。
因此網頁遊戲為了安全都會將封包加密再發送,而又為了減輕伺服器的負擔,封包的加密不會很複雜,且封包的加密算法一般都可在本地緩存的swf文件中找到。
目前有許多Flash加密技術,如Alchemy、DoSwf等,但Flash類似Java,是一種跨平臺,解釋型語言,AS代碼被編譯成字節碼代碼運行在AS的虛擬機中,所以代碼最終都會被解密。而且加密Flash的成本很高,這為攻擊者帶來可乘之機。
據我所知封包加密算法更新過三次(內部小更新忽略不計)。
從最初的沒有驗證,可以隨意發送封包不掉線,到後來加入了封包序列段,從00遞增到FF循環,如果封包序列不符合則斷開連接。
再後來序列段的遞增改為加密算法,序列段的值和包體及上一個封包的序列號有關。
最後,有序列段驗證的同時加密了包體(封包尾部)。
加密算法尋找過程
遊戲有很多子類和模塊,我們需要找到發送封包的函數,Flash中發送封包一般都是通過重寫Socket類進行的,所以我們可以搜索這個類名(flash.net.Socket)找到最底層的封包發送類,然後通過調用關係找到封包加密的算法。
幾種思路:
1.提取緩存目錄中的swf文件反編譯分析。
2.通過截包得到的swf文件反編譯分析。
3.由於緩存中的swf很多是加密過的,我們可以在一個進程裡打開遊戲的網頁,內存中搜索swf文件頭,提取swf文件並全部保存到同一個目錄中,然後批量反編譯成as代碼,在文件夾搜索關鍵字socket,定位到swf文件中分析。
經過分析找到發送封包代碼所在文件:TaomeeCoreDLL.SWF
同時找到幾個關鍵基本類:
com.core.socketlogic.baseSocket.BaseSocket
com.mole.net.SocketImpl
接著我們搜索哪裡實現了這個類的功能
找到了:
com.core.socketlogic.baseSocket.MoleSocket
分析重載的send方法:
這個方法就是用來發送封包的,我們先來看看封包是什麼樣子:
我們可以在遊戲中執行相同動作,然後通過通過封包工具查看封包:
這個封包是多次喊話數字1的封包十六進位文本(打碼部分是帳號的十六進位),我們可以看出只有第5位(這裡我們以一個字節一位)在變化,其他部分都一樣。
由此可以斷定這一位就是封包驗證段,也就是序列號。
我們可以從代碼中分析封包的結構:
public static const HEAD_LEN:uint = 17;
override public function send(param1:uint, param2:Array, param3:String = null) : void
{
var _loc4_:ByteArray = null;
var _loc5_:uint = 0;
if(connected)
{
_loc4_ = this.createBody(param2,param3);
_loc5_ = HEAD_LEN + _loc4_.length;
this._seq = this.versionEvent(param1,_loc5_,this._seq,_loc4_);
_loc4_ = this.encryptBody(_loc4_,param3);
_loc5_ = HEAD_LEN + _loc4_.length;
writeUnsignedInt(_loc5_);
MsgHead.Version = this._seq;
writeByte(MsgHead.Version);
writeUnsignedInt(MsgHead.Command);
writeUnsignedInt(MsgHead.UserID);
writeUnsignedInt(MsgHead.Result);
writeBytes(_loc4_,0,_loc4_.length);
flush();
this.parselogger(LoggerType.OUTPUT,param1,_loc4_.length);
}
}
封包一位前面是 17加上 包體長度,那麼可以推測出包頭的長度就是17
然後是MsgHead.Version(這個來自函數versionEvent返回值,後面分析)接著是
MsgHead.Command,推測是封包的命令標識,相同的動作這個是一樣的,接著是MsgHead.UserID,這個也就是米米號,再接著是Result(返回碼),最後是包體_loc4_,是createBody的返回值。
我們重點需要分析的是MsgHead.Version和包體的生成,因為其他的都是固定的值。
從代碼中可以看到MsgHead.Version的值來自this._seq,也就是上面調用的this.versionEvent(param1,_loc5_,this._seq,_loc4_);的返回值。
我們分析下versionEvent這個函數:
是一個簡單的加密算法,後面還調用了getCrc
也是一個簡單的算法,我們可以直接轉換成相應的語言調用。
轉換成易語言代碼如圖:
知道這個後,剩下的就是包體的加密了:
_loc4_ = this.encryptBody(_loc4_,param3);
調用了encryptBody方法,這個方法調用了MessageEncrypt.encrypt
我們找這個方法的定義:
如圖,調用了com.fcc中的方法:
我們定位到該方法:
發現這個旁邊還有個MDecrypt,但是代碼應該是混淆處理過,還有一些亂七八糟的東西,非常亂,基本無法還原。
但是我們需要構建封包就必須解密封包體,然後再調用上面分析的序列號計算方法,再加密封包發送。
既然無法還原代碼,那我們是否能調用這個方法呢?我之前學過一點Flash,知道通過Loader類可以實現獲取其他swf中的類,然後調用。
於是設想使用Loader加載這個類,然後通過ExternalInterface,也就是和JS通訊的Callback方式傳遞參數和返回值。
接下來就開始實踐了。
實踐Loader
Flash Builder新建一個as3項目,然後新建asstest文件夾,把這個函數所在的swf放進去:
使用Embed引入swf文件,然後寫一個函數調用返回ByteArray:
定義變量:
實現Loader:
Loader加載完畢事件中,用getDefinition獲取類定義,並添加callback。
getClass方法:
其中output是我加的編輯框,用於顯示輸出文字,方便調試。
最後導出:
測試:
測試使用Flash對象加載這個swf,執行腳本的CallFunction函數調用我們添加的callback方法。
測試解密方法:
測試加密方法:
都得出正常結果。
至此我們成功攻破了摩爾莊園的封包加密,之後便可以隨心所欲地發送和修改封包了。
結語
通過我們的分析得知摩爾莊園的封包加密目前來說還是有難度的,但部分關鍵算法和發送封包的方法都沒有進行混淆和加密,類名和變量的命名都一覽無餘,甚至開發調試用的Log和Trace都沒有清除,這為逆向分析者提供了極大的便利。
謹以此文,祭奠我在摩爾莊園裡度過的快樂童年。
聲明
本篇內容僅供研究學習,禁止用於非法和商業用途,由此帶來的一切後果自行承擔,與本文作者無關。
來源:specher-郵件投稿
*轉載請註明來自遊戲安全實驗室(GSLAB.QQ.COM)
近期精品文章:
【遊戲漏洞】CSGO人物結構分析
【遊戲漏洞】《幻想神域》線程發包分析
【外掛分析】CSGO公眾作弊ezfrags外掛分析
【遊戲漏洞】《魔力寶貝》步步遇敵分析
∨
投稿文章:gslab@tengcent.com