smali文件和語(yǔ)法

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)約定,表示后面跟隨的字符串為一個(gè)類。

第二行,Lcom/droider/crackme0502/MainActivity; 的父類是 Landroid/app/Activity;

第三行,.source指令指定了當(dāng)前類的源文件名

經(jīng)過混淆的dex文件,反編譯出來的smali代碼可能沒有源文件信息,因此,.source可能為空。

前三行代碼過后是類的主體部分,一個(gè)類可以由多個(gè)字段或方法組成。smali文件中字段的聲明使用.field指令。字段有靜態(tài)字段與實(shí)例字段兩種。靜態(tài)字段的聲明格式如下:

# static fields
.field<訪問權(quán)限>[修飾關(guān)鍵字]<字段名>:<字段類型>

實(shí)例字段的聲明:

# 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]  // 使用的局部變量個(gè)數(shù)
    <代碼體>
.end method

虛方法的聲明和直接方法相同,只是起始處的注釋為virtual methods

如果一個(gè)類實(shí)現(xiàn)了接口,會(huì)在.smali文件中使用.implement指令指出。聲明入下:

# interface
.implements<接口名>

注解格式聲明:

# annotations
.annotation[注解屬性]<注解類名>
    [注解字段=值]
.end annotation

注解的作用范圍可以是類、方法和字段。如果作用范圍是類,指令會(huì)直接定義在smali文件中,如果是方法或者字段,指令會(huì)包含在方法或字段的定義中。例如:

# 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;  // 前面表示對(duì)象所在包路徑,分號(hào)表示類結(jié)束

寄存器操作

p命名法和v命名法:

假設(shè)一個(gè)函數(shù)中用到M個(gè)寄存器,實(shí)際傳入的參數(shù)是N個(gè)。

根據(jù)傳參規(guī)則,參數(shù)使用后N個(gè)寄存器,局部變量使用0到M-N個(gè)寄存器。

假如用到5個(gè)寄存器,2個(gè)局部參數(shù),3個(gè)傳入?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    #把類對(duì)象取出

變量賦值

move  vx,vy   # 將vy的值賦值給vx,也可以是move-object等
move-result vx  # 將上個(gè)方法調(diào)用后的結(jié)果賦值給vx,也可以是move-result-object
return-object vx # 將vx的對(duì)象作為函數(shù)返回值
new-instance v0, ChallengePagerAdapter  # 實(shí)例化一個(gè)對(duì)象存入v0中

對(duì)象賦值

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ù)操作一般有以下四個(gè):

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
<=對(duì)應(yīng)源碼=>
super.onCreate(savedInstanceState);  // 其中p0是this,其父類是FragmentActivity,p1,是savedInstanceState,其原型是Bundle;即調(diào)用p0->onCreate(p1)

程序相關(guān)語(yǔ)法

判斷語(yǔ)句

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)語(yǔ)句

public void encrypt(String str) {
    String ans = "";
    for (int i = 0 ; i < str.length();i++){
        ans += str.charAt(i);
    }
    Log.e("ans:",ans);
}
<=對(duì)應(yīng)smali=>
.method public encrypt(Ljava/lang/String;)V   # 方法:public void encrypt(String str)
.locals 4           # 四個(gè)變量
.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)建實(shí)例 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語(yǔ)句

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);
    }
<=對(duì)應(yīng)smali=>
.method public encrypt(I)V  # 方法 public void encrypt(int flag)
    .locals 2  # 兩個(gè)變量
    .param p1, "flag"    # 一個(gè)參數(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.無(wú)規(guī)則switch
sparse-switch p1,:sswitch_data_0
...
sswitch_data_0
.sparse-switch
    0xa -> : sswitch_0
    0xb -> : sswitch_1 # 字符會(huì)轉(zhuǎn)化成數(shù)組

try-catch語(yǔ)句

public void encrypt(int flag) {
    String ans = null;
    try {
        ans = "ok!";
    } catch (Exception e){
        ans = e.toString();
    }
    Log.d("error",ans);
}
<=對(duì)應(yīng)smali=>
.method public encrypt(I)V      # public void encrypt(int flag) {
    .locals 3           # 3個(gè)變量
    .param p1, "flag"    # 參數(shù)
    .prologue           # 代碼開始
    const/4 v0, 0x0     
    .line 20
    .local v0, "ans":Ljava/lang/String;     # String ans = null;
    :try_start_0  # 第一個(gè)try開始,
    const-string v0, "ok!"  # ans = "ok"
    :try_end_0   # 第一個(gè)try結(jié)束(主要是可能有多個(gè)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 #第一個(gè)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在反編譯時(shí),為每個(gè)類單獨(dú)生成一個(gè)smali文件,內(nèi)部類作為一個(gè)獨(dú)立類,也擁有自己獨(dú)立的smali文件,只是內(nèi)部類的文件名形式為[外部類]$[內(nèi)部類].smali

class Outer{
    class Inner{}
}

上述代碼生成兩個(gè)文件:Outer.smaliOut$Inner.smali

this$0 是什么? 是內(nèi)部類自動(dòng)保留的一個(gè)指向所在外部類的引用。左邊的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軟件安全與逆向分析》

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Smali 概述我們都知道,Dalvik 虛擬機(jī)(Dalvik VM)是 Google 專門為 Android 平...
    大表哥007閱讀 33,278評(píng)論 0 9
  • 1.ios高性能編程 (1).內(nèi)層 最小的內(nèi)層平均值和峰值(2).耗電量 高效的算法和數(shù)據(jù)結(jié)構(gòu)(3).初始化時(shí)...
    歐辰_OSR閱讀 30,286評(píng)論 8 265
  • 有時(shí)候明明很欣賞 但感受到對(duì)方自己更加欣賞自己 就不知道怎么夸才能表示心意了 所以我謙虛的時(shí)候 是在等著? 不妨請(qǐng)...
    dew清澈閱讀 184評(píng)論 0 0
  • 本書說到刻意練習(xí)才有效,10000個(gè)小時(shí)定律事實(shí)上是需要在刻意練習(xí)的前提下,才能產(chǎn)生作用的。如果只是泛泛練習(xí),那么...
    傳世玉印閱讀 354評(píng)論 0 2
  • 身體是革命的本錢, 那些拿命拼未來的, 多半沒有未來。 生病以后才知道健康多重要, 我羨慕?jīng)]有生病之前的自己, 坐...
    渠六億閱讀 296評(píng)論 2 3

友情鏈接更多精彩內(nèi)容