Android——Smali语法

安卓系统里使用的是Java虚拟机(Dalvik),Dalvik虚拟机和Jvm一样,也有自己的一套指令集,类似汇编语言。smali文件就是Dalvik的寄存器语言,被广泛地用于 APP 广告注入、汉化和破解,ROM 定制等方面。
利用Android Killer,反编译classes.dex文件,就可以得到以smali为后缀的文件。


Android开发是使用Java语言来开发的,编写的源码文件.java(MainActivity.java、Login.java)——> 编译后生成.dex文件 ——> 反编译.dex文件生成.smali文件
在这里插入图片描述

一、寄存器

Android 的变量都存储于寄存器中。变量分为 v 与 p 两种格式
对于dalviks字节码寄存器都是32位的,它能够表示任何类型,2个寄存器用于表示64位的类型(Long and Double)

  1. v 变量表示方法中非参数变量。
    在这里插入图片描述
  2. p 变量表示方法中参数变量。
    如果方法是非静态方法,p0 表示 this
    如果方法是静态方法,p0 表示第一个参数
    在这里插入图片描述

二、数据类型

基本数据类型
在这里插入图片描述
数组类型
[.... --- array

在基本类型前加上前中括号“[”即表示该类型的数组,如: [B 表示byte数组 [i 表示int数组

对象类型的数组为”[“加上对象类型表示符来表示,如String类型表示为:[Ljava/lang/String

对象类型
L ..../....; --- object

如果是对象类型,则以L作为开头,格式是LpackageName / objectName;packageName/表示该对象所在的包,ObjectName 是对象的名字,“;” 表示对象名称的结束。相当于 java 中的 packageName.ObjectName。

如:Ljava/lang/String; :表示 String 对象。其中 java/lang 对应 java.lang 包, String 是该包的一个对象。类对象中的内部类则使用“$”来连接

三、Smali文件关键词

Smali文件与java中的类是一一对应的,包括内部类和匿名内部类也会生成对应的smali文件


在这里插入图片描述
.registers 和 .locals 基本区别
指令.registers指定了在这个方法中有多少个可用的寄存器(包括存方法参数的寄存器与非参寄存器)
指令.locals指明了在这个方法中非参(non-parameter) 寄存器的数量

Smali类的格式

1. 头文件

.class <访问权限修饰符> [非权限修饰符] <类名>
.super <父类名>
.source <源文件名称>

访问权限修饰符即public,protected,private,非权限修饰符指的是final,abstract,static,两者都可以为空。
在这里插入图片描述
是android.support.v4.util.LogWriter这个package下的一个类(第1行)

继承自java.io.Writer这个类(第2行)

这是一个由SourceFile.java编译得到的smali文件(第3行)

2. 接口实现

#interfaces
.implements <接口名称>

其中# interfaces为注释
在这里插入图片描述

3. 变量定义

静态变量

#static fields
.field <访问权限> static [修饰词] <字段名>:<变量类型>
.field <访问权限> static final <常量名>:<常量类型> = 常量值

在这里插入图片描述
普通变量

#instance fields
.field <访问权限修饰符> [非权限修饰符] <变量名>:<变量类型>

在这里插入图片描述

4. 方法定义

smali中的方法以 .method/.end method 进行描述,有分两种方法,一种是直接方法,一种是虚方法

直接方法就是不能被覆写的方法,包括用static,private修饰的方法

虚方法表示可以被覆写的方法,包括public,protected修饰的方法。

两者在smali中的注释分别是直接方法(#direct methods),虚方法(#virtual methods),一般直接方法在smali文件的前半部分,虚方法在后半部分。

#direct methods/#virtual methods
.method <访问权限修饰符> [非访问权限修饰符] <方法名> (Para-Type1Para-Type2Para-Type3...)Return-Type
      <.locals>
      [.parameter]
      [.prologue]
      [.line]     #对应Java源码的一行代码
      <代码逻辑>
.end method

其中.parameter,.prologue,.line是可选的。

参数与参数之间没有任何的间隔,对象后面有分号
在这里插入图片描述
构造方法

.method <访问权限修饰符> [非访问权限修饰符] constructor <init>(Para-Type1Para-Type2Para-Type3...) Return-Type

在这里插入图片描述
静态代码块

.method <访问权限修饰符> static constructor <clinit>()V

静态变量的初始化,也必须在clinit内执行
在这里插入图片描述

5. 注解

#annotations
.annotation [注解的属性] <注解类名>
    [注解字段=]
    ...
.end annotation

其中# annotations为注释
在这里插入图片描述

四、方法描述

方法对应的类(全包名路径)->方法名(参数类型描述符)返回值类型描述符;

在这里插入图片描述

五、变量描述

变量对应的类(全包名路径)->变量名:类型描述符**;**

Dalvik对变量的描述会指明变量定义的和变量的类型
在这里插入图片描述

六、常见Dalvik指令集

1. 方法调用指令

在这里插入图片描述

invoke-xxxxx {参数}, 方法所属类(全包名路径);->方法名称(方法参数描述符)方法返回值类型描述符

xxxxx 为direct,virtual,static,super,interface其中一种

在这里插入图片描述

.class public LTest;	#声明类
.super Ljava/lang/Object;	#声明父类

.method public constructor <init>(Ljava/lang/String;)v

	invoke-direct {p0}, Ljava/lang/Object;-><init>()v	#调用父类构造方法
	invoke-virtual {p0}, LTest;->getName()Ljava/lang/String;	#调用getName方法
	
	return-void
.end method

#声明getName方法
.method public getName()Ljava/lang/String;
	
	const-String v0,"hello"	 #定义局部字符常量
	
	return-object v0	#返回常量
.end method

在这里插入图片描述

.class public LTest;	#声明类
.super Ljava/lang/Object;	#声明父类

.method public constructor <init>(Ljava/lang/String;)v

	invoke-direct {p0}, Ljava/lang/Object;-><init>()v	#调用父类构造方法
	invoke-static {}, LTest;->getName()Ljava/lang/String;	#调用getName方法
	move-result-object v0	#将返回值赋给v0
	
	return-void
.end method

#声明getName方法
.method public getName()Ljava/lang/String;
	
	const-String v0,"hello"	 #定义局部字符常量
	
	return-object v0	#返回常量
.end method

invoke-static后的{ }里不需要p0,因为静态方法的调用不需要传入this实例
在这里插入图片描述

.method protected onCreate(Landroid/os/Bundle)V
	.register 2
	
	invoke-super {p0, p1}, Landroid/app/Activity;->onCreate(Landroid/os/Bundle;)V
	
	return-void
.end method

2. 方法返回指令

在这里插入图片描述
在这里插入图片描述

3. 创建对象指令

#声明实例
new-instance +变量名,对象全包名路径;

#调用构造方法 (如果构造方法内还定义了成员变量,那么在调用之前需要提前声明,然后在invoke的时候当做参数一并传入)
invoke-direct {变量名},对象全包名路径;-><init>(方法参数描述符)方法返回值类型描述符

在这里插入图片描述

4. 空操作指令

空操作指令的助记符为nop,它的值为00,通常nop指令被用来作对齐代码之用,无实际操作。
在这里插入图片描述

5. 数据定义指令

数据定义指令用于定义代码中使用的常量、字符串、类等数据,基础字节码是const
在这里插入图片描述
字符串数据

const-string 变量名,”字符串”

在这里插入图片描述
字节码数据

const-class 变量名,字节码对象;

在这里插入图片描述
在这里插入图片描述
数值类型数据(默认定义最高位为符号位)

#占用一个寄存器(32位)
const/4 变量名,#十六进制数

const/16 变量名,#十六进制数

const 变量名,#十六进制数

const/high16 变量名,#十六进制数

#占用相邻两个容器(64位)
const-wide 变量名,#十六进制数

const-wide/16 变量名,#十六进制数

const-wide/32 变量名,#十六进制数

const-wide/high16 变量名,#十六进制数

注意

  1. const-wide只用写出一个寄存器,另一个寄存器默认为其下一个,即const-wide vx,寄存器为 vx 与 v(x+1)
    如const-wide v2,#FF763D33,其寄存器为 v2 和 v3
  2. 在使用const/high16时,数值补齐32位,即十六进制8位,不足的末尾+0
    如用const/ high16给 v0 赋0xFF7F,代码为const/ high16 v0,0xFF7F0000(补满8位)只取最高16位,即将 #0xFF7F 赋给v0

6.变量操作指令

用于将源寄存器的值移动到目标寄存器中,此类操作常用于赋值。


基础格式move 目标寄存器 源寄存器
在这里插入图片描述

7.变量操作指令

字段操作指令表示对对象字段进行赋值和取值操作,就像Java代码中的set和get方法,基本指令是iput-type,iget-type,sput-type,sget-type.type表示数据类型。


普通字段读写操作
前缀是i的iput-type和iget-type指令用于字段的读写操作
在这里插入图片描述


  • 取值
    iget vA,vB,filed_id 读取vA寄存器中的对象中的filed_id的值给vB寄存器,用于操作int类型(float类型)
    在这里插入图片描述
    iget-wide vA,vB,filed_id 读取vA寄存器中的对象中的filed_id的值给vB寄存器,用于操作wide型字段在这里插入图片描述
    iget-boolean vA,vB,filed_id 读取vA寄存器中的对象中的filed_id的值给vB寄存器,用于操作布尔类型
    在这里插入图片描述
    iget-byte vA,vB,filed_id 读取vA寄存器中的对象中的filed_id的值给vB寄存器,用于操作字节类型
    在这里插入图片描述
    iget-char vA,vB,filed_id 读取vA寄存器中的对象中的filed_id的值给vB寄存器,用于操作字符类型
    在这里插入图片描述
    iget-object vA,vB,filed_id 读取vA寄存器中的对象中的filed_id对象的引用值给vB寄存器,用于操作对象引用
    在这里插入图片描述
    iget-short vA,vB,filed_id 读取vA寄存器中的对象中的filed_id的值给vB寄存器,用于操作short类型

    赋值

    iput vA,vB,filed_id 把vA寄存器的值给vB寄存器中的int类型(float类型)在这里插入图片描述
    iput-wide vA,vB,filed_id 把vA寄存器的值给vB寄存器中的wide类型
    在这里插入图片描述
    iput-boolean vA,vB,filed_id 把vA寄存器的值给vB寄存器中的boolean类型
    在这里插入图片描述
    iput-byte vA,vB,filed_id 把vA寄存器的值给vB寄存器中的字节类型
    在这里插入图片描述
    iput-char vA,vB,filed_id 把vA寄存器的值给vB寄存器中的字符类型
    在这里插入图片描述
    iput-object vA,vB,filed_id 把vA寄存器指向的对象的引用赋值给vB寄存器中的filed_id对象
    在这里插入图片描述
    iput-short vA,vB,filed_id 把vA寄存器的值给vB寄存器中的short类型

静态字段读写操作

前缀是s的sput-type和sget-type指令用于静态字段的读写操作(操作静态变量。因此没有目标对象)
在这里插入图片描述

8. 数据运算指令

数据运算主要包括:算数运算、逻辑运算、位运算


算术运算指令
在这里插入图片描述
逻辑运算指令
在这里插入图片描述
移位运算
在这里插入图片描述

9. 跳转指令

跳转指令用于从当前地址跳转到指定的偏移处。Dalvik指令集中有三种跳转指令:无条件跳转(goto),分支跳转(switch)与条件跳转(if)


无条件跳转指令
goto :goto_N 无条件跳转到指定偏移处
在这里插入图片描述
条件跳转指令
在这里插入图片描述


  • if-eq vA, vB, : cond_** 如果vA等于vB,则跳转到:cond_** ,Java语法表示为“if(vA == vB)”
    在这里插入图片描述
    if-ne vA, vB, :cond_** 如果vA不等于vB,则跳转到:cond_** ,Java语法表示为“if(vA != vB)”
    在这里插入图片描述
    if-le vA, vB, :cond_** 如果vA小于等于vB,则跳转到:cond_** ,Java语法表示为“if(vA <= vB)”
    在这里插入图片描述
    if-lt vA, vB, :cond_** 如果vA小于vB,则跳转到:cond_** ,Java语法表示为“if(vA < vB)”
    在这里插入图片描述
    if-ge vA, vB, :cond_** 如果vA大于等于vB,则跳转到:cond_** ,Java语法表示为“if(vA >= vB)”
    在这里插入图片描述
    if-gt vA, vB, :cond_** 如果vA大于vB,则跳转到:cond_** ,Java语法表示为“if(vA > vB)”
    在这里插入图片描述
    if-eqz vA, :cond_** 如果vA等于0,则跳转到:cond_** ,Java语法表示为“if(vA == 0)”
    在这里插入图片描述
    if-nez vA, :cond_** 如果vA不等于0,则跳转到:cond_** ,Java语法表示为“if(vA != 0)”
    在这里插入图片描述
    if-ltz vA, :cond_** 如果vA小于0,则跳转到:cond_** ,Java语法表示为“if(vA < 0)”
    在这里插入图片描述
    if-lez vA, :cond_** 如果vA小于等于0,则跳转到:cond_** ,Java语法表示为“if(vA <= 0)”
    在这里插入图片描述
    if-gtz vA, :cond_** 如果vA大于0,则跳转到:cond_** ,Java语法表示为“if(vA > 0)”
    在这里插入图片描述
    if-gez vA, :cond_** 如果vA大于等于0,则跳转到:cond_** ,Java语法表示为“if(vA >= 0)
    在这里插入图片描述

10. 比较指令

基础格式:cmp 目标寄存器 va, vb
在这里插入图片描述
xxx可以表示为 float 或 double,后跟 -float 表示比较两个 float 型数据。后跟 -double 表示比较两个 double 型数据。

在这里插入图片描述

11. 数据转换指令

基础格式xxxx vA, vB 表示对vB寄存器的中值进行操作,并将结果保存在vA寄存器中
在这里插入图片描述

12. 异常指令

在这里插入图片描述


版权声明:本文为dcmbpg原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。