微信5.0 Android版飛機大戰破解無敵模式手記

小雨vicky發表於2013-11-27
轉載於:http://www.blogjava.net/zh-weir/archive/2013/08/14/402821.html

最近微信出了5.0,新增了遊戲中心,並內建了一個經典遊戲《飛機大戰》。遊戲其實很簡單,但由於可以和好友一起競爭排名,一時間受到大家的追捧,小夥伴們進入“全民打飛機”時代。
 
ios 版出來不久就被破解出了無敵模式。Android版出後好像一直風平浪靜。週末無事,加之看雪zmworm版主邀請,於是花了一天的工夫研究了下。也出了個Android版的無敵模式增強版。具體來說就是無敵、雙排子彈加無限炸彈。當然,這個不是重點,我們的重點當然是技術細節啦!
 
微信的遊戲繼承了Android版手機QQ遊戲中心的思想,也採用外掛動態載入方式。具體來說,就是外掛及遊戲以jar包形式存在,jar包中有classes.dex及其他資原始檔,在執行時動態載入資源及classes.dex程式碼。這樣的好處是靈活管理,易於擴充套件。以後更多的遊戲只要上架到微信的伺服器,使用者就能在微信應用內部下載、安裝、執行。具體原理可以參考我2011年的一篇文章《Android類動態載入技術》 。
 
當然,那篇文章講的只是基本原理,而微信在程式碼動態載入方面則走得更遠。針對外掛的管理及安全,它有一套完整的框架,並自稱為sandbox。由於程式碼有做混淆,加之程式碼量挺大,所以我僅算管中窺豹,看到的也只是冰山一角。與實際情況有所出入,還請見諒!
 
一、微信遊戲外掛的安全校驗
 
其實說實話,微信在遊戲外掛的安全架構方面花了不少功夫。我能破解並不是利用微信在安全方面的漏洞,而是Android系統本身的安全漏洞。這個漏洞也就是我前段時間發的以為是bluebox上報google的漏洞,後來被證實不是。詳情請看《Bluebox Security最新提報Android漏洞的初步探討》 。
 
那微信是如何對遊戲外掛進行載入及安全校驗的呢?
 
飛機大戰的遊戲外掛以jar包的形式,放在微信apk的assets/preload資料夾下:

   
jar包中包括classex.dex、so本地庫及drawable圖片資源或者還有xml資源。微信處理外掛載入的程式碼在com.tencent.mm.compatible.loader包中。載入外掛資源的類叫做PluginResourceLoader,它是android.content.res.Resources的子類。
 
而最核心的載入類應該是PluginClassLoader。上面說的PluginResourceLoader也是它的成員變數。它似乎負責整個外掛載入的各個環節排程。
 
Android動態載入類有一個弊端,就是dex檔案必須釋放為本地檔案。這是dalvik虛擬機器機制決定的。一直以為google或者dalvik會改,不過似乎到現在還沒見改進。釋放到本地快取的dex是很容易受到攻擊的,不過微信在這些細節上處理得還挺好,沒有明顯漏洞。這個後面再說。
 
PluginClassLoader會在微信安裝後第一次啟動時,掃描外掛的情況,並將外掛拷貝到自己應用data下面的app_dex資料夾下。接下來會對外掛進行處理。將so庫釋放到app_lib資料夾下,將jar包中的classes.dex重新命名釋放到app_cache資料夾下。
 
在拷貝外掛jar包的過程中,會對外掛進行第一次校驗——簽名校驗。關於簽名校驗的原理,可以參看我2011年的另一篇文章《Android APK 簽名比對》 。微信的簽名校驗就是微信APK的簽名需要和外掛jar包的簽名一致。這裡我考慮過用bluebox上報的ANDROID-8219321漏洞繞過外掛jar包的簽名檢測。經過一段時間的研究發現了它還有第二次校驗。。。
 
由於是在拷貝之前進行簽名校驗,所以我考慮過拷貝完成後,直接替換app_dex和app_lib下的檔案的方法。發現均不可行。繼續分析發現了第二次校驗——MD5校驗。一開始看到jar包命名就很疑惑,檔名後面一串數字是幹嘛的。想過是MD5碼,沒做驗證。直到碰壁之後,才發現了這裡的奧祕。後面這一串數字就是jar包的MD5值。外掛載入的時候會去解析這個MD5值,並存起來。在載入執行的時候會對這個MD5值進行校驗,如果快取中的檔案MD5值不同,會用重新釋放apk中的外掛覆蓋。快取中的dex應該也有類似的機制。這部分程式碼分析得不是很透徹,大概原理如此,有感興趣的朋友可以繼續深入。
 
MD5值作檔名+簽名校驗,以為著利用ANDROID-8219321漏洞的企圖落空了。因為ANDROID-8219321漏洞的前提是apk中檔案的檔名需要保持不變,這樣才能通過重名檔案繞過簽名校驗。然而只要我們改了外掛jar包,MD5值就必須得變,從而導致檔名改變。因此此路不通了。(也許可以通過修改MD5校驗和簽名校驗的smali繞過校驗,無奈我暫時沒找到具體MD5校驗的程式碼,只能作罷)。
 
MD5值作檔名+外掛簽名校驗,再加上安裝APK本身的簽名校驗。三重校驗保證了微信遊戲的安全性。
 
因此我只能採用《Bluebox Security最新提報Android漏洞的初步探討》 一文中所述的安全漏洞。此漏洞針對system/app和vendor/app下的apk只會校驗manifest.xml檔案簽名。因此我可以任意修改外掛jar包,在重新生成新包之後計算出新包的MD5值,並對新包進行重新命名。對於外掛的簽名校驗,則直接通過修改smali程式碼,遮蔽掉微信簽名校驗的函式功能,直接返回true:


這就是我們修改外掛之後得以正常執行的理論基礎及可行性保證。有了上面的理論,我們就可以開始修改遊戲了!
 
 
二、飛機大戰遊戲破解
 
飛機大戰這個遊戲據說是騰訊一個程式設計師一週時間開發完成的作品。其實考慮到這個遊戲的規模,除GUI和互動設計外,程式設計師一週時間應該也差不多了。沒有太大出入。
 
此遊戲採用的遊戲引擎是libgdx。相信做過Android遊戲的朋友對此款引擎不會陌生。如果在Android平臺開源遊戲引擎裡,cocos2d當仁不讓地排第一的話,那麼libgdx也可以當仁不讓地排第二了。cocos2d主要採用C++開發,而libgdx則主要採用java方式開發。學習成本低,開發週期短,是它的優勢。當然它也是跨多平臺的遊戲引擎,執行效率方面稍有欠缺但也不錯。因此廣大的Android單機小遊戲都是採用libgdx作為遊戲引擎。
 
微信飛機大戰的程式碼量不大,有興趣的朋友可以研究下,移植成為一款獨立的單機遊戲應該也不難。下面我詳細介紹下飛機大戰遊戲破解的技術細節。
 
第一步就是將飛機大戰遊戲的外掛包從apk中釋放出來。我們可以採用反編譯APK的方式反編譯這個外掛包。修改smali程式碼之後,再打包回jar包檔案。如果還有朋友對APK破解流程不熟悉的話,可以參考我以前的一篇文章《APK Crack》 。這裡我們主要介紹遊戲的架構及破解思路。
 
解壓之後,smali部分其實可以分為兩個包:com.badlogic.gdx和com.tencent.mm.plugin.shoot。前面一個是libgdx匯入的jar包,這個不是我們關心的內容。我們的重點就在com.tencent.mm.plugin.shoot這個包中。
 
遊戲主要有兩個Activity:ShootMainUI和ShootFlashUI。它們都繼承自com.badlogic.gdx.backends.android.AndroidApplication,這個類事實上繼承自Android系統的Activity。它們一個是主載入介面,一個是我們停留時間最長的遊戲介面。當然需要了解,但都不是重點,重點是我們遊戲中的各種角色:
 

這些角色構成了整個遊戲的演員,他們都繼承自同一個類:GameSprite。相當於遊戲引擎中精靈的概念。它們都有生命值、寬高、速度、型別、狀態等屬性。這些類的定義都在actor子包內。在遊戲過程中會對每個精靈做碰撞檢測,當你發現你的飛機爆炸時,就是碰撞檢測在起作用。順便說一下,libgdx引擎採用的物理引擎是C++版的box2d,效能非常不錯。
 
好了,我們具體的破解特性,我會以任務的形式一個一個娓娓道來。下面我們接到的第一個任務就是“永久雙子彈”!
 
任務1、永久雙子彈!
 
在玩飛機大戰時,雙子彈意味著更大的威力。可以消滅更多的敵機,化險為夷。然而在實際遊戲中我們只有吃到PROPS_DOUBLE之後才能擁有一段有限時間的雙子彈狀態。
 
雙子彈屬性屬於HERO的,對應的類是Player和PlayerActor。Player繼承自GameSprite,而PlayerActor則是libgdx中的actor類的概念。兩個前者注重狀態和屬性,後者注重邏輯和動作。
 
Player在建構函式初始化時就會設定子彈型別:

 
我們只需要把BulletType從NORMAL改為DOUBLE就可以了。
 
PlayerActor會對子彈型別進行定時地檢測,檢測是會將雙子彈還原為單子彈。應該是為了處理吃到PROPS_DOUBLE後,一段時間子彈還原的問題。所以我們一併改掉:

 
OK,雙子彈破解任務完成!
 
 
任務2、炸彈無限!
 
炸彈是個好東西,威力無窮。關鍵時候全靠它清屏,消滅所有敵機!而且它還是刷分利器。當然,只有在它變為無限的時候,我們才能用它來刷分。
 
這裡我試圖修改Player的getBombNumber和setBombNumber方法,發現均不行。後來轉變思路,只要在使用炸彈後炸彈數量不減少,就能實現無限炸彈的功能。經過程式碼追蹤,最後定位到一處混淆程式碼處。將-0x1改為了0x0。

 
修改的結果,在吃到兩個炸彈後使用炸彈不會減少炸彈數量。吃一個炸彈時,使用炸彈後炸彈按鈕消失,因此無法做到無限。請記住一定要存到兩個炸彈之後才能無限炸彈。無限炸彈破解任務完成!
 
 
任務3、開啟無敵模式!
 
長生不死一直是我們人類的終極夢想,在遊戲中也不例外。iphone版微信也是因為有了飛機大戰無敵模式而被各大新聞站點競相轉載。讓我們Android版也無敵一下吧~
 
前面提到了GameSprite是所有角色的父類,在遊戲用物理引擎做碰撞檢測後,會呼叫GameSprite類的hit方法。hit方法中將GameSprite的liftCount減一,如果減到0則將狀態設定為DEAD。
 
GameSprite的狀態有如下一些:
 
DEAD
EXPLODING
FLIGTHING
HITING
INVINCIBLE
 
在飛機正常的死亡過程中,是先HITING,再EXPLODING,再DEAD。FLIGTHING我不清楚幹嘛的,INVINCIBLE應該是無敵模式。但是在我的破解裡,並沒有使用這個模式,而是強制在碰撞檢測結果中,把它列在了生死薄之外。至於INVINCIBLE的方式,大家可以試試能不能很好的維護這個狀態。
 
具體來說就是hit方法不管GameSprite是hero也好,enemy也罷,均一視同仁,生命值減一,或者死掉。然而我們可以通過修改smali程式碼,將hero列在生死薄之外:

 
其中goto_1標籤跳轉到return-void。這樣我們的hero將永遠不會被hit,因此也就無敵啦!
 
 
任務4、獨孤求敗。。。
 
本以為完成任務3就大功告成了,誰知我們缺遇到了無敵的尷尬——死不了。。。死不了,意味著永遠無法結束遊戲,永遠不會有機會上傳自己的得分進入排行榜。哎,現在終於明白為什麼獨孤求敗了。。。
 
基於此,我們得想個辦法觸發飛機非自然死亡。想來想去,我還是覺得讓飛機自己決定自己的生死最合理。具體就是當飛機飛到螢幕最上方時觸發死亡。因為一般情況,我們不會把飛機飛到螢幕最上方,所以誤操作概率極低。
 
通過前面我們知道hero飛機的類就是Player。而Player中有一個函式更新飛機的座標位置:updatePosition。所以我們可以在這個函式中進行我們想要的操作:

 
其中0x64就是我指定的y座標下限100。當飛機座標y在100以內時,我會把飛機的LiftCount設定為0,然後再將狀態設定為EXPLODING。飛機就會爆炸死亡了~
OK,任務完成,打完收工!
 
 
三、一些掃尾工作
 
 外掛包修改完成後,我們通過apktool,將其打包回jar包。res資源包需要手動新增會jar包中。然後按照第一節所說的,生成jar報的MD5碼,重新命名jar包。
 微信APK也需要按第一節的方法,將外掛的簽名校驗遮蔽掉。編譯出classes.dex,替換微信原始包中的classes.dex。
再將APK包中的飛機大戰外掛換為我們編譯出來重新命名的這個jar包。
 
OK,APK準備好了。
 
由於我利用的是《Bluebox Security最新提報Android漏洞的初步探討》 一文中所述的安全漏洞,所以安裝此APK的過程並不是菜鳥能玩的。。。簡單來說,你需要root許可權,並能將system分割槽mount為可寫。
 
然後解除安裝你原本的微信。將這個apk放到/system/app/資料夾下。稍等片刻,你就是打飛機的高手了!

相關文章