今天要給大家介紹一項(xiàng)調(diào)試技巧——反編譯葫辐。因?yàn)槲覀冇龅降膯栴}有時(shí)會(huì)跟第三方應(yīng)用有關(guān),對(duì)于這些我們沒有代碼的應(yīng)用维贺,如果不進(jìn)行反編譯,很難對(duì)其進(jìn)行分析彬檀。
說(shuō)到apk反編譯不得不說(shuō)代碼混淆帆啃,在簽名打包時(shí)啟動(dòng)代碼混淆可以幫助我們保護(hù)代碼。曾經(jīng)聽說(shuō)有小公司發(fā)布了未做代碼混淆的應(yīng)用窍帝,結(jié)果被別人反編譯后上架努潘,辛辛苦苦做出的應(yīng)用就這樣被別人竊取了,所以保護(hù)好自己的勞動(dòng)成果很重要盯桦。除了代碼混淆慈俯,有些應(yīng)用商店還會(huì)要求開發(fā)者進(jìn)行安裝包加固,以提高安全性拥峦。
反編譯就是把對(duì)代碼做的這些保護(hù)解開贴膘,讓我們了解別人代碼的部分細(xì)節(jié)。比如下面要介紹的例子略号,GoogleSetupWizard 引起的問題刑峡,雖然我們沒有它的源碼,但是通過反編譯我們可以了解內(nèi)部的邏輯玄柠。
反編譯工具
在處理這個(gè)問題之前我用的反編譯工具一直是Dex2Jar+jd-gui突梦,用法簡(jiǎn)單,只要將 apk解壓縮羽利,把解壓出的dex文件用Dex2Jar反編譯宫患,然后用jd-gui工具閱讀即可,對(duì)于沒有混淆的apk这弧,閱讀起來(lái)跟閱讀代碼體驗(yàn)差不多娃闲。但是在反編譯 GoogleSetupWizard 的時(shí)候卻出錯(cuò)(提示notsupport version)。咨詢了別的同事建議試試apktool匾浪,解出smali文件皇帮。關(guān)于smali,這里有一份文檔供大家參考Smali學(xué)習(xí)筆記蛋辈。
apktool的下載和使用: 將apktool.jar和apktool放到/usr/local/bin 目錄(需要root權(quán)限)属拾;運(yùn)行apktool d <apk-file> 進(jìn)行反編譯;
反編譯舉例
問題描述:
在從Owner賬戶切換到Guest賬戶時(shí)失敗冷溶,自動(dòng)又切回Owner賬戶渐白,并且狀態(tài)欄無(wú)法正常下拉。
問題分析:
該問題第一個(gè)難點(diǎn)是確認(rèn)誰(shuí)觸發(fā)了切回Owner賬戶的動(dòng)作挂洛。這里我們用android.util.Log類的Log.getStackTraceString(newThrowable()) 方法來(lái)完成礼预。賬戶切換會(huì)調(diào)用ActivityManagerNative類的switchUser方法,我們?cè)谠摲椒ㄖ刑砑尤缦抡Z(yǔ)句:
Log.i(TAG,Log.getStackTraceString(newThrowable()));
這樣調(diào)用關(guān)系就一目了然了虏劲。結(jié)果如下:
ActivityManagerNative: at android.app.ActivityManagerProxy.switchUser(ActivityManagerNative.java:5866)
ActivityManagerNative: atcom.google.android.setupwizard.util.UserHelper.removeThisUser(UserHelper.java:43)
ActivityManagerNative: atcom.google.android.setupwizard.lifecycle.ProvisioningFlagsLifecycleCallback.onExit
(ProvisioningFlagsLifecycleCallback.java:49)
ActivityManagerNative: at com.google.android.setupwizard.lifecycle.LifecycleManager.notifyExit(LifecycleManager.java:115)
ActivityManagerNative: atcom.google.android.setupwizard.util.ExitHelper.finishSetup(ExitHelper.java:45)
ActivityManagerNative: at
**com.google.android.setupwizard.util.SetupWizardUserInitReceiver**
.onReceive (SetupWizardUserInitReceiver.java:33)
可以看出調(diào)用者是com.google.android.setupwizard應(yīng)用的SetupWizardUserInitReceiver類托酸,它是一個(gè)Google gms 應(yīng)用。我們用apktool將其反編譯柒巫,SetupWizardUserInitReceiver.smali
文件如下:
.superLandroid/content/BroadcastReceiver;
.source"SetupWizardUserInitReceiver.java"
#direct methods
.methodpublic constructor <init>()V
.locals0
.prologue
.line 28
invoke-direct{p0}, Landroid/content/BroadcastReceiver;-><init>()V
return-void
.endmethod
#virtual methods
.methodpublic onReceive(Landroid/content/Context;Landroid/content/Intent;)V
.locals2
.paramp1, "context" # Landroid/content/Context;
.paramp2, "intent" # Landroid/content/Intent;
.prologue
.line 31
const-string/jumbov0, "android.intent.action.USER_INITIALIZE"
invoke-virtual{p2}, Landroid/content/Intent;->getAction()Ljava/lang/String;
move-result-objectv1
invoke-virtual{v0, v1}, Ljava/lang/String;->equals(Ljava/lang/Object;)Z
move-resultv0
if-eqzv0, :cond_0
.line 32
invoke-static{p1},Landroid/os/UserManager;->get(Landroid/content/Context;)Landroid/os/UserManager;
move-result-objectv0
invoke-virtual{v0}, Landroid/os/UserManager;->isGuestUser()Z
move-resultv0
.line 31
if-eqzv0, :cond_0
.line 33
invoke-static{p1},Lcom/google/android/setupwizard/util/ExitHelper;->finishSetup(Landroid/content/Context;)V
.line 30
:cond_0
return-void
.endmethod
這是一個(gè)BroadcastReceiver励堡,我們要關(guān)注的是它的onReceive方法。通過網(wǎng)上搜索smali語(yǔ)法堡掏,可以判斷出當(dāng)為Guest用戶時(shí)會(huì)調(diào)用ExitHelper的finishSetup方法应结。我們?cè)倏催@個(gè)方法,
.methodpublic static finishSetup(Landroid/content/Context;)V
.locals2
.paramp0, "context" # Landroid/content/Context;
.prologue
.line 45
invoke-static{},Lcom/google/android/setupwizard/lifecycle/LifecycleManager;->get()Lcom/google/android/setupwizard/lifecycle/LifecycleManager;
move-result-objectv0
invoke-virtual{v0, p0},Lcom/google/android/setupwizard/lifecycle/LifecycleManager;->notifyExit(Landroid/content/Context;)V
.line 49
>new-instancev0, Landroid/content/Intent;
const-string/jumbov1, "com.google.android.setupwizard.SETUP_WIZARD_FINISHED"
invoke-direct{v0, v1}, Landroid/content/Intent;-><init>(Ljava/lang/String;)V
invoke-virtual{p0, v0},Landroid/content/Context;->sendBroadcast(Landroid/content/Intent;)V
.line 42
return-void
.endmethod
上面先拿到了一個(gè)LifecycleManager 實(shí)例泉唁,然后調(diào)用了LifecycleManager的notifyExit方法鹅龄,再發(fā)送了一個(gè)com.google.android.setupwizard.SETUP_WIZARD_FINISHED廣播。最終亭畜,我們跟蹤到調(diào)用用戶切換的代碼如下:
50 const-string/jumbo v3, "device_provisioned"**
51
52 invoke-static {v0, v3, v6},Landroid/provider/Settings$Global;>putInt(Landroid/content/ContentResolver;Ljava/lang/String;I)Z
53
54 .line 56
55 :cond_0
56 :goto_0
57 const-string/jumbo v3, "user_setup_complete"
58
59 invoke-static {v0, v3, v6},Landroid/provider/Settings$Secure;->putInt(Landroid/content/ContentResolver;Ljava/lang/String;I)Z
60
61 .line 34
62 return-void
63
64 .line 45
65 :cond_1
66 invoke-static {p1},Lcom/android/setupwizardlib/util/WizardManagerHelper;->isDeviceProvisioned(Landroid/content/Context;)Z
67
68 move-result v3
69
70 if-nez v3, :cond_0
71
72 .line 48
73 invoke-virtual {v2}, Landroid/os/UserManager;->getUserHandle()I
74
75 move-result v1
76
77 .line 49
78 .local v1, "user":I
79 invoke-static {p1},Lcom/google/android/setupwizard/util/UserHelper;->removeThisUser(Landroid/content/Context;)Z
上面會(huì)去讀device_provisioned這個(gè)setting項(xiàng)扮休,當(dāng)為false時(shí)會(huì)退出當(dāng)前賬戶。通過在源碼下搜索拴鸵,果然發(fā)現(xiàn)在packages/apps/Provision模塊下有對(duì)應(yīng)的提交玷坠,這樣就定位到了問題點(diǎn)。
總結(jié)
反編譯作為一項(xiàng)調(diào)試技巧劲藐,在通往高手之路上是不必可少的八堡。雖然反編譯后的代碼比較難懂,但是多加練習(xí)總有一天會(huì)得心應(yīng)手聘芜。