Android Dalvik 指令集

本篇文章同時收錄在個人我的博客:Android Dalvik 指令集java

前言

目前 DEX 可執行文件主流的反彙編工具備 BakSmali 與 Dedexer,本篇文章 Dalvik 指令的語法都採用的 Smali 語法格式。android

瞭解 Dalvik 寄存器

Dalvik 虛擬機基於寄存器架構,做用與特定的 CPU 上運行,設計之初採用了 ARM 架構,ARM 架構的 CPU 自己集成了多個寄存器,Dalvik 將部分寄存器映射到了 ARM 寄存器上,還有一部分則經過調用棧進行模擬,Dalvik 中用到的寄存器都是 32 位的,支持任何類型,64 位類型用 2 個相鄰的寄存器表示數組

Dalvik 寄存器的取值返回:v0 ~ v65535,從語法 "op vAAAA , vBBBB" 能夠看出,每一個大寫字母是 4 位,因此就是 2 的 16 次方減 1。安全

v 命名法與 p 命名法

假設一個函數使用到了 M 個寄存器, 而且該函數有 N 個參數,根據 Dalvik 虛擬機參數傳遞方式中的規定:參數使用最後 N 個寄存器,局部變量使用 v0 開始的前 M-N 個寄存器。bash

v 命名法 p 命名法 寄存器含義
v0 v0 第一個局部變量寄存器
v1 v1 第二個局部變量寄存器
... ... 中間的局部變量寄存器依次遞增且名稱相同
vM-N p0 第一個參數寄存器
... ... 中間的參數寄存器分別依次遞增
vM-1 pN-1 第 N 個參數寄存器

Dalvik 字節碼的類型、方法、字段的表示方式

類型

Dalvik 字節碼只有兩種類型,基本類型與引用類型,Dalvik 使用這兩種類型來表示 Java 語言的 所有類型,除了對象和數組屬於引用類型,其餘 Java 類型都是基本類型。多線程

Dalvik 字節碼類型描述符: 架構

語法 含義
V void, 只用於返回值類型
Z boolean
B byte
S short
C char
I int
J long
F float
D double
L Java class 類型
[ 數組類型

L 類型能夠表示 java class 類型中的任何類型,這些類在 Java 代碼中以 package.name.ObjectName 方式引用。
在 Dalvik 彙編代碼中, 它們以 Lpackage/name/ObjectName; 形式表示,注意最後有個分號,L 表示後面跟着一個 Java 類, package/name/ 表示對象所在的包,ObjectName 表示對象的名稱,最後分號表示對象名結束。
例:Ljava/lang/String; 表示 java.lang.Stringide

[ 類型能夠表示全部的基本類型數組。[ 後面緊跟基本類型描述符,如 [I 表示一個整型一維數組,至關於 int[][[I 表示 int[][],多維數組的維數最大爲 255。函數

L 與 [ 但是同時使用用來表示對象數組。如 [Ljava/lang/String; 就表示 Java 中的 String[]工具

方法

Dalvik 使用 方法名、類型參數、返回值 來詳細描述一個方法。
格式以下:

Lpackage/name/ObjectName;->MethodName(III)Z

Lpackage/name/ObjectName;:類型
MethodName:方法名
(III):方法的參數,這裏是三個 int 類型
Z:返回類型,這裏是 boolean 類型

再看個栗子:

method (I[[IILjava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;

對應的 java 代碼以下:

String method(int.int[][],int,String,Object[])

BakSmali 生成的方法代碼以 .method 指令開始,以 .end method 指令結束,根據方法類型的不一樣,在方法指令開始前可能會用 「#」 加以註釋,如 「# virtual methods」 表示虛方法,「# direct methods」 表示直接方法。

字段

Lpackage/name/ObjectName;->FieldName:Ljava/lang/String;

對應的 java 代碼是

String FieldName

BakSmali 生成的字段代碼以 .field 指令開頭,在字段指令的開始可能會用 「#」 號加以註釋,如 「# instance fields」 表示實例字段,「# static fields」 表示靜態字段。

Dalvik 指令集

指令特色

  • 參數採用從目標(destination)到源(source)的方式
  • 根據字節碼的大小與類型不一樣,一些字節碼添加了名稱後綴以消除歧義。
    • 32 位常規類型的字節碼未添加任何後綴
    • 64 位常規類型的字節碼添加 -wide 後綴
    • 特殊類型字節碼根據具體類型添加後綴。它們能夠是 -boolean、-byte、-char、...、-object、-class、-void
  • 根據字節碼的佈局與選項不一樣,一些字節碼添加了 字節碼後綴 以消除歧義,這些後綴經過在字節碼主名稱後添加 「/」 來分隔開。
  • 在指令集的描述中,寬度值中每一個字母表示數據寬度爲 4 位。

舉個栗子:

move-wide/from16 vAA,vBBBB

move:基礎字節碼(base opcode)。標識這是基本操做。
wide:名稱後綴(name suffix)。標識指令操做的數據寬度(64 位)。
from16:字節碼後綴(opcode suffix)。標識源爲一個 16 位的寄存器引用變量。
vAA:目的寄存器。範圍 v0 ~ v255。
vBBBB:源寄存器。範圍 v0 ~ v65535。

空操做指令

空操做指令的助記符爲 nop。它的值爲 00,一般 nop 指令被用來作對齊代碼之用,無實際操做。

數據操做指令

指令 描述
move vA,vB 將 vB 寄存器的值賦值給 vA 寄存器,源寄存器和目的寄存器都是 4 位。
move/from16 vAA,vBBBB 將 vBBBB 寄存器的值賦值給 vAA 寄存器,源寄存器爲 16 位,目的寄存器爲 8 位。
move/16 vAAAA,vBBBB 將 vBBBB 寄存器的值賦值給 vAAAA 寄存器,源寄存器和目的寄存器都是 16 位。
move-wide vA,vB 爲 4 位的寄存器對賦值,源寄存器和目的寄存器都是 4 位。
move-wide/from16 vAA,vBBBB 同 move-wide
move-wide/16 vAA,vBBBB 同 move-wide
move-object vA,vB 爲對象賦值,源寄存器和目的寄存器都是 4 位。
move-object/from16 vAA,vBBBB 爲對象賦值,源寄存器爲 16 位,目的寄存器爲 8 位。
move-object/16 vAAAA,vBBBB 爲對象賦值,源寄存器和目的寄存器都是 16 位。
move-result vAA 將上一個 invoke 類型指令操做的單字非對象結果賦給 vAA 寄存器
move-result-wide vAA 將上一個 invoke 類型指令操做的雙字非對象結果賦給 vAA 寄存器
move-result-object vAA 將上一個 invoke 類型指令操做的對象結果賦給 vAA 寄存器
move-exception vAA 保存一個運行時發生的異常到 vAA 寄存器。這條指令必須是異常發生時的異常處理器的一條指令,不然指令無效。

返回指令

返回指令是函數結尾時運行的最後一條指令。

指令 描述
return-void 表示函數從一個 void 方法返回
return vAA 表示函數返回一個 32 位非對象類型的值,返回值寄存器爲 8 位的寄存器 vAA
return-wide vAA 表示函數返回一個 64 位非對象類型的值,返回值寄存器爲 8 位的寄存器對 vAA
return-object vAA 表示函數返回一個對象類型的值,返回值爲 8 位的寄存器 vAA

數據定義指令

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

指令 描述
const/4 vA,#+B 將數值符號擴展爲 32 位後賦給寄存器 vA。
const/16 vAA,#+BBBB 將數值符號擴展爲 32 位後賦給寄存器 vAA。
const vAA,#+BBBBBBBB 將數值賦給寄存器 vAA。
const/high16 vAA,#+BBBB0000 將數值右邊零擴展爲 32 位後賦給寄存器 vAA
const-wide/16 vAA,#+BBBB 將數值擴展爲 64 位後賦給寄存器對 vAA
const-wide/32 vAA,#+BBBBBBBB 將數值擴展爲 64 位後賦給寄存器對 vAA
const-wide vAA,#+BBBBBBBBBBBBBBBB 將數值賦給寄存器對 vAA
const-wide/hight16 vAA,#+BBBB000000000000 將數值右邊零擴展爲 64 位後賦給寄存器對 vAA
const-string vAA,string@BBBB 經過字符串索引構造一個字符串並賦給寄存器 vAA
const-string/jumbo vAA,string@BBBBBBBB 經過字符串索引(較大)構造一個字符串並賦給寄存器 vAA
const-class vAA,type@BBBB 經過類型索引獲取一個類引用並賦給寄存器 vAA
const-class/jumbo vAAAA,type@BBBBBBBB 經過類型索引獲取一個類引用並賦給寄存器 vAA(這條指令佔用兩個字節,值爲 0x00ff)

鎖指令

鎖指令多用在多線程中對同一對象的操做,Dalvik 提供了兩條鎖指令。

指令 描述
monitor-enter vAA 爲指定的對象獲取鎖
monitor-exit vAA 釋放指定對象的鎖

實例操做指令

與實例相關的操做包括實例的類型轉換,檢查及新建等。

指令 描述
check-cast vAA,type@BBBB 將 vAA 寄存器中的對象引用轉換成指定的類型,若是失敗會拋出 ClassCastException 異常,若是類型 B 指定的是基本類型,對於非基本類型的 A 來講,運行時始終會失敗。
instance-of vA,vB,type@CCCC 判斷 vB 寄存器中的對象是否能夠轉換成指定的類型,若是能夠 vA 賦給 1,不然賦給 0。
new-instance vAA,type@BBBB 構造一個指定類型對象的新實例,並將對象引用賦給 vAA, 類型符 type 指定的類型不是數組。
check-cast/jumbo vAAAA,type@BBBBBBBB 同 check-cast vAA,type@BBBB ,只是寄存器值和指令的索引取值返回更大(android 4.0 新增)
instance-of/jumbo vAAAA,vBBBB,type@CCCCCCCC 同 instance-of vA,vB,type@CCCC ,只是寄存器值和指令的索引取值返回更大(android 4.0 新增)
new-instance/jumbo vAAAA,type@BBBBBBBB 同 new-instance vAA,type@BBBB ,只是寄存器值和指令的索引取值返回更大(android 4.0 新增)

數組操做指令

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

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

異常指令

Dalvik 指令集中有一條指令用來拋出異常。

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

跳轉指令

跳轉指令用於從當前地址調轉到指定的偏移處,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 指向一個 packed-switch-payload 格式的偏移表,表中的值是無規律的偏移量
if-test vA,vB,+CCCC 條件跳轉指令,比較 vA 和 vB 的值,若是比較結果知足條件就跳轉到 CCCC 指定的偏移處,偏移量 CCCC 不能爲 0
if-testz vAA,+BBBB 條件跳轉指令,vAA 和 0 比較,若是比較結果知足或值爲 0 時就跳轉到 BBBB 指定的偏移處, 偏移量 BBBB 不能爲 0
if-test 類型的指令 對應的 Java 語法 描述
if-eq if(vA==vB) 若是 vA 等於 vB 則跳轉
if-ne if(vA!=vB) 若是 vA 不等於 vB 則跳轉
if-lt if(vA<vB) 若是 vA 小於 vB 則跳轉
if-ge if(vA>=vB) 若是 vA 大於等於 vB 則跳轉
if-gt if(vA>vB) 若是 vA 大於 vB 則跳轉
if-le if(vA<=vB) 若是 vA 小於等於 vB 則跳轉
if-testz 類型的指令 對應的 Java 語法 描述
if-eqz if(!vAA) vAA 爲 0 則跳轉
if-nez if(vAA) vAA 不爲 0 則跳轉
if-ltz if(vAA<0) vAA 小於 0 則跳轉
if-gez if(vAA>=0) vAA 大於等於 0 則跳轉
if-gtz if(vAA>0) vAA 大於 0 則跳轉
if-lez if(vAA<=0) vAA 小於等於 0 則跳轉

比較指令

比較指令用於對兩個寄存器的值(浮點型或者長整型)進行比較。它的格式爲 「cmpkind vAA,vBB,vCC」,其中 vBB 寄存器與 vCC 寄存器是須要比較的兩個寄存器或兩個寄存器對,比較的結果放到 vAA 寄存器。

指令 描述
cmpl-float 比較兩個單精度浮點數。 若是 vBB 寄存器大於 vCC 寄存器,則結果爲 -1,相等則爲 0,小於則爲 1。
cmpg-float 比較兩個單精度浮點數。 若是 vBB 寄存器大於 vCC 寄存器,則結果爲 1,相等則爲 0,小於則爲 -1。
cmpl-double 比較兩個雙精度浮點數。 若是 vBB 寄存器對大於 vCC 寄存器對,則結果爲 -1,相等則爲 0,小於則爲 1。
cmpg-double 比較兩個雙精度浮點數。 若是 vBB 寄存器對大於 vCC 寄存器對,則結果爲 1,相等則爲 0,小於則爲 -1。
cmp-long 比較兩個長整型數。 若是 vBB 寄存器大於 vCC 寄存器,則結果爲 1,相等則爲 0,小於則爲 -1。

字段操做指令

字段操做指令用來對 對象實例 的字段進行讀寫操做,字段的類型能夠是 java 中有效的數據類型。對普通字段和靜態字段操做有兩種指令集。

普通字段

iinstanceop vA,vB,field@CCCC

普通字段指令前綴爲 i,對普通字段進行讀操做使用 iget 指令,寫操做使用 iput 指令

靜態字段

sstaticop vAA,field@BBBB

靜態字段指令前綴爲 s,對靜態字段進行讀操做使用 sget 指令,寫操做使用 sput 指令

方法調用指令

方法調用指令負責調用類實例的方法,它的基礎指令爲 invoke,方法調用指令有 「invoke-kind {vC,vD,vE,vF,vG},meth@BBBB」「invoke-kind/range{vCCCC .. vNNNN},meth@BBBB」 兩類,後者只是在設置參數寄存器時使用了 range 來指定寄存器的範圍。

指令 描述
invoke-virtual 或 invoke-virtual/range 調用實例的虛方法
invoke-super 或 invoke-super/range 調用實例的父類方法
invoke-direct 或 invoke-direct/range 調用實例的直接方法
invoke-static 或 invoke-static/range 調用實例的靜態方法
invoke-interface 或 invoke-interface/ragne 調用實例的接口方法

方法調用指令的返回值必須使用 move-result* 指令來獲取。以下面兩條指令:

invoke-static {}, Landroid/os/Parcel;->obtain()Landroid/os/Parcel;  

move-result-object v0複製代碼

數據轉換指令

數據轉換指令用於將一種類型的數值轉換成另外一種類型,它的格式爲 「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 類指令分別多出了 2addr、lit1六、lit8 等指令後綴,四類指令中基礎字節碼相同的指令的運算操做是相似的,第一類指令中,根據數據類型不一樣會在基礎字節碼後面加上數據類型後綴,如 -int 或 -long 分別表示操做的數據類型爲整型和長整型。
第一類指令可歸類以下:

指令 描述
add-type vBB + vCC
sub-type vBB - vCC
mul-type vBB * vCC
div-type vBB / vCC
rem-type vBB % vCC
and-type vBB AND vCC
or-type vBB OR vCC
xor-type vBB XOR vCC (異或)
shl-type vBB << vCC (有符號數)
shr-type vBB >> vCC (有符號數)
ushr-type vBB >> vCC (無符號數)

其中基礎字節碼後面的 -type 能夠是 -int,-long,-float,-double。後面 3 類指令與之相似。

每一個指令的字節碼值佔用一個字節,範圍是 0x0 ~ 0x0ff 。

在 android 4.0 中,增長了一部分擴展指令,主要是在指令助記符後添加了 jumbo 後綴,增長了寄存器和常量的取值範圍。

說明

本篇文章內容整理自『Android 軟件安全與逆向分析』。

相關文章
相關標籤/搜索