Dalvik虚拟机解释语言:Smali
Smali与Baksmali介绍
- Dalvik是冰岛的一个渔村,在冰岛语中,Smali表示汇编,Baksmali表示反汇编
- Smali语法大致基于Jasmin与Dedexer的语法
- Smali语法用来标记Dalvik字节码,并支持dex格式的全部功能,如注释、调试信息、行信息等
- Smali对应Java的smali文件转化成dex文件的汇编器
- Baksmali对应Java的dex文件转化成smali文件的反汇编器
什么是JVM、Dalvik、ART
- JVM是JAVA虚拟机,运行JAVA字节码程序
- Dalvik是Google专门为Android设计的一个虚拟机,Dalvik有专属的文件执行格式dex(Dalvik executable)
- Art(Android Runtime)相当于Dalvik的升级版,本质与Dalvik无异
类型
Smali类型 | Java类型 |
---|---|
V | void |
Z | boolean |
B | byte |
S | short |
C | char |
I | int |
J | long (64 bits) |
F | float |
D | double (64 bits) |
L | 对象类型 |
[ | 数组类型 |
方法返回关键字
主要有四种方法:
- return-void
- return-object
- return
- return-wide
使用对应关系表
数据类型 | Smali对应的方法返回关键字 |
---|---|
V | return-void |
Z | return |
B | return |
S | reutrn |
C | return |
I | reutrn |
J | return-wide |
F | return |
D | return-wide |
L | return-object |
[ | return-object |
方法调用关键字
- invoke-virtual: 用于非私有实例方法的调用
- invoke-direct: 用于构造方法以及私有方法的调用
- invoke-static: 静态方法的调用
- invoke-super: 父类方法的调用
- invoke-interface: 接口方法的调用
invoke-virtual
调用方法:invoke-virtual {参数}, 方法所属类名;->方法名(参数类型)返回值类型;
Java代码:
public class Test {
public Test(string a) {
getName()
}
public String getName() {
return "Hello"
}
}
对应Smali语法:
.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; # 调用成员方法
return-void
.end method
.method public getName()Ljava/lang/String;
const-string v0, "Hello" # 定义局部字符串常量
return-object v0 # 返回常量
.end method
invoke-direct
调用方法:invoke-direct {参数}, 方法所属类名;->方法名(参数类型)返回值类型;
Java代码:
public class Test {
public Test(string a) {
getName()
}
private String getName() {
return "Hello"
}
}
对应Smali语法:
.class public LTest; # 申明类
.super Ljava/lang/Object; # 申明父类
.method public constructor <init>(Ljava/lang/String;)V
invoke-direct {p0}, Ljava/lang/Object;-><init>()V # 调用父类构造方法
invoke-direct {p0}, LTest;->getName()Ljava/lang/String; # 调用成员方法
return-void
.end method
.method public getName()Ljava/lang/String;
const-string v0, "Hello" # 定义局部字符串常量
return-object v0 # 返回常量
.end method
invoke-static
调用方法:invoke-static {参数}, 方法所属类名;->方法名(参数类型)返回值类型;
Java代码:
public class Test {
public Test(string a) {
String b = getName()
System.out.print(b)
}
private static String getName() {
return "Hello"
}
}
对应Smali语法:
.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 {p0}, LTest;->getName()Ljava/lang/String; # 调用成员方法
move-result-object v0 # 将返回值赋值给v0
return-void
.end method
.method public getName()Ljava/lang/String;
const-string v0, "Hello" # 定义局部字符串常量
return-object v0 # 返回常量
.end method
invoke-super
调用方法:invoke-super {参数}, 方法所属类名;->方法名(参数类型)返回值类型;
Java代码:
@override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
对应Smali语法:
.class public LTest; # 申明类
.super Ljava/lang/Object; # 申明父类
.method protected onCreate(Landroid/os/Boundle;)V
.registers 2
invoke-super {p0, p1}, Landroid/app/Activity;->onCreate(Landroid/os/Boundle;)V
return-void
.end method
invoke-interface
调用方法:invoke-interface {参数}, 方法所属类名;->方法名(参数类型)返回值类型;
Java代码:
public class Test {
private InterTest a = new Test2()
public Test(string a) {}
public void setAa() {
InterTest aa = a
aa.est2()
}
public class Test2 implements InterTest {
public Test2(){}
public void est2(){}
}
interface InterTest {
public void est2();
}
}
对应Smali语法:
.class public LTest; # 申明类
.super Ljava/lang/Object; # 申明父类
.method public constructor <init>(Ljava/lang/String;)V
invoke-direct {p0}, Ljava/lang/Object;-><init>()V
return-void
.end method
.method public setAa()V
.registers 2
iget-object v0, p0, LTest;->a:LTest$InterTest;
invoke-interface {v0}, LTest$InterTest;->est2()V
return-void
.end method
Smali的声明语法
类申明
声明语法:.class 权限修饰符 类名
Java代码:
public class Test implements CharSequence {
}
对应Smali语法:
.class public LTest; # 申明类
.super Ljava/lang/Object; # 申明父类
.implements Ljava/lang/CharSequence; # 对应的接口代码
.source "Test.java" # 对应的源文件, 非必须
字段申明
声明语法:.field 权限修饰符 静态修饰符 变量名:变量全类名路径
Java代码:
public class Test {
private static String a;
}
对应Smali语法:
.class public LTest; # 申明类
.super Ljava/lang/Object; # 申明父类
.field private static a:Ljava/lang/String;
常量申明
声明语法:.field 权限修饰符 静态修饰符 final 变量名:变量全类名路径 = 常量值
Java代码:
public class Test {
private static final String a = "hello";
}
对应Smali语法:
.class public LTest; # 申明类
.super Ljava/lang/Object; # 申明父类
.field private static final a:Ljava/lang/String; = "Hello"
方法/函数申明
声明语法:
.method 权限修饰符 静态修饰符 方法名(参数)返回值类型
# 方法体
.end method # 方法结尾标识
不带参数不带返回值
Java代码:
public class Test {
public static void getName() {}
}
对应Smali语法:
.class public LTest; # 申明类
.super Ljava/lang/Object; # 申明父类
.method public static getName()V
return-void
.end method
带参数带返回值
Java代码:
public class Test {
public static void getName(String a, Int b) {
return "Hello"
}
}
对应Smali语法:
.class public LTest; # 申明类
.super Ljava/lang/Object; # 申明父类
.method public static getName(Ljava/lang/String; Ijava/lang/Int)V
const-string v0, "Hello"
return-object v0
.end method
构造方法/构造函数申明
声明语法:
.method 权限修饰符 constructor <init>(参数)返回值类型
# 方法体
.end method # 方法结尾标识
Java代码:
public class Test {
public Test() {}
}
对应Smali语法:
.class public LTest; # 申明类
.super Ljava/lang/Object; # 申明父类
.method public constructor <init>(Ljava/lang/String;)V
invoke-direct {p0}, Ljava/lang/Object;-><init>()V # 调用父类的构造方法
return-void
.end method
静态代码块的申明
声明语法:
.method static constructor <clinit>()V
# 方法体
.end method # 方法结尾标识
Java代码:
public class Test {
static{}
}
对应Smali语法:
.class public LTest; # 申明类
.super Ljava/lang/Object; # 申明父类
.method public static constructor <clinit>()V
return-void
.end method
创建对象
对象的创建分两步:
- 声明实例:
new-instance 变量名, 对象全包名路径;
- 调用构造方法:
invoke-direct {变量名}, 对象全包名路径;-><init>(参数)返回类型
Java代码:
class Test{}
new Test()
对应Smali语法:
new-instance v0, LTest;
invoke-direct {v0}, LTest;-><init>()V
数据定义
关键词 | 含义 | 示例 |
---|---|---|
const-string | 用于定义字符串 | const-string v0, "Hello" | |
const-class | 定义字节码对象 | const-class v0, LGoActivity; |
const/4 | 定义一个容器,最大存放4字节数值 | const/4 v0, 0x2 |
const/16 | 定义一个容器,最大存放16字节数值 | const/16 v0, 0xABCD |
const/hight16 | 定义一个容器,最大存放高16字节数值 | const v0, 0xA |
const | 定义一个容器,最大存放32字节数值 | const v0, 0xA |
const-wide | 定义两个相连容器,最大存放64字节数值 | - |
const-wide/16 | 定义两个相连容器,最大存放16字节数值 | - |
const-wide/32 | 定义两个相连容器,最大存放32字节数值 | - |
const-wide/hight16 | 定义两个相连容器,最大存放高16字节数值 | - |
取值与赋值
取值使用iget/sget,赋值使用iput/sput,其中i表示instance(实例)用于实例属性,s表示static(静态)用于静态属性
Smali类型 | 取值 | 赋值 |
---|---|---|
Z | iget-boolean | iput-boolean |
B | iget-byte | iput-byte |
S | iget-short | iput-short |
C | iget-char | iput-char |
I | iget | iput |
J | iget-wide | iput-wide |
F | iget | iput |
D | iget-wide | iput-wide |
条件判断与跳转
关键词 | 英文助记 | 举例 | 含义 |
---|---|---|---|
if-eq | equal | if-eq v0, v1, :cond_* | 如果v0等于v1则跳转到cond_* |
if-ne | not equal | if-ne v0, v1, :cond_* | 如果v0不等于v1则跳转到cond_* |
if-lt | less than | if-lt v0, v1, :cond_* | 如果v0小于v1则跳转到cond_* |
if-le | less equal | if-le v0, v1, :cond_* | 如果v0小于等于v1则跳转到cond_* |
if-gt | greater then | if-gt v0, v1, :cond_* | 如果v0大于v1则跳转到cond_* |
if-ge | greater equal | if-ge v0, v1, :cond_* | 如果v0大于等于v1则跳转到cond_* |
if-eqz | equal zero | if-eqz v0, :cond_* | 如果v0等于0则跳转到cond_* |
if-nez | not equal zero | if-nez v0, :cond_* | 如果v0不等于0则跳转到cond_* |
if-ltz | less than zero | if-ltz v0, :cond_* | 如果v0小于0则跳转到cond_* |
if-lez | less equal zero | if-lez v0, :cond_* | 如果v0小于等于0则跳转到cond_* |
if-gtz | greater then zero | if-gtz v0, :cond_* | 如果v0大于0则跳转到cond_* |
if-gez | greater equal zero | if-gez v0, :cond_* | 如果v0大于等于0则跳转到cond_* |
寄存器
Dalvik 使用的寄存器是32位的,对于64位类型的变量,如double类型,它会使用两个相邻的寄存器来表示.
Dalvik最多支持65536个寄存器(编号从0~65535),但是ARM架构的cpu中只有37个寄存器, 每个Dalvik虚拟机维护了一个调用栈,该调用栈用来支持虚拟寄存器和真实寄存器相互映射,从而支持65536个寄存器的调用.
函数中必须使用.registers
或.locals
声明寄存器数量,其中.registers
表示参数寄存器和局部变量寄存器数量之和,.locals
表示局部寄存器的数量.
申请的寄存器用于参数和局部变量的存储,一般p命名的寄存器用于参数,v命名的寄存器用于局部变量, 如果仅使用v命名法则需要注意,参数的命名需要在局部变量的命名之后命名,如假设有2个局部变量,3个参数变量:
声明方式 | 第1个局部变量 | 第2个局部变量 | 第1个参数 | 第2个参数 | 第3个参数 |
---|---|---|---|---|---|
.registers | v0 | v1 | p0 | p1 | p2 |
.locals | v0 | v1 | v2 | v3 | v4 |
常用关键字
关键字 | 作用 | 示例 |
---|---|---|
.line | 表示源码中的行号 | |
:cond_N | 条件分支配合if使用 | |
:goto_N | goto跳转分支,配合goto关键字使用 | |
.prologue | 表示程序的开始 | |
.local | 局部变量设置别名 | .local v0, "b":Ljava/lang/String; |
.param | 参数变量设置别名 | .local p0, "b":Ljava/lang/String; |
参考
- 胖薯code的Smali语言入门到精通:https://www.bilibili.com/video/BV1Vf4y1q7gh 🔗
- 正己:https://www.52pojie.cn/thread-1701353-1-1.html 🔗
- CTF wiki: https://ctf-wiki.org/android/basic_operating_mechanism/java_layer/smali/smali/#smali 🔗