本篇文章同時收錄在個人我的博客:Android Dalvik 指令集java
目前 DEX 可執行文件主流的反彙編工具備 BakSmali 與 Dedexer,本篇文章 Dalvik 指令的語法都採用的 Smali 語法格式。android
Dalvik 虛擬機基於寄存器架構,做用與特定的 CPU 上運行,設計之初採用了 ARM 架構,ARM 架構的 CPU 自己集成了多個寄存器,Dalvik 將部分寄存器映射到了 ARM 寄存器上,還有一部分則經過調用棧進行模擬,Dalvik 中用到的寄存器都是 32 位的,支持任何類型,64 位類型用 2 個相鄰的寄存器表示。 數組
Dalvik 寄存器的取值返回:v0 ~ v65535,從語法 "op vAAAA , vBBBB" 能夠看出,每一個大寫字母是 4 位,因此就是 2 的 16 次方減 1。安全
假設一個函數使用到了 M 個寄存器, 而且該函數有 N 個參數,根據 Dalvik 虛擬機參數傳遞方式中的規定:參數使用最後 N 個寄存器,局部變量使用 v0 開始的前 M-N 個寄存器。bash
v 命名法 | p 命名法 | 寄存器含義 |
---|---|---|
v0 | v0 | 第一個局部變量寄存器 |
v1 | v1 | 第二個局部變量寄存器 |
... | ... | 中間的局部變量寄存器依次遞增且名稱相同 |
vM-N | p0 | 第一個參數寄存器 |
... | ... | 中間的參數寄存器分別依次遞增 |
vM-1 | pN-1 | 第 N 個參數寄存器 |
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.String
。 ide
[ 類型能夠表示全部的基本類型數組。[ 後面緊跟基本類型描述符,如 [I
表示一個整型一維數組,至關於 int[]
。[[I
表示 int[][]
,多維數組的維數最大爲 255。函數
L 與 [ 但是同時使用用來表示對象數組。如 [Ljava/lang/String;
就表示 Java 中的 String[]
。工具
Dalvik 使用 方法名、類型參數、返回值 來詳細描述一個方法。
格式以下:
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」 表示直接方法。
對應的 java 代碼是
String FieldName
BakSmali 生成的字段代碼以 .field 指令開頭,在字段指令的開始可能會用 「#」 號加以註釋,如 「# instance fields」 表示實例字段,「# static fields」 表示靜態字段。
舉個栗子:
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 軟件安全與逆向分析』。