美文网首页
Smali 语法

Smali 语法

作者: leach_chen | 来源:发表于2022-10-19 08:20 被阅读0次

    原文地址1:https://leach-chen.github.io/blog/nixiang-smali1/
    原文地址2:https://leach-chen.github.io/blog/nixiang-smali2/
    个人网站:https://www.leachchen.com/

    主要摘记Smali相关语法

    分两篇博客来记录,第一篇是比较全面的介绍,第二篇主要记录与smali操作相关的指令

    <a href="https://blog.csdn.net/u012573920/article/details/44034397" style="text-decoration: none;" target="_blank" title="">本博文参考至</a> 我觉得写得比较好就直接套过来了,<Android软件安全与逆向分析>3.2.5章节也有类似介绍

    (1)数据类型

    dalvik字节码有两种类型,原始类型和引用类型。对象和数组是引用类型,其它都是原始类型。

    基本类型的表示形式:

    smali数据类型都是用一个字母表示,如果你熟悉Java的数据类型,你会发现表示smali数据类型的字母其实是Java基本数据类型首字母的大写,除boolean类型外,在smail中用大写的”Z”表示boolean类型。

    类型 描述
    V void,只能用于返回值类型
    Z boolean
    B byte
    S short
    C char
    I int
    J long (64 bits)
    F float
    D double (64 bits)
    L Java类型
    [ 数组类型

    对象以Lpackage/name/ObjectName;的形式表示。前面的L表示这是一个对象类型,package/name/是该对象所在的包,ObjectName是对象的名字,“;”表示对象名称的结束。相当于java中的package.name.ObjectName。

    例如:Ljava/lang/String;相当于java.lang.String

    数组的表示形式:

    [I——表示一个整型一维数组,相当于java中的int[]。 对于多维数组,只要增加[就行了。[[I相当于int[][],[[[I相当于int[][][]。注意每一维的最多255个。

    对象数组的表示:

    [Ljava/lang/String;表示一个String对象数组。

    (2)方法

    方法通常必须详细的指定方法类型(?the type that contains the method) 方法名,参数类型,返回类型,所有这些信息都是为虚拟机是能够找到正确的方法并执行。

    方法表示形式:Lpackage/name/ObjectName;->MethodName(III)Z

    在上面的例子中,Lpackage/name/ObjectName;表示类型,MethodName是方法名。III为参数(在此是3个整型参数),Z是返回类型(bool型)。

    方法的参数是一个接一个的,中间没有隔开。

    一个更复杂的例子:method(I[[IILjava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;

    在java中则为:String method(int, int[][], int, String, Object[])

    一个比较全面的例子:

    .class public interface abstract Lcom/kit/network/CachableImage;
    .super Ljava/lang/Object;
    .source "SourceFile"
    
    # virtual methods
    .method public abstract getIsLarge()Z
    .end method
    .method public abstract getUrl()Ljava/lang/String;
    .end method
    
    .method public abstract getViewContext()Landroid/content/Context;
    .end method
    
    .method public abstract setBitmap(Landroid/graphics/Bitmap;Z)V
    .end method
    .method public abstract setIsLarge(Z)V
    .end method
    .method public abstract setUrl(Ljava/lang/String;)V
    .end method
    
    

    上面的smali代码还原后的java代码为:

    //#注:在实际代码中我们还必须引入相关的包
    import android.content.Context;
    import android.graphics.Bitmap;
    
    public interface CachableImage {
    
        public abstract boolean getIsLarge();
    
        public abstract String getUrl();
    
        public abstract Context getViewContext();
    
        public abstract void setBitmap(Bitmap bitmap);
    
        public abstract void setIsLarge(boolean islarge);
    
        public abstract void setUrl(String url);
    }
    

    (3)字段

    表示形式:Lpackage/name/ObjectName;->FieldName:Ljava/lang/String;即包名,字段名和各字段类型。例如:

    .field private _requestLayout:Z
    
    .field public isLarge:Z
    
    .field public resize:Z
    
    .field public thumbnailSize:I
    
    .field public url:Ljava/lang/String;
    

    上面的smali代码还原后的java代码为:

    public boolean _requestLayout;
    public boolean isLarge;
    public boolean resize;
    public int thumbnailSize;
    public String url;
    

    (4)示例

    新建一个HelloWorld安卓项目,在MainActivity中只保留onCreate函数。代码如下:

    package com.fusijie.helloworld;
      import android.app.Activity;
      import android.os.Bundle;
      public class MainActivity extends Activity {
      @Override
      protected void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
          setContentView(R.layout.activity_main);
      }
     }
    

    反编译后的Smali文件如下:

    .class public Lcom/fusijie/helloworld/MainActivity;
      .super Landroid/app/Activity;
      .source "MainActivity.java"
      # direct methods
      .method public constructor ()V
        .locals 0
        .prologue
        .line 14
        invoke-direct {p0}, Landroid/app/Activity;->()V
        return-void
      .end method
      # virtual methods
      .method protected onCreate(Landroid/os/Bundle;)V
        .locals 1
        .parameter "savedInstanceState"
        .prologue
        .line 18
        invoke-super {p0, p1}, Landroid/app/Activity;->onCreate(Landroid/os/Bundle;)V
        .line 19
        const/high16 v0, 0x7f03
        invoke-virtual {p0, v0}, Lcom/fusijie/helloworld/MainActivity;->setContentView(I)V
        .line 20
        return-void
      .end method
    

    对比一下,可以比较清楚的看出来,smali代码其实就是对java代码一个翻译,只是没有java看起来那么简单,smali把很多应该复杂的东西还原成复杂的状态了。简单解释下这段代码。

    前三行指明了类名,父类名,和源文件名。
    类名以“L”开头相信熟悉Jni的童鞋都比较清楚。
    “#”是smali中的注释。
    “.method”和“.end method”类似于Java中的大括号,包含了方法的实现代码段。
    方法的括号后面指明了返回类型,这同样类似与Jni的调用。
    “.locals”指明了这个方法用到的寄存器数量,当然寄存器可以重复利用,从“V0”起算。
    “.prologue”指定了代码开始处。
    “.line”表明这是在java源码中的第几行,其实这个值无所谓是多少,可以任意修改,主要用于调试。
    “invoke-direct”这是对方法的调用,可以看到这里调用了是Android.app.Activity的init方法,这在java里是隐式调用的。
    “return-void”表明了返回类型,这和java不一样,即使没有返回值,也需要这样写。
    接下来是onCreate方法,“.parameter”指明了参数名,但是一般没有用,需要注意的是p0代表的是this,p1开始代表函数参数,静态函数没有this,所以从p0开始就代表参数。
    在实现里先是调用了父类的方法,然后再调用setContentView,注意这里给了一个传参。整形的传参,这个值是先赋给寄存器v0,然后再调用的使用传递进去的。smali中都是这么使用,所有的值必须通过寄存器来中转。这点和汇编很像。
    

    对比了Java代码和Smali代码,可以很清楚的看到,原本只有几行的代码到了Smali,内容被大大扩充了。Smali还原了Java隐藏的东西,同时显式地指定了很多细节。这还只是个最基本的HelloWorld的onCreate函数,如果有内部类,还会分文件显示。

    (5)动手尝试

    了解了Smali的基本语法,那我们要动手试一下,Smali能做什么?仍然以HelloWorld为例,假如我们没有Android项目的源代码,只有一个APK,给他加个新功能吧!

    这个功能很简单,只是在HelloWorld中输出一个“Hello, Smali”。

    (1)第一步还是先使用apktool来反编译HelloWorld.apk。

    (2)打开smali下的com/fusijie/helloworld/MainActivity.smali文件。

    (3)原本我们在Java中要写的代码是:

    Toast.makeText(this, "Hello, Smali", Toast.LENGTH_LONG).show();
    

    翻译成Smali就是:

    .line xx
       const-string v0, "Hello, Smali"
       const/4 v1, 0x1
       invoke-static {p0, v0, v1}, Landroid/widget/Toast;->makeText(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;
       move-result-object v0
       invoke-virtual {v0}, Landroid/widget/Toast;->show()V
    

    (4)最后在插入Smali的时候,我们需要修改2个地方:

    “.locals 1”,因为本来只用到了v0,现在多用了一个v1,所以改为“.locals 2”。

    “.line xx” xx随意改为一个不重复的值即可。

    (5)使用apktool打包成apk,因为打包完后原有的密钥会丢失,所以需要重新打上我们自己的密钥


    1. .method和.end method
    类似于Java中的大括号,包含了方法的实现代码段。
    
    1. .locals
    指明了这个方法用到的寄存器数量,当然寄存器可以重复利用,从“V0”起算
    使用这个指定表明方法中非参寄存器的总数,放在方法的第一行。
    
    1. .registers
     使用这个指令指定方法中寄存器的总数
    
    1. v、p0、p1
    public void int foo(int a,int b)
    {
      return (a+b)*(a-b);
    }
    v命名法采用以小写字母v开头的方式表示函数中用到的局部变量与参数,所有的寄存器命名从v0开始,v0,v1用来表示函数的局部变量寄存器,v2表示传入的对象引用,v3,v4表示两个传入的整形参数
    p命名法,v0,v1用来表示函数的局部变量寄存器,p0表示传入的对象的引用,p1,p2分别表示传入的两个整形参数
    p0代表的是this,p1开始代表函数参数,静态函数没有this,所以从p0开始就代表参数
    
    1. .prologue
    指定了代码开始处
    
    1. .line
    表明这是在java源码中的第几行,其实这个值无所谓是多少,可以任意修改,主要用于调试
    

    7 invoke-direct

    这是对方法的调用,可以看到这里调用了是Android.app.Activity的init方法,这在java里是隐式调用的
    
    1. .parameter
    指明了参数名
    
    1. return-void
    返回类型
    
    1. invoke-static
    用于调用static函数,invoke-static后面有一对大括号“{}”,其实是调用该方法的实例+参数列表
    invoke-static {v3,v0}, Landroid/util/log;->v(Ljava/lang/String;Ljava/lang/String;)
    
    1. invoke-virtual
    用于调用protected或public函数,同样注意修改smali时不要错用invoke-direct或invoke-static
    invoke-virtual {v1}, Landroid/app/Activity;->getApplicationContext()Landroid/content/Context;
    
    1. invoke-direct
    调用private函数
    invoke-direct {v0, v4}, Lcom/android/server/LightsService;-><init>(Landroid/content/Context;)V
    
    1. invoke-super
    调用父类方法用的指令,一般用于调用onCreate、onDestroy等方法。
    invoke-super {p0, p1}, Landroid/preference/PreferenceActivity;->onCreate(Landroid/os/Bundle;)V    //p0:this,p1:Bundle
    invoke-super {p0, p1, p2}, Landroid/view/View;->onKeyUp(ILandroid/view/KeyEvent;)Z
    invoke-super {p0, p1}, Landroid/app/Activity;->onCreate(Landroid/os/Bundle;)V
    
    1. invokeinterface
    调用接口方法,调用的方法运行时确认实际调用,即会在运行时才确定一个实现此接口的对象。
    
    1. const
    const v0, 0x7f04001b  //将值0x7f04001b 放入寄存器v0
    const/4 v2, 0x1       //4字节常量   v2=1
    
    1. new-instance
    新建一个实例
    new-instance v1, Lcom/droider/crackme0502/MainActivity$1; #新建一个MainActivity$1实例  
    invoke-direct {v1, p0}, Lcom/droider/crackme0502/MainActivity$1;-><init>(Lcom/droider/crackme0502/MainActivity;)V # 初始化MainActivity$1实例  
    
    1. iput-object
    对象赋值
    iput-object v1, p0, Lcom/leachchen/testnixiang/MainActivity;->tree:Lcom/leachchen/testnixiang/Tree;  相当于tree = new Tree(); tree是类成员变量
    
    1. iget-object
    调用对象
    iget-object v1, p0, Lcom/leachchen/testnixiang/MainActivity;->tree:Lcom/leachchen/testnixiang/Tree; 相当于获取到tree对象
    invoke-virtual {v1}, Lcom/leachchen/testnixiang/Tree;->go()V  相当于调用Tree类的go方法,tree.go();
    
    1. if-eq vA, vB, :cond_
    如果vA等于vB则跳转到:cond_
    
    1. if-ne vA, vB, :cond_
    如果vA不等于vB则跳转到:cond_**
    
    1. if-lt vA, vB, :cond_
    如果vA小于vB则跳转到:cond_**
    
    1. if-ge vA, vB, :cond_
    如果vA大于等于vB则跳转到:cond_**
    
    1. if-gt vA, vB, :cond_
    如果vA大于vB则跳转到:cond_**
    
    1. if-le vA, vB, :cond_
    如果vA小于等于vB则跳转到:cond_**
    
    1. if-eqz vA, :cond_
    如果vA等于0则跳转到:cond_**
    
    1. if-nez vA, :cond_
    如果vA不等于0则跳转到:cond_**
    
    1. if-ltz vA, :cond_
    如果vA小于0则跳转到:cond_**
    
    1. if-gez vA, :cond_
    如果vA大于等于0则跳转到:cond_**
    
    1. if-gtz vA, :cond_
    如果vA大于0则跳转到:cond_**
    
    1. if-lez vA, :cond_
    如果vA小于等于0则跳转到:cond_**
    
    1. sput-boolean
    Puts boolean value in vx into a static field.
    

    相关文章

      网友评论

          本文标题:Smali 语法

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