幕后小黑

我的Android逆向学习之旅(二)——Dalvik指令集
关于第一篇文章中,我相信各位还有一大堆问号,还有很多细节没有讲到,看的有点懵逼,那是因为那些内容涉及Android...
扫描右侧二维码阅读全文
07
2018/06

我的Android逆向学习之旅(二)——Dalvik指令集

关于第一篇文章中,我相信各位还有一大堆问号,还有很多细节没有讲到,看的有点懵逼,那是因为那些内容涉及Android Dalvik指令集,所以这里单开一篇来讲解这些个问题
那么我们开始带着上一篇文章中的问题来看接下来的学习内容吧

什么是Smali

Smali是用于Dalvik(Android虚拟机)的反汇编程序实现,汇编工具(将Smali代码汇编为dex文件)为smali.jar,与之对应的baksmali.jar则是反汇编程序(戳这里下载),官方所说的基于Jasmin/dedexer语法,抱歉,其实我也不懂大千世界语法太多请恕我说一句——学不来。。。

Smali支持注解、调试信息、行数信息等基本Java的基本特性,可以说是很接近Java编译在JVM上的中间语言了,一般用来做Android程序的逆向工程,还可以用来搞事,你懂得就好莫要被抓了就行。。

基本类型

上一篇已经讲过了,这边来个总结吧,免得后面看下面的内容还要来回切换比较麻烦

类型关键字对应Java中的类型说明(关于 ‘64bits’ 不多解释,不懂的去补基础知识)
Vvoid,只能用于返回类型
Zboolean
Bbyte
Sshort
Cchar
Iint
Jlong (64 bits)
Ffloat
Ddouble (64 bits)

对象

Object类型,即引用类型的对象,在引用时,使用L开头,后面紧接着的是完整的包名,比如:

java.lang.String

对应的Smali语法则是

Ljava/lang/String

数组

数组定义比较有意思,一维数组在类型的左边加一个方括号,比如:

int[] a;

[I等同于Java的int[]

.field a:[I

方法声明及调用

官方Wiki中给出的Smali引用方法的模板如下:

    Lpackage/name/ObjectName;->MethodName(III)Z

第一部分Lpackage/name/ObjectName;用于声明具体的类型,以便JVM寻找

第二部分MethodName(III)Z,其中MethodName为具体的方法名,()中的字符,表示了参数数量和类型,即3个int型参数,Z为返回值的类型,即返回Boolean类型

由于方法的参数列表没有使用逗号这样的分隔符进行划分,所以只能从左到右,根据类型定义来区分参数个数,这一点需要比较仔细来观察

如果需要调用构造方法,则MethodName为:

寄存器声明及使用

在Smali中,如果需要存储变量,必须先声明足够数量的寄存器,1个寄存器可以存储32位长度的类型,比如Int,而两个寄存器可以存储64位长度类型的数据,比如Long或Double

声明可使用的寄存器数量的方式为:

.registers N

N代表需要的寄存器的总个数,同时,还有一个关键字

.locals N

它用于声明非参数的寄存器个数(包含在registers声明的个数当中),也叫做本地寄存器,只在一个方法内有效,但不常用,一般使用registers即可
参数表示方式为:

.param p1, "xxxx" #不要问我为什么不写p0,因为p0永远表示this

PS:事实上我们看到的 .registers 很少,反而 .locals 更多,至今我还没有写出过带 .registers 函数,不知道是怎么写的,有小伙伴知道请在留言区告知我,谢谢

Java代码截取如下

public class MyTest extends Test{
        String mString = "mString";

        public MyTest(){
            test(10,"xiaoming");
        }

        private void test(int age,String name){
            String log = "Log";
            //......
        }
}

Smali代码截取如下(注意看#号后面的注释讲解

.method private test(ILjava/lang/String;)V
    .locals 3  #包含局部变量2个。上篇讲过N-1
    .param p1, "age"    # I
    .param p2, "name"    # Ljava/lang/String;

    .line 13
    const-string v0, "Log"  #局部变量寄存器存放 Log 字符串

    .line 14
    .local v0, "log":Ljava/lang/String;  #定义局部变量log,将v0给log赋值
    #......
    return-void
.end method

此时寄存器的对应情况如下:

寄存器名称对应的引用
v0存储字符串Log
p0this
p1int 类型的 age
p2String 类型的 name

如果方法体内含有常量、变量等定义,则需要根据情况增加寄存器个数,数量只要满足需求,保证需要获取的值不被后面的赋值冲掉即可,方法有:存入类中的字段中(存入后,寄存器可被重新赋值),或者长期占用一个寄存器

换个通俗易懂说法。v0存储的“Log”在使用结束后,后面没有人在使用,那么后续可以继续使用v0来存储其他内容,如果知道最后一行代码还在用,那么v0寄存器长期被占用

Dalvik指令集

如果需要使用Smali编写程序,还需要掌握常用的Dalvik虚拟机指令,其合集称为Dalvik指令集。这些指令有点类似x86汇编的指令,但指令更多,使用也非常简单方便。最详尽的介绍,可以参考Android官方的Dalvik相关文档:官场传送门

下面上一波常用的,其他自己看官文,太多了,自己慢慢记吧。

赋值指令

指令说明
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、/16、/hight16) v1 xxx将常量xxx赋值给v1寄存器,/后的类型,需要根据xxx的长度选择
const-wide(/16、/32、/hight16) v1 xxx将双字型常量xxx赋值给v1寄存器,/后的类型,需要根据xxx的长度选择
const-string(/jumbo) v1 “aaa”将字符串常量”aaa”赋给v1寄存器,过长时需要加上jumbo
const-class v1 La/b/TargetClass将Class常量a.b.TargetClass赋值给v1,等价于a.b.TargetClass.class

调用指令

指令说明
invoke-virtual用于调用一般的,非private、非static、非final、非构造函数的方法,它的第一个参数往往会传p0,也就是this指针
invoke-super用于调用父类中的方法,其他和invoke-virtual保持一致
invoke-direct用于调用private修饰的方法,或者构造方法
invoke-static用于调用静态方法,比如一些工具类
invoke-interface用于调用interface中的方法

判断指令

指令说明
if-eq v1,v2判断两个寄存器中的值是否相等
if-ne v1,v2判断两个寄存器中的值是否不相等
if-lt v1,v2判断v1寄存器中的值是否小于v2寄存器中的值(less than)
if-ge v1,v2判断v1寄存器中的值是否大于或等于v2寄存器中的值(great than or equals)
if-gt v1,v2判断v1寄存器中的值是否大于v2寄存器中的值(great than)
if-le v1,v2判断v1寄存器中的值是否小于或等于v2寄存器中的值(less than or equals)

Java代码截取如下:

private void test() {
    int a = 0;
    int b = 1;
    String result;
    if (a > b) {
        result = "a great than b";
    } else {
        result = "a less than or equals b";
    }
}

Smali代码截取如下:

.method private test()V
    .registers 4

    .line 24
    const/4 v0, 0x0

    .line 25
    .local v0, "a":I
    const/4 v1, 0x1

    .line 27
    .local v1, "b":I
    if-le v0, v1, :cond_7

    .line 28
    const-string v2, "a great than b"

    .line 28
    .local v2, "result":Ljava/lang/String;
    goto :goto_9

    .line 30
    .end local v2    # "result":Ljava/lang/String;
    :cond_7
    const-string v2, "a less than or equals b"

    .line 32
    .restart local v2    # "result":Ljava/lang/String;
    :goto_9
    return-void
.end method

属性指令

属性操作的分为:取值(get)和赋值(put)
目标类型分为:数组(array)、实例(instance)和静态(static)三种,对应的缩写前缀就是a、i、s
长度类型分为:默认(什么都不写)、wide(宽,64位)、object(对象)、boolean、byte、char、short(后面几种就不解释了,和Java一致)

指令说明
iget取值,用于操作int值类型
iget-wide取值,用于操作wide型字段
iget-object取值,用于操作对象引用
iget-boolean取值,用于操作布尔类型
iget-byte取值,用于操作字节类型
iget-char取值,用于操作字符类型
iget-char取值,用于操作字符类型
iget-short取值,用于操作short类型
iput赋值,用于操作int值类型
iput-xxxx取值用于操作xxxx类型,和iget有一套对应的,自行脑补

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

所有常规使用都在这里
关于加减乘除运算等表达式去参考官方文档或者动手写写代码吧。

避免某些人看不见上面传送链接再附一次传送门
官方文档走你:
https://source.android.com/devices/tech/dalvik/dalvik-bytecode#instructions

《我的Android逆向学习之旅(三)——实战》

1核/512M/20M独享/洛杉矶/128元/年 ---- 双十一特价 102.4元/年
2核/512M/20M独享/洛杉矶CN2 GT/229元/年/39元/月 ---- 双十一特价 183.2元/年/31.2元/月
2核/1G/5M独享/贵州电信/260元/年/40元/月 ---- 双十一特价 208元/年/32元/月
双十一活动时间:10月29日-11月19日!

Last modification:June 28th, 2018 at 08:28 pm
If you think my article is useful to you, please feel free to appreciate

Leave a Comment