文章目录
安卓系统里使用的是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)
- v 变量表示方法中非参数变量。
- 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 变量名,#十六进制数
注意
- const-wide只用写出一个寄存器,另一个寄存器默认为其下一个,即const-wide vx,寄存器为 vx 与 v(x+1)
如const-wide v2,#FF763D33,其寄存器为 v2 和 v3 - 在使用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寄存器中