之前做了一段時間的安卓手游破解正版驗證工作,總結一些基本的方法和知識,便于分享交流。破解的目的是讓國內一些“被閹割”過(無GooglePlay框架)的安卓手機也能暢快地玩兒上GooglePlay上的游戲,所以文章主要討論海外游戲市場下載的apk包如何去除正版驗證。國內的手游大部分還是做了防破解工作的,加殼、防反編譯等等(此篇不予討論)。大概海外市場的氛圍較好,所以GooglePlay、Amazon商店的游戲基本都可以反編譯成功,頂多加了代碼混淆、調用了GP/Amazon的驗證SDK而已。
適合誰看?
破解初學者、懂一些編程知識的童鞋(完全不懂編程也可以,只能破解那些只需要替換一些文件/文本的游戲)、以及對apk破解/安全性維護感興趣的童鞋。
什么是Smali?
先說Dalvik是google專門為Android操作系統設計的一個虛擬機,經過深度的優化。雖然Android上的程序是使用java來開發的,但是Dalvik和標準的java虛擬機JVM還是兩回事。Dalvik VM是基于寄存器的,而JVM是基于棧的;Dalvik有專屬的文件執行格式dex(dalvik executable),而JVM則執行的是java字節碼。Dalvik VM比JVM速度更快,占用空間更少。
通過Dalvik的字節碼我們不能直接看到原來的邏輯代碼,這時需要借助如Apktool或dex2jar+jd-gui工具來幫助查看。但是,注意的是最終我們修改APK需要操作的文件是.smali文件,而不是導出來的Java文件重新編譯(況且這基本上不可能)。
詳細的Smali語法學習可以參考這篇文章:http://blog.csdn.net/lpohvbe/article/details/7981386,里面詳細介紹了Smali中的數據類型、方法調用等等。
此外,反編譯過程中遇到不懂的關鍵字,可參考http://pallergabor.uw.hu/androidblog/dalvik_opcodes.html,非常實用的文檔。
如何去除正版驗證?
你需要的工具
工欲善其事,必先利其器,反編譯工具很多,針對點也不同,本文主要用的是APKIDE(也叫APK改之理,一個類似APK studio的可視化反編譯軟件,內部已經集成了dex轉smali、dex轉jar、重新編譯、簽名、搜索等功能,推薦使用),其他工具和功能如下:
apktool,命令行工具,可以decode和build apk文件,主要用來解析xml等資源文件。
dex2jar,一系列命令行工具,能將文件在dex、jar和smali之間相互轉換,反編譯時可以dex轉為smali后修改smali代碼,再將smali轉回成dex。
jd_guid,可視化工具,將jar文件反編譯為java代碼,便于閱讀代碼邏輯,至于直接編輯java代碼什么的,你想多了。
baksmali,命令行工具,也是用來轉換dex和smali文件。
apksigner,命令行簽名工具,反編譯重新打包之后用來重新簽名apk。
apk studio,類似APK IDE,英文界面,有時候會反編譯失敗而APK IDE不會,猜想可能是內嵌的baksmali之類的版本不一樣或者編碼問題吧。
以上工具在百度、谷歌上下載都可搜到下載。
常用方法
通常不能運行的游戲表現有這幾種:
1. 提示未安裝谷歌框架,不能游戲;或者提示谷歌驗證此游戲為盜版(可能你的apk不是從GooglePlay下載而來)。
2. 提示當前賬號并未購買此游戲(同樣,可能此游戲并非GooglePlay上購買后直接下載的)
你打開游戲可能會卡在這樣的頁面:
第一種情況是因為代碼中調用了Google官方的驗證SDK,僅驗證游戲是否來自GooglePlay,而第二種添加了Google賬號驗證,檢查你是否購買了此游戲,這兩種情況都可以通過繞開Google驗證來破解:
去除GooglePlay驗證(可寫成腳本進行自動化破解)
最簡單的思路其實是找到代碼中調用驗證的地方,將其注釋掉,直接調用callback函數(一般此類SDK都是異步返回,開發者實現一個帶callback的Listener)中驗證成功的方法,拷貝復制即可。至于調用的地方,一般都在入口Activity類中,函數名一般帶有verify、certificate、auth之類的字樣。這也的確是個可行的辦法,但用這個辦法破解了幾款游戲后發現游戲之間差異很大,你必須針對每款游戲去找調用入口,太費時了,有沒有規律性的、可自動化的方法呢?
答案是肯定的,既然是SDK進行的驗證,我們只修改SDK內的代碼不就好了嗎?只要驗證的結果每次都返回成功就OK了。按Google文檔上的教程,接入驗證使用的是sdk\extras\google\play_licensing下的sdk工程,引入后就一個包:com.google.android.vending.licensing,用APK IDE打開如下:
其內部大致做的事情就是驗證apk簽名,請求Google服務器檢查驗證是否通過、并得到游戲的擴展數據包信息(obb文件名、大小等),其中夾雜超時處理、緩存過期時間等等。
找到了負責驗證的sdk包,修改哪里就可以強制返回成功了呢?一個是LicenseValidator,負責解析Google服務器返回的responseCode,另一個是實現Policy接口的類,負責解析從服務器獲取的額外信息(擴展包文件等)。這里偷了一點懶,沒仔細研究SDK,而是找到個市面上比較好用的安卓破解器:幸運破解器(裝在手機上就能直接破解游戲的神器,還能破解內購),用它破解一款游戲后導出apk,反編譯出smali文件,寫個腳本批量對比前后文件變化,過濾掉注釋等無用信息得到的就是我們要修改的地方了!總結后如下:
1. 工程中所有調用java/security/Signature;->verify的地方,將下一行的
move-result v3
改為
const/4 v3, 0x1
move-result v3的意思是將verify函數的返回結果賦值給v3,我們直接替換成聲明一個值為true的v3變量即可。verify函數用以驗證簽名,我們在破解后必然要重新簽名,無法保留原有開發者簽名,所以所有驗證簽名的地方都需要進行這一步修改。
2. 實現了Policy接口的類(一般是APKExpansionPolicy和ServerManagedPolicy)中的函數allowAccess,將函數開頭的聲明
const/4 v1, 0x0
改為
const/4 v1, 0x1
(其中v1命名不確定,可能叫v0或v2等等)此函數根據上次請求結果和重試次數判斷是否驗證通過,v1是默認返回值,初始化時為false,后續代碼判斷滿足某些條件后將v1賦值為true,函數退出時必然返回v1。所以我們將v1默認值設為true就相當于默認驗證通過了。
注意,這里可能v1默認值已經是true了,而是在判斷驗證未通過時將v1賦值為false再返回,因此可以將此函數所有的變量聲明都改為默認值為true。
3.?smali\com\google\android\vending\licensing\LicenseValidator.smali中的函數verify,將
0x1 -> :sswitch_1
0x2 -> :sswitch_0
改為
0x1 -> :sswitch_0
0x2 -> :sswitch_1
這里是將switch中驗證失敗的情況指向了驗證成功時要執行的代碼段。
4. 為了以防萬一,可以在繼承了Lcom/google/android/vending/licensing/LicenseCheckerCallback接口的類中修改dontAllow函數,在函數一開始加入:
const/16 v1, 0x100
invoke-interface {p0, v1}, Lcom/google/android/vending/licensing/LicenseCheckerCallback;->allow(I)V
return-void
這里的意思是直接調用listener的allow函數,p0是callback自己,變量v1聲明的初始值為0x100,是代表成功的常量,調用p0的allow函數,參數為v1,然后直接返回,之后的代碼就不會被執行了。這樣就確保必然調用allow,萬無一失。
5. 完成以上的步驟就可以通過GooglePlay驗證了,但打開游戲后會開始下載游戲擴展包(obb文件),強制忽略手機上已存在的obb文件。這是因為正常的sdk在驗證的同時會返回此游戲最新的擴展包名、包大小等信息,方便用戶下載、開發者更新等。因此為了可以順暢游戲,還要在實現了Lcom/google/android/vending/licensing/Policy接口的類中修改getExpansionFileName等一系列函數,直接將游戲擴展包的信息寫在代碼中直接返回。
完成以上步驟即可順利游戲了,因為這些步驟都是有跡可循、規律的,所以寫個腳本就可以一鍵破解了(腳本正在編寫中)。
至于為什么不用破解后的驗證sdk直接覆蓋替換其他游戲的sdk,是因為各自游戲開發時間不同,采用的sdk版本不同,直接覆蓋可能會導致有些方法找不到,而以上的這些修改都是在sdk核心邏輯中,親自驗證了幾個版本發現這些核心邏輯并沒有改變。
可能遇到的特殊處理
有些游戲可能在進行完以上步驟會出現閃退、報錯等情況,這可能是由于該游戲接入的第三方工具在搗亂。以“拔拔曼陀羅”為例,本人完成以上步驟后報錯NullPointer,遂打開Eclipse查找Logcat(不得不說查Logcat是非常實用的辦法,只要眼疾手復制出來即可),發現的錯誤如下:
1. 無com.android.vending.CHECK_LICENSE權限錯誤:游戲中有個corona的包,搜索后得知這是個打包插件,方便開發者集成Google、Amazon等sdk、方便build。在Google驗證處調用enforceCallingOrSelfPermission時報錯,但Manifest中已添加此權限,未找到原因,反正已經破解了驗證,直接注釋掉,運行通過。
2. 空指針報錯:同樣還是Corona的坑,在獲取谷歌框架服務時未判斷null,大概corona的開發者覺得全世界的安卓機都應該有谷歌框架吧,呵呵,注釋后運行成功。
當然需要特殊處理的地方肯定會很多,并不是所有游戲都能自動化破解,但有了自動化腳本就已經節省不少工作量了。
那些難以跨越的坑
1. 代碼混淆
混淆后的代碼幾乎找不到從哪個類下手、從哪個方法下手,因為放眼望去所有類名、方法名全部都是a、b、c。海外市場也有不少游戲和應用添加混淆,但這也僅僅是“幾乎”而已,并不是完全沒辦法,具體如何破解下一篇再來闡述。
2. 加殼等防反編譯措施
還有很多種辦法可以防止apk被反編譯,如字符串混淆讓反編譯后的String類型不可讀、用花指令或動態加載等方式讓反編譯看不到源碼甚至無法反編譯源代碼。所幸目前見到的海外市場游戲較少采取此種措施,無需對此進行破解。