Smali背景
Smali,Baksmali分别是指安卓系统里的Java虚拟机(Dalvik)所使用的一种.dex格式文件的汇编器,反汇编器。其语法是一种宽松式的Jasmin/dedexer语法,而且它实现了.dex格式所有功能(注解,调试信息,线路信息等)。
Smali,Baksmali分别是冰岛语中编译器,反编译器的叫法。也许你会问为什么是冰岛语呢,因为Dalvik是一个冰岛渔村名字。
简单介绍
Davlik字节码中,寄存器都是32位的,能够支持任何类型,64位类型(Long/Double)用2个寄存器表示;
根据一些文档说明,以及实践检测,Dalvik字节码有两种类型:原始类型;引用类型。熟悉JAVA的同学,应该很快能够反应过来,原始类型也就是我们JAVA中的8中基础类型;引用类型,在smali中主要是对象和数组。事实上smali语法就是根据java来的,可以理解成java的一种变种。
基本类型(原始类型)
类型关键字 | 对应Java中的类型说明 | 位数 |
---|---|---|
V | void | 0 |
Z | boolean | 4 |
B | byte | 8 |
S | short | 16 |
C | char | 16 |
I | int | 32 |
J | long | 64 |
F | float | 32 |
D | double | 64 |
引用类型
对象类型
Object类型,即引用类型的对象,在引用时,使用L开头,后面紧接着的是完整的包名,比如:java.lang.String
对应的Smali语法则是Ljava/lang/String
数组类型
一维数组在类型的左边加一个方括号,比如:[I
等同于Java的int[]
,每多一维就加一个方括号,最多可以设置255维
方法声明及调用
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
声明可使用的寄存器数量的方式为:.registers N
,N代表需要的寄存器的总个数,同时,还有一个关键字.locals
,它用于声明非参数的寄存器个数(包含在registers声明的个数当中),也叫做本地寄存器,只在一个方法内有效,但不常用,一般使用registers即可
确定寄存器的个数
由于非static方法,需要占用一个寄存器以保存this指针,那么这类方法的寄存器个数,最低就为1,如果还需要处理传入的参数,则需要再次叠加,此时还需要考虑Double和Float这种需要占用两个寄存器的参数类型。
寄存器的命名方式
有两种方式——V命名方式和P命名方式。P命名方式中的第一个寄存器就是方法中的第一个参数寄存器。在下表中我们用这两种命名方式来表示有5个寄存器和3个参数的方法。
对应V寄存器 | 对应P寄存器 | 寄存器类型 |
---|---|---|
v0 | 第一个local register | |
v1 | 第二个local register | |
v2 | p0 | 第一个parameter register |
v3 | p1 | 第二个parameter register |
v4 | p2 | 第三个parameter register |
寄存器的命名
1 | .local vA, "name":type; |
将vA命名为name name为type类型;
例如:
1 | .local v0, "bu":Landroid/widget/Button; |
v0 相当于 Button bu;
1 | .param pA, "name" # type; |
将pA命名为name name 为type类型;
例如
1 | .param p1, "a" # I |
p1 相当于 int a;
Dalvik指令集
https://source.android.com/devices/tech/dalvik/dalvik-bytecode#instructions
以下寄存器的位数与指令注意事项可在官方文档中查看
一般的指令格式为:[op]-[type](可选)/[位宽,默认4位] [目标寄存器],[源寄存器](可选)
,比如:move v1,v2
,move-wide/from16 v1,v2
移位
指令 | 说明 |
---|---|
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语句中的异常 |
返回
指令 | 说明 |
---|---|
return-void | 返回void,即直接返回 |
return v1 | 返回v1寄存器中的值 |
return-object v1 | 返回v1寄存器中的对象指针 |
return-wide v1 | 返回双字型结果给v1寄存器 |
常量
指令 | 说明 |
---|---|
const/4 vA, #+B | 将4位常量B符号扩展为32位赋值给vA寄存器 |
const/16 vAA, #+BBBB | 将16位常量B符号扩展为32位赋值给vA寄存器 |
const vAA, #+BBBBBBBB | 将32位常量B赋值给vA寄存器 |
const/high16 vAA, #+BBBB0000 | 将16位常量B右零扩展(右边添0)为32位赋值给vA寄存器 |
const-wide/16 vAA, #+BBBB | 将16位常量B符号扩展为64位赋值给vA寄存器 |
const-wide/32 vAA, #+BBBBBBBB | 将32位常量B符号扩展为64位赋值给vA寄存器 |
const-wide vAA, #+BBBBBBBBBBBBBBBB | 将64位常量B赋值给vA寄存器 |
const-wide/high16 vAA, #+BBBB000000000000 | 将16位常量B右零扩展(右边添0)为64位赋值给vA寄存器 |
const-string(/jumbo) vAA string@BBBBBBBB | 将字符串常量赋给vA寄存器,过长时需要加上jumbo |
const-class vA La/b/TargetClass | 将Class常量a.b.TargetClass赋值给vA,等价于a.b.TargetClass.class |
判断
指令 | 说明 |
---|---|
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) |
属性
属性操作的分为:取值(get)和赋值(put)
目标类型分为:数组(array)、实例(instance)和静态(static)三种,对应的缩写前缀就是a、i、s
长度类型分为:默认(什么都不写)、wide(宽,64位)、object(对象)、boolean、byte、char、short
指令格式:[指令名] [源寄存器], [目标字段所在对象寄存器], [字段指针]
下面列出用于实例字段的指令,其中i都可以换成a或者s,分别用于操作数组字段或者静态字段
指令 | 说明 |
---|---|
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类型 |
调用
基本格式:invoke-kind {vC, vD, vE, vF, vG}, meth@BBBB
其中,BBBB代表方法引用(参见上面介绍的方法定义及调用),vC~G为需要的参数
指令 | 说明 |
---|---|
invoke-virtual | 用于调用一般的,非private、非static、非final、非构造函数的方法,它的第一个参数往往会传p0,也就是this指针 |
invoke-super | 用于调用父类中的方法,其他和invoke-virtual保持一致 |
invoke-direct | 用于调用private修饰的方法,或者构造方法 |
invoke-static | 用于调用静态方法,比如一些工具类 |
invoke-interface | 用于调用interface中的方法 |
数学运算
运算码 | C 语义 | 备注 |
---|---|---|
neg-int | int32 a; int32 result = -a; |
一元二进制补码。 |
not-int | int32 a; int32 result = ~a; |
一元反码。 |
neg-long | int64 a; int64 result = -a; |
一元二进制补码。 |
not-long | int64 a; int64 result = ~a; |
一元反码。 |
neg-float | float a; float result = -a; |
浮点否定。 |
neg-double | double a; double result = -a; |
浮点否定。 |
int-to-long | int32 a; int64 result = (int64) a; |
将 int32 符号扩展为 int64 。 |
int-to-float | int32 a; float result = (float) a; |
使用最近舍入,将 int32 转换为 float 。这会导致某些值不够精准。
|
int-to-double | int32 a; double result = (double) a; |
将 int32 转换为 double 。 |
long-to-int | int64 a; int32 result = (int32) a; |
将 int64 截断为 int32 。 |
long-to-float | int64 a; float result = (float) a; |
使用最近舍入,将 int64 转换为 float 。这会导致某些值不够精准。
|
long-to-double | int64 a; double result = (double) a; |
使用最近舍入,将 int64 转换为 double 。这会导致某些值不够精准。
|
float-to-int | float a; int32 result = (int32) a; |
使用向零舍入,将 float 转换为 int32 。NaN 和 -0. (负零)转换为整数 0 。无穷数和因所占幅面过大而无法表示的值根据符号转换为 0x7fffffff 或 -0x80000000 。
|
float-to-long | float a; int64 result = (int64) a; |
使用向零舍入,将 float 转换为 int64 。适用于 float-to-int 的特殊情况规则也适用于此,但超出范围的值除外,这些值根据符号转换为 0x7fffffffffffffff 或 -0x8000000000000000 。
|
float-to-double | float a; double result = (double) a; |
将 float 转换为 double ,值依然精准。
|
double-to-int | double a; int32 result = (int32) a; |
使用向零舍入,将 double 转换为 int32 。适用于 float-to-int 的特殊情况规则也适用于此。
|
double-to-long | double a; int64 result = (int64) a; |
使用向零舍入,将 double 转换为 int64 。适用于 float-to-long 的特殊情况规则也适用于此。
|
double-to-float | double a; float result = (float) a; |
使用最近舍入,将 double 转换为 float 。这会导致某些值不够精准。
|
int-to-byte | int32 a; int32 result = (a << 24) >> 24; |
符号扩展结果,将 int32 截断为 int8 。
|
int-to-char | int32 a; int32 result = a & 0xffff; |
无需符号扩展,将 int32 截断为 uint16 。
|
int-to-short | int32 a; int32 result = (a << 16) >> 16; |
符号扩展结果,将 int32 截断为 int16 。
|
add-int | int32 a, b; int32 result = a + b; |
二进制补码加法。 |
sub-int | int32 a, b; int32 result = a - b; |
二进制补码减法。 |
rsub-int | int32 a, b; int32 result = b - a; |
二进制补码反向减法。 |
mul-int | int32 a, b; int32 result = a * b; |
二进制补码乘法。 |
div-int | int32 a, b; int32 result = a / b; |
二进制补码除法,向零舍入(即截断为整数)。如果 b == 0 ,则会抛出 ArithmeticException 。
|
rem-int | int32 a, b; int32 result = a % b; |
二进制补码除后取余数。结果的符号与 a 的符号相同,可更精确地定义为 result == a - (a / b) * b 。如果 b == 0 ,则会抛出 ArithmeticException 。
|
and-int | int32 a, b; int32 result = a & b; |
按位 AND。 |
or-int | int32 a, b; int32 result = a | b; |
按位 OR。 |
xor-int | int32 a, b; int32 result = a ^ b; |
按位 XOR。 |
shl-int | int32 a, b; int32 result = a << (b & 0x1f); |
按位左移(带掩码参数)。 |
shr-int | int32 a, b; int32 result = a >> (b & 0x1f); |
按位有符号右移(带掩码参数)。 |
ushr-int | uint32 a, b; int32 result = a >> (b & 0x1f); |
按位无符号右移(带掩码参数)。 |
add-long | int64 a, b; int64 result = a + b; |
二进制补码加法。 |
sub-long | int64 a, b; int64 result = a - b; |
二进制补码减法。 |
mul-long | int64 a, b; int64 result = a * b; |
二进制补码乘法。 |
div-long | int64 a, b; int64 result = a / b; |
二进制补码除法,向零舍入(即截断为整数)。如果 b == 0 ,则会抛出 ArithmeticException 。
|
rem-long | int64 a, b; int64 result = a % b; |
二进制补码除后取余数。结果的符号与 a 的符号相同,可更精确地定义为 result == a - (a / b) * b 。如果 b == 0 ,则会抛出 ArithmeticException 。
|
and-long | int64 a, b; int64 result = a & b; |
按位 AND。 |
or-long | int64 a, b; int64 result = a | b; |
按位 OR。 |
xor-long | int64 a, b; int64 result = a ^ b; |
按位 XOR。 |
shl-long | int64 a; int32 b; int64 result = a << (b & 0x3f); |
按位左移(带掩码参数)。 |
shr-long | int64 a; int32 b; int64 result = a >> (b & 0x3f); |
按位有符号右移(带掩码参数)。 |
ushr-long | uint64 a; int32 b; int64 result = a >> (b & 0x3f); |
按位无符号右移(带掩码参数)。 |
add-float | float a, b; float result = a + b; |
浮点加法。 |
sub-float | float a, b; float result = a - b; |
浮点减法。 |
mul-float | float a, b; float result = a * b; |
浮点乘法。 |
div-float | float a, b; float result = a / b; |
浮点除法。 |
rem-float | float a, b; float result = a % b; |
浮点除后取余数。该函数不同于 IEEE 754 取余,定义为 result == a - roundTowardZero( 。
|
add-double | double a, b; double result = a + b; |
浮点加法。 |
sub-double | double a, b; double result = a - b; |
浮点减法。 |
mul-double | double a, b; double result = a * b; |
浮点乘法。 |
div-double | double a, b; double result = a / b; |
浮点除法。 |
rem-double | double a, b; double result = a % b; |
浮点除后取余数。该函数不同于 IEEE 754 取余,定义为 result == a - roundTowardZero( 。
|
其他
指令 | 说明 |
---|---|
monitor-enter vAA | 获取指定对象的监视锁 |
monitor-exit vAA | 释放指定对象的监视锁 |
check-cast vAA, type@BBBB | 如果VA寄存器中的引用不能转型为type指定的类型,则抛出 ClassCastException |
instance-of vA, vB, type@CCCC | 如果VB是给定type类型的实例,则给VA赋值 1,否则赋值 0 |
array-length vA, vB | 将指定数组的长度(条目个数)赋值给给定目标寄存器 |
new-instance vAA, type@BBBB | 构造新type实例赋值给VA寄存器(类型必须引用非数组类) |
new-array vA, vB, type@CCCC | 根据指定的type类型和VB大小构造新数组VA(类型必须引用数组类) |
throw vAA | 抛出指定异常 |
goto(/16,/32) +AA | 无条件跳转 |
常用smali代码
Toast
1 | .method private toast()V |
Log
1 | .method private log()V |
LogCallStack
1 | .method private logCallStack()V |
AlertDialog
1 | new-instance v1,Landroid/app/AlertDialog$Builder; |
BroadcastReceiver
1 | .# static fields |