作者:lpohvbe | http://blog.csdn.net/lpohvbe/article/details/7981386
這部分涉及的內(nèi)容比較多,我會盡量從最基礎(chǔ)開始說起,但需要讀者一定的android開發(fā)基礎(chǔ)。但注意可能講解詳細得令人作嘔,請根據(jù)個人理解程度斟酌。
大家都應該知道APK文件其實就是一個MIME為ZIP的壓縮包,我們修改ZIP后綴名方式可以看到內(nèi)部的文件結(jié)構(gòu),例如修改后綴后用RAR打開鱷魚小頑皮APK能看到的是(Google Play下載的完整版版本):
Where's My Water.zip\
asset\ <資源目錄1:asset和res都是資源目錄但有所區(qū)別,見下面說明>
lib\ <so庫存放位置,一般由NDK編譯得到,常見于使用游戲引擎或JNI native調(diào)用的工程中>
|---armeabi\ |---<so庫文件分為不同的CPU架構(gòu)>
|---armeabi-v7a\
META-INF\ <存放工程一些屬性文件,例如Manifest.MF>
res\ <資源目錄2:asset和res都是資源目錄但有所區(qū)別,見下面說明>
|---drawable\ |---<圖片和對應的xml資源>
|---layout\ |---<定義布局的xml資源>
|---...
AndroidManifest.xml <Android工程的基礎(chǔ)配置屬性文件>
classes.dex <Java代碼編譯得到的Dalvik VM能直接執(zhí)行的文件,下面有介紹>
resources.arsc <對res目錄下的資源的一個索引文件,保存了原工程中strings.xml等文件內(nèi)容>
無關(guān)緊要地注:asset和res資源目錄的不同在于:
1. res目錄下的資源文件在編譯時會自動生成索引文件(R.java),在Java代碼中用R.xxx.yyy來引用;而asset目錄下的資源文件不需要生成索引,在Java代碼中需要用AssetManager來訪問;
2. 一般來說,除了音頻和視頻資源(需要放在raw或asset下),使用Java開發(fā)的Android工程使用到的資源文件都會放在res下;使用C++游戲引擎(或使用Lua binding等)的資源文件均需要放在asset下。
因為Where's My Water是使用迪斯尼公司自家的DMO游戲引擎開發(fā),所以游戲中用到的所有資源文件都存放在asset下,除了應用圖標這些資源仍需要放在res下。
Dalvik是google專門為Android操作系統(tǒng)設(shè)計的一個虛擬機,經(jīng)過深度的優(yōu)化。雖然Android上的程序是使用java來開發(fā)的,但是Dalvik和標準的java虛擬機JVM還是兩回事。Dalvik VM是基于寄存器的,而JVM是基于棧的;Dalvik有專屬的文件執(zhí)行格式dex(dalvik executable),而JVM則執(zhí)行的是java字節(jié)碼。Dalvik VM比JVM速度更快,占用空間更少。
通過Dalvik的字節(jié)碼我們不能直接看到原來的邏輯代碼,這時需要借助如Apktool或dex2jar+jd-gui工具來幫助查看。但是,注意的是最終我們修改APK需要操作的文件是.smali文件,而不是導出來的Java文件重新編譯(況且這基本上不可能)。
好了,對Dalvik有一定認識后,下面介紹重點:smali,及其語法。
簡單的說,smali就是Dalvik VM內(nèi)部執(zhí)行的核心代碼。它有自己的一套語法,下面即將介紹,如果有JNI開發(fā)經(jīng)驗的童鞋則能夠很快明白。
一、smali的數(shù)據(jù)類型
在smali中,數(shù)據(jù)類型和Android中的一樣,只是對應的符號有變化:
B---byte
C---char
D---double
F---float
I---int
J---long
S---short
V---void
Z---boolean
[XXX---array
Lxxx/yyy---object
這里解析下最后兩項,數(shù)組的表示方式是:在基本類型前加上前中括號“[”,例如int數(shù)組和float數(shù)組分別表示為:[I、[F;對象的表示則以L作為開頭,格式是LpackageName/objectName;(注意必須有個分號跟在最后),例如String對象在smali中為:Ljava/lang/String;,其中java/lang對應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)容具體介紹
下面開始進一步分析smali中的具體例子,取鱷魚小頑皮中的WMWActivity.smali來分析(怎么獲得請參考下一節(jié)的APK反編譯之二:工具介紹,暫時先介紹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
- class WMWActivity extends BaseActivity implements IBurstlyAdListener{
- //...
- class MessageHandler {
- //...
- }
- class FinishActivityArgs{
- //...
- }
- }
const/4 v0, 0x0iput-boolean v0, p0, Lcom/disney/WMW/WMWActivity;->isRunning:Z
sget-object v0, Lcom/disney/WMW/WMWActivity;->PREFS_INSTALLATION_ID:Ljava/lang/String;
iget-object v0, p0, Lcom/disney/WMW/WMWActivity;->_view:Lcom/disney/common/WMWView;
const/4 v3, 0x0 sput-object v3, Lcom/disney/WMW/WMWActivity;->globalIapHandler:Lcom/disney/config/GlobalPurchaseHandler;
.local v0, wait:Landroid/os/Message; const/4 v1, 0x2 iput v1, v0, Landroid/os/Message;->what:I
smali中的函數(shù)和成員變量一樣也分為兩種類型,但是不同成員變量中的static和instance之分,而是direct和virtual之分。那么direct method和virtual method有什么區(qū)別呢?直白地講,direct method就是private函數(shù),其余的public和protected函數(shù)都屬于virtual method。所以在調(diào)用函數(shù)時,有invoke-direct,invoke-virtual,另外還有invoke-static、invoke-super以及invoke-interface等幾種不同的指令。當然其實還有invoke-XXX/range 指令的,這是參數(shù)多于4個的時候調(diào)用的指令,比較少見,了解下即可。
(1)、invoke-static:顧名思義就是調(diào)用static函數(shù)的,因為是static函數(shù),所以比起其他調(diào)用少一個參數(shù),例如:
invoke-static {}, Lcom/disney/WMW/UnlockHelper;->unlockCrankypack()Z
這里注意到invoke-static后面有一對大括號“{}”,其實是調(diào)用該方法的實例+參數(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>
(2)、invoke-super:調(diào)用父類方法用的指令,在onCreate、onDestroy等方法都能看到,略。
(3)、invoke-direct:調(diào)用private函數(shù)的,例如:
invoke-direct {p0}, Lcom/disney/WMW/WMWActivity;->getGlobalIapHandler()Lcom/disney/config/GlobalPurchaseHandler;
這里GlobalPurchaseHandler getGlobalIapHandler()就是定義在WMWActivity中的一個private函數(shù),如果修改smali時錯用invoke-virtual或invoke-static將在回編譯后程序運行時引發(fā)一個常見的VerifyError(更多錯誤匯總可參照APK反編譯之番外三:常見錯誤匯總)。
(4)、invoke-virtual:用于調(diào)用protected或public函數(shù),同樣注意修改smali時不要錯用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
invoke-static/range {v0 .. v5}, Lcn/game189/sms/SMS;->checkFee(Ljava/lang/String;Landroid/app/Activity;Lcn/game189/sms/SMSListener;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Z
有人也許注意到,剛才看到的例子都是“調(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
invoke-virtual {v2}, Ljava/lang/String;->length()I move-result v2
v2保存的則是調(diào)用String.length()返回的整型。
下面開始介紹函數(shù)實體,其實沒有什么特別的地方,只是在植入代碼時有一點需要特別注意,舉例說明:
.method protected onDestroy()V .locals 0 .prologue .line 277 invoke-super {p0}, Lcom/disney/common/BaseActivity;->onDestroy()V .line 279 return-void.end method
.method protected onDestroy()V .locals 1 .prologue .line 277 const/4 v0, 0x1 iput-boolean v0, p0, Lcom/disney/WMW/WMWActivity;->exited:Z invoke-super {p0}, Lcom/disney/common/BaseActivity;->onDestroy()V .line 279 return-void.end method