APK反編譯之一:基礎(chǔ)知識--smali文件閱讀

用反編譯工具apktool得到src為的smali文件,因此需要了解下smali的語法如下轉(zhuǎn)載博客:

http://blog.csdn.net/lpohvbe/article/details/7981386

APK反編譯之一:基礎(chǔ)知識

本人接觸不久,有錯誤望請各位神牛不吝賜教,僅僅希望把自己這段時間研究的東西分享一下,如果可以幫助到有需要的童鞋萬感榮幸。歡迎評論轉(zhuǎn)載,但請加上轉(zhuǎn)載來源謝謝!請尊重開發(fā)者勞動成果!請勿用于非法用途!

作者:lpohvbe |?http://blog.csdn.net/lpohvbe/article/details/7981386

這部分涉及的內(nèi)容比較多,我會盡量從最基礎(chǔ)開始說起,但需要讀者一定的android開發(fā)基礎(chǔ)。但注意可能講解詳細得令人作嘔,請根據(jù)個人理解程度斟酌。

APK、Dalvik字節(jié)碼和smali文件

APK文件

? ? 大家都應(yīng)該知道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\ ? ? ? ? ? ? ? |---<圖片和對應(yīng)的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下,除了應(yīng)用圖標這些資源仍需要放在res下。

Dalvik字節(jié)碼

? ? ? 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文件,而不是導(dǎo)出來的Java文件重新編譯(況且這基本上不可能)。

smali文件

? ? ? 好了,對Dalvik有一定認識后,下面介紹重點:smali,及其語法。

? ? ? 簡單的說,smali就是Dalvik VM內(nèi)部執(zhí)行的核心代碼。它有自己的一套語法,下面即將介紹,如果有JNI開發(fā)經(jīng)驗的童鞋則能夠很快明白。

? ? ? 一、smali的數(shù)據(jù)類型

? ? ? 在smali中,數(shù)據(jù)類型和Android中的一樣,只是對應(yīng)的符號有變化:

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對應(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)容具體介紹

下面開始進一步分析smali中的具體例子,取鱷魚小頑皮中的WMWActivity.smali來分析(怎么獲得請參考下一節(jié)的APK反編譯之二:工具介紹,暫時先介紹smali語法),它的內(nèi)容大概是這樣子的:

[plain]view plaincopy

.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??


看得一頭霧水的話那是正常的?,F(xiàn)在我將逐一解析,理解這些符號的含義令你在后面注入代碼的時候事半功倍。

1、smali中的繼承、接口、包信息

? ? ? 首先看看開頭的幾行:

? 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實現(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文件:


[java]view plaincopy

class?WMWActivity?extends?BaseActivity?implements?IBurstlyAdListener{??

//...??

class?MessageHandler?{??

//...??

????}??

class?FinishActivityArgs{??

//...??

????}??

}??


? ? ? 沒錯,這就是本來WMWActivity.java的大概框架了,成員變量和函數(shù)信息?別急,下面正要分析。


? ? ? 在繼續(xù)分析之前,有些東西需要先說明一下。前面說過,Dalvik VM與JVM的最大的區(qū)別之一就是Dalvik VM是基于寄存器的?;诩拇嫫魇鞘裁匆馑寄兀恳簿褪钦f,在smali里的所有操作都必須經(jīng)過寄存器來進行:本地寄存器用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ù)(因為Java的static方法中沒有this方法)。本地寄存器沒有限制,理論上是可以任意使用的,下面是例子:

[plain]view plaincopy

const/4?v0,?0x0??

iput-boolean?v0,?p0,?Lcom/disney/WMW/WMWActivity;->isRunning:Z??

? ? ? 在上面的兩句中,使用了v0本地寄存器,并把值0x0存到v0中,然后第二句用iput-boolean這個指令把v0中的值存放到com.disney.WMW.WMWActivity.isRunning這個成員變量中。即相當于:this.isRunning = false;(上面說過,在非static函數(shù)中p0代表的是“this”,在這里就是com.disney.WMW.WMWActivity實例)。關(guān)于這兩句話的具體指令和含義暫可不用理會,先把Dalvik VM的機制弄明白就可以了,其實語法上和匯編語言非常相似,具體的指令會在后面逐一介紹。


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ū)別的,當然區(qū)別很明顯,那就是static fields是static的,而instance則不是。根據(jù)這個區(qū)別來獲取這些不同的成員變量時也有不同的指令。一般來說,獲取的指令有: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的指令類似是:

[plain]view plaincopy

sget-object?v0,?Lcom/disney/WMW/WMWActivity;->PREFS_INSTALLATION_ID:Ljava/lang/String;??

? ? ? sget-object就是用來獲取變量值并保存到緊接著的參數(shù)的寄存器中,在這里,把上面出現(xiàn)的PREFS_INSTALLATION_ID這個String成員變量獲取并放到v0這個寄存器中,注意:前面需要該變量所屬的類的類型,后面需要加一個冒號和該成員變量的類型,中間是“->”表示所屬關(guān)系。


(2)、獲取instance fields的指令與static fields的基本一樣,只是由于不是static變量,不能僅僅指出該變量所在類的類型,還需要該變量所在類的實例??蠢樱?/p>

[plain]view plaincopy

iget-object?v0,?p0,?Lcom/disney/WMW/WMWActivity;->_view:Lcom/disney/common/WMWView;??

? ? ? 可以看到iget-object指令比sget-object多了一個參數(shù),就是該變量所在類的實例,在這里就是p0即“this”。


? ? ? (3)、獲取array的還有aget和aget-object,指令使用和上述類似,不細述。


? ? ? (4)、put指令的使用和get指令是統(tǒng)一的,直接看例子不解釋:

[plain]view plaincopy

const/4?v3,?0x0??

sput-object?v3,?Lcom/disney/WMW/WMWActivity;->globalIapHandler:Lcom/disney/config/GlobalPurchaseHandler;??

? ? ? 相當于:this.globalIapHandler = null;(null = 0x0)


[plain]view plaincopy

.local?v0,?wait:Landroid/os/Message;??

const/4?v1,?0x2??

iput?v1,?v0,?Landroid/os/Message;->what:I??

? ? ??相當于:wait.what = 0x2;(wait是Message的實例)

? ? ? ? 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ù)時,有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ù),例如:

[plain]view plaincopy

invoke-static?{},?Lcom/disney/WMW/UnlockHelper;->unlockCrankypack()Z??

這里注意到invoke-static后面有一對大括號“{}”,其實是調(diào)用該方法的實例+參數(shù)列表,由于這個方法既不需參數(shù)也是static的,所以{}內(nèi)為空,再看一個例子:

[plain]view plaincopy

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ù)的,例如:

[plain]view plaincopy

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,例子:

[plain]view plaincopy

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:當方法的參數(shù)多于5個時(含5個),不能直接使用以上的指令,而是在后面加上“/range”,使用方法也有所不同:?

[plain]view plaincopy

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??

? ? ? 這個是電信SDK中的付費接口,需要傳遞6個參數(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(返回對象)指令:

[plain]view plaincopy

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實例。

[plain]view plaincopy

invoke-virtual?{v2},?Ljava/lang/String;->length()I??

move-result?v2??

? ? ? v2保存的則是調(diào)用String.length()返回的整型。

4、smali中函數(shù)實體分析

? ? ? 下面開始介紹函數(shù)實體,其實沒有什么特別的地方,只是在植入代碼時有一點需要特別注意,舉例說明:

[plain]view plaincopy

.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,這句話很重要,標明了你在這個函數(shù)中最少要用到的本地寄存器的個數(shù)。在這里,由于只需要調(diào)用一個父類的onDestroy()處理,所以只需要用到p0,所以使用到的本地寄存器數(shù)為0。如果不清楚這個規(guī)則,很容易在植入代碼后忘記修改.local 的值,那么回編譯后運行時將會得到一個VerifyError錯誤,而且極難發(fā)現(xiàn)問題所在。我正是被這個問題困擾了很多次,最后研究發(fā)現(xiàn).local的值有這個規(guī)律,于是在文檔查證了一下果然是這個問題。例如我往onDestroy()增加一句:this.existed = true;那么應(yīng)該改為(注意修改.local的值為1——使用到了v0這一個本地寄存器):

[plain]view plaincopy

.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??

? ? ? 另外注意到.line這個標識,它是標注了該代碼在原Java文件中的行數(shù),它也很有用,想想使用eclipse開發(fā)時,遇到錯誤崩潰時,在catLog不是有提示哪個文件哪一行崩潰的么?Dalvik VM運行到.line XX時就將這個值存起來,如果在這一行運行時出錯了,就往catLog輸出這個值,這樣我們就能看到具體是哪一行的問題了。jd-gui這個工具也是通過分析這些信息將smali代碼還原成我們喜聞樂見的Java代碼的。當然,它不是必須的,去掉也沒有關(guān)系,只不過為了方便調(diào)試還是保留一下吧。

以上一些smali語法規(guī)則可以參詳這里。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容