Android逆向分析(二)之Smali 指令集及文件詳解

指令集

  • 特色java

    • 1 參數採用從目標到源的方式。android

    • 2 根據字節碼的大小與類型不一樣,一些字節碼添加了名稱後綴以消除歧義數組

      ● 32位常規類型的字節碼未添加任何後綴
      
        ● 64常規類型的字節碼添加 -wide 後綴
      
        ● 特殊類型的字節碼根據具體類型添加後綴。它們能夠是 -boolean、-byte、-char、-short、 -int、-long、-float、-double、-object、-string、-void之一。
    • 3 根據字節碼的佈局與選項不一樣,一些字節碼添加了字節碼後綴以消除歧義。這些後綴經過在字節碼主名稱後綴添加斜槓「/」來分割開。app

    • 4 在指令集的描述中,寬度值中每一個字幕表示寬度爲4位。less

    舉個例子: move-wide/from16 vAA, vBBBBide

    該指令表示:move爲基礎字節碼,標識這是基本操做。wide爲名稱後綴,標識指令操做的數據寬度(64位)。from16爲字節碼後綴(opcode suffix),標識源爲一個16位的寄存器引用變量。vAA爲目的寄存器,它始終在源的前面,取值範圍爲v0~v255。vBBBB爲源寄存器,取值範圍爲v0~v65535。 指令集中大多數指令用到了寄存器做爲目的操做數或源操做數,其中 A/B/C/D/E/F/G/H 表明一個4位的數值, 可用來表示v0~v15的寄存器。 AA/BB/.../HH表明一個8位的數值。 AAAA/BBBB/.../HHHH 表明一個16位的數值函數

  • 數據操做指令工具

    數據操做指令爲move。move指令的原型爲「move destination,source」,move指令根據字節碼的大小與類型不一樣,後面會跟上不一樣的後綴。 eg:佈局

    - 「move vA, vB」:將vB寄存器的值賦給vA寄存器,源寄存器與目的寄存器都爲4位。
      - "move /from 16 VAA,VBBBB":將VBBBB寄存器的值賦給VAA寄存器,源寄存器爲16位,目標寄存器爲8位
      - 「move /from 16 VAAAA,VBBBB」:將VBBBB寄存器的值賦給VAAAA,源寄存器和目標寄存器都爲16位
      - 「move-wide vA, vB」:爲4位的寄存器對賦值。源寄存器與目的寄存器都爲4位
      - "move-object vA,vB":將vB寄存器中的對象引用賦值給vA寄存器,vA寄存器和vB寄存器都是4位
      - "move-result vAA":將上一個「invoke」(方法調用)指令,操做的單字(32位)
      - 「move-result-wide vAA」 :將上一個invoke指令操做的雙字(64位)非對象結果賦值給vAA寄存器
      - 「mvoe-result-object vAA」:將上一個invoke指令操做的對象結果賦值給vAA寄存器
      - 「move-exception vAA」:保存上一個運行時發生的異常到vAA寄存器
  • 數據定義指令this

    數據定義指令用來定義程序中用到的常量、字符串、類等數據。它的基礎字節碼爲const

    - const/4 vA,#+B 將數值符號擴展爲32位後賦給寄存器 vA
      - const/16 vAA,#+BBBB 將數值符號擴展爲32位後賦給寄存器 vAA
      - const vAA,#+BBBBBBBB 將數值賦給寄存器vAA
      - const/high16 vAA,#+BBBB0000 將數值右邊 0 擴展爲32位後賦給寄存器vAA
      - const-wide/16 vAA,#+BBBB 將數值符號擴展64位後賦給寄存器對vAA
      - const-wide vAA,#+BBBBBBBBBBBBBBBB 將數值賦給寄存器對vAA
      - const-wide/high16 vAA,#+BBBB000000000000 將數值右邊 0 擴展爲64位後付賦值給寄存器 vAA
      - const-string vAA,string[@BBBB](https://my.oschina.net/u/205605) 經過字符串索引構造一個字符串並賦給寄存器對 vAA
      - const-string/jumbo vAA,string[@BBBBBBBB](https://my.oschina.net/u/2326784) 經過字符串索引(較大) 構造一個字符串並賦值給寄存器對vAA
      - const-class vAA,type[@BBBB](https://my.oschina.net/u/205605) 經過類型索引獲取一個類引用並賦值給寄存器 vAA
      - const-class/jumbo vAAAA,type[@BBBBBBBB](https://my.oschina.net/u/2326784) 經過給定的類型那個索引獲取一個類索引並賦值給寄存器vAAAA(這條指令佔用兩個字節,值爲0x00ff,是Android4.0中新增的指令)
  • 數據返回指令

    返回指令指的是函數結尾時運行的最後一條指令。它的基礎字節碼爲return,共有如下四條返回指令:

    - "return-void":表示什麼也不返回
      - 「return vAA」:表示函數返回一個32位非對象類型的值
      - 「return-wide vAA」:表示函數返回一個64位非對象類型的值
      - 「return-object vAA」:表示函數返回一個對象類型的值
  • 數組操做指令

    數組操做包括讀取數組長度、新建數組、數組賦值、數組元素取值與賦值等操做。

    - array-length vA,vB 獲取給定vB寄存器中數組的長度並將值賦給vA寄存器,數組長度指的是數組的條目個數。
      - new-array vA,vB,type[@CCCC](https://my.oschina.net/u/157616) 構造指定類型(type@CCCC)與大小(vB)的數組,並將值賦給vA寄存器。
      - new-array/jumbo vAAAA,vBBBB,type@CCCCCCCC 指令功能與上一條指令相同,只是寄存器與指令的索引取值範圍更大(Android4.0中新增的指令)
      - filled-new-array {vC,vD,vE,vF,vG},type@BBBB 構造指定類型(type@BBBB)與大小(vA)的數組並填充數組內容。vA寄存器是隱含使用的,除了指定數組的大小外還制訂了參數的個數,vC~vG是使用到的參數寄存器序列
      - filled-new-array/range {vCCCC, ... ,vNNNN},type@BBBB 指定功能與上一條指令相同,只是參數寄存器使用range字節碼後綴指定了取值範圍,vC是第一個參數寄存器, N=A+C-1。
      - filled-new-array/jumbo {vCCCC, ... ,vNNNN},type@BBBBBBBB 指令功能與上一條指令相同,只是寄存器與指令的索引取值範圍更大(Android4.0中新增的指令)
      - fill-array-data vAA, +BBBBBBBB 用指定的數據來填充數組,vAA寄存器爲數組引用,引用必須爲基礎類型的數組,在指令後面會緊跟一個數據表
      - arrayop vAA,vBB,vCC 對vBB寄存器指定的數組元素進入取值與賦值。vCC寄存器指定數組元素索引,vAA寄存器用來寄放讀取的或須要設置的數組元素的值。讀取元素使用aget類指令,元素賦值使用aput指令,元素賦值使用aput類指令,根據數組中存儲的類型指令後面會緊跟不一樣的指令後綴,指令列表有aget、aget-wide、aget-object、aget-boolean、aget-byte、aget-char、aget-short、aput、aput-wide、aput-boolean、aput-byte、aput-char、aput-short。
  • 數據轉換指令

    數據轉換指令用於將一種類型的數值轉換成另外一種類型,它的格式爲 unop vA,vB 。 vB寄存器或vB寄存器對存放須要轉換的數據,轉換後的結果保存在vA寄存器或vA寄存器對中。

    neg-int 對整型數求補
      not-int  對整型數求反
      neg-long 對長整型求補
      not-long 對長整型求反
      neg-float 對單精度浮點型數求補
      neg-double 對雙精度浮點型數求補
      int-to-long 將整型數轉換爲長整型
      int-to-float 將整型數轉換爲單精度浮點型
      int-to-double 將整型數轉換爲雙精度浮點型
      long-to-int 將長整型數轉換爲整型
      long-to-float 將長整型數轉換爲單精度浮點型
      long-to-double 將長整型數轉換爲雙精度浮點型
      float-to-int 將單精度浮點型數轉換爲整型
      float-to-long 將單精度浮點型數轉換爲長整型
      float-to-double 將單精度浮點型數轉換爲雙精度浮點型
      double-to-int 將雙精度浮點型數轉換爲整型
      double-to-long 將雙精度浮點型數轉換爲長整型
      double-to-float 將雙精度浮點型數轉換爲單精度浮點型
      int-to-byte 將整型轉換爲字節型
      int-to-char 將整型轉換爲字符串
      int-to-short 將整型轉換爲短整型
  • 數據運算指令

    數據運算指令包括算術運算指令與邏輯運算指令。算術運算指令主要進行數值間如加、減、乘、除、模、移位等運算,邏輯運算主要進行數值間與、或、非、異或等運算。數據運算指令有以下四類(數據運算時可能在寄存器或寄存器對間進行,下面的指令做用講解時使用寄存器來描述):

    binop vAA,vBB,vCC 將vBB寄存器與vCC寄存器進行運算,結果保存到vAA寄存器
      binop/2addr vA,vB 將vA寄存器與vB寄存器進行運算,結果保存到vA寄存器
      binop/lit16 vA,vB,#+CCCC 將vB寄存器與常量CCCC進行運算,結果保存到vA寄存器
      binop/lit8 vAA,vBB,#+CC 將vBB寄存器與常量CC進行運算,結果保存到vAA寄存器

    後面3類指令比第1類指令分別多了addr、lit1六、lit8等指令後綴。四類指令中基礎字節碼後面加上數據類型後綴,如-int或-long分別表示操做的數據類型那個爲整型與長整型。第1類指令可歸類以下:

    add-type     vBB寄存器與vCC寄存器值進行加法運算(vBB  + vCC)
      sub-type     vBB寄存器與vCC寄存器值進行減法運算(vBB  - vCC)
      mul-type     vBB寄存器與vCC寄存器值進行乘法運算(vBB  * vCC)
      div-type     vBB寄存器與vCC寄存器值進除法運算(vBB  / vCC)
      rem-type     vBB寄存器與vCC寄存器值進行模運算(vBB  % vCC)
    
      and-type     vBB寄存器與vCC寄存器值進行與運算(vBB  & vCC)
      or-type     vBB寄存器與vCC寄存器值進行或運算(vBB  | vCC)
      xor-type     vBB寄存器與vCC寄存器值進行異或運算(vBB  ^ vCC)
    
      shl-type     vBB寄存器(有符號數)左移vCC位(vBB << vCC)
      shr-type     vBB寄存器(有符號數)右移vCC位(vBB >> vCC)
      ushr-type     vBB寄存器(無符號數)右移vCC位(vBB >> vCC)
      其中基礎字節碼後面的-type能夠是-int、-long、-float、-double。後面3類指令與之相似。
  • 對象操做指令

    與對象實例相關的操做,好比對象建立,對象檢查等.

    - new-instance vAA,type@BBBB 構造一個指定類型對象的新實例,並將對象引用賦值給vAA寄存器,類型符號type指定的類型不能是數組類。
      - instance-of vA,vB,type@CCCC 判斷vB寄存器中的對象引用是否能夠轉換成指定的類型,若是能夠vA寄存賦值爲1,不然vA寄存器爲0
      - check-cast vAA,type@BBBB 將vAA寄存器中對象的引用轉成指定類型,成功則將結果賦值給vAA,不然拋出ClassCastException異常.
  • 跳轉指令

    跳轉指令用於從當前地址跳轉到指定的偏移處。Dalvik指令集中有三種跳轉指令:無條件跳轉(goto),分支跳轉(switch)與條件跳轉(if)。

    goto +AA 無條件跳轉到指定偏移處,偏移量AA不能爲0
      goto/16 +AAAA 無條件跳轉到指定偏移處,偏移量AAAA不能爲0。
      goto/32 +AAAAAAAA 無條件跳轉到指定偏移處。
      packed-switch vAA,+BBBBBBBB 分支跳轉指令。vAA寄存器爲switch分支中須要判斷的值,BBBBBBBB指向一個packed-switch-payload格式的偏移表,表中的值是有規律遞增的。
      sparse-switch vAA,+BBBBBBBB 分支跳轉指令。vAA寄存器爲switch分支中須要判斷的值,BBBBBBBB指向一個sparse-switch-payload格式的偏移表,表中的值是無規律的偏移表,表中的值是無規律的偏移量。
    
      if-test vA,vB,+CCCC 條件跳轉指令。比較vA寄存器與vB寄存器的值,若是比較結果知足就跳轉到CCCC指定的偏移處。偏移量CCCC不能爲0。if-test類型的指令有如下幾條:
      	 ● if-eq 若是vA不等於vB則跳轉。Java語法表示爲 if(vA == vB)
           ● if-ne 若是vA不等於vB則跳轉。Java語法表示爲 if(vA != vB)
           ● if-lt 若是vA小於vB則跳轉。Java語法表示爲 if(vA < vB)
           ● if-le 若是vA小於等於vB則跳轉。Java語法表示爲 if(vA <= vB)
           ● if-gt 若是vA大於vB則跳轉。Java語法表示爲 if(vA > vB)
           ● if-ge 若是vA大於等於vB則跳轉。Java語法表示爲 if(vA >= vB)
    
      if-testz vAA,+BBBB 條件跳轉指令。拿vAA寄存器與 0 比較,若是比較結果知足或值爲0時就跳轉到BBBB指定的偏移處。偏移量BBBB不能爲0。 if-testz類型的指令有一下幾條:
           ● if-nez 若是vAA爲 0 則跳轉。Java語法表示爲 if(vAA == 0)
           ● if-eqz 若是vAA不爲 0 則跳轉。Java語法表示爲 if(vAA != 0)
           ● if-ltz 若是vAA小於 0 則跳轉。Java語法表示爲 if(vAA < 0)
           ● if-lez 若是vAA小於等於 0 則跳轉。Java語法表示爲 if(vAA <= 0)
           ● if-gtz 若是vAA大於 0 則跳轉。Java語法表示爲 if(vAA > 0)
           ● if-gez 若是vAA大於等於 0 則跳轉。Java語法表示爲 if(vAA >= 0)
  • 比較指令

    ** 比較指令用於比較兩個寄存器中值的大小,其基本格式格式是cmp+kind-type vAA,vBB,vCC,type表示比較數據的類型,如-long,-float等;kind則表明操做類型,所以有cmpl,cmpg,cmp三種比較指令.coml是compare less的縮寫,cmpg是compare greater的縮寫,所以cmpl表示vBB小於vCC中的值這個條件是否成立,是則返回1,不然返回-1,相等返回0;cmpg表示vBB大於vCC中的值這個條件是否成立,是則返回1,不然返回-1,相等返回0. cmp和cmpg的語意一致,即表示vBB大於vCC寄存器中的值是否成立,成立則返回1,不然返回-1,相等返回0 **

    eg:

    cmpl-float vAA,vBB,vCC	比較兩個單精度的浮點數.若是vBB寄存器中的值大於vCC寄存器的值,則返回-1到vAA中,相等則返回0,小於返回1
      cmpg-float vAA,vBB,vCC	比較兩個單精度的浮點數,若是vBB寄存器中的值大於vCC的值,則返回1,相等返回0,小於返回-1
      cmpl-double vAA,vBB,vCC	比較兩個雙精度浮點數,若是vBB寄存器中的值大於vCC的值,則返回-1,相等返回0,小於則返回1
      cmpg-double vAA,vBB,vCC	比較雙精度浮點數,和cmpl-float的語意一致
      cmp-double vAA,vBB,vCC	等價與cmpg-double vAA,vBB,vCC指令
  • 字段操做指令

    字段操做指令表示對對象字段進行設值和取值操做,就像是你在代碼中長些的set和get方法.基本指令是iput-type,iget-type,sput-type,sget-type.type表示數據類型.

    *前綴是i的iput-type和iget-type指令用於普通字段的讀寫操做.*
      iget-byte vA,vB,filed_id	     讀取vB寄存器中的對象中的filed_id字段值賦值給vA寄存器
      iput-byte vA,vB,filed_id	     設置vB寄存器中的對象中filed_id字段的值爲vA寄存器的值
      iget-boolean vA,vB,filed_id	
      iput-boolean vA,vB,filed_id	
      iget-long vA,vB,filed_id	
      iput-long vA,vB,filed_id	
      前綴是s的sput-type和sget-type指令用於靜態字段的讀寫操做
      sget-byte vA,vB,filed_id	
      sput-byte vA,vB,filed_id	
      sget-boolean vA,vB,filed_id	
      sput-boolean vA,vB,filed_id	
      sget-long vA,vB,filed_id	
      sput-long vA,vB,filed_id
  • 方法調用指令

    Davilk中的方法指令和JVM的中指令大部分很是相似.目前共有五條指令集:

    invoke-direct{parameters},methodtocall	調用實例的直接方法,即private修飾的方法.此時須要注意{}中的第一個元素表明的是當前實例對象,即this,後面接下來的纔是真正的參數.好比指令invoke-virtual {v3,v1,v4},Test2.method5:(II)V中,v3表示Test2當前實例對象,而v1,v4纔是方法參數
      invoke-static{parameters},methodtocall	調用實例的靜態方法,此時{}中的都是方法參數
      invoke-super{parameters},methodtocall	調用父類方法
      invoke-virtual{parameters},methodtocall	調用實例的虛方法,即public和protected修飾修飾的方法
      invoke-interface{parameters},methodtocall	調用接口方法
    
      這五種指令是基本指令,除此以外,你也會遇到invoke-direct/range,invoke-static/range,invoke-super/range,invoke-virtual/range,invoke-interface/range指令,該類型指令和以上指令惟一的區別就是後者能夠設置方法參數能夠使用的寄存器的範圍,在參數多於四個時候使用.
    
      再此強調一遍對於非靜態方法而言{}的結構是{當前實例對象,參數1,參數2,…參數n},而對於靜態方法而言則是{參數1,參數2,…參數n}

    若是要獲取方法執行有返回值,須要經過上面說道的move-result指令獲取執行結果.

  • 同步指令

    同步一段指令序列一般是由java中的synchronized語句塊表示,則JVM中是經過monitorenter和monitorexit的指令來支持synchronized關鍵字的語義的,而在Davilk中一樣提供了兩條相似的指令來支持synchronized語義:

    monitor-enter vAA	爲指定對象獲取鎖操做
      monitor-exit vAA	爲指定對象釋放鎖操做
  • 異常指令

    throw vAA	拋出vAA寄存器中指定類型的異常

Samil文件詳解

  • 經過反編譯工具反編譯出來每一個.smali,都對應與java中的一個類,每一個smali文件都是Davilk指令組成的,並遵循必定的結構.smali存在不少的指令用於描述對應的java文件,全部的指令都以」.」開頭,經常使用的指令以下:

    .filed						定義字段
      .method…end method			定義方法
      .annotation…end annotation	定義註解
      .implements					定義接口指令
      .local						指定了方法內局部變量的個數
      .registers					指定方法內使用寄存器的總數
      .prologue					表示方法中代碼的開始處
      .line						表示java源文件中指定行
      .paramter					指定了方法的參數
      .param						和.paramter含義一致,可是表達格式不一樣
  • 下面咱們來寫一個簡單的Hello World 來解釋一下

    JAVA 源代碼以下:

    public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    
          private static final String TAG = "MainActivity";
          private TextView tvShowText;
    
          private static final String HELLO = "HELLO";
          private static final String WORLD = "WORLD";
    
          @Override
          protected void onCreate(Bundle savedInstanceState) {
              super.onCreate(savedInstanceState);
              setContentView(R.layout.activity_main);
              initView();
              setListener();
          }
    
          private void setListener() {
              tvShowText.setOnClickListener(this);
          }
    
          private void initView() {
              tvShowText = (TextView) findViewById(R.id.tv_show_text);
          }
    
          @Override
          public void onClick(View v) {
              Log.d(TAG, "onClick: TextView");
              tvShowText.setText(getText());
          }
    
          private String getText() {
              return HELLO + WORLD;
          }
      }
    
      反編譯後smali文件以下
    
      #文件頭描述
      .class public Lorg/professor/helloworld/MainActivity;
      #指定基類
      .super Landroid/support/v7/app/AppCompatActivity;
      #源文件名稱
      .source "MainActivity.java"
    
      #代表實現了View.OnClickListener接口
      # interfaces
      .implements Landroid/view/View$OnClickListener;
    
      #定義String靜態字段 
      # static fields
      .field private static final HELLO:Ljava/lang/String; = "HELLO"
    
      .field private static final TAG:Ljava/lang/String; = "MainActivity"
    
      .field private static final WORLD:Ljava/lang/String; = "WORLD"
    
      #定義TextView靜態字段 
      # instance fields
      .field private tvShowText:Landroid/widget/TextView;
    
      #構造方法
      # direct methods
      .method public constructor <init>()V
          .locals 0 #表示函數中無局部變量
    
          .prologue #表示方法中代碼正式開始
          .line 9   #表示對應與java源文件的第8行
    
      	#調用AppCompatActivity中的init()方法
          invoke-direct {p0}, Landroid/support/v7/app/AppCompatActivity;-><init>()V
#調用返回指令,此處沒有返回任何值
	    return-void
	.end method     #方法結束
	
	 
	.method private getText()Ljava/lang/String;
	    .locals 1
	
	    .prologue
	    .line 40

		#v0寄存器中賦值爲HELLOWORLD
	    const-string v0, "HELLOWORLD"
		
		#調用返回指令,返回v0中的值
	    return-object v0 
	.end method
	
	.method private initView()V
	    .locals 1
	
	    .prologue
	    .line 30
		#v0寄存器賦值爲0x7f0b005e
	    const v0, 0x7f0b005e  

	    #調用方法findViewById
	    invoke-virtual {p0, v0}, Lorg/professor/helloworld/MainActivity;->findViewById(I)Landroid/view/View;
	
	    move-result-object v0
		
		#寄存器中對象的引用轉成指定類型
	    check-cast v0, Landroid/widget/TextView;
		#設置p0寄存器中的對象中tvShowText字段的值爲v0寄存器的值
	    iput-object v0, p0, Lorg/professor/helloworld/MainActivity;->tvShowText:Landroid/widget/TextView;
	
	    .line 31
	    return-void
	.end method
	
	.method private setListener()V
	    .locals 1
	
	    .prologue
	    .line 26
		#設置v0寄存器中的對象爲p0中tvShowText字段的值
	    iget-object v0, p0, Lorg/professor/helloworld/MainActivity;->tvShowText:Landroid/widget/TextView;
	    #調用 v0的setOnClickListener
	    invoke-virtual {v0, p0}, Landroid/widget/TextView;->setOnClickListener(Landroid/view/View$OnClickListener;)V
	
	    .line 27
	    return-void
	.end method
	
	
	# virtual methods
	.method public onClick(Landroid/view/View;)V
	    .locals 2   #表示函數中2局部變量
	    .param p1, "v"    # Landroid/view/View;
	
	    .prologue
	    .line 35
	    const-string v0, "MainActivity"
	
	    const-string v1, "onClick: TextView"
	
	    invoke-static {v0, v1}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I
	
	    .line 36
	    iget-object v0, p0, Lorg/professor/helloworld/MainActivity;->tvShowText:Landroid/widget/TextView;
	
	    invoke-direct {p0}, Lorg/professor/helloworld/MainActivity;->getText()Ljava/lang/String;
	
	    move-result-object v1
	
	    invoke-virtual {v0, v1}, Landroid/widget/TextView;->setText(Ljava/lang/CharSequence;)V
	
	    .line 37
	    return-void
	.end method
	
	.method protected onCreate(Landroid/os/Bundle;)V
	    .locals 1
	    .param p1, "savedInstanceState"    # Landroid/os/Bundle;   #參數savedInstancestate
	
	    .prologue
	    .line 19
		
		#調用父類方法onCreate()
	    invoke-super {p0, p1}, Landroid/support/v7/app/AppCompatActivity;->onCreate(Landroid/os/Bundle;)V
	
	    .line 20
		#v0寄存器賦值爲0x7f04001b
	    const v0, 0x7f04001b
		
		#調用方法setContentView()
	    invoke-virtual {p0, v0}, Lorg/professor/helloworld/MainActivity;->setContentView(I)V
	
	    .line 21
	    invoke-direct {p0}, Lorg/professor/helloworld/MainActivity;->initView()V
	
	    .line 22
	    invoke-direct {p0}, Lorg/professor/helloworld/MainActivity;->setListener()V
	
	    .line 23
	    return-void
	.end method
相關文章
相關標籤/搜索