听大佬的话,让学下Android逆向相关知识,正好以前也了解过一点PC端的逆向感觉还是不错的,正好最近在学习Android开发,了解下逆向也应该不是很困难吧。可惜你不会汇编,硬伤,挖坑待填
APK的组成
如果你直接打开apk文件,如用RAR
asset、res
这两个属于资源目录,但是他们也是有区别的
res目录下的文件编译的时候会自动生成索引文件,在Java代码中用R.XX.XX引用
asset目录下的文件不会生成索引,在Java代码中需要用AssetManager来访问
一般来说除了视频、音频、游戏相关资源放在raw或asset下其他都会放在res下META-INF文件夹
一般存放工程的属性文件,如Manifest.MFclasses.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 | .class public Lcom/disney/WMW/WMWActivity; |
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 | .class <访问权限> [修饰关键字] <类名> |
关于寄存器的补充
在smali里的所有操作都必须经过寄存器来进行:本地寄存器用v开头数字结尾的符号来表示,如v0、v1、v2、…参数寄存器则使用p开头数字结尾的符号来表示,如p0、p1、p2、…特别注意的是,p0不一定是函数中的第一个参数,在非static函数中,p0代指“this”,p1表示函数的第一个参数,p2代表函数中的第二个参数…而在static函数中p0才对应第一个参数(因为Java的static方法中没有this方法)。本地寄存器没有限制,理论上是可以任意使用的
1 | const/4 v0, 0x0 |
在上面的两句中,使用了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 | const/4 v3, 0x0 |
相当于 this.timer=null(null=0x0),因为是obj类型的如果是bool的话….你懂得~
1 | .local v0, args:Landroid/os/Message; |
相当于 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
2const-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
2sget-object v0,Lcom/dddd;->bbb:Lcom/ccc
invoke-virtual {v0,v1},Lcom/ccc;->Messages(Ljava/lang/object;)Vv0就是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 | const/4 v2, 0x0 |
v1保存的就是调用getPreferences(int)方法返回的SharedPreferences实例。
1 | invoke-virtual {v2}, Ljava/lang/String;->length()I |
v2保存的则是调用String.length()返回的整型。
下面就是函数实体的例子了:
1 | .method private ifReg()V |
评论框加载失败,无法访问 Disqus
你可能需要魔法上网~~