Smali是什么?
简介
Smali支持注解、调试信息、行数信息等基本Java的基本特性,可以说是很接近Java编译在JVM上的中间语言了,一般用来做Android程序的逆向工程
由于Smali是用于Dalvik和Art(Android虚拟机)的反汇编程序实现
APK文件>dex文件>smali文件>修改代码
所以smali语言是Dalvik的反汇编语言
B---byte
C---char
D---double
F---float
I---int
J---long
S---short
V---void
Z---boolean
[XXX---array
Lxxx/yyy---object
这里解析下最后两项,数组的表示方式是:在基本类型前加上前中括号“[”,例如int数组和float数组分别表示为:[I、[F;对象的表示则以L作为开头,格式是LpackageName/objectName;(注意必须有个分号跟在最后),例如String对象在smali中为:Ljava/lang/String;,其中java/lang对应java.lang包,String就是定义在该包中的一个对象。
或许有人问,既然类是用LpackageName/objectName;来表示,那类里面的内部类又如何在smali中引用呢?答案是:LpackageName/objectName$subObjectName;。也就是在内部类前加“$”符号
二、函数的定义
函数的定义一般为:
Func-Name (Para-Type1Para-Type2Para-Type3...)Return-Type
参数与参数之间没有任何分隔符:
1. foo ()V
>>>>void foo()。
2. foo (III)Z
>>>>boolean foo(int, int, int)。
3. foo (Z[I[ILjava/lang/String;J)Ljava/lang/String;
>>>>String foo (boolean, int[], int[], String, long)
1 .class public Lcom/disney/WMW/WMWActivity; #是com.disney.WMW这个package下的一个类 2 .super Lcom/disney/common/BaseActivity;#继承自com.disney.common.BaseActivity 3 .source "WMWActivity.java" #这是一个由WMWActivity.java编译得到的smali文件 4 5 # interfaces接口信息 6 .implements Lcom/burstly/lib/ui/IBurstlyAdListener;#这个WMWActivity实现了一个com.burstly.lib.ui这个package下(一个广告SDK)的IBurstyAdListener接口。 7 8 # annotations内部类 9 .annotation system Ldalvik/annotation/MemberClasses; 10 value = {#有两个成员内部类 11 Lcom/disney/WMW/WMWActivity$MessageHandler;, 12 Lcom/disney/WMW/WMWActivity$FinishActivityArgs; 13 } 14 .end annotation 15
由此可以的出源java文件
class WMWActivity extends BaseActivity implements IBurstlyAdListener{ //... 继承 接口 class MessageHandler { //... } class FinishActivityArgs{ //... } }
Lpackage/name/ObjectName;->MethodName(III)Z
第一部分Lpackage/name/ObjectName;用于声明具体的类型,以便JVM寻找
第二部分MethodName(III)Z,其中MethodName为具体的方法名,()中的字符,表示了参数数量和类型,即3个int型参数,Z为返回值的类型,即返回Boolean类型
寄存器声明及使用
.method private test(I)V .registers 4 # 声明总共需要使用4个寄存器 const-string v0, "LOG" # 将v0寄存器赋值为字符串常量"LOG" move v1, p1 # 将int型参数的值赋给v1寄存器 return-void .end method
结合Dalvik常用的指令进行操作,即可实现一些需要的功能
那么,如何确定需要使用的寄存器的个数?
由于非static方法,需要占用一个寄存器以保存this指针,那么这类方法的寄存器个数,最低就为1,如果还需要处理传入的参数,则需要再次叠加,此时还需要考虑Double和Float这种需要占用两个寄存器的参数类型,举例来看:
如果一个Java方法声明如下:
myMethod(int p1, float p2, boolean p3)
那么对应的Smali则为:
method LMyObject;->myMethod(IJZ)V
此时,寄存器的对应情况如下:
寄存器名称 对应的引用
p0 this
p1 int型的p1参数
p2, p3 float型的p2参数
p4 boolean型的p3参数
那么最少需要的寄存器个数则为:5
如果方法体内含有常量、变量等定义,则需要根据情况增加寄存器个数,数量只要满足需求,保证需要获取的值不被后面的赋值冲掉即可,方法有:存入类中的字段中(存入后,寄存器可被重新赋值),或者长期占用一个寄存器
Dalvik指令集
如果需要使用Smali编写程序,还需要掌握常用的Dalvik虚拟机指令,其合集称为Dalvik指令集。这些指令有点类似x86汇编的指令,但指令更多,使用也非常简单方便。最详尽的介绍,可以参考Android官方的Dalvik相关文档:https://source.android.com/devices/tech/dalvik/dalvik-bytecode#instructions
一般的指令格式为:[op]-[type](可选)/[位宽,默认4位] [目标寄存器],[源寄存器](可选),比如:move v1,v2,move-wide/from16 v1,v2
这里也列举一些常用的指令,并结合Smali进行说明:
移位操作:
此类操作常用于赋值
- 返回操作:
用于返回值,对应Java中的return语句
- 常量操作:
用于声明常量,比如字符串常量(仅声明,String a = “abc”这种语句包含声明和赋值)
- 调用操作:
用于调用方法,基本格式:invoke-kind {vC, vD, vE, vF, vG}, meth@BBBB,其中,BBBB代表方法引用(参见上面介绍的方法定义及调用),vC~G为需要的参数,根据顺序一一对应
- 判断操作:
判断操作用来比较一个寄存器中的值,是否与目标寄存器中的值相等或不等,对应Java中的if语句,格式为:if-[test] v1,v2, [condition],其衍生操作还有专门与0进行比较的if-[test]z v1, [condition],其中[condition]为符合判断结果后的跳转条件,需要提前定义好。判断操作也通常和goto配合使用,用来实现循环或者if-else语句
需要注意的是,在Java中编写的if语句,往往在对应的Smali中,会变成相反的判断逻辑,如下面所示:
1 private void test() { 2 int a = 0; 3 int b = 1; 4 String result; 5 if (a > b) { 6 result = "a great than b"; 7 } else { 8 result = "a less than or equals b"; 9 } 10 }
上面的Java代码逻辑很简单——一个很简单的if语句,为了在Smali中看的更清楚,我只做了字符串赋值操作。下面是对应的Smali代码:
1 .method private test()V 2 .registers 4 3 4 .line 24 5 const/4 v0, 0x0 6 7 .line 25 8 .local v0, "a":I 9 const/4 v1, 0x1 10 11 .line 27 12 .local v1, "b":I 13 if-le v0, v1, :cond_7 14 15 .line 28 16 const-string v2, "a great than b" 17 18 .line 28 19 .local v2, "result":Ljava/lang/String; 20 goto :goto_9 21 22 .line 30 23 .end local v2 # "result":Ljava/lang/String; 24 :cond_7 25 const-string v2, "a less than or equals b" 26 27 .line 32 28 .restart local v2 # "result":Ljava/lang/String; 29 :goto_9 30 return-void 31 .end method
仔细观察可以发现:
if判断
属性操作:
属性操作的分为:取值(get)和赋值(put)
目标类型分为:数组(array)、实例(instance)和静态(static)三种,对应的缩写前缀就是a、i、s
长度类型分为:默认(什么都不写)、wide(宽,64位)、object(对象)、boolean、byte、char、short(后面几种就不解释了,和Java一致)
指令格式:[指令名] [源寄存器], [目标字段所在对象寄存器], [字段指针],示例代码如下,操作是为int型的类成员变量mIntA赋值为100:
1 const/16 v0, 0x64 2 3 iput v0, p0, Lcom/coderyuan/smali/MainActivity;->mIntA:I
下面列出用于实例字段的指令,其中i都可以换成a或者s,分别用于操作数组字段或者静态字段
举例:
以下Java代码是进行的是最基本的类成员变量的赋值、取值操作
private String mStringA; private int mIntA; private Activity mActivityA; public void fieldTest() { mStringA = "Put String to mStringA"; mIntA = 100; mActivityA = this; int len = mStringA.length(); }
对应的Smali代码如下:
# instance fields .field private mActivityA:Landroid/app/Activity; .field private mIntA:I .field private mStringA:Ljava/lang/String; # virtual methods .method public fieldTest()V .registers 2 .line 55 const-string v0, "Put String to mStringA" iput-object v0, p0, Lcom/coderyuan/smali/MainActivity;->mStringA:Ljava/lang/String; .line 56 const/16 v0, 0x64 iput v0, p0, Lcom/coderyuan/smali/MainActivity;->mIntA:I .line 57 iput-object p0, p0, Lcom/coderyuan/smali/MainActivity;->mActivityA:Landroid/app/Activity; .line 59 iget-object v0, p0, Lcom/coderyuan/smali/MainActivity;->mStringA:Ljava/lang/String; invoke-virtual {v0}, Ljava/lang/String;->length()I move-result v0 .line 60 .local v0, "len":I return-void .end method
根据Java和Smali代码的对比,值得注意的是,Smali获取类成员变量的方法,比较接近函数调用,只不过没有函数调用时的参数
其他指令:
参考:https://blog.csdn.net/yuanguozhengjust/article/details/80493963