smail语法
学smail去反编译分析代码?(别干坏事)
语法
基本数据类型
| 类型关键字 | 对应Java中的类型说明 |
|---|---|
| V | void,只能用于返回类型 |
| Z | boolean |
| B | byte |
| S | short |
| C | char |
| I | int |
| J | long (64 bits) |
| F | float |
| D | double (64 bits) |
注意了,布尔值对应的字母为Z,long对应的字母为J,其他的都是首字母大写
对象
Object类型,即引用类型的对象,在引用时,使用L开头,后面紧接着的是完整的包名,比如:java.lang.String对应的Smali语法则是 Ljava/lang/String
数组
数组定义比较有意思,一维数组在类型的左边加一个方括号,比如:[I等同于Java的 int[],每多一维就加一个方括号,[[I就等同于Java的int[][],最多可以设置255维
方法声明和调用
官方Wiki中给出的Smali引用方法的模板如下:
Lpackage/name/ObjectName;->MethodName(III)Z
第一部分 Lpackage/name/ObjectName;用于声明具体的类型,以便JVM寻找
第二部分 MethodName(III)Z,其中MethodName为具体的方法名,()中的字符,表示了参数数量和类型,即3个int型参数,Z为返回值的类型,即返回Boolean类型
由于方法的参数列表没有使用逗号这样的分隔符进行划分,所以只能从左到右,根据类型定义来区分参数个数,这一点需要比较仔细来观察
如果需要调用构造方法,则MethodName为:<init>
寄存器声明
在Smali中,如果需要存储变量,必须先声明足够数量的寄存器,1个寄存器可以存储32位长度的类型,比如Int,而两个寄存器可以存储64位长度类型的数据,比如Long或Double, String类型实际上是一个对象,只需要一个寄存器
声明可使用的寄存器数量的方式为:.registers N,N代表需要的寄存器的总个数
.registers 3表示声明3个寄存器
还有一个关键字
.locals,它用于声明非参数的寄存器个数(包含在registers声明的个数当中),也叫做本地寄存器,只在一个方法内有效,但不常用,一般使用registers即可
寄存器有两种命名方式,V命名方式和P命名方法。P命名方式中的第一个寄存器就是方法中的第一个参数寄存器,在非static方法中有个this指针需要一个寄存器,一般为p0,则p1为第一个参数,依次类推,v0,v1,…为自己定义的本地寄存器
1 | .method static test(I)V |
再举例,如果一个Java方法声明如下:myMethod(int p1, float p2, boolean p3)Z
那么对应的Smali则为:method LMyObject;->myMethod(IJZ)V
此时,寄存器的对应情况如下:
| 寄存器名称 | 对应的引用 |
|---|---|
| p0 | this |
| p1 | int型的p1参数 |
| p2, p3 | float型的p2参数 |
| p4 | boolean型的p3参数 |
那么最少需要的寄存器个数则为:5
指令集
1. 赋值
| 指令 | 说明 |
|---|---|
| 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语句中的异常 |
2.返回值
| 指令 | 说明 |
|---|---|
| return-void | 返回void,即直接返回 |
| return v1 | 返回v1寄存器中的值 |
| return-object v1 | 返回v1寄存器中的对象指针 |
| return-wide v1 | 返回双字型结果给v1寄存器 |
3.常量操作
| 指令 | 说明 |
|---|---|
| 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 |
4. 方法调用
| 指令 | 说明 |
|---|---|
| invoke-virtual | 用于调用一般的,非private、非static、非final、非构造函数的方法,它的第一个参数往往会传p0,也就是this指针 |
| invoke-super | 用于调用父类中的方法,其他和invoke-virtual保持一致 |
| invoke-direct | 用于调用private修饰的方法,或者构造方法 |
| invoke-static | 用于调用静态方法,比如一些工具类 |
| invoke-interface | 用于调用interface中的方法 |
5.if语句
模板是 if-[test] v0,v1 [condition],test如下,condition为条件满足后的语句,一般会是跳转,另外需要注意的是,Java判断和smail判断一般是相反的
if-le v0, v1, :cond_7翻译过来就是 if(v0<=v1):{:cond_7}
if-ez v0 :cond_4翻译过来是 if(v0==0){:cond_4}
| 指令 | 说明 |
|---|---|
| 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) |
if-ez v1 表示 if(v1==0),其他的 nez,ltz,gtz,lez等等都是与零比较(z= zero)
6.取值赋值
属性操作的分为:取值(get)和赋值(put)
目标类型分为:数组(array)、实例(instance)和静态(static)三种,对应的缩写前缀就是a、i、s
长度类型分为:默认(什么都不写)、wide(宽,64位)、object(对象)、boolean、byte、char、short(后面几种就不解释了,和Java一致)
指令格式:[指令名] [源寄存器], [目标字段所在对象寄存器], [字段指针] 例如 int mIntA = 100 :
1 | const/4 v0, 0x64 |
以下是一些指令
| 指令 | 说明 |
|---|---|
| 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类型 |
例如
1 | private String mStringA; |
1 |
|