1.smali
apk文件通过apktool反编译出来的都有一个smali文件夹,里面都是以.smali结尾的文件。
smali语言是Davlik的寄存器语言,语法上和汇编语言相似,Dalvik VM与JVM的最大的区别之一就是Dalvik VM是基于寄存器的。基于寄存器的意思是,在smali里的所有操作都必须经过寄存器来进行。
B—byte
C—char
D—double
F—float
I—int
S—short
V—void
J—long
Z—boolean
注意J、Z两个不是对应类型的首字母;
在dalvik字节码中,寄存器都是32位的,能够支持任何类型,Long和Double类型是64位的,需要2个寄存器;
V 只能用于返回值类型;
数组的表示方式是在基本类型前加上前中括号“[”,例如int数组和float数组分别表示为:[I、[F;
对象类型以L作为开头来表示,格式是Lpackage/ClassName;(用分号表示对象结束是必须的),示例:
String对象在smali中为:Ljava/lang/String;
Class1对象的一个boolean成员表示为:Lcom/disney/Class1;->isRunning:Z
Class1对象的一个String对象成员表示为:Lcom/disney/Class1;->name:Ljava/lang/String;
可以总结为格式为对象类型->成员名:成员类型,->表示所属关系,类型尾部必须包括一个分号。
内部类表示为:Lpackage/ClassName$innerObjectName;,也就是在内部类前加“$”符号。
格式:Func-Name (Para-Type1Para-Type2Para-Type3…)Return-Type
返回类型在最后,参数之间没有任何分隔符,示例:
void fun()
fun()V
boolean fun(int, int, int)
fun(III)Z
String fun(boolean, int[], int[], String, long)
fun(Z[I[ILjava/lang/String;J)Ljava/lang/String;
#标记,构造函数的返回类型为V,名字为
# static fields 定义静态变量的标记 # instance fields 定义实例变量的标记 # direct methods 定义静态方法的标记?? # virtual methods 定义非静态方法的标记??
.class public Lcom/disney/WMW/WMWActivity; .super Lcom/disney/common/BaseActivity; .source "WMWActivity.java" .implements Lcom/burstly/lib/ui/IBurstlyAdListener;
上面这几行代码表示类名,父类名,源文件名,实现了接口。
.annotation 内部类 .end annotation
本地寄存器(local register,非参寄存器)用v开头数字结尾的符号来表示,如v0、v1、v2、…,
参数寄存器(parameter register)用p开头数字结尾的符号来表示,如p0、p1、p2、…,
.registers 用来标明方法中寄存器的总数,即参数寄存器和非参寄存器的总数。
.local 0,标明在这个函数中最少要用到的本地寄存器的个数,出现在方法中的第一行。在这里,由于只需要调用一个父类的onDestroy()处理,所以只需要用到p0,所以使用到的本地寄存器数为0,在植入代码后不要忘记可能要修改.local的值。
如 .local 4,则可以使用的寄存器是v0-v3。
当一个方法被调用的时候,方法的参数被置于最后N个寄存器中。
在实例函数中,p0代指“this”,p1表示函数的第一个参数,p2代表函数中的第二个参数…,
在static函数中,p1表示函数的第一个参数,p2代表函数中的第二个参数…,因为Java的static方法中没有this方法。
示例:
const/4 v0, 0x0 iput-boolean v0, p0, Lcom/disney/Class1;->isRunning:Z
上面第一句中把值0x0存到v0本地寄存器中,
第二句用iput-boolean指令把v0中的值存放到this.isRunning这个成员变量中,即this.isRunning = false; 因为在实例函数中p0代表的是“this”,Lcom/disney/Class1;是类名,对应实例是p0。
# static fields .field private static final PREFS_INSTALLATION_ID java/lang/String; = "installationId" # instance fields .field private _activityPackageName java/lang/String;
获取和操作静态成员变量和实例成员变量有不同的指令。
读取的指令有:iget、sget、iget-boolean、sget-boolean、iget-object、sget-object等,
赋值的指令有:iput、sput、iput-boolean、sput-boolean、iput-object、sput-object等。
带“-object”表示操作的成员变量是对象类型,没有“-object”后缀的表示操作的成员变量对象是基本数据类型,特别地boolean类型则使用带“-boolean”的指令操作。
获取static fields的指令示例:
sget-object v0, Lcom/disney/Class1;->PREFS_INSTALLATION_ID:Ljava/lang/String;
上句中sget-object指令把PREFS_INSTALLATION_ID这个String成员变量获取并放到v0寄存器中。
获取instance fields的指令与static fields的类似,需要指明对象所属的实例。示例:
iget-object v0, p0, Lcom/disney/Class1;->_view:Lcom/disney/Class2;
上句iget-object指令比sget-object多了一个参数p0,就是该变量所在类的实例,在这里就是p0即“this”。
put指令的使用和get指令是统一的,示例:
const/4 v3, 0x0 sput-object v3, Lcom/disney/Class1;->globalIapHandler:Lcom/disney/config/GlobalPurchaseHandler;
上句相当于Class1.globalIapHandler = null;
smali中的函数调用也分为direct和virtual两种类型,direct method就是private函数,public和protected函数都属于virtual method。在调用函数时,有invoke-direct,invoke-virtual,invoke-static、invoke-super以及invoke-interface等几种不同的指令。还有invoke-XXX/range 指令的,这是参数多于4个的时候调用的指令,比较少见。
invoke-static:就是调用static函数的,示例:
invoke-static {}, Lcom/disney/Class1;->fun()Z
上句invoke-static后面有一对大括号“{}”,内部是调用该方法的实例和参数列表,由于这是static方法也不需要参数,所以{}内为空。
invoke-super:调用父类方法,在onCreate、onDestroy等方法都能看到。
invoke-direct:调用private函数,示例:
invoke-direct {p0}, Lcom/disney/Class1;->getGlobalIapHandler()Lcom/disney/config/GlobalPurchaseHandler;
上句即this->getGlobalIapHandler(),函数GlobalPurchaseHandler getGlobalIapHandler()是定义在Class1中的一个private函数。
invoke-virtual:用于调用protected或public函数,示例:
sget-object v0, Lcom/disney/Class1;->shareHandler:Landroid/os/Handler; invoke-virtual {v0, v3}, Landroid/os/Handler;->removeCallbacksAndMessages(Ljava/lang/Object;)V
上句v0是shareHandler android/os/Handler,v3是传递给removeCallbackAndMessage方法的Ljava/lang/Object参数。
在smali里调用函数和返回函数结果需要分开来完成,在调用的函数返回非void后,用move-result(返回基本数据类型)和move-result-object(返回对象)指令获取返回结果。
示例:
const/4 v2, 0x0 invoke-virtual {p0, v2}, Lcom/disney/Class1;->getPreferences(I)Landroid/content/SharedPreferences; move-result-object v1
上句v1保存的就是调用this.getPreferences(int)方法返回的SharedPreferences实例。
.method 和 .end method之间。
示例:
.method protected onDestroy()V .locals 0 .prologue .line 277 invoke-super {p0}, Lcom/disney/common/BaseActivity;->onDestroy()V .line 279 return-void .end method
上段是onDestroy()函数。
.line 277,标注了该代码在原Java文件中的行数,它不是必须的,去掉没有编译问题。它在出错时可以指出错误位置,jd-gui工具即是通过分析这些信息将smali代码还原成Java代码的。
if-eq p1, v0, :cond_8 :cond_8 invoke-direct {p0}, Lcom/paul/test/a;->d()V
上段表示如果p1和v0相等,则执行cond_8的流程:调用com.paul.test.a的d()方法
if-ne p1, v0, :cond_b :cond_b const/4 v0, 0x0 invoke-virtual {p0, v0}, Lcom/paul/test/a;->setPressed(Z)V invoke-super {p0, p1, p2}, Landroid/view/View;->onKeyUp(ILandroid/view/KeyEvent;)Z move-result v0
上段表示不相等则执行cond_b的流程。
////////////////////////////
一、头信息——类的主体信息
在打开smali文件的时候,它的头三行描述了当前类的一些信息。
.class <访问权限> [关键修饰字] <类名>;
.super <父类名>;
.source <源文件名>
例如:
//===================================================================
public class MainActivity extends AppCompatActivity {
// ......
}
//===================================================================
.class public Ltestdemo/hpp/cn/test/MainActivity;
.super Landroid/support/v7/app/AppCompatActivity;
.source "MainActivity.java"
//===================================================================
.class指令表示当前的类名,类的访问权限是public,类名为Ltestdemo/hpp/cn/test/MainActivity,类开头的L是遵循Dalvik字节码的相关约定,表示后面跟随的字符串是一个类。
.super指定了当前类所继承的父类,后面指的就是这个父类的类名,L表示后面跟的字符串是一个类
.source指定了当前类的源文件名
注意:经过混淆的dex文件,反编译出来的smali代码可能没有源文件信息,因此source行的代码可能为空。
这三行就是类的主体部分了,另外一个类是由多个字段或者方法组成。
二、接口
如果一个类实现了一个接口,那么会在smali文件中使用.implements指令指出。
#interfaces
.implements <接口名>
同样,#interfaces是注释,.implements是接口关键字。
例如:
//===================================================================
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
// ......
}
//===================================================================
# interfaces
.implements Landroid/view/View$OnClickListener;
//===================================================================
Davlik字节码中,寄存器都是32位的,能够支持任何类型,64位类型(Long/Double)用2个寄存器表示;
Dalvik字节码有两种类型:原始类型;引用类型(包括对象和数组)
1、原始类型
V void (只能用于返回值类型)
Z boolean
B byte
S short
C char
I int
J long(64位)
F float
D double(64位)
2、对象类型
Lpackage/name/ObjectName; 相当于java中的package.name.ObjectName;
L 表示这是一个对象类型
package/name 该对象所在的包
ObjectName 对象名称
; 标识对象名称的结束
3、数组类型
[I :表示一个整形的一维数组,相当于java的int[];
对于多维数组,只要增加[ 就行了,[[I = int[][];注:每一维最多255个;
对象数组的表示形式:
[Ljava/lang/String 表示一个String的对象数组;
4、寄存器与变量
android变量都是存放在寄存器中的,寄存器为32位,可以支持任何类型,其中long和double是64为的,需要使用两个寄存器保存。
寄存器采用v和p来命名,v表示本地寄存器,p表示参数寄存器。
例如:
//===================================================================
private void print(String string) {
Log.d(TAG, string);
}
//===================================================================
.method private print(Ljava/lang/String;)V
.registers 3
.param p1, "string" # Ljava/lang/String;
.prologue
.line 29
const-string v0, "MainActivity"
invoke-static {v0, p1}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I
.line 30
return-void
.end method
//===================================================================
5、基本指令
smali字节码是类似于汇编的,如果你有汇编基础,理解起来是非常容易的。
move v0, v3 把v3寄存器的值移动到寄存器v0上
const-string v0, “MainActivity” 把字符串”MainActivity”赋值给v0寄存器
invoke-super 调用父函数
return-void 函数返回void
new-instance 创建实例
iput-object 对象赋值
iget-object 调用对象
invoke-static 调用静态函数
invoke-direct 调用函数
例如:
//===================================================================
@Override
public void onClick(View view) {
String str = "Hello World!";
print(str);
}
//===================================================================
# virtual methods
# 参数类型为Landroid/view/View,返回类型为V
.method public onClick(Landroid/view/View;)V
# 表示有三个寄存器
.registers 3
# 参数View类型的view变量对应的是寄存器p1
.param p1, "view" # Landroid/view/View;
.prologue
.line 24
#将"Hello World!"字符串放到寄存器v0中
const-string v0, "Hello World!"
.line 25
# 定义一个Ljava/lang/String类型的str变量对应本地寄存器v0
.local v0, "str":Ljava/lang/String;
# 调用该类的print方法,该方法的参数类型为Ljava/lang/String,返回值为V
# 调用print方法传入的参数为{p0, v0},及print(p0, v0),p0为this,v0为"Hello World!"字符串
invoke-direct {p0, v0}, Ltestdemo/hpp/cn/annotationtest/MainActivity;->print(Ljava/lang/String;)V
.line 26
return-void
.end method
//===================================================================
6、if判断语句
if判断一共有12条指令:
if-eq vA, VB, cond_** 如果vA等于vB则跳转到cond_**。相当于if (vA==vB)
if-ne vA, VB, cond_** 如果vA不等于vB则跳转到cond_**。相当于if (vA!=vB)
if-lt vA, VB, cond_** 如果vA小于vB则跳转到cond_**。相当于if (vAif-le vA, VB, cond_** 如果vA小于等于vB则跳转到cond_**。相当于if (vA<=vB)
if-gt vA, VB, cond_** 如果vA大于vB则跳转到cond_**。相当于if (vA>vB)
if-ge vA, VB, cond_** 如果vA大于等于vB则跳转到cond_**。相当于if (vA>=vB)
if-eqz vA, :cond_** 如果vA等于0则跳转到:cond_** 相当于if (VA==0)
if-nez vA, :cond_** 如果vA不等于0则跳转到:cond_**相当于if (VA!=0)
if-ltz vA, :cond_** 如果vA小于0则跳转到:cond_**相当于if (VA<0)
if-lez vA, :cond_** 如果vA小于等于0则跳转到:cond_**相当于if (VA<=0)
if-gtz vA, :cond_** 如果vA大于0则跳转到:cond_**相当于if (VA>0)
if-gez vA, :cond_** 如果vA大于等于0则跳转到:cond_**相当于if (VA>=0)
7、循环语句
常用的循环结构有:迭代器循环,for循环,do while循环。
8、switch分支语句
9、try/catch语句
四、字段
smali文件中,字段的声明使用.field指令,字段分为静态字段和实例字段。
1、静态字段
#static fields
.field <访问权限> static [修饰关键字] <字段名>:<字段类型>
可以看到,baksmali在生成smali文件时,会在静态字段声明的起始处添加注释”static fields”,注释是以#开头。
访问权限包括:private、protected、public(三者之一)
修饰关键字为字段的其他属性,例如,final
字段名和类型就不用解释了
例如:
//===================================================================
private static final String TAG = "MainActivity";
//===================================================================
# static fields
.field private static final TAG:Ljava/lang/String; = "MainActivity"
//===================================================================
2、实例字段
相比于静态自动就少了一个static的静态声明而已,其他都一样。
#instance fields
.field <访问权限> [修饰关键字] <字段名>:<字段类型>
例如:
//===================================================================
private Button mButton;
//===================================================================
# instance fields
.field private mButton:Landroid/widget/Button;
//===================================================================
五、方法
smali的方法声明使用的.method指令,方法分为直接方法和虚方法两种。
1、直接方法
直接方法指的是该类中定义的方法。
#direct methods
.method <访问权限> [修饰关键字] <方法原型>
<.registers>
[.param]
[.prologue]
[.line]
<.local>
<代码体>
.end method
#direct methods是注释,是baksmali添加的,访问权限和修饰关键字跟字段是一样的。
方法原型描述了方法的名称、参数与返回值。
.registers 指令指定了方法中寄存器的总数,这个数量是参数和本地变量总和。
.param表明了方法的参数,每个.param指令表示一个参数,方法使用了几个参数就有几个.parameter指令。
.prologue指定了代码的开始处,混淆过的代码可能去掉了该指令。
.line指明了该处代码在源代码中的行号,同样,混淆后的代码可能去掉了行号。
.local 使用这个指定表明方法中非参寄存器
//===================================================================
private void print(String string) {
Log.d(TAG, string);
}
//===================================================================
.method private print(Ljava/lang/String;)V
.registers 3
.param p1, "string" # Ljava/lang/String;
.prologue
.line 29
const-string v0, "MainActivity"
invoke-static {v0, p1}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I
.line 30
return-void
.end method
//===================================================================
2、虚方法
虚方法指的是从父类中继承的方法或者实现的接口的方法,它的声明跟直接方法相同,只是起始的初始为virtual methods
//===================================================================
@Override
public void onClick(View view) {
String str = "Hello World!";
print(str);
}
//===================================================================
# virtual methods
.method public onClick(Landroid/view/View;)V
.registers 3
.param p1, "view" # Landroid/view/View;
.prologue
.line 24
const-string v0, "Hello World!"
.line 25
.local v0, "str":Ljava/lang/String;
invoke-direct {p0, v0}, Ltestdemo/hpp/cn/annotationtest/MainActivity;->print(Ljava/lang/String;)V
.line 26
return-void
.end method
//===================================================================
3、静态方法
//===================================================================
public static void setTag(String str) {
TAG = str;
}
//===================================================================
.method public static setTag(Ljava/lang/String;)V
.registers 1
.param p0, "str" # Ljava/lang/String;
.prologue
.line 64
sput-object p0, Ltestdemo/hpp/cn/annotationtest/MainActivity;->TAG:Ljava/lang/String;
.line 65
return-void
.end method
//===================================================================
六、注解
如果一个类使用了注解,那么smali中会使用.annotation指令。
#annotations
.annotation [注解属性] <注解类名>
[注解字段 = 值]
.end annotation
注解的作用范围可以是类、方法或者字段。如果注解的作用范围是类,.annotation指令会直接定义在smali文件中,如果是方法或者字段,.annotation指令则会包含在方法或者字段的定义中。
1、注解类
//===================================================================
@BindInt(100)
public class MainActivity extends AppCompatActivity {
}
//===================================================================
# annotations
.annotation build Ltestdemo/hpp/cn/annotationtest/BindInt;
value = 0x64
.end annotation
//===================================================================
2、注解字段
//===================================================================
@BindView(R.id.button)
public Button mButton;
//===================================================================
# instance fields
.field public mButton:Landroid/widget/Button;
.annotation build Lbutterknife/BindView;
value = 0x7f0c0050
.end annotation
.end field
//===================================================================
3、注解方法
//===================================================================
@OnClick(R.id.button)
public void click() {
String str = "Hello World!";
print(str);
}
//===================================================================
# virtual methods
.method public click()V
.registers 2
.annotation build Lbutterknife/OnClick;
value = {
0x7f0c0050
}
.end annotation
.prologue
.line 29
const-string v0, "Hello World!"
.line 30
.local v0, "str":Ljava/lang/String;
invoke-direct {p0, v0}, Ltestdemo/hpp/cn/annotationtest/MainActivity;->print(Ljava/lang/String;)V
.line 31
return-void
.end method
//===================================================================