Android代码在 Dalvik虚拟机 运行
baksmali 是安卓系统中Dalvik中所用的汇编器 和 反汇编器
dex文件 反汇编后得到 smali文件
smali代码拥有自己特定的格式语法
smali目前在Google code是一个开源项目,被广泛的用于广告注入、汉化、破解等方面
java 数据类型 | Type descriptor 字节码类型描述符 |
---|---|
int | I |
float | F |
double | D |
boolean | Z |
char | C |
byte | B |
short | S |
long | J |
void | V |
java 对象类型 | Type descriptor 字节码类型描述符 |
---|---|
package.name.Object | Lpackage/name/Object; |
L表示 对象类型,这是一个对象 | |
xxx.yyy.zzz 变成 xxx/yyy/zzz 格式的目录层级结构 | |
; 表示对象名称结束 |
String字符串本身是个对象
java 数据类型 | Type descriptor 字节码类型描述符 |
---|---|
int [ ] | [ I |
object [ ] [ ] | [ [ Ljava/lang/Object |
#声明了三个不同类型的二维数组
.field private double_int:[[I
.field private double_obj:[[Ljava/lang/Object;
.field private double_string:[[Ljava/lang/String;
方法在smali中以.method 指令声明
# direct methods
.method <访问权限> [关键字] 方法名(参数)返回值类型
.locals 指定局部变量的个数
.param 方法的参数
.line 代码在原文件中的行号
.end 结尾
Lcom/example/hello/MainActivity;->methodName(IZD)Z
java 中字段的表现形式 | smali 中字段的表现形式 |
---|---|
private objectType fieldName; | .field fieldName:objectType |
# instance fields 实例字段
.field <访问权限> [修饰关键字] <字段名> : 类型
# static field 静态字段
.field <访问权限> static [修饰关键字] <字段名> : 类型
文件格式:
无论是 普通类、抽象类、接口类、内部类,反编译是,都会用单独的smali文件存放。
java中:
smali中:
.class <访问权限> [修饰关键字] <类名称>
.super <父类>
.source <源文件名称>
每个smali文件开头都是如下三行:
.class public Lcom/example/hello/MainActivity;
.super Landroidx/appcompat/app/AppCompatActivity;
.source "MainActivity.java"
在Dalvil虚拟机字节码中,寄存器有两种命名方式:
v 命名法 | p 命名法 | 寄存器含义 |
---|---|---|
v0 | v0 | 第一个局部变量寄存器 |
v1 | v1 | 第二个局部变量寄存器 |
…… | …… | 中间的局部变量寄存器依次递增且名称相同 |
vM-N | p0 | 第一个参数寄存器 |
…… | …… | 中间的参数寄存器分别依次递增 |
vM-1 | pN-1 | 第N局部变量寄存器 |
比较v命名法与p命名法:
v 命名法 | p 命名法 |
---|---|
以 .registers开头 | 以 .limit开头 |
以 v2 作为this的引用 | 以 p0 作为this的引用 |
以M-N命名N 个参数的寄存器 | 用 p 命名法 |
v 指向寄存器
p 一般是某个java原有的代码有参数,或者指向this的或者直接变量
Dalvil参数传递规则:如果一个函数使用了M个寄存器、拥有N个参数,则参数使用最后N个寄存器,局部变量从v0开始一直递增到前M-N个(被传入的“隐藏”的对象引用this)
指令助记符 | 指令功能描述 |
---|---|
nop | 代码对齐,无操作 |
指令基本格式:[op]-[type](可选)/[位宽,默认4位] [目标寄存器],[源寄存器](可选)
指令助记符 | 指令功能描述 |
---|---|
move v0,v1 | 将v1寄存器的数据赋值给v0寄存器,非对象类型 |
move-object v0,v1 | 将v1寄存器的数据赋值给v0寄存器,对象类型 |
move-wide v0,v1 | 将寄存器对v1中的值移入到v0寄存器对中 |
move-result v1 | 将这个指令的上一条指令计算结果,移入到v1寄存器中(需要配合invoke-static、invoke-virtual等指令使用) |
move-result-object v1 | 将上条计算结果的对象指针移入v1寄存器(多用于函数的返回值) |
move-result-wide v1 | 将上条计算结果(双字)的对象指针移入v1寄存器 |
move-exception v1 | 将异常移入v1寄存器,用于捕获try-catch语句中的异常 |
指令助记符 | 指令功能描述 |
---|---|
return-void | 返回void |
return v1 | 返回v1寄存器中的值 |
return-object v1 | 返回v1寄存器中的对象指针 |
return-void | 返回void |
指令助记符 | 指令功能描述 |
---|---|
const/4 vA,#+B | 将4位宽度的立即数 带符号 扩展到32位,赋值到vA寄存器 |
const/16 vA,#+BBBB | 将16位宽度的立即数 带符号 扩展到32位,赋值到vA寄存器 |
const vA,#+BBBBBBBB | 将32位宽度的立即数 赋值到vA寄存器 |
const wide/16 vA,#+BBBB | 将16位宽度的立即数 带符号 扩展64位,赋值到vA寄存器 |
const wide/32 vA,#+BBBB | 将32位宽度的立即数 带符号 扩展到64位,赋值到vA寄存器 |
const wide vA,#+BBBBBB | 将64位宽度的立即数 ,赋值到vA寄存器 |
const-string vA,string@aaaa | 将字符串常量的引用 赋值给vA |
const/high16 vA+BBBB0000 | 将16位宽度的立即数 右扩展扩展到32位,赋值给vA寄存器 |
const-class vA,type@AAA | 将一个类class 的引用赋值给vA寄存器 |
const/4和const/16的区别:
const/4 表示半个字节,也就是4位。-23~23-1 -8~7
const/16 表示两个字节,16位。-215~215-1 -32768~32767
局部变量定义:
.local v0,“a”:I:将v0的值赋值给 变量a
调用方法的基本格式:invoke-kind{vA,vB,vC,vD},meth@BBBB
指令助记符 | 指令功能描述 |
---|---|
invoke-virtual | 调用实例的虚方法,通常成员对象实例的方法都有该指令调用(用于调用一般的,非private、非static、非final、非构造函数的方法,它的第一个参数往往会传p0,也就是this指针) |
invode-super | 调用父类的虚拟方法(用于调用父类中的方法,其他和invoke-virtual保持一致) |
invoke-direct | 调用直接方法,通常私有方法都以该指令调用(用于调用private修饰的方法,或者构造方法) |
invoke-static | 调用静态方法(比如一些工具类) |
invoke-interface | 用于调用interface中的方法 |
invoke-kind / range {vB-vN},method@BBB | 参数列表是连续的寄存器列表 |
cmp:比较两个寄存器中值的大小,并将结果存储在目标寄存器中。
基本格式为:cmp 目标寄存器 vB vC
指令助记符 | 指令功能描述 |
---|---|
cmpl vA,vB,vC(less than) | 比较 vB,vC 较小值。如果vB=vC ,则vA=0 ;如果 vC较小 ,则vA存储正数。vB小 ,则vA存储负数 |
cmpg vA,vB,vC(greater than) | 比较 vB,vC 较大值。如果vB=vC ,则vA=0 ;如果 vC较大 ,则vA存储正数。vB大 ,则vA存储负数 |
指令助记符 | 指令功能描述 |
---|---|
cmpl-float vA,vB,vC | 比较浮点型和长整型的数据。 |
cmpg-float vA,vB,vC | 比较浮点型和长整型的数据。 |
cmpl-double vA,vB,vC | 比较double型的数据 |
cmpg-double vA,vB,vC | 比较double型的数据 |
cmpg-long vA,vB,vC | 比较long型的数据 |
指令助记符 | 指令功能描述 |
---|---|
if-test vA,vB,+CCC | 条件跳转指令,比较两个寄存器vA和vB的值,然后进行条件跳转 |
if-eq v1,v2,:cond_0 | 如果v1 = v2,则进入分支cond_0 |
if-ne v1,v2,:cond_0 | 如果v1 != v2,则进入分支cond_0 |
if-gt v1,v2,:cond_0 | 如果v1 > v2,则进入分支cond_0 |
if-ge v1,v2,:cond_0 | 如果v1 >= v2,则进入分支cond_0 |
if-lt v1,v2,:cond_0 | 如果v1 < v2,则进入分支cond_0 |
if-le v1,v2,:cond_0 | 如果v1 <= v2,则进入分支cond_0 |
就与0比较: | |
if-testz vA,+cccc | 条件跳转指令,比较寄存器vA与0的值,满足后跳转 |
if-eqz v1,:cond_0 | 如果寄存器v1的值 ==0,则跳转到cond_0 |
if-gtz v1,:cond_0 | 如果寄存器v1的值 >0,则跳转到cond_0 |
if-gez v1,:cond_0 | 如果寄存器v1的值 >=0,则跳转到cond_0 |
if-ltz v1,:cond_0 | 如果寄存器v1的值 <0,则跳转到cond_0 |
if-lez v1,:cond_0 | 如果寄存器v1的值 <=0,则跳转到cond_0 |
其他跳转: | |
goto +cccc | 无条件跳转到指定位置,偏移量为8位 宽度,且不为0 |
goto/16 +cccc | 无条件跳转到指定位置,偏移量为16位 宽度,且不为0 |
goto/32 +cccc | 无条件跳转到指定位置,偏移量为32位 宽度,且不为0 |
packed-switch vA,+bbbbbb | 根据+bbbbbb给定的跳转偏移列表进行匹配vA寄存器的值,跳转到指定指令。其中,偏移量的匹配值是 有规律递增的 |
sparse-switch vA,+bbbbbb | 根据+bbbbbb给定的跳转偏移列表进行匹配vA寄存器的值,跳转到指定指令。其中,偏移量的匹配值是 无规律的 |
作业1:
在java中:
# 实际中不这样写,需要用一个函数,这里只说逻辑
private int age = 20;
private String identity;
if (age>18){
identity = "成年";
}else{
identity = "未成年";
}
在smali中:
.field private age:I
.field private identity:Ljava/lang/String;
const/4 v0,0x14
const/4 v1,0x12
if-le v0,v1,:cond_0 #这里与原java代码是反着判断的
const-string v4,"成年"
cond_0:
const-string v3,"未成年"
基本格式:iinstance-op vA,vB,field @CCCC
指令助记符 | 指令功能描述 |
---|---|
普通字段操作: | |
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类型 |
以上的 i 都可以换成 a 或者 s,分别用于操作 数组字段 或者 静态字段。
作业2:
在java中:
private String name1 = "zhangsan";
private String name2 = "lisi";
private int age1 = 20;
private Object names[]= {name1,name2,"wangwu"};
在smali中:
# instance fields
.field private name1:Ljava/lang/String;
.field private name2:Ljava/lang/String;
.field private age1:I
.field private names:[Ljava/lang/Object;
# direct methods
.method public constructor ()V
.locals 9
#调用了一个方法
invoke-direct {p0}, Landroidx/appcompat/app/AppCompatActivity;->()V
#上面是声明了,这里需要为声明的对象赋值
const-string v0, "zhangsan"
iput-object v0, p0, Lcom/example/hello/Regist;->name1:Ljava/lang/String;
const-string v1, "lisi"
iput-object v1, p0, Lcom/example/hello/Regist;->name2:Ljava/lang/String;
const/4 v2,0x14
# 用来放数组长度
const/4 v6,0x3
# 声明一个新数组,寄存器位置为v5,长度为v6
new-array v5, v6, [Ljava/lang/Object;
const/4 v7,0x0
aput-object v0, v5, v7 # 将v0的数据放入v5中,下标位置为v7
const/4 v8,0x1
aput-object v1, v5, v8 # 将v1的数据放入v5中,下标位置为v8
const/4 v9,0x2
const-string v4,"wangwu"
aput-object v4, v5, v9 # 将v4的数据放入v5中,下标位置为v9
# 将v5中的数据放入对象names中
iput-object v5, p0, Lcom/example/hello/MainActivity;->names:[Ljava/lang/Object;
return-void
.end method
作业3:
循环:
private int a=2;
private int aa;
public void test(){
for(int i=0;i<4;i++){}
aa=a;
}
.class public Lcom/example/hello/Regist;
.super Landroidx/appcompat/app/AppCompatActivity;
.source "Regist.java"
# instance fields
.field private a:I
.field private aa:I
# direct methods
.method public constructor ()V
invoke-direct {p0}, Landroidx/appcompat/app/AppCompatActivity;->()V
const/16 v0, 0x2
iput v0, p0, Lcom/example/hello/Regist;->a:I
.end method
# virtual methods
.method public test()V
#准备一个0
const/4 v1, 0x0
#准备一个i
.local v2, "i":I
#开始循环
:goto_0
# 准备一个4
const/4 v1, 0x4
#判断i>=4
if-ge v2, v1, :cond_0
#这里中间是循环体中的内容
# i自增
add-int/lit8 v2, v2, 0x1
#再跳转到循环
goto :goto_0
.end local v2 # "i":I
#判断不成功,跳出循环
:cond_0
iget v0, p0, Lcom/example/hello/LoginSuccess;->a:I # 取出a的值
iput v0, p0, Lcom/example/hello/LoginSuccess;->aa:I # 赋值给aa
return-void
.end method
基本格式:bin-op vA,vB,vC(对寄存器vB和vC进行binop算数运算,结果复制到vA寄存器)
指令助记符 | 指令功能描述 |
---|---|
算数运算: | |
add-type | 加法(type有:int、long、float、double) |
sub-type | 减法 |
mul-type | 乘法 |
div-type | 除法 |
rem-type | 取模 |
逻辑运算与位运算: | |
and-type2 | 逻辑与(type2只有:int、long) |
or-type2 | 逻辑或 |
xor-type2 | 逻辑非 |
shl-type2 | 有符号数左移 |
shr-type2 | 有符号数右移 |
ushr-type2 | 无符号数右移 |
binop/lit8 vA,vB,#+cccc | 将寄存器vB的值与常量cccc进行算数运算,结果赋值给vA |
binop/lit16 vA,vB,#+cccc | |
binop/2addr vA,vB | 将vA寄存器与vB寄存器的值进行binop运算,值赋值给vA |
指令助记符 | 指令功能描述 |
---|---|
unop vA,vB | 数据类型转换 |
neg-type vx,vy | 计算 vx =- vy(type-后可以接类型int、long、float、double) |
not-type vx,vy | |
type1-to-type2 | 转换类型 |
int-to-type | |
int-to-char | |
int-to-short | |
例如: | |
double-to-int v0,v1 |
指令助记符 | 指令功能描述 |
---|---|
锁:(多用在多线程程序中,对同一对象操作) | |
monitor-enter vA | 为指定的对象获取锁 |
monitor-exit vA | 释放指定对象的锁 |
异常: | |
throw | 抛出一个指定类型的异常 |
指令助记符 | 指令功能描述 |
---|---|
new-instance vA,type@BB | 构造一个指定类型的实例,并把引用值给寄存器 |
instance-of vA,vB,type@CC | 判断vB寄存器的引用对象是否可以转为指定类型,是vA就复制1,否vA复制0 |
check-cast vA,type@BB | 把寄存器引用对象转为指定类型,不行就抛出异常 |