smali文件格式
smali文件的頭3行描述了當(dāng)前類的一些信息刻撒,格式如下:
.class<訪問權(quán)限>[修飾關(guān)鍵字]<類名>
.super<父類名>
.source<源文件名>
打開MainAcivity.smali嚣镜,頭三行代碼:
.class public Lcom/droider/crackme0502/MainActivity;
.super Landroid/app/Activity;
.source "MainActivity.java"
第一行權(quán)限public
,類名為Lcom/droider/crackme0502/MainActivity;
吧黄,類名開頭的L
是遵循Dalvik字節(jié)碼的相關(guān)約定柬帕,表示后面跟隨的字符串為一個類。
第二行铅搓,Lcom/droider/crackme0502/MainActivity;
的父類是 Landroid/app/Activity;
第三行瑟押,.source
指令指定了當(dāng)前類的源文件名
經(jīng)過混淆的dex文件,反編譯出來的smali代碼可能沒有源文件信息星掰,因此多望,.source
可能為空。
前三行代碼過后是類的主體部分氢烘,一個類可以由多個字段或方法組成怀偷。smali文件中字段的聲明使用.field
指令。字段有靜態(tài)字段與實例字段兩種威始。靜態(tài)字段的聲明格式如下:
# static fields
.field<訪問權(quán)限>[修飾關(guān)鍵字]<字段名>:<字段類型>
實例字段的聲明:
# instance field
.field private btnAnno:Landroid/widget/Button;
private
表示私有枢纠;字段btnAnno
,它的類型是Landroid/widget/Button;
直接方法的聲明:
# direct methods
.method <訪問權(quán)限> <方法名>(參數(shù)原型) <方法原型>
[.prologue] // 指定代碼開始位置
[.param] // 指定方法參數(shù)
[.line] // 指定代碼在源代碼中的行數(shù)黎棠,混淆后可能不存在
[.locals] // 使用的局部變量個數(shù)
<代碼體>
.end method
虛方法的聲明和直接方法相同晋渺,只是起始處的注釋為virtual methods
如果一個類實現(xiàn)了接口,會在.smali文件中使用.implement
指令指出脓斩。聲明入下:
# interface
.implements<接口名>
注解格式聲明:
# annotations
.annotation[注解屬性]<注解類名>
[注解字段=值]
.end annotation
注解的作用范圍可以是類木西、方法和字段。如果作用范圍是類随静,指令會直接定義在smali文件中八千,如果是方法或者字段,指令會包含在方法或字段的定義中燎猛。例如:
# instace fields
.field public sayWhat:Ljava/lang/String;
.annotation runtime Lcom/droider/anno/MyAnnoField;
info = "Hello my friend"
.end annotation
.end field
轉(zhuǎn)換成Java代碼:
@ com.droid.anno MyAnnoField(info = "Hello my friend")
public String sayWhat;
原始類型
B—byte
C—char
D—double
F—float
I—int
J—long
S—short
V—void
Z—boolean
[XXX—array
Lpackage/name/ObjName—object; // 前面表示對象所在包路徑,分號表示類結(jié)束
寄存器操作
p命名法和v命名法:
假設(shè)一個函數(shù)中用到M個寄存器恋捆,實際傳入的參數(shù)是N個。
根據(jù)傳參規(guī)則重绷,參數(shù)使用后N個寄存器沸停,局部變量使用0到M-N個寄存器。
假如用到5個寄存器昭卓,2個局部參數(shù)愤钾,3個傳入?yún)?shù)。
v命名法:
v0,v1,v2,v3,v4;
p命名法:
v0,v1,p0,p1,p2;
只改變傳入?yún)?shù)寄存器名候醒。
常量賦值
const v0, 0x7F030018 # R.layout.activity_challenge #從R中取出靜態(tài)值
const/4 v3, 0x2 #4也可以換成16或者h(yuǎn)igh16能颁,表示取整數(shù)值
const-string v2, "Challenge" # 取字符串
const-class v2, Context #把類對象取出
變量賦值
move vx,vy # 將vy的值賦值給vx,也可以是move-object等
move-result vx # 將上個方法調(diào)用后的結(jié)果賦值給vx倒淫,也可以是move-result-object
return-object vx # 將vx的對象作為函數(shù)返回值
new-instance v0, ChallengePagerAdapter # 實例化一個對象存入v0中
對象賦值
iput-object a,(this),b 將a的值給b伙菊,一般用于b的初始化
iget-object a,(this),b 將b的值給a,一般用于獲取b的地址,接著調(diào)用它
# eg.
iput-object v0, p0, ChallengeActivity->actionBar:ActionBar
iget-object v0, p0, ChallengeActivity->actionBar:ActionBar
函數(shù)操作
最基礎(chǔ)的函數(shù)操作一般有以下四個:
1.private:invoke-direct
2.public|protected: invoke-virtual
3.static:invoke-static
4.parent: invoke-super
基本調(diào)用形式:invoke-xxx {參數(shù)},類;->函數(shù)(參數(shù)原型)
# eg.
invoke-super {p0, p1}, Landroid/support/v4/app/FragmentActivity;->onCreate(Landroid/os/Bundle;)V
<=對應(yīng)源碼=>
super.onCreate(savedInstanceState); // 其中p0是this占业,其父類是FragmentActivity绒怨,p1,是savedInstanceState,其原型是Bundle谦疾;即調(diào)用p0->onCreate(p1)
程序相關(guān)語法
判斷語句
if-eq vA, vB, :cond_X 如果vA等于vB則跳轉(zhuǎn)到:cond_X
if-ne vA, vB, :cond_X 如果vA不等于vB則跳轉(zhuǎn)到:cond_X
if-lt vA, vB, :cond_X 如果vA小于vB則跳轉(zhuǎn)到:cond_X
if-ge vA, vB, :cond_X 如果vA大于等于vB則跳轉(zhuǎn)到:cond_X
if-gt vA, vB, :cond_X 如果vA大于vB則跳轉(zhuǎn)到:cond_X
if-le vA, vB, :cond_X 如果vA小于等于vB則跳轉(zhuǎn)到:cond_X
if-eqz vA, :cond_X 如果vA等于0則跳轉(zhuǎn)到:cond_X
if-nez vA, :cond_X 如果vA不等于0則跳轉(zhuǎn)到:cond_X
if-ltz vA, :cond_X 如果vA小于0則跳轉(zhuǎn)到:cond_X
if-gez vA, :cond_X 如果vA大于等于0則跳轉(zhuǎn)到:cond_X
if-gtz vA, :cond_X 如果vA大于0則跳轉(zhuǎn)到:cond_X
if-lez vA, :cond_X 如果vA小于等于0則跳轉(zhuǎn)到:cond_X
循環(huán)語句
public void encrypt(String str) {
String ans = "";
for (int i = 0 ; i < str.length();i++){
ans += str.charAt(i);
}
Log.e("ans:",ans);
}
<=對應(yīng)smali=>
.method public encrypt(Ljava/lang/String;)V # 方法:public void encrypt(String str)
.locals 4 # 四個變量
.param p1, "str" # 方法參數(shù):Ljava/lang/String;
.prologue # 代碼起始處
const-string v0, "" # 賦值給ans
.local v0, "ans":Ljava/lang/String;
const/4 v1, 0x0 # 賦值給 i
.local v1, "i":I
:goto_0 # 循環(huán)的地方
invoke-virtual {p1}, Ljava/lang/String;->length()I # 調(diào)用虛函數(shù)(參數(shù)p1)String類中的length方法南蹂,返回int
move-result v2 #把前一步的結(jié)果放在v2中
if-ge v1, v2, :cond_0 # 如果v1<v2,即i<str.length(),就跳到:cond_0
new-instance v2, Ljava/lang/StringBuilder; # 創(chuàng)建實例 v2 念恍,類型是Ljava/lang/StringBuilder;
invoke-direct {v2}, Ljava/lang/StringBuilder;-><init>()V # v2初始化
invoke-virtual {v2, v0}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder; # v2.append(v0)
move-result-object v2 # v2.append(v0) => v2 這里 v2是v0的值六剥,v2=ans
invoke-virtual {p1, v1}, Ljava/lang/String;->charAt(I)C # str.charAt(i)
move-result v3 # str.charAt(i) => v3
invoke-virtual {v2, v3}, Ljava/lang/StringBuilder;->append(C)Ljava/lang/StringBuilder; # ans + v3
move-result-object v2 # ans + v3 =>v2
invoke-virtual {v2}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String; # v2.toString()
move-result-object v0 # v2=>v0
add-int/lit8 v1, v1, 0x1 # i++
goto :goto_0 # 跳轉(zhuǎn)指令
:cond_0
const-string v2, "ans:" # 常量賦值 v2 = "ans:"
invoke-static {v2, v0}, Landroid/util/Log;->e(Ljava/lang/String;Ljava/lang/String;)I # Log.e(v2,v0)
return-void
.end method
switch語句
public void encrypt(int flag) {
String ans = null;
switch (flag){
case 0:
ans = "ans is 0";
break;
default:
ans = "noans";
break;
}
Log.v("ans:",ans);
}
<=對應(yīng)smali=>
.method public encrypt(I)V # 方法 public void encrypt(int flag)
.locals 2 # 兩個變量
.param p1, "flag" # 一個參數(shù) flag
.prologue
const/4 v0, 0x0 # v0賦值,
.local v0, "ans":Ljava/lang/String; # String ans = null; v0就是ans
packed-switch p1, :pswitch_data_0 # pswitch_data_0指定case區(qū)域的開頭及結(jié)尾
const-string v0, "noans" # 默認(rèn) 賦值 ans="noans"
:goto_0
const-string v1, "ans:" # 賦值 v1 = "ans:"
invoke-static {v1, v0}, Landroid/util/Log;->v(Ljava/lang/String;Ljava/lang/String;)I # Log.v
return-void
:pswitch_0 #pswitch_<case的值> case 0: ans="ans is 0"
const-string v0, "ans is 0"
goto :goto_0 # break
nop
:pswitch_data_0 #case區(qū)域的結(jié)束
.packed-switch 0x0 #定義case的情況
:pswitch_0 #case 0
.end packed-switch
.end method
其中case定義情況有兩種:
1.從0開始遞增
packed-switch p1, :pswitch_data_0
...
:pswitch_data_0
.packed-switch 0x0
:pswitch_0
:pswitch_1
2.無規(guī)則switch
sparse-switch p1,:sswitch_data_0
...
sswitch_data_0
.sparse-switch
0xa -> : sswitch_0
0xb -> : sswitch_1 # 字符會轉(zhuǎn)化成數(shù)組
try-catch語句
public void encrypt(int flag) {
String ans = null;
try {
ans = "ok!";
} catch (Exception e){
ans = e.toString();
}
Log.d("error",ans);
}
<=對應(yīng)smali=>
.method public encrypt(I)V # public void encrypt(int flag) {
.locals 3 # 3個變量
.param p1, "flag" # 參數(shù)
.prologue # 代碼開始
const/4 v0, 0x0
.line 20
.local v0, "ans":Ljava/lang/String; # String ans = null;
:try_start_0 # 第一個try開始峰伙,
const-string v0, "ok!" # ans = "ok"
:try_end_0 # 第一個try結(jié)束(主要是可能有多個try)
.catch Ljava/lang/Exception; {:try_start_0 .. :try_end_0} :catch_0
:goto_0
const-string v2, "error"
invoke-static {v2, v0}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I
return-void
:catch_0 #第一個catch
move-exception v1
.local v1, "e":Ljava/lang/Exception;
invoke-virtual {v1}, Ljava/lang/Exception;->toString()Ljava/lang/String;
move-result-object v0
goto :goto_0
.end method
類
baksmali在反編譯時疗疟,為每個類單獨生成一個smali文件,內(nèi)部類作為一個獨立類瞳氓,也擁有自己獨立的smali文件策彤,只是內(nèi)部類的文件名形式為[外部類]$[內(nèi)部類].smali
class Outer{
class Inner{}
}
上述代碼生成兩個文件:Outer.smali
和Out$Inner.smali
this$0 是什么? 是內(nèi)部類自動保留的一個指向所在外部類的引用匣摘。左邊的this表示為父類的引用店诗,右邊的0表示引用層數(shù)。
public class Outer{ //this$0
public class FirstInner{ //this$1
public class SecondInner{ //this$2
public class ThirdInner{
//在ThirdInner中訪問FirstInner類的引用為this$1
}
}
}
}
this$X型字段都被指定了synthetic屬性音榜,表明他們是被編譯器合成的虛構(gòu)的庞瘸,開發(fā)者并沒有聲明該字段。
Reference
https://www.anquanke.com/post/id/85035
《Android軟件安全與逆向分析》