Android逆向工程反编译(一)初识Smali语法

1.什么是Smali?

官方解释:

Smali是指安卓系统里的Java虚拟copy机(Dalvik)所使用的一种.dex格式文件的汇编器,反汇编器。其语法是一种宽松式的Jasmin/dedexer语法,而且它实现了.dex格式所有功能(注解,调试信息,线路信息等zhidao)。

其实说白了smali使用一种语言,是Dalvik的反编译语言。

那么问题又来了,什么是Dalvik?
Dalvik是Google公司自己设计用于Android平台的虚拟机。它支持.dex(即“Dalvik Executable”)格式的Java应用程序的运行。.dex格式是专为Dalvik设计的一种压缩格式,适合内存和处理器速度有限的系统。Dalvik虚拟机是基于寄存器架构的虚拟机。

不同于java虚拟机JVM编译后生成的class文件,Android虚拟机Dalvik在执行在重新整合打包后生成的dex文件,dex文件反编译之后就是smali代码。

所以步入Android逆向工程的第一步就是了解Smali语法,所以有了本篇文章。

2.Smali语法

数据类型

对比于Java,smali也有相应的数据类型

类型关键字 对应Java中的类型
V void,只能用于返回类型
Z boolean
B byte
S short
C char
I int
J long (64 bits)
F float
D double (64 bits)
[XXX array
Lxxx/yyy object

对象

   smali语法             java语法
Ljava/lang/String     java.lang.String

方法

Lpackage/name/ObjectName;->MethodName(III)Z

第一部分Lpackage/name/ObjectName;用于声明具体的类型,以便JVM寻找

第二部分MethodName(III)Z,其中MethodName为具体的方法名,()中的字符,表示了参数数量和类型,即3个int型参数,Z为返回值的类型,即返回Boolean类型

寄存器

在Smali中,如果需要存储变量,必须先声明足够数量的寄存器,1个寄存器可以存储32位长度的类型,比如Int,而两个寄存器可以存储64位长度类型的数据,比如Long或Double。

声明可使用的寄存器数量的方式为:.registers N,N代表需要的寄存器的总个数

例子:

.method private test(I)V
    .registers 1  # 声明总共需要使用1个寄存器

    const-string v0, "MRyan"  # 将v0寄存器赋值为字符串常量"MRyan

    return-void #返回空
.end method #方法结束

如何确定需要使用的寄存器的个数?
就小学数学加法呗
非static方法,需要占用一个寄存器以保存this指针,那么这类方法的寄存器个数,最低就为1,其余按照数据类型叠加

例如下面一个Java代码

 Void M1(int a1, float a2, Double a3);

对应的Smali代码如下:

method LMyObject;->M1(IFD)V

寄存器的对应情况如下:

寄存器名称	对应的引用         寄存器个数
p0	          this                 1
p1	       int型的a1参数            1
p2, p3	       float型的a2参数          2
p4,p5	       Double型的a3参数         2

1+1+2+2=6,所以就需要6个寄存器

3.Dalvik指令集(此处转载)

寄存参数使用p寄存器,形如p0,p1,用.param定义,且从p1开始计数,因为p0为非静态方法自动创建的寄存器,存储着this,也就是该方法所属类的实例本身

寄存方法中的内部变量使用v寄存器,形如v0,v1,用.locals在方法开头表示所需的内部变量数,在声明变量时用.local …声明。值得一提的是方法中的内部类也属于方法的内部变量。

1.移位操作:

一般的指令格式为:[op]-type/[位宽,默认4位] [目标寄存器],源寄存器,比如:move v1,v2,move-wide/from16 v1,v2

 指令	                      说明
move v1,v2	               将v2中的值移入到v1寄存器中(4位,支持int型)
move/from16 v1,v2	       将16位的v2寄存器中的值移入到8位的v1寄存器中
move/16 v1,v2	       将16位的v2寄存器中的值移入到16位的v1寄存器中
move-wide v1,v2            将寄存器对(一组,用于支持双字型)v2中的值移入到v1寄存器对中 
                          (4位,猜测支持float、double型)
move-wide/from16 v1,v2	将16位的v2寄存器对(一组)中的值移入到8位的v1寄存器中
move-wide/16 v1,v2	        将16位的v2寄存器对(一组)中的值移入到16位的v1寄存器中
move-object v1,v2	        将v2中的对象指针移入到v1寄存器中
move-object/from16 v1,v2	将16位的v2寄存器中的对象指针移入到v1(8位)寄存器中
move-object/16 v1,v2	将16位的v2寄存器中的对象指针移入到v1(16位)寄存器中
move-result v1	        将这个指令的上一条指令计算结果,移入到v1寄存器中(需要配合invoke-static、invoke-virtual等指令使用)
move-result-object v1	将上条计算结果的对象指针移入v1寄存器
move-result-wide v1	        将上条计算结果(双字)的对象指针移入v1寄存器
move-exception v1	        将异常移入v1寄存器,用于捕获try-catch语句中的异常

在invoke-virtual {v0, p0~pn}…中,v0存有该方法所属的类,p0至pn表示该方法需要的传参,…表示v0所存类与该方法的联系

返回操作:

指令	             说明
return-void	         返回void,即直接返回
return v1	         返回v1寄存器中的值
return-object v1	返回v1寄存器中的对象指针
return-wide v1	返回双字型结果给v1寄存器

常量操作:

用于声明常量,比如字符串常量(仅声明,String a = “abc”这种语句包含声明和赋值)

指令	说明
const(/4、/16、/hight16) v1 xxx	      将常量xxx赋值给v1寄存器,/后的类型,需要根 
                                          据xxx的长度选择
const-wide(/16、/32、/hight16) v1 xxx	将双字型常量xxx赋值给v1寄存器,/后的类 
                                          型,需要根据xxx的长度选择
const-string(/jumbo) v1 “aaa”	       将字符串常量”aaa”赋给v1寄存器,过长时需要 
                                            加上jumbo
const-class v1 La/b/TargetClass	       将Class常量a.b.TargetClass赋值给v1,等价 
                                            于a.b.TargetClass.class

调用操作:

用于调用方法,基本格式:invoke-kind {vC, vD, vE, vF, vG}, meth@BBBB,其中,BBBB代表方法引用(参见上面介绍的方法定义及调用),vC~G为需要的参数,根据顺序一一对应

   指令	              说明
invoke-virtual	  用于调用一般的,非private、非static、非final、非构造函数的方法,它的第一个参数往往会传p0,  也就是this指针
invoke-super	用于调用父类中的方法,其他和invoke-virtual保持一致
invoke-direct	用于调用private修饰的方法,或者构造方法
invoke-static	用于调用静态方法,比如一些工具类
invoke-interface	用于调用interface中的方法

判断操作:

指令	               说明
if-eq v1,v2	           判断两个寄存器中的值是否相等
if-ne v1,v2	           判断两个寄存器中的值是否不相等
if-lt v1,v2	           判断v1寄存器中的值是否小于v2寄存器中的值(lt == less than)
if-ge v1,v2	           判断v1寄存器中的值是否大于或等于v2寄存器中的值(ge == great 	           	           	           
                        than or equals)
if-gt v1,v2	           判断v1寄存器中的值是否大于v2寄存器中的值(gt == great than)
if-le v1,v2	           判断v1寄存器中的值是否小于或等于v2寄存器中的值(le == less than 
                        or equals)

属性操作:

属性操作的分为:取值(get)和赋值(put)

目标类型分为:数组(array)、实例(instance)和静态(static)三种,对应的缩写前缀就是a、i、s

长度类型分为:默认(什么都不写)、wide(宽,64位)、object(对象)、boolean、byte、char、short(后面几种就不解释了,和Java一致)

指令格式:[指令名] [源寄存器], [目标字段所在对象寄存器], [字段指针],示例代码如下,操作是为int型的类成员变量mIntA赋值为100:

const/16 v0, 0x64

iput v0, p0, Lcom/coderyuan/smali/MainActivity;->mIntA:I


指令	                       说明
iget	               取值,用于操作int这种的值类型
iget-wide	               取值,用于操作wide型字段
iget-object	               取值,用于操作对象引用
iget-boolean	       取值,用于操作布尔类型
iget-byte		       取值,用于操作字节类型
iget-char	               取值,用于操作字符类型
iget-short	               取值,用于操作short类型
iput	               赋值,用于操作int这种的值类型
iput-wide	               赋值,用于操作wide型字段
iput-object	               赋值,用于操作对象引用
iput-boolean	       赋值,用于操作布尔类型
iput-byte	               赋值,用于操作字节类型
iput-char	               赋值,用于操作字符类型
iput-short	               赋值,用于操作short类型

其中注意一个Smali文件对应的是一个Java.class文件。

热热身!例子!

在Idea插件中安装java2smali可以将java编译成smali文件,我们可以对比来学习。

###例子1:简单的赋值操作
java代码:简单的赋值操作

/**
 * @description:简单例子1赋值分析
 * @Author MRyan
 * @Date 2020/4/19 11:53
 * @Version 1.0
 */
public class text {
    public static void main(String[] args) {
        int a = 15;
        int b = 4;
        b = a;
        System.out.println(b);
    }
}

smali代码:

#静态函数main 参数String数组,函数返回值Void
.method public static main([Ljava/lang/String;)V
    #定义4个寄存器
    .registers 4
    #第一个 寄存器 相当于this
    .param p0, "args"

    .prologue
    .line 11
    #第二个寄存器 16字节的 声名常量15并赋值给寄存器v0  
    const/16 v0, 0xf

    .line 13
    #定义 int型的a变量指向寄存器v0  a=15的意思
    .local v0, "a":I
    #将V0寄存器的数值移动到v1寄存器中
    move v1, v0

    .line 14
    #定义 int型的b变量指向寄存器v1 现在b=15
    .local v1, "b":I

    #sget是对静态字段读操作
    sget-object v2, Ljava/lang/System;->out:Ljava/io/PrintStream;

    #调用方法
    invoke-virtual {v2, v1}, Ljava/io/PrintStream;->println(I)V

    .line 15
    #返回空
    return-void
.end method

###例子2:简单的条件分支分析
java代码:简单的条件分支分析

/**
 * @description:简单的条件分支
 * @Author MRyan
 * @Date 2020/3/28 11:53
 * @Version 1.0
 */
public class text {
    public static void main(String[] args) {
        boolean istrue=false;
        if(istrue){
            System.out.println("true");
        }else{
            System.out.println("false");
        }
    }
}

Smali代码:

.method public static main([Ljava/lang/String;)V
    #定义4个寄存器
    .registers 4
    .param p0, "args"    # [Ljava/lang/String;

    .prologue
    .line 11
    #声名常量0并赋值给v0寄存器 也就是false
    const/4 v0, 0x0

    .line 12
    #本地寄存器v0 名称istrue 数据类型boolean
    .local v0, "istrue":Z
    #如果寄存器中数值等于0 则跳转到cond_b
    if-eqz v0, :cond_b

    .line 13
    #符合条件分支执行
    sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream;

    const-string v2, "true"
    #输出true
    invoke-virtual {v1, v2}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V


    .line 17
    #跳转goto_a 结束
    :goto_a
    return-void

    .line 15
    #如果寄存器中数值等于0
    :cond_b
    sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream;

    const-string v2, "false"
     #输出false
    invoke-virtual {v1, v2}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V

    goto :goto_a
.end method

###例子3: 复杂的条件分支

/**
 * @description:复杂的条件分支
 * @Author MRyan
 * @Date 2020/3/28 12:05
 * @Version 1.0
 */
public class text {
    public static void main(String[] args) {
        int a = 0;
        if (a == 1) {
            System.out.println("1");
        } else if (a == 2) {
            System.out.println("2");
        } else if (a == 0) {
            System.out.println("0");
        }
    }
}

Smali代码:

.method public static main([Ljava/lang/String;)V
    #定义四个寄存器
    .registers 4
    .param p0, "args"    # [Ljava/lang/String;

    .prologue
    .line 11
    #声名常量0并赋值给v0寄存器 数值为0
    const/4 v0, 0x0

    .line 12
    #本地寄存器v0 为int类型 名称为a
    .local v0, "a":I
    #声名常量1赋值给寄存器v1 数值为1
    const/4 v1, 0x1

    #如果v0,v1不相等(也就是a不等于1)就跳转cond_c
    if-ne v0, v1, :cond_c

    .line 13
    #其他分支 v0和v1相等 (也就是a等于1)
    sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream;
    #常量1赋值给寄存器v2
    const-string v2, "1"
    #输出1
    invoke-virtual {v1, v2}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V

    .line 19
    #结束
    :cond_b
    :goto_b
    return-void

    .line 14
    #如果v0,v1不相等(也就是a不等于1)
    :cond_c
   #声明变量2赋值给寄存器v1
    const/4 v1, 0x2
    #如果寄存器v0数值不等于寄存器v1数值 也就是(a不等于2) 跳转 cond_17
    if-ne v0, v1, :cond_17

    .line 15
    #其他分支 如果寄存器v0数值等于寄存器v1数值 也就是(a等于2)
    sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream;

    const-string v2, "2"
    #输出2
    invoke-virtual {v1, v2}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
    #返回
    goto :goto_b

    .line 16
    :cond_17
    #如果寄存器v0中的值不等于0 (也就是a不等于0) 执行 cond_b
    if-nez v0, :cond_b

    .line 17
    #如果寄存器v0中的值等于0 (也就是a等于0)
    sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream;

    const-string v2, "0"
    #输出0
    invoke-virtual {v1, v2}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
    #结束
    goto :goto_b
.end method

猜你喜欢

转载自blog.csdn.net/qq_35416214/article/details/105880046