日韩黑丝制服一区视频播放|日韩欧美人妻丝袜视频在线观看|九九影院一级蜜桃|亚洲中文在线导航|青草草视频在线观看|婷婷五月色伊人网站|日本一区二区在线|国产AV一二三四区毛片|正在播放久草视频|亚洲色图精品一区

分享

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

 songen2216 2020-02-03

本人接觸不久,有錯誤望請各位神牛不吝賜教,僅僅希望把自己這段時(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\

  • 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目錄下的資源文件在編譯時(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)的符號有變化:

  • B---byte

  • C---char

  • D---double

  • F---float

  • I---int

  • J---long

  • S---short

  • V---void

  • Z---boolean

  • [XXX---array

  • Lxxx/yyy---object

     這里解析下最后兩項(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


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

       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實(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文件:
  1. class WMWActivity extends BaseActivity implements IBurstlyAdListener{
  2. //...
  3. class MessageHandler {
  4. //...
  5. }
  6. class FinishActivityArgs{
  7. //...
  8. }
  9. }
      沒錯,這就是本來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 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變量,不能僅僅指出該變量所在類的類型,還需要該變量所在類的實(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)一的,直接看例子不解釋:
      const/4 v3, 0x0      sput-object v3, Lcom/disney/WMW/WMWActivity;->globalIapHandler:Lcom/disney/config/GlobalPurchaseHandler;
      相當(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 {}, Lcom/disney/WMW/UnlockHelper;->unlockCrankypack()Z

      這里注意到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ù)的,例如:

      invoke-direct {p0}, Lcom/disney/WMW/WMWActivity;->getGlobalIapHandler()Lcom/disney/config/GlobalPurchaseHandler;

      這里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”,使用方法也有所不同: 
      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中的付費(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í)例。

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

      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這一個本地寄存器):
.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這個標(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)試還是保留一下吧。

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多