什么是smali
Smali,Baksmali分别是指安卓系统里的Java虚拟机(Dalvik)所使用的一种.dex格式文件的汇编器,反汇编器。其语法是一种宽松式的Jasmin/dedexer语法,而且它实现了.dex格式所有功能,当我们对apk反编译之后,便会生成此类文件,其中在dalvik字节码中,寄存器都是32位的,可以支持任何类型,64位类型要用2个寄存器表示,其中Dalvik字节码有两种类型:原始类型
和`引用类型(包括对象和数组)
为什么要学会smali
当我们反编译之后得到jar或者smali文件,android采用java语言开发,但是android系统使用自己的dalvik虚拟机,代码编译最终不是采用java的class,而是使用smali。因此我们反编译得到的代码,jar的话很多地方无法正确的解释出来,如果我们反编译的是smali则可以正确的理解程序的意思,因此学会smali十分有必要
smali语法
类型:
首先介绍下 Dalvik 字节码是什么
Dalvik是google专门为android 操作系统设计的一个虚拟机,经过深度优化。虽然android上的程序是使用java来开发的,但是dalvik和标准java虚拟机是两回事。Dalvik虚拟机是基于寄存器的,而JVM是基于栈的。Dalvik有专属的文件执行格式dex(dalvik executable),而JVM则执行的是java字节码,Dalvik vm比jvm速度更快,占用空间更少。
通过Dalvik的字节码我们不能直接看到原来的逻辑代码,这时需要借助ApkTool或者dex2jar+jd-jui工具来查看,但是,注意的是最终我们修改apk需要操作的文件是.smali文件,而不是导出来的java文件重新编译
Dalvik字节码有两种类型:原始类型
和引用类型(包括对象和数组)
原始类型
类型 | 描述 | |
---|---|---|
V | void - can only be used for return types | |
Z | boolean | |
B | byte | |
S | short | |
C | char | |
I | int | |
J | long(64bits) | |
F | float | |
D | double(64bits) | |
[XXX | array | |
Lxxx/yyy | object |
- 对象类型
Lpackage/name/ObjectName; 相当于java中的package.name.ObjectName;
L:表示这是一个对象类型
package/name:对象所在的包
ObjectName:对象名称
;: 标识对象结束
其中内部类 LpakeageName/objectName$subObjectname
- 数据类型
[i 表示一个int类型的一维数组,相当于int[]
增加一个维度增加一个[,如[[i,表示int[][]
其中:每一维最多255个
对象数组表示也是类似,如String数组的表示是[Ljava/lang/String
- 方法表示
Lpackage/name/ObjectName;->MethodName(III)Z
Lpackage/name/ObjectName 表示类型
methodName 表示方法名
III 表示参数(这里表示为3个整型参数)
说明:方法的参数是一个接一个的,中间没有隔开
方法的定义一般为:
Func-Name(Para-Type1Para-Type2Para-Type3……)Return-Type
其中参数之间没有分隔符,例如:
hello()V 对应 void hello()
hello(III)Z 对应 boolean hello(int,int,int)
hello(Z[I[ILjava/lang/String;j) Ljava/lang/String 对应 String hello(boolean,int[],int[],String,long)
- 字段表示
Lpackage/name/ObjectName;->FieldName:Ljava/lang/String;
说明: 包名,字段名和各字段类型
- 寄存器与变量
什么是寄存器:
在smali里所有的才做都必须经过寄存器来进行:本地寄存器用v开头以数字结尾的符号来表示,如v0,v1
参数寄存器则使用p开头数字结尾的符号来表示,如p0,p1,特别要注意的是,p0不一定是函数中的第一个参数,在非static函数中,p0代指this,p1表示函数的第一个参数,p2表示函数的第二个参数,而在static函数中p0才对应第一个参数(因为java的static方法中没有this方法)
简单指令解析
set-object v0,Lcom/aaa;->ID:Ljava/lang/String
set-object 就是用来获取变量值保存到紧接着的参数寄存器中,上面的表示它获取ID这个String类型的成员变量并放到v0这个寄存器中
iget-object v0,p0,Lcom/aaa;->view:Lcom/aaa/view;
这里可以看到iget-object指令比sget-object多了一个参数,该变量就是所在类的实例,在这里就是p0即,this
put指令的使用和get指令是统一的如下:
const/4 v3,0x0
sput-object v3,Lcom/aaa;->timer:Lcom/aaa/timer;
相当于:this.timer = null;
.loacl v0,args:Landroid/os/Message;
const/4 v1,0x12
input v1,v0,Landroid/os/Message;->what:l
相当于:args.what = 18;
指定一个方法中有多少寄存器可以使用有两种方式
.registers 指令指定了方法中寄存器的总数
.locals 指令表明了方法中非参寄存器的总数,出现在方法中的第一行
说明:
寄存器采用v和p来命名
v表示本地寄存器,p表示参数寄存器,二者关系如下:
Long和Double类型是64位,需要2个寄存器
如果有一个方法有两个本地变量,有3个参数,则表示如下:
v0 第一个本地寄存器
v1 第二个本地寄存器
v2 p0 (this)
v3 p1 第一个参数
v4 p2 第二个参数
v5 p3 第三个参数
- 基本语法
.filed private isFlag:z 定义变量
.nethod 方法
.parameter 方法参数
.prologue 方法开始
.line 123 此方法位于123行
invoke-super 调用父函数
const/high16 v0,0x7fo3 把0x7fo3赋值给v0
invoke-direct 调用函数
return-void 函数返回void
.end method 函数结束
new-instance 创建实例
iput-object 对象赋值
iget-object 调用对象
invoke-static 调用静态函数
- 条件跳转
“if-eq VA,VB,:cond_xx “ 如果VA=VB则跳转到cond_xx
“if-ne VA,VB,:cond_xx “ 如果VA!=VB则跳转到cond_xx
“if-lt VA,VB,:cond_xx “ 如果VA<VB则跳转到cond_xx
“if-ge VA,VB,:cond_xx “ 如果VA>=VB则跳转到cond_xx
“if-gt VA,VB,:cond_xx “ 如果VA>VB则跳转到cond_xx
“if-le VA,VB,:cond_xx “ 如果VA<=VB则跳转到cond_xx
“if-eqz VA,:cond_xx “ 如果VA=0则跳转到cond_xx
“if-nez VA,:cond_xx “ 如果VA!=0则跳转到cond_xx
“if-ltz VA,:cond_xx “ 如果VA<0则跳转到cond_xx
“if-gez VA,:cond_xx “ 如果VA>=0则跳转到cond_xx
“if-gtz VA,:cond_xx “ 如果VA>0则跳转到cond_xx
“if-lez VA,:cond_xx “ 如果VA<=0则跳转到cond_xx
smali 函数分析
函数调用
smali中的函数和成员变量一样也分为两种类型
- direct
- virtual
简而言之 direct method就是private函数,其余的public和protected都属于virtual method。因此在函数调用时有invoke-direct,invoke-virtual,,另外还有invoke-static、invoke-super以及invoke-interface
1、invoke-static: 用于调用static函数的
如:
invoke-static{},Lcom/aaa;->CheckSignature()Z
const-string v0,”NDKLIB”
invoke-static {v0},Ljava/lang/System;->loadLibray(Ljava/lang/String;)V
这两句对应着 static void System.loadLiary(String)来加载NDK编译的so库方法,这里的v0就是参数“NDKLIB”
2、invoke-super:调用父类方法用的指令,一般用于调用onCreate、onDestroy等方法
3、invoke-direct 调用private函数
如:
invoke-direct{p0},Landroid/app/TabActivity;->
对应就是在TabActvity中定义一个private函数
4、invoke-virtual
用于调用protected和public函数
5、 invoke-xxx/range:当方法
当方法的参数多于5个时(含5个),不能直接使用上面的指令,而是在后面加上/range,并且传递参数不需要写全,可以以省略号替代,如:
invoke-direct/range{v0……v5},Lcmb/pb/ui/PBContainerActivity;->h(ILjava/lang/CharSeqence;Ljava/lang/String;Landroid/content/Intent;I)Z
函数返回结果操作
在java代码汇总调用函数和返回结果可以用一条语句完成,而在smali里则需要分开来完成,使得使用上述指令后,如果调用的函数返回非void,那么还需要用到move-result(返回基本数据类型)和move-result-object(返回对象)指令,如:
const-string v0,“hello”
invoke-static{v0},Lcmb/pbi;->t(Ljava/lang/String;)Ljava/lang/String;
move-result-object v2
其中v2保存的就是调用t方法返回的String字符串
示例
.local 4 //本地寄存器4个,即v0,v1,v2,v3
const/4 v2 0x1 //4字节常量v2=1
const/16 v1 0x10 //16字节常量 v1=16
:local v1,“length”:I //本地寄存器int length =v1
if-nez v1,: cond_1 //如果v1!=0,跳转至cond_1
:cond_0 //cond_0标签
:goto_0 //goto_0标签
return v2 //返回v2
:cond_1 //开始执行cond_1标签代码
const/4 v0,0x0 //4字节常量 v0=0
:local v0,”i”:I //本地寄存器 int i=0
:goto_1 //开始执行goto_1标签代码
if-lt v0,v1,:cond_2 //如果v0<v1,则跳转cond_2
const/16 v3,0x28 //接上,如果v0>=v1,则执行 16字节常量v3=40
if-le v1,v3,:cond_0 //接上,如果v1<=v3,则跳转至cond_0,即返回v2的值
const/4 v2,0x0 //接上,如果v1大于v3,则4字节常量 v2=0
goto:goto_0 //跳转至goto_0,即返回v2值
:cond_2 //cond_2标签
xor-int/lit8 v1,v1,0x3b //将第二个v1寄存器中的值与0x3b(59)进行异或运算,得到的赋值给第一个v1寄存器中
add-int/lit8 v0,v0,0x01 //将第二个v0寄存器中的值加上0x1(1),所得的值放入到第一个v0寄存器中
goto:goto_1 //跳转至goto_1,标签,这里可以看出cond_2,实际是一个for循环,而不是简单的IF判断
翻译成代码
int v2 = 1;
int v1 = 16;
if (v1 != 0)
{
for (int v0 = 0; v0 < v1;)
{
v1 = v1 ^ 59;
v0++;
}
if (v1 > 40)
{
v2 = 0;
}
}
return v2;
java代码转变为samli代码
方法一:
可以使用android SDK提供的一个工具dx,该jar包位于android-sdk\build-tools\23.0.1\lib,找到该包后执行以下命令
javac Test.java 得到Test.class
java -jar dx.jar –dex –output=Test.dex Test.class 得到dex文件
这时候会使用到另外一个工具baksmali,该工具位于android-sdk\platform-tools\,找到该包后执行以下命令
java -jar baksmali.jar Test.dex 得到smali文件
但是jdk1.8第二步无效,jdk1.7可以
方法二:
Android studio装 code2smali插件
https://github.com/ollide/intellij-java2smali
参考:
http://blog.isming.me/2015/01/14/android-decompile-smali/