本人接觸不久,有錯誤望請各位神牛不吝賜教,僅僅希望把自己這段時(shí)間研究的東西分享一下,如果可以幫助到有需要的童鞋萬感榮幸。歡迎評論轉(zhuǎn)載,但請加上轉(zhuǎn)載來源謝謝!請尊重開發(fā)者勞動成果!請勿用于非法用途!作者:lpohvbe | http://blog.csdn.net/lpohvbe/article/details/7981386 這部分涉及的內(nèi)容比較多,我會盡量從最基礎(chǔ)開始說起,但需要讀者一定的android開發(fā)基礎(chǔ)。但注意可能講解詳細(xì)得令人作嘔,請根據(jù)個人理解程度斟酌。 APK、Dalvik字節(jié)碼和smali文件APK文件大家都應(yīng)該知道APK文件其實(shí)就是一個MIME為ZIP的壓縮包,我們修改ZIP后綴名方式可以看到內(nèi)部的文件結(jié)構(gòu),例如修改后綴后用RAR打開鱷魚小頑皮APK能看到的是(Google Play下載的完整版版本): Where's My Water.zip\
無關(guān)緊要地注:asset和res資源目錄的不同在于: 1. res目錄下的資源文件在編譯時(shí)會自動生成索引文件(R.java),在Java代碼中用R.xxx.yyy來引用;而asset目錄下的資源文件不需要生成索引,在Java代碼中需要用AssetManager來訪問; 2. 一般來說,除了音頻和視頻資源(需要放在raw或asset下),使用Java開發(fā)的Android工程使用到的資源文件都會放在res下;使用C++游戲引擎(或使用Lua binding等)的資源文件均需要放在asset下。 因?yàn)閃here's My Water是使用迪斯尼公司自家的DMO游戲引擎開發(fā),所以游戲中用到的所有資源文件都存放在asset下,除了應(yīng)用圖標(biāo)這些資源仍需要放在res下。 Dalvik字節(jié)碼Dalvik是google專門為Android操作系統(tǒng)設(shè)計(jì)的一個虛擬機(jī),經(jīng)過深度的優(yōu)化。雖然Android上的程序是使用java來開發(fā)的,但是Dalvik和標(biāo)準(zhǔn)的java虛擬機(jī)JVM還是兩回事。Dalvik VM是基于寄存器的,而JVM是基于棧的;Dalvik有專屬的文件執(zhí)行格式dex(dalvik executable),而JVM則執(zhí)行的是java字節(jié)碼。Dalvik VM比JVM速度更快,占用空間更少。 通過Dalvik的字節(jié)碼我們不能直接看到原來的邏輯代碼,這時(shí)需要借助如Apktool或dex2jar+jd-gui工具來幫助查看。但是,注意的是最終我們修改APK需要操作的文件是.smali文件,而不是導(dǎo)出來的Java文件重新編譯(況且這基本上不可能)。 smali文件好了,對Dalvik有一定認(rèn)識后,下面介紹重點(diǎn):smali,及其語法。 簡單的說,smali就是Dalvik VM內(nèi)部執(zhí)行的核心代碼。它有自己的一套語法,下面即將介紹,如果有JNI開發(fā)經(jīng)驗(yàn)的童鞋則能夠很快明白。 一、smali的數(shù)據(jù)類型 在smali中,數(shù)據(jù)類型和Android中的一樣,只是對應(yīng)的符號有變化:
這里解析下最后兩項(xiàng),數(shù)組的表示方式是:在基本類型前加上前中括號“[”,例如int數(shù)組和float數(shù)組分別表示為:[I、[F;對象的表示則以L作為開頭,格式是LpackageName/objectName;(注意必須有個分號跟在最后),例如String對象在smali中為:Ljava/lang/String;,其中java/lang對應(yīng)java.lang包,String就是定義在該包中的一個對象。 或許有人問,既然類是用LpackageName/objectName;來表示,那類里面的內(nèi)部類又如何在smali中引用呢?答案是:LpackageName/objectName$subObjectName;。也就是在內(nèi)部類前加“$”符號,關(guān)于“$”符號更多的規(guī)則將在后面談到。 二、函數(shù)的定義 函數(shù)的定義一般為: Func-Name (Para-Type1Para-Type2Para-Type3...)Return-Type 注意參數(shù)與參數(shù)之間沒有任何分隔符,同樣舉幾個例子就容易明白了: 1. foo ()V 沒錯,這就是void foo()。 2. foo (III)Z 這個則是boolean foo(int, int, int)。 3. foo (Z[I[ILjava/lang/String;J)Ljava/lang/String; 看出來這是String foo (boolean, int[], int[], String, long) 了嗎? 三、smali文件內(nèi)容具體介紹 下面開始進(jìn)一步分析smali中的具體例子,取鱷魚小頑皮中的WMWActivity.smali來分析(怎么獲得請參考下一節(jié)的APK反編譯之二:工具介紹,暫時(shí)先介紹smali語法),它的內(nèi)容大概是這樣子的: .class public Lcom/disney/WMW/WMWActivity; .super Lcom/disney/common/BaseActivity;.source 'WMWActivity.java' # interfaces.implements Lcom/burstly/lib/ui/IBurstlyAdListener; # annotations.annotation system Ldalvik/annotation/MemberClasses; value = { Lcom/disney/WMW/WMWActivity$MessageHandler;, Lcom/disney/WMW/WMWActivity$FinishActivityArgs; }.end annotation # static fields.field private static final PREFS_INSTALLATION_ID:Ljava/lang/String; = 'installationId'//... # instance fields.field private _activityPackageName:Ljava/lang/String;//... # direct methods.method static constructor <clinit>()V .locals 3 .prologue //... return-void.end method .method public constructor <init>()V .locals 3 .prologue //... return-void.end method .method static synthetic access$100(Lcom/disney/WMW/WMWActivity;)V .locals 0 .parameter 'x0' .prologue .line 37 invoke-direct {p0}, Lcom/disney/WMW/WMWActivity;->initIap()V return-void.end method .method static synthetic access$200(Lcom/disney/WMW/WMWActivity;)Lcom/disney/common/WMWView; .locals 1 .parameter 'x0' .prologue .line 37 iget-object v0, p0, Lcom/disney/WMW/WMWActivity;->_view:Lcom/disney/common/WMWView; return-object v0.end method //... #virtual methods.method public captureScreen()V .locals 4 .prologue //... goto :goto_0.end method .method public didScreenCaptured()V .locals 6 .prologue //... goto :goto_0.end method 首先看看開頭的幾行: 1] .class public Lcom/disney/WMW/WMWActivity; 2] .super Lcom/disney/common/BaseActivity; 3] .source 'WMWActivity.java' 4] 5] # interfaces 6] .implements Lcom/burstly/lib/ui/IBurstlyAdListener; 7] 8] # annotations 9] .annotation system Ldalvik/annotation/MemberClasses; 10] value = { 11] Lcom/disney/WMW/WMWActivity$MessageHandler;, 12] Lcom/disney/WMW/WMWActivity$FinishActivityArgs; 13] } 14] .end annotation 1-3行定義的是基本信息:這是一個由WMWActivity.java編譯得到的smali文件(第3行),它是com.disney.WMW這個package下的一個類(第1行),繼承自com.disney.common.BaseActivity(第2行)。 5-6行定義的是接口信息:這個WMWActivity實(shí)現(xiàn)了一個com.burstly.lib.ui這個package下(一個廣告SDK)的IBurstyAdListener接口。 8-14行定義的則是內(nèi)部類:它有兩個成員內(nèi)部類——MessageHandler和FinishActivityArgs,內(nèi)部類將在后面小節(jié)中會有提及。 分析完smali文件開頭的這些信息,我們已經(jīng)能在大腦中構(gòu)造出一個大概這樣的Java文件:
沒錯,這就是本來WMWActivity.java的大概框架了,成員變量和函數(shù)信息?別急,下面正要分析。 在繼續(xù)分析之前,有些東西需要先說明一下。前面說過,Dalvik VM與JVM的最大的區(qū)別之一就是Dalvik VM是基于寄存器的?;诩拇嫫魇鞘裁匆馑寄??也就是說,在smali里的所有操作都必須經(jīng)過寄存器來進(jìn)行:本地寄存器用v開頭數(shù)字結(jié)尾的符號來表示,如v0、v1、v2、...參數(shù)寄存器則使用p開頭數(shù)字結(jié)尾的符號來表示,如p0、p1、p2、...特別注意的是,p0不一定是函數(shù)中的第一個參數(shù),在非static函數(shù)中,p0代指“this”,p1表示函數(shù)的第一個參數(shù),p2代表函數(shù)中的第二個參數(shù)…而在static函數(shù)中p0才對應(yīng)第一個參數(shù)(因?yàn)镴ava的static方法中沒有this方法)。本地寄存器沒有限制,理論上是可以任意使用的,下面是例子: const/4 v0, 0x0iput-boolean v0, p0, Lcom/disney/WMW/WMWActivity;->isRunning:Z 在上面的兩句中,使用了v0本地寄存器,并把值0x0存到v0中,然后第二句用iput-boolean這個指令把v0中的值存放到com.disney.WMW.WMWActivity.isRunning這個成員變量中。即相當(dāng)于:this.isRunning = false;(上面說過,在非static函數(shù)中p0代表的是“this”,在這里就是com.disney.WMW.WMWActivity實(shí)例)。關(guān)于這兩句話的具體指令和含義暫可不用理會,先把Dalvik VM的機(jī)制弄明白就可以了,其實(shí)語法上和匯編語言非常相似,具體的指令會在后面逐一介紹。 2、smali中的成員變量 下面繼續(xù)介紹有關(guān)成員變量的內(nèi)容: 1 ] # static fields 2 ] .field private static final PREFS_INSTALLATION_ID:Ljava/lang/String; = 'installationId' 3 ] //... 4 ] 5 ] 6 ] # instance fields 7 ] .field private _activityPackageName:Ljava/lang/String; 8 ] //... 上面定義的static fields和instance fields均為成員變量,格式是:.field public/private [static] [final] varName:<類型>。然而static fields和instance fields還是有區(qū)別的,當(dāng)然區(qū)別很明顯,那就是static fields是static的,而instance則不是。根據(jù)這個區(qū)別來獲取這些不同的成員變量時(shí)也有不同的指令。一般來說,獲取的指令有:iget、sget、iget-boolean、sget-boolean、iget-object、sget-object等,操作的指令有:iput、sput、iput-boolean、sput-boolean、iput-object、sput-object等。沒有“-object”后綴的表示操作的成員變量對象是基本數(shù)據(jù)類型,帶“-object”表示操作的成員變量是對象類型,特別地,boolean類型則使用帶“-boolean”的指令操作。 (1)、獲取static fields的指令類似是:
sget-object就是用來獲取變量值并保存到緊接著的參數(shù)的寄存器中,在這里,把上面出現(xiàn)的PREFS_INSTALLATION_ID這個String成員變量獲取并放到v0這個寄存器中,注意:前面需要該變量所屬的類的類型,后面需要加一個冒號和該成員變量的類型,中間是“->”表示所屬關(guān)系。 (2)、獲取instance fields的指令與static fields的基本一樣,只是由于不是static變量,不能僅僅指出該變量所在類的類型,還需要該變量所在類的實(shí)例。看例子: iget-object v0, p0, Lcom/disney/WMW/WMWActivity;->_view:Lcom/disney/common/WMWView; 可以看到iget-object指令比sget-object多了一個參數(shù),就是該變量所在類的實(shí)例,在這里就是p0即“this”。 (3)、獲取array的還有aget和aget-object,指令使用和上述類似,不細(xì)述。 (4)、put指令的使用和get指令是統(tǒng)一的,直接看例子不解釋:
相當(dāng)于:this.globalIapHandler = null;(null = 0x0) .local v0, wait:Landroid/os/Message; const/4 v1, 0x2 iput v1, v0, Landroid/os/Message;->what:I 相當(dāng)于:wait.what = 0x2;(wait是Message的實(shí)例) 3、smali中的函數(shù)調(diào)用smali中的函數(shù)和成員變量一樣也分為兩種類型,但是不同成員變量中的static和instance之分,而是direct和virtual之分。那么direct method和virtual method有什么區(qū)別呢?直白地講,direct method就是private函數(shù),其余的public和protected函數(shù)都屬于virtual method。所以在調(diào)用函數(shù)時(shí),有invoke-direct,invoke-virtual,另外還有invoke-static、invoke-super以及invoke-interface等幾種不同的指令。當(dāng)然其實(shí)還有invoke-XXX/range 指令的,這是參數(shù)多于4個的時(shí)候調(diào)用的指令,比較少見,了解下即可。 (1)、invoke-static:顧名思義就是調(diào)用static函數(shù)的,因?yàn)槭莝tatic函數(shù),所以比起其他調(diào)用少一個參數(shù),例如:
這里注意到invoke-static后面有一對大括號“{}”,其實(shí)是調(diào)用該方法的實(shí)例+參數(shù)列表,由于這個方法既不需參數(shù)也是static的,所以{}內(nèi)為空,再看一個例子: const-string v0, 'fmodex' invoke-static {v0}, Ljava/lang/System;->loadLibrary(Ljava/lang/String;)V<span style='font-family:Verdana, sans-serif;'> </span> 這個是調(diào)用static void System.loadLibrary(String)來加載NDK編譯的so庫用的方法,同樣也是這里v0就是參數(shù)'fmodex'了。 (2)、invoke-super:調(diào)用父類方法用的指令,在onCreate、onDestroy等方法都能看到,略。 (3)、invoke-direct:調(diào)用private函數(shù)的,例如:
這里GlobalPurchaseHandler getGlobalIapHandler()就是定義在WMWActivity中的一個private函數(shù),如果修改smali時(shí)錯用invoke-virtual或invoke-static將在回編譯后程序運(yùn)行時(shí)引發(fā)一個常見的VerifyError(更多錯誤匯總可參照APK反編譯之番外三:常見錯誤匯總)。 (4)、invoke-virtual:用于調(diào)用protected或public函數(shù),同樣注意修改smali時(shí)不要錯用invoke-direct或invoke-static,例子: sget-object v0, Lcom/disney/WMW/WMWActivity;->shareHandler:Landroid/os/Handler; invoke-virtual {v0, v3}, Landroid/os/Handler;->removeCallbacksAndMessages(Ljava/lang/Object;)V 這里相信大家都已經(jīng)明白了,主要搞清楚v0是shareHandler:Landroid/os/Handler,v3是傳遞給removeCallbackAndMessage方法的Ljava/lang/Object參數(shù)就可以了。 (5)、invoke-xxxxx/range:當(dāng)方法的參數(shù)多于5個時(shí)(含5個),不能直接使用以上的指令,而是在后面加上“/range”,使用方法也有所不同:
這個是電信SDK中的付費(fèi)接口,需要傳遞6個參數(shù),這時(shí)候大括號內(nèi)的參數(shù)需要用省略形式,且需要連續(xù)(未求證是否需要從v0開始)。 有人也許注意到,剛才看到的例子都是“調(diào)用函數(shù)”這個操作而已,貌似沒有取函數(shù)返回的結(jié)果的操作? 在Java代碼中調(diào)用函數(shù)和返回函數(shù)結(jié)果是一條語句完成的,而在smali里則需要分開來完成,在使用上述指令后,如果調(diào)用的函數(shù)返回非void,那么還需要用到move-result(返回基本數(shù)據(jù)類型)和move-result-object(返回對象)指令: const/4 v2, 0x0 invoke-virtual {p0, v2}, Lcom/disney/WMW/WMWActivity;->getPreferences(I)Landroid/content/SharedPreferences; move-result-object v1 v1保存的就是調(diào)用getPreferences(int)方法返回的SharedPreferences實(shí)例。
v2保存的則是調(diào)用String.length()返回的整型。 4、smali中函數(shù)實(shí)體分析下面開始介紹函數(shù)實(shí)體,其實(shí)沒有什么特別的地方,只是在植入代碼時(shí)有一點(diǎn)需要特別注意,舉例說明: .method protected onDestroy()V .locals 0 .prologue .line 277 invoke-super {p0}, Lcom/disney/common/BaseActivity;->onDestroy()V .line 279 return-void.end method 這是onDestroy()函數(shù),它的作用大家都知道。首先看到函數(shù)內(nèi)第一句:.local 0,這句話很重要,標(biāo)明了你在這個函數(shù)中最少要用到的本地寄存器的個數(shù)。在這里,由于只需要調(diào)用一個父類的onDestroy()處理,所以只需要用到p0,所以使用到的本地寄存器數(shù)為0。如果不清楚這個規(guī)則,很容易在植入代碼后忘記修改.local 的值,那么回編譯后運(yùn)行時(shí)將會得到一個VerifyError錯誤,而且極難發(fā)現(xiàn)問題所在。我正是被這個問題困擾了很多次,最后研究發(fā)現(xiàn).local的值有這個規(guī)律,于是在文檔查證了一下果然是這個問題。例如我往onDestroy()增加一句:this.existed = true;那么應(yīng)該改為(注意修改.local的值為1——使用到了v0這一個本地寄存器):
另外注意到.line這個標(biāo)識,它是標(biāo)注了該代碼在原Java文件中的行數(shù),它也很有用,想想使用eclipse開發(fā)時(shí),遇到錯誤崩潰時(shí),在catLog不是有提示哪個文件哪一行崩潰的么?Dalvik VM運(yùn)行到.line XX時(shí)就將這個值存起來,如果在這一行運(yùn)行時(shí)出錯了,就往catLog輸出這個值,這樣我們就能看到具體是哪一行的問題了。jd-gui這個工具也是通過分析這些信息將smali代碼還原成我們喜聞樂見的Java代碼的。當(dāng)然,它不是必須的,去掉也沒有關(guān)系,只不過為了方便調(diào)試還是保留一下吧。 |
|