Smali入門手冊

一、Smali 簡介

首先,提到smali就先說下逆向,逆向通常是安全工程師(逆向工程師),系統分析三方APP以及做破解等惡意分子因爲某些利益在做(apk二次打包插入廣告、破解收費應用、惡意代碼植入、剽竊api等)的一種手段。

當然技術是一把雙刃劍,在於使用技術的人而不再技術本身。

迴歸正題,Smali是一種寬鬆式的Jasmin/dedexer語法,是Davlik的寄存器語言,語法上和彙編語言相似,Dalvik VM與JVM的最大的區別之一就是Dalvik VM是基於寄存器的。基於寄存器的意思是,在smali裏的所有操作都必須經過寄存器來進行。

Smali,Baksmali分別是冰島語中編譯器,反編譯器的叫法。也許你會問爲什麼是冰島語呢,因爲Dalvik是一個冰島漁村名字。

二、Smali 語法

1. 數據類型

Davlik字節碼中,寄存器都是32位的,能夠支持任何類型,64位類型(Long/Double)用2個寄存器表示。
Dalvik字節碼有兩種類型:基本類型;引用類型(包括對象和數組)。

(1)基本類型

type 解釋
V void只能用於返回值類型
Z boolean
B byte
S short
C char
I int
J long(64位)
F float
D double(64位)

(2)引用類型

type 解釋
L 對象類型(Lpackage/ObjectName; 相當於java中的package.ObjectName;)
[I 表示一個整形的一維數組,相當於java的int[];
[Ljava/lang/String 表示一個String的對象數組

① 對象類型:

“L“:表示這是一個對象類型
”package/ObjectName“:該對象所在的包與類名,比如Ljava/lang/String =>java.lang.String
”;“:表示對象名稱的結束

② 數組的表示形式:

” [I “ :表示一個整形的一維數組,相當於java的int[];
對於多維數組,只要增加”[“ 就行了,[[I => int[][]; 注:每一維最多255個;

③ 對象數組的表示形式:

[Ljava/lang/String 表示一個String的對象數組;

2. 基礎語法

2.1 表達式

Java源代碼:

    public void smaliExpression(){
        //加法運算
        int a = 1;
        double b = 2.5;
        double c = a + b;

        //減法運算
        double d = b - a;

        //乘法運算
        double e = a * b;

        //除法運算
        double f = b / a;

        //異或運算
        int g = 3;
        int h = a ^ g;

        //三目運算
        int i = a > b?a:g;
    }

Smali代碼:

.method public smaliExpression()V
    .locals 15

    .line 16
    const/4 v0, 0x1    #1

    .line 17
    .local v0, "a":I
    const-wide/high16 v1, 0x4004000000000000L    # 2.5

    .line 18
    .local v1, "b":D
    int-to-double v3, v0   //將int型的 1 強轉爲double的 1.0

    add-double/2addr v3, v1  //兩個double類型相加

    .line 21
    .local v3, "c":D
    int-to-double v5, v0

    sub-double v5, v1, v5    //減法 V5 = V1-V5

    .line 24
    .local v5, "d":D
    int-to-double v7, v0

    mul-double/2addr v7, v1   //乘法

    .line 27
    .local v7, "e":D
    int-to-double v9, v0

    div-double v9, v1, v9   //除法

    .line 30
    .local v9, "f":D
    const/4 v11, 0x3

    .line 31
    .local v11, "g":I
    xor-int v12, v0, v11  //異或

     //異或語句,代條件語句
    .line 34
    .local v12, "h":I
    int-to-double v13, v0

    cmpl-double v13, v13, v1  //cmpl-double 比較

    if-lez v13, :cond_0

    move v13, v0

    goto :goto_0

    :cond_0
    move v13, v11       //將寄存器v11值給v13

    .line 35
    .local v13, "i":I
    :goto_0
    return-void
.end method

整理如下:

java運算符 samli運算符
加法 add-double/2addr
減法 sub-double
乘法 mul-double/2addr
除法 div-double
異或 xor-int
三目運算 條件語句

可以看到三目運算符本身就是條件語句,所以我們看下詳細的條件語句是怎麼樣的。

2.2 條件語句

先上對比代碼。
java:

    public void smaliIf(){
        int a = 1, b = 2;
        int c = 0;
        if(a > b){
            c = a;
        }
        if(a < b){
            c = b;
        }
        if(a>=b){
            c = a;
        }
        if(a <= b){
            c = b;
        }
        if(a==b){
            c = a;
        }
        if(a != b){
            c = b;
        }
    }

samli:

.method public smaliIf()V
    .locals 3

    .line 38
    const/4 v0, 0x1

    .local v0, "a":I
    const/4 v1, 0x2

    .line 39
    .local v1, "b":I
    const/4 v2, 0x0

    .line 40
    .local v2, "c":I
    if-le v0, v1, :cond_0

    .line 41
    move v2, v0

    .line 43
    :cond_0
    if-ge v0, v1, :cond_1

    .line 44
    move v2, v1

    .line 46
    :cond_1
    if-lt v0, v1, :cond_2

    .line 47
    move v2, v0

    .line 49
    :cond_2
    if-gt v0, v1, :cond_3

    .line 50
    move v2, v1

    .line 52
    :cond_3
    if-ne v0, v1, :cond_4

    .line 53
    move v2, v0

    .line 55
    :cond_4
    if-eq v0, v1, :cond_5

    .line 56
    move v2, v1

    .line 58
    :cond_5
    return-void
.end method

歸納總結整理如下:

if-eq vA, vB, :cond_*” 如果vA等於vB則跳轉到:cond_*,否則繼續向下執行

“if-ne vA, vB, :cond_*” 如果vA不等於vB則跳轉到:cond_*,否則繼續向下執行

“if-lt vA, vB, :cond_*” 如果vA小於vB則跳轉到:cond_*,否則繼續向下執行

“if-ge vA, vB, :cond_*” 如果vA大於等於vB則跳轉到:cond_*,否則繼續向下執行

“if-gt vA, vB, :cond_*” 如果vA大於vB則跳轉到:cond_*,否則繼續向下執行

“if-le vA, vB, :cond_*” 如果vA小於等於vB則跳轉到:cond_*,否則繼續向下執行

“if-eqz vA, :cond_*” 如果vA等於0則跳轉到:cond_*,否則繼續向下執行

“if-nez vA, :cond_*” 如果vA不等於0則跳轉到:cond_**,否則繼續向下執行

“if-ltz vA, :cond_*” 如果vA小於0則跳轉到:cond_*,否則繼續向下執行

“if-gez vA, :cond_*” 如果vA大於等於0則跳轉到:cond_*,否則繼續向下執行

“if-gtz vA, :cond_*” 如果vA大於0則跳轉到:cond_*,否則繼續向下執行

“if-lez vA, :cond_*” 如果vA小於等於0則跳轉到:cond_*,否則繼續向下執行

其中的:cond_* 中 “ * ” 是編號(代表1,2,3.。。),在一個方法裏如果有多個條件則這個編號不可以重複。

2.3 循環語句

還是一樣,先上對照代碼:
java:

    public void smaliWhile(){
        //while
        int a = 0;
        while(a<=3){
            a++;
        }

        //for
        int b = 0;
        for(int i = 0;i<3;i++){
            b++;
        }

        //do...while
        int c = 0;
        do{
            c++;
        }while (c <= 3);
    }

smali:

.method public smaliWhile()V
    .locals 5

    .line 62
    const/4 v0, 0x0

    move v1, v0

    .line 63
    .local v1, "a":I
    :goto_0
    const/4 v2, 0x3

    if-gt v1, v2, :cond_0   //如果a > 3跳轉 cond_0

    .line 64
    add-int/lit8 v1, v1, 0x1   //上面if條件不存在,a自增1

    goto :goto_0  //循環主體,繼續從goto_0向下執行

    .line 68
    :cond_0
    const/4 v3, 0x0

    .line 69
    .local v3, "b":I
    move v4, v3

    move v3, v0

    .local v3, "i":I
    .local v4, "b":I
    :goto_1
    if-ge v3, v2, :cond_1      // i >= 3

    .line 70
    add-int/lit8 v4, v4, 0x1   // b++;

    .line 69
    add-int/lit8 v3, v3, 0x1   //i++;

    goto :goto_1     //循環主體

    .line 74
    .end local v3    # "i":I
    :cond_1
    nop             //表示空操作,什麼都不幹

    .line 76
    .local v0, "c":I
    :cond_2
    add-int/lit8 v0, v0, 0x1

    .line 77
    if-le v0, v2, :cond_2

    .line 78
    return-void
.end method

所以循環關鍵點在於循環體開始地方使用 :goto_* 標識,執行循環的地方使用 goto :goto_*,判斷是否跳出循環仍然使用上面我們將的條件語句。

2.4 try-catch語句

繼續上代碼
java:

    public void smaliTryCatch(){
        Object a = null;
        try {
            a = null;
        }catch (Exception e){
            a.toString();
        }finally {
            a = new Object();
        }
    }

smali:

.method public smaliTryCatch()V
    .locals 2
    
    .line 81
    const/4 v0, 0x0

    .line 83
    .local v0, "a":Ljava/lang/Object;
    const/4 v0, 0x0

    .line 87        //v1: new Object();
    new-instance v1, Ljava/lang/Object;   

    invoke-direct {v1}, Ljava/lang/Object;-><init>()V

    move-object v0, v1

    .line 88
    nop

    .line 89
    return-void
.end method

這裏的try-catch很奇怪,好像catch部分執行了nop也就是空操作,是smali不會去管異常嗎?我也不清楚了,如果大家有知道的可以告訴我。
這裏主要就是有一個new Object過程,new一個對象在smali中是怎麼樣的?就是上面這樣的.line87的內容。

說到這裏,基本的語法就先到這裏了,下面用一個簡單的類文件來看下smali類文件結構。

3. 類文件結構

因爲一個類文件的Smali代碼比較長,所以我們分開來解釋。

3.1 頭信息

.class public Lcom/justart/samlidemo/MainActivity;
.super Landroid/app/Activity;
.source "MainActivity.java"

.class 表示類路徑 包+類名
.super 表示父類的路徑和地址
.source 表示源碼文件名

3.2 構造方法

# direct methods
.method public constructor <init>()V
    .locals 0

    .line 6
    invoke-direct {p0}, Landroid/app/Activity;-><init>()V

    return-void
.end method

因爲源碼中我沒有重寫構造方法,所以默認的無參構造方法裏直接調用父類Activity的無參構造方法。

3.3 其他方法

這裏以Activity的onCreate方法爲例:

# virtual methods   //表示是一個虛方法
.method protected onCreate(Landroid/os/Bundle;)V
    .locals 1
    .param p1, "savedInstanceState"    # Landroid/os/Bundle;

    .line 10
    invoke-super {p0, p1}, Landroid/app/Activity;->onCreate(Landroid/os/Bundle;)V

    .line 11
    const/high16 v0, 0x7f050000

    invoke-virtual {p0, v0}, Lcom/justart/samlidemo/MainActivity;->setContentView(I)V

    .line 12
    return-void
.end method

(1)方法以 .method開始, .end method 結束;
(2)方法第一行最後 V表示返回類型爲void,其他返回類型見第一節數據類型;
(3)方法參數也遵循smali數據類型,這裏表示參數是一個Bundle對象類型;
(4).param 表示 方法的參數,默認參數使用p0表示;
(5)最後是方法的返回類型 這裏表示返回void。

3.4 附錄

下面簡單總結一下類中常用的一些關鍵詞:

關鍵詞 說明
.class 定義類類型 包名+類名
.super 定義父類的路徑和地址
.source 表示源碼文件名
filed 定義字段
.method…end method 定義方法
.annotation…end annotation 定義註解
.implements 定義接口指令
.local 指定了方法內局部變量的個數
.registers 指定方法內使用寄存器的總數
.prologue 表示方法中代碼的開始處
.line 表示java源文件中指定行
.paramter .param 指定了方法的參數

三、總結

文章寫得比較粗糙,希望大家給點建議,有錯誤的地方可以指出來,謝謝大家!

說下我的學習感受吧,梳理一遍smali語法,發現同時有助於瞭解java代碼的執行流程,也可以瞭解寄存器相關的入門知識,同時熟悉smali之後可以調試三方或者修改APP。簡單的修改三方APP代碼可以參考我的另外一篇文章反編譯三方apk並添加debug log

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章