美文网首页
smali文件和语法

smali文件和语法

作者: Ireliaaa | 来源:发表于2018-07-23 15:50 被阅读0次

    smali文件格式

    smali文件的头3行描述了当前类的一些信息,格式如下:

    .class<访问权限>[修饰关键字]<类名>
    .super<父类名>
    .source<源文件名>
    
    

    打开MainAcivity.smali,头三行代码:

    .class public Lcom/droider/crackme0502/MainActivity;
    .super Landroid/app/Activity;
    .source "MainActivity.java"
    
    

    第一行权限public ,类名为Lcom/droider/crackme0502/MainActivity;,类名开头的L是遵循Dalvik字节码的相关约定,表示后面跟随的字符串为一个类。

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

    第三行,.source指令指定了当前类的源文件名

    经过混淆的dex文件,反编译出来的smali代码可能没有源文件信息,因此,.source可能为空。

    前三行代码过后是类的主体部分,一个类可以由多个字段或方法组成。smali文件中字段的声明使用.field指令。字段有静态字段与实例字段两种。静态字段的声明格式如下:

    # static fields
    .field<访问权限>[修饰关键字]<字段名>:<字段类型>
    
    

    实例字段的声明:

    # instance field
    .field private btnAnno:Landroid/widget/Button;
    
    

    private 表示私有;字段btnAnno,它的类型是Landroid/widget/Button;

    直接方法的声明:

    # direct methods
    .method <访问权限> <方法名>(参数原型) <方法原型>
        [.prologue]    // 指定代码开始位置
        [.param]       // 指定方法参数
        [.line]        // 指定代码在源代码中的行数,混淆后可能不存在
        [.locals]  // 使用的局部变量个数
        <代码体>
    .end method
    
    

    虚方法的声明和直接方法相同,只是起始处的注释为virtual methods

    如果一个类实现了接口,会在.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
    
    

    转换成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;  // 前面表示对象所在包路径,分号表示类结束
    
    

    寄存器操作

    p命名法和v命名法:

    假设一个函数中用到M个寄存器,实际传入的参数是N个。

    根据传参规则,参数使用后N个寄存器,局部变量使用0到M-N个寄存器。

    假如用到5个寄存器,2个局部参数,3个传入参数。

    v命名法

    v0,v1,v2,v3,v4;

    p命名法

    v0,v1,p0,p1,p2;

    只改变传入参数寄存器名。

    常量赋值

    const                   v0, 0x7F030018  # R.layout.activity_challenge   #从R中取出静态值
    const/4                 v3, 0x2   #4也可以换成16或者high16,表示取整数值
    const-string            v2, "Challenge"  # 取字符串
    const-class             v2, Context    #把类对象取出
    
    

    变量赋值

    move  vx,vy   # 将vy的值赋值给vx,也可以是move-object等
    move-result vx  # 将上个方法调用后的结果赋值给vx,也可以是move-result-object
    return-object vx # 将vx的对象作为函数返回值
    new-instance v0, ChallengePagerAdapter  # 实例化一个对象存入v0中
    
    

    对象赋值

    iput-object a,(this),b   将a的值给b,一般用于b的初始化
    iget-object a,(this),b   将b的值给a,一般用于获取b的地址,接着调用它
    # eg.
    iput-object v0, p0, ChallengeActivity->actionBar:ActionBar
    iget-object v0, p0, ChallengeActivity->actionBar:ActionBar
    
    

    函数操作

    最基础的函数操作一般有以下四个:

    1.private:invoke-direct
    2.public|protected: invoke-virtual
    3.static:invoke-static
    4.parent:  invoke-super
    基本调用形式:invoke-xxx {参数},类;->函数(参数原型)
    # eg.
    invoke-super {p0, p1}, Landroid/support/v4/app/FragmentActivity;->onCreate(Landroid/os/Bundle;)V
    <=对应源码=>
    super.onCreate(savedInstanceState);  // 其中p0是this,其父类是FragmentActivity,p1,是savedInstanceState,其原型是Bundle;即调用p0->onCreate(p1)
    
    

    程序相关语法

    判断语句

    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
    
    

    循环语句

    public void encrypt(String str) {
        String ans = "";
        for (int i = 0 ; i < str.length();i++){
            ans += str.charAt(i);
        }
        Log.e("ans:",ans);
    }
    <=对应smali=>
    .method public encrypt(Ljava/lang/String;)V   # 方法:public void encrypt(String str)
    .locals 4           # 四个变量
    .param p1, "str"    # 方法参数: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             # 循环的地方
    invoke-virtual {p1}, Ljava/lang/String;->length()I   # 调用虚函数(参数p1)String类中的length方法,返回int
    move-result v2      #把前一步的结果放在v2中
    if-ge v1, v2, :cond_0  # 如果v1<v2,即i<str.length(),就跳到:cond_0
    new-instance v2, Ljava/lang/StringBuilder;      # 创建实例 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    # 跳转指令
    :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);
        }
    <=对应smali=>
    .method public encrypt(I)V  # 方法 public void encrypt(int flag)
        .locals 2  # 两个变量
        .param p1, "flag"    # 一个参数 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区域的开头及结尾
        const-string v0, "noans"     # 默认 赋值 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区域的结束
        .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.无规则switch
    sparse-switch p1,:sswitch_data_0
    ...
    sswitch_data_0
    .sparse-switch
        0xa -> : sswitch_0
        0xb -> : sswitch_1 # 字符会转化成数组
    
    

    try-catch语句

    public void encrypt(int flag) {
        String ans = null;
        try {
            ans = "ok!";
        } catch (Exception e){
            ans = e.toString();
        }
        Log.d("error",ans);
    }
    <=对应smali=>
    .method public encrypt(I)V      # public void encrypt(int flag) {
        .locals 3           # 3个变量
        .param p1, "flag"    # 参数
        .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结束(主要是可能有多个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文件,内部类作为一个独立类,也拥有自己独立的smali文件,只是内部类的文件名形式为[外部类]$[内部类].smali

    class Outer{
        class Inner{}
    }
    
    

    上述代码生成两个文件:Outer.smaliOut$Inner.smali

    this$0 是什么? 是内部类自动保留的一个指向所在外部类的引用。左边的this表示为父类的引用,右边的0表示引用层数。

    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属性,表明他们是被编译器合成的虚构的,开发者并没有声明该字段。

    Reference

    https://www.anquanke.com/post/id/85035

    《Android软件安全与逆向分析》

    相关文章

      网友评论

          本文标题:smali文件和语法

          本文链接:https://www.haomeiwen.com/subject/frjjmftx.html