Android逆向基础

听大佬的话,让学下Android逆向相关知识,正好以前也了解过一点PC端的逆向感觉还是不错的,正好最近在学习Android开发,了解下逆向也应该不是很困难吧。
可惜你不会汇编,硬伤,挖坑待填

APK的组成

如果你直接打开apk文件,如用RAR

  • asset、res
    这两个属于资源目录,但是他们也是有区别的
    res目录下的文件编译的时候会自动生成索引文件,在Java代码中用R.XX.XX引用
    asset目录下的文件不会生成索引,在Java代码中需要用AssetManager来访问
    一般来说除了视频、音频、游戏相关资源放在raw或asset下其他都会放在res下

  • META-INF文件夹
    一般存放工程的属性文件,如Manifest.MF

  • classes.dex
    java代码编译得到的,Dalvik VM能直接执行的

  • resource.arsc
    对res目录下的资源的一个索引文件,保留了原工程中strings.xml等文件内容

其实除了这些还有其他一些文件,这里不说了,搞开发的应该很清楚了 = =!
关于apktool的使用,这里暂时不说,因为我一直在用集成环境

Dalvik 相关介绍

上面也出现过这个词,到底是什么呢?这个也算是是学习逆向的基础吧,要了解下
Dalvik是google专门为Android操作系统设计的一个虚拟机,Dalvik字节码是专门为Dalvik VM设计的一种指令集。和java虚拟机不同的是,java虚拟机是基于堆栈设计的,而Dalvik虚拟机是基于寄存器的(专属文件执行格式dex),效率要比java虚拟机快很多。
每一个进程对应一个Dalvik 虚拟机实例,Dalvik 字节码文件是由Java字节码文件转换而来

需要注意的是通过Dalvik字节码我们也不能看到原来的逻辑代码,这时候就需要用一些工具来进行查看,但是最终我们要修改的文件是smali文件而不是Java文件。

PS:从Android 5.0版起,Android Runtime(ART)替换Dalvik成为系统内默认虚拟机。但应该不影响我们逆向,毕竟要向下兼容。

Smali入门

什么是Smali

作为我们在逆向接触最多的,有必要认真的进行学习。
简单的说,smali就是Dalvik VM内部执行的核心代码。它有自己的一套语法。
Smali,Baksmali分别是指安卓系统里的Java虚拟机(Dalvik)所使用的一种.dex格式文件的汇编器,反汇编器。其语法是一种宽松式的Jasmin/dedexer语法,而且它实现了.dex格式所有功能(注解,调试信息,线路信息等)。

Davlik字节码中,寄存器都是32位的,能够支持任何类型,64位类型(Long/Double)用2个寄存器表示;
Dalvik字节码有两种类型:原始类型;引用类型(包括对象和数组)

smali的数据类型

原始类型:

  • B—byte
  • C—char
  • D—double(64)
  • F—float
  • I—int
  • J—long (64)
  • S—short
  • V—void
  • Z—boolean

对象类型:

  • Lxxx/yyy—object
    Lpackage/name/ObjectName; 相当于Java中的package.name.ObjectName;

    ​解释如下:
    ​L:表示这是一个对象类型
    ​package/name:该对象所在的包
    ;:表示对象名称的结束

    这里提一下,关于内部类的表示是在内部类前加“$”符号
    就这样:LpackageName/objectName$subObjectName;

数组的表示形式:

  • [XXX—array
    可以看出数组就是在前面加了个[,例如[i == int[],二维数组就是加[[咯~
    以及[Ljava/lang/String 表示一个String的对象数组了

smali的方法/函数表示

举个例子:Lpackage/name/ObjectName;——>methodName(III)Z 详解如下:
Lpackage/name/ObjectName 表示类型
methodName 表示方法名
III 表示参数(这里表示为3个整型参数)
说明:方法的参数是一个接一个的,中间没有隔开;
方法以.method指令开始,以.end method指令结束。根据方法类型的不同,在方法指令开始前可能会用#加以注释,例如:#virtual methods表示这是一个虚方法,# direct methods表示这是一个直接方法。

foo ()V
没错,这就是void foo()。

foo (III)Z
这个则是boolean foo(int, int, int)。

foo (Z[I[ILjava/lang/String;J)Ljava/lang/String;
看出来这是String foo (boolean, int[], int[], String, long) 了吗?

Smail中字段表示

类似:Lpackage/name/ObjectName;——>FieldName:Ljava/lang/String;

Lpackage/name/ObjectName;包名
FieldName:字段名
Ljava/lang/String;字段类型

BakSmali生成的字段代码以.field指令开头,根据字段类型的不同,在字段指令的开始可能会有相应的注释,例如:# instance fields表示这是一个实例字段,# static fields表示这是一个静态字段。

Smali基本语法

.field private isFlag:z ——定义变量
.method——方法
.parameter——方法参数
.prologue——方法开始
.line 12——此方法位于第12行
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*” 如果vA等于vB则跳转到:cond
“if-ne vA, vB, :cond_
“ 如果vA不等于vB则跳转到:cond*
“if-lt vA, vB, :cond
“ 如果vA小于vB则跳转到:cond_
“if-ge vA, vB, :cond*” 如果vA大于等于vB则跳转到:cond
“if-gt vA, vB, :cond_
“ 如果vA大于vB则跳转到:cond*
“if-le vA, vB, :cond
“ 如果vA小于等于vB则跳转到:cond_
“if-eqz vA, :cond*” 如果vA等于0则跳转到:cond
“if-nez vA, :cond_
“ 如果vA不等于0则跳转到:cond*
“if-ltz vA, :cond
“ 如果vA小于0则跳转到:cond_
“if-gez vA, :cond*” 如果vA大于等于0则跳转到:cond
“if-gtz vA, :cond_
“ 如果vA大于0则跳转到:cond*
“if-lez vA, :cond
“ 如果vA小于等于0则跳转到:cond_

这里只是贴了下经常见到的,更详细的指令可以看这里,或者见后面的参考

smali中的继承、接口、包信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
 .class public Lcom/disney/WMW/WMWActivity; 
.super Lcom/disney/common/BaseActivity;
.source "WMWActivity.java"

# interfaces
.implements Lcom/burstly/lib/ui/IBurstlyAdListener;

# annotations
.annotation system Ldalvik/annotation/MemberClasses;
value = {
Lcom/disney/WMW/WMWActivity$MessageHandler;,
Lcom/disney/WMW/WMWActivity$FinishActivityArgs;
}
.end annotation

1-3行定义的是基本信息:这是一个由WMWActivity.java编译得到的smali文件(第3行),它是com.disney.WMW这个package下的一个类(第1行),继承自com.disney.common.BaseActivity(第2行)。

5-6行定义的是接口信息:这个WMWActivity实现了一个com.burstly.lib.ui这个package下(一个广告SDK)的IBurstyAdListener接口。

8-14行定义的则是内部类:它有两个成员内部类——MessageHandler和FinishActivityArgs

PS:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
.class <访问权限> [修饰关键字] <类名>
.super <父类名>
.source <源文件名>

//静态属性
# static fields
.field <访问权限> static [修饰关键字] <字段名>:<字段类型>

//实例属性
# instance fields
.field <访问权限> static [修饰关键字] <字段名>:<字段类型>

//方法
# direct methods
.method <访问权限> [修饰关键字] <方法原型>
<.locals> 局部变量个数
[.parameter] 方法参数
[.prologue] 代码开始,混淆后可能去掉
[.line] 在源码中的位置
<代码体>
.end method

关于寄存器的补充

在smali里的所有操作都必须经过寄存器来进行:本地寄存器用v开头数字结尾的符号来表示,如v0、v1、v2、…参数寄存器则使用p开头数字结尾的符号来表示,如p0、p1、p2、…特别注意的是,p0不一定是函数中的第一个参数,在非static函数中,p0代指“this”,p1表示函数的第一个参数,p2代表函数中的第二个参数…而在static函数中p0才对应第一个参数(因为Java的static方法中没有this方法)。本地寄存器没有限制,理论上是可以任意使用的

1
2
const/4 v0, 0x0  
iput-boolean v0, p0, Lcom/disney/WMW/WMWActivity;->isRunning:Z

在上面的两句中,使用了v0本地寄存器,并把值0x0存到v0中,然后第二句用iput-boolean这个指令把v0中的值存放到com.disney.WMW.WMWActivity.isRunning这个成员变量中。即相当于:this.isRunning = false;(上面说过,在非static函数中p0代表的是“this”,在这里就是com.disney.WMW.WMWActivity实例)

Smali中的成员变量

格式是:.field public/private [static][final] varName:<类型>

对于不同的成员变量有不同的指令
获取的指令有:iget、sget、iget-boolean、sget-boolean、iget-object、sget-object等,操作的指令有:iput、sput、iput-boolean、sput-boolean、iput-object、sput-object等。没有“-object”后缀的表示操作的成员变量对象是基本数据类型,带“-object”表示操作的成员变量是对象类型,特别地,boolean类型则使用带“-boolean”的指令操作。
下面是几个例子:

1
sget-object v0, Lcom/aaa;->ID:Ljava/lang/String;

sget-object就是用来获取变量值并保存到紧接着的参数的寄存器中,在这里,把上面出现的ID这个String成员变量获取并放到v0这个寄存器中,因为只指出了该类的所属的类型,可以看出这是个静态的(static fields)
注意:前面需要该变量所属的类的类型,后面需要加一个冒号和该成员变量的类型,中间是“->”表示所属关系。

1
iget-object v0, p0, Lcom/aaa;->view:Lcom/aaa/view;

只是由于不是static变量,不能仅仅指出该变量所在类的类型,还需要该变量所在类的实例
可以看到iget-object指令比sget-object多了一个参数,就是该变量所在类的实例,在这里就是p0即“this”。

获取array的还有aget和aget-object,指令使用和上述类似,略

put指令的使用和get指令是统一的,对比下面两个看效果更佳:

1
2
const/4 v3, 0x0  
sput-object v3, Lcom/aaa;->timer:Lcom/aaa/timer;

相当于 this.timer=null(null=0x0),因为是obj类型的如果是bool的话….你懂得~

1
2
3
.local v0, args:Landroid/os/Message;  
const/4 v1, 0x12
iput v1, v0, Landroid/os/Message;->what:I

相当于 args.what = 18; (args是Message的实例)

Smali中的函数调用

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
    很显然是调用静态函数的

    1
    invoke-static{},Lcom/aaa;->CheckSignature()z

    后面的一对大括号其实是调用该方法的实例+参数列表,由于这个方法是静态的,也不需要参数,所以括号内就是空了。

    1
    2
    const-string v0,"NDKLIB"
    invoke-static{v0},Ljava/lang/System;->loadLibrary(Ljava/lang/string;)V

    这个翻译过来就是调用了static void System.loadLibrary(String)来加载NDK的so库引用的方法,同样这里的V0就是参数“NDKLIB”了。

  • invoke-super

    很显然,调用父类方法的指令,一般用于调用onCreate、onDestroy等生命周期函数

  • invoke-direct

    调用private的函数

    1
    invoke-direct{p0},Landroid/app/TabActivity;-><init>()V

    这里就是一个定义在TabActivity中的一个private函数init()

  • invoke-virtual

    用于调用protected或public的函数
    修改smali的时候不要错用哦

    1
    2
    sget-object v0,Lcom/dddd;->bbb:Lcom/ccc
    invoke-virtual {v0,v1},Lcom/ccc;->Messages(Ljava/lang/object;)V

    v0就是bbb:Lcom/ccc
    v1就是传递给Messages方法的Ljava/lang/Object类型的参数

  • invoke-xxx/range

    当方法的参数多于5个时(含5个)就不能直接使用上面的指令了,而是在后面加上”/range”,range表示范围,使用的方法也有所不同。

    1
    invoke-direct/range {v0...v5},Lcmb/pb/ui/PBContainerActivity;->h(ILjava/lang/CharSequence;Ljava/lang/String;Landroid/content/intent;I)Z

    需要传递v0到v5一共6个参数,这时候大括号内的参数会采用省略的形式,并且需要连续。

前面说的都是函数的调用,貌似没有返回之类的指令呢,确实,在Java代码中调用函数和返回函数结果是一条语句完成的,而在smali里则需要分开来完成,在使用上述指令后,如果调用的函数返回非void,那么还需要用到move-result(返回基本数据类型)和move-result-object(返回对象)指令

1
2
3
const/4 v2, 0x0  
invoke-virtual {p0, v2}, Lcom/disney/WMW/WMWActivity;->getPreferences(I)Landroid/content/SharedPreferences;
move-result-object v1

v1保存的就是调用getPreferences(int)方法返回的SharedPreferences实例。

1
2
invoke-virtual {v2}, Ljava/lang/String;->length()I  
move-result v2

v2保存的则是调用String.length()返回的整型。

下面就是函数实体的例子了:

1
2
3
4
5
6
7
8
9
10
11
12
13
.method private ifReg()V  
.locals 2 //本函数中本地寄存器的个数
.prologue
const/4 v0,0x1 //v0赋值为1
.local v0,tempFlag:Z
if-eqz v0,:cond_0 //如果v0等于0则跳到cond_0标签
const/4 v1,0x1 //符合条件的分支 根据上面就是不等于0了
:goto_0 //标签
return v1 //返回v1的值
:cond_0 //标签
const/4 v1,0x0 //处于cond_0标签分支了
goto:goto_0 //跳到goto_0标签,就是执行return v1
.end method

参考

smali语法介绍
Android反编译-基础知识
smali语法-关键是指令集
smali指令集-官方

喜欢就请我吃包辣条吧!

评论框加载失败,无法访问 Disqus

你可能需要魔法上网~~