1. 程式人生 > >Smali語法

Smali語法

基礎介紹

怎麼得到smali檔案?

apk檔案通過apktool反編譯出來的都有一個smali資料夾,裡面都是以.smali結尾的檔案。

Smali是什麼?

Smali是安卓系統裡的Java虛擬機器(Dalvik)所使用的一種.dex格式檔案的彙編器,而Baksmali是反彙編器。其語法是一種寬鬆式的Jasmin/dedexer語法。

Smali語言其實就是一種面向Dalvik的組合語言(組合語言是一種用於電子計算機、微處理器、微控制器,或其他可程式設計器件的低階語言。在不同的裝置中,組合語言對應著不同的機器語言指令集。使用匯編語言編寫的原始碼,然後通過相應的彙編程式將它們轉換成可執行的機器程式碼。這一過程被稱為彙編過程。),我們用smali語言寫的程式碼通過彙編器就轉換成Dalvik可以執行的dex檔案。

對應的有兩個工具,smali.jar將Smali檔案轉換為dex檔案,baksmali.jar則是將dex檔案轉換為smali檔案。apktool能反編譯出smali檔案就是因為它裡面用了baksmali工具。

為什麼要學習Smali?

smali語法是逆向的基礎,它能幫助我們理解別人程式碼的邏輯(當然我們也可以用dex2jar, 更方便檢視程式碼),但是當我們要修改別人的程式碼邏輯,或者加上自己的邏輯,最終是要修改smali檔案的,如果我們不懂smali語法,就無從下手。

如何學習?

smali的語法細節太多了,各種指令,看的人頭疼,我們其實也不需要全都學會,因為現在有工具幫我們了。

AndroidStudio有個外掛: Java2Smali,方便快捷把java程式碼轉換為smali程式碼。有了這個工具,我們可以結合它在實踐中學習smali語法,同時它也可以幫我們省很多事,當我們想在別人的邏輯中增加程式碼時,我們可以把自己的程式碼先用這個工具轉成smali語句, 然後做微調就行了。

當然我們還是需要學習一些基礎語法,瞭解一些常用指令。

如果想完整學習smali語法,可以看看Android軟體安全與逆向分析這本書,網上的很多資料也是來源於這本書。

語法簡介

Dalvik是基於暫存器結構的,在Dalvik位元組碼中,暫存器都是32位的,能夠支援任何型別,64位型別(Long/Double)用2個暫存器表示;Dalvik位元組碼有兩種型別:原始型別;引用型別(包括物件和陣列)。

原始型別

簡寫 含義
B byte
C char
D double (64 bits)
F float
I int
J long (64 bits)
S short
V void 只能用於返回值型別
Z boolean

物件型別

形式:Lxxx/yyy/zzz;

L 表示這是一個物件型別
xxx/yyy是該物件所在的包
zzz是物件名稱
;標識物件名稱的結束

如:Ljava/lang/String;

陣列型別

形式:[XXX

[I表示一個int型的一維陣列,相當於int[]
增加一個維度增加一個[,如[[I表示int[][]
陣列每一個維度最多255個;
物件陣列表示也是類似

如:String陣列的表示是[Ljava/lang/String

方法

形式:Lxxx/yyy/zzz;->methodName(Lxxx/yyy/zzz;Lxxx/yyy/zzz;I)Z

Lxxx/yyy/zzz 表示物件的型別
methodName 表示方法名
Lxxx/yyy/zzz;Lxxx/yyy/zzz;I 表示三個引數,兩個物件型別,一個整型(方法的引數是一個接一個的,中間沒有隔開)
Z返回值型別,這裡是boolean型別

如:Log.i("xxx", msg); 轉換成smali語句之後是

Landroid/util/Log;->i(Ljava/lang/String;Ljava/lang/String;)I

欄位

形式:Lxxx/yyy/zzz;->FieldName:Lxxx/yyy/zzz;

Lxxx/yyy/zzz; 表示物件的型別

FieldName表示欄位名稱

Lxxx/yyy/zzz表示欄位型別

如:ff = "aa"; 轉換後是 Lcom/example/reforceapk/MyLog;->ff:Ljava/lang/String

暫存器和變數

Dalvik變數都是存放在暫存器中的,暫存器的命名方法有兩種,v命名法和p命名法,一般我們都用p命名法。

p命名法:暫存器採用v和p來命名,v表示本地暫存器,p表示引數暫存器。

如果一個非靜態方法有兩個本地變數,有三個引數,需要的暫存器關係如下:

v0 第一個本地暫存器
v1 第二個本地暫存器
p0 // 指代呼叫這個方法的this物件。
p1 第一個引數
p2 第二個引數
p3 第三個引數

如果是靜態方法,那麼就不需要this物件了,需要的暫存器是v0, v1, p0, p1, p2。

.registers使用這個指令指定方法中暫存器的總數
.locals使用這個指定表明方法中非參暫存器的總數,放在方法的第一行。

常用指令

.field private isFlag:z — 定義變數

.method — 方法

.prologue — 方法開始

.end method — 方法結束

.parameter — 方法引數

.line 12 — 此方法位於第12行

invoke-super — 呼叫父函式

invoke-direct — 呼叫函式

invoke-static — 呼叫靜態函式

const/high16 v0, 0x7f03 — 把0x7f03賦值給v0

return-void — 函式返回void

new-instance — 建立例項

move-result v0 — 將上一個invoke型別的指令操作的非物件結果賦值給v0暫存器

move-result-object v0 — 將上一個invoke型別指令操作的物件賦值給v0暫存器

欄位操作指令

對於例項欄位和靜態欄位有兩類指令:
iget, iput 對例項欄位進行讀,寫
sget, sput 對靜態欄位進行讀,寫

會根據不同型別新增不同的字尾:

iget,iget-object,iget-boolean,iget-byte,iget-char,iget-short
iput,iput-object,iput-boolean,iput-byte,iput-char,iput-short

獲取static fields的指令示例:

sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;

上句中sget-object指令把out這個變數獲取並放到v0暫存器中。

獲取instance fields的指令與static fields的類似,需要指明物件所屬的例項。示例:

iget-object v0, p0, Lcom/example/reforceapk/MyLog;->ff:Ljava/lang/String;

上句iget-object指令比sget-object多了一個引數p0,就是該變數所在類的例項,在這裡就是p0即“this”。

條件跳轉指令

“if-eq vA, vB, :cond_x” — 如果vA等於vB則跳轉到:cond_x
“if-ne vA, vB, :cond_x” — 如果vA不等於vB則跳轉到:cond_x
“if-lt vA, vB, :cond_x” — 如果vA小於vB則跳轉到:cond_x
“if-ge vA, vB, :cond_x” — 如果vA大於等於vB則跳轉到:cond_x
“if-gt vA, vB, :cond_x” — 如果vA大於vB則跳轉到:cond_x
“if-le vA, vB, :cond_x” — 如果vA小於等於vB則跳轉到:cond_x
“if-eqz vA, :cond_x” — 如果vA等於0則跳轉到:cond_x
“if-nez vA, :cond_x” — 如果vA不等於0則跳轉到:cond_x
“if-ltz vA, :cond_x” — 如果vA小於0則跳轉到:cond_x
“if-gez vA, :cond_x” — 如果vA大於等於0則跳轉到:cond_x
“if-gtz vA, :cond_x” — 如果vA大於0則跳轉到:cond_x
“if-lez vA, :cond_x” — 如果vA小於等於0則跳轉到:cond_x