一文对反编译android的smali进行梳理总结

1,971 阅读7分钟

1.反编译apk高频出现指令

const/4 v1, #int 2 表示存入int型常量2到v1,目的寄存器在第二个自己的低4位,常量2在更高的4位。

const/16 v0, #int 10 表示存入int型常量10到v0中。

move-object/from16 v1, v21 表示将v21寄存器中的对象引用到v1上。

check-cast v4 Test3 检查v4寄存器中的对象引用是否可以转换为Test3的实例。

Instance-of v0, v4, Test3 检查V4寄存器中的对象引用是否是Test3的实例,如果是V0存入非0值,否则v0存入0。

array-length v0, v1 计算v1寄存器中数组引用的元素长度并将长度存入V0。

cmpl-float v0, v6, v7 比较v6和v7的float值,并在v0存入int型的返回值。非数值默认为小于,如果参数为非数值将返回-1。

cmpg-float v0, v6, v7 比较v6和v7的float值,并在v0存入int型的返回值。非数值默认为大于,如果参数为非数值将返回1。

age v7,v3,v6 从数组获取一个int型值到v7,对象数组的引用位于v3,需获取的元素的索引位于v6。

age-byte v0, v0,v1 从byte数组获取一个byte值到v0,数组的引用位于v0,徐获取的元素的的索引位于v1。

aput v0, v3,v5 将v0的int值作为元素存入int数组,数组的引用位于v3,元素的索引位于v5。

move-result v0 移动上一次方法调用的返回值到v0。

move-result-wide v2 移动上一次方法调用的long/double型返回值到v2,v3。

new-array v2,v1, char[] 新建一个char数组,v1存入数组的长度,v2存入数组的引用。

invoke-virtual{v4,v0,v1,v2,v3}, Test.method:(IIII)V 表示调用Test的method方法,该指令共有5个参数,参数v4是this实例,v0,v1,v2,v3 是method方法的参数,返回值为Void

sput-object v0, test3.os1:Ljava/lang/object 将v0寄存器中的对象引用赋值到Test3的对象引用静态字段os1上。

iget-object v1,v2, LineReader.fis:Ljava/io/FileInputStream 读取FileInputStream对象引用字段fis到v1,v2寄存器中是LineReader实例的引用。

invoke-direct, java.lang.object.:()V 表示java.lang.object 的方法,参数v1是this的实例,()V表示方法没有参数,v是返回值为void。

intvoke-direct{0}, Landroid/app/TabActivity;->()v 这里init()就是定义TabActivity中的一个private函数。

iput-object v0, v2,LineRader.bis:Ljava/io/BufferedInputStream 将V0寄存器的值存入实例的对象引用字段bis,v2寄存器中是BufferedInputStream实例的引用。

rem-int v0 v2,v3计算v3%v2并将结果存入v0

add-int/lit8 v0, v0, 0x1 表示 v0++的意思

2.高频的结构

.method public constructor ()V #这是class的构造函数实现。

.registers 1 #这个方法所使用的寄存器数量。

invoke-direct {p0}, Ljava/lang/Object;->()V#调用Object的构造方法,p0相当于"this" 指针。

invoke-virtual {p0}, Ljava/lang/Object;->toString()Ljava/lang/String;#调用toString()函数。

其中for循环跟while循环的识别方法是for循环的加加放在后面(add-int/lit8 v0, v0, 0x1),而while循环的加加放在判断语句的后面。

new-instance v5, Ljava/lang/StringBuilder; 表示新建一个临时的StringBuilder对象,并将其引用赋值给v5寄存器。

invoke-direct {v1}, Ljava/lang/StringBuilder;->()V 调用stringBuilder中的构造方法。

invoke-virtual {v1, v2}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder; 表示{v1, v2}大括号中v1寄存器中存储的是StringBuilder对象的引用,调用StringBuilder中的append(String, str)方法,v2寄存器则是参数寄存器。

3.反编译的出现高频语法数据解析

#创建StringBuilder对象,并将其引用赋值给v1寄存器 new-instance v1, Ljava/lang/StringBuilder;

#调用StringBuilder中的构造方法 invoke-direct {v1}, Ljava/lang/StringBuilder;->()V

#v2寄存器中赋值为ther result: const-string v2, "the result:" #{v1,v2}大括号中v1寄存器中存储的是StringBuilder对象的引用.

#调用StringBuilder中的append(String str)方法,v2寄存器则是参数寄存器. invoke-virtual {v1, v2}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;

#获取上一个方法的执行结果,此时v1中存储的是append()方法执行后的结果,此处之所以仍然返回v1的
#原因在与append()方法返回的就是自身的引用 move-result-object v1

#继续调用append方法(),p0表示第一个参数寄存器,即上面提到的result参数 invoke-virtual {v1, p0}, Ljava/lang/StringBuilder;->append(I)Ljava/lang/StringBuilder;

#同上

move-result-object v1

#调用StringBuilder对象的toString()方法 invoke-virtual {v1}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;

#获取上一个方法执行结果,toString()方法返回了一个新的String对象,因此v1中此时存储了String对象的引用 move-result-object v1

new-instance 表示java类中的new语句 例如: new-instance v1, Lcom/nd/tq/GameEntity; invoke-direct {v1}, Lcom/nd/tq/GameEntity;->()V iput-object v1, p0, Lcom/nd/tq/MainActivity;->entity:Lcom/nd/tq/GameEntity; java代码 entity =new GameEntity();

内部类格式:[外部类]内部类.smali,如果是匿名的内部类那么格式:[外部类]内部类.smali ,如果是匿名的内部类那么格式:[外部类]NUM.smali。其中NUM代表从1开的数字。 对于Java文件中的每一个内部类,都会产生一个单独的smali文件,比如ActivityThread1.smali。这些文件的命名规范是如果是匿名内部类,则命名规则是外部类+1.smali。这些文件的命名规范是如果是匿名内部类,则命名规则是外部类++数字,否则的话是外部类++内部类的名字例如:classOuterclassInner反编译上述代码后会生成两个文件:Outer.smaliOuter+内部类的名字 例如: class Outer { class Inner{} } 反编译上述代码后会生成两个文件:Outer.smali 与OuterInner.smali

静态方法中没有this指针,也就是p0不再是this指针,而是第一个参数, 非静态方法p0表示this指针。 invoke-virtual {p0} 这个p0就是this参数: invoke-virtual {p0}, Lcom/nd/tq/NDSecDataType;->toString()Ljava/lang/String; 这个表示 NDSecDataTyep.toString() invoke-static {p1} 这个p1表示具体的参数非this 。 invoke-static {p1}, Ljava/lang/Integer;->parseInt(Ljava/lang/String;)I 表示 Integer.parseInt(p1)

long和double型的值占用两个寄存器。例如:一个在v0寄存器的double值实际占用V0、v1两个寄存器。 boolean的值实际是转成int型的值进行操作,存储实际是1和0.

mov-object vx,vy:移动对象引用,从vy到vx。
mov-result-wide vx 移动上一次方法调用的long/double型返回值到vx,vx+1。
const-class vx,类型ID:存入类对象常量到vx,通过类型id或类型
例如:const-class v0,test3 表示存入test3.class的引用到v0。
check-cast vx,类型ID:检查vx寄存器中的对象引用是否可以转换城类型ID对应类型的实例。 如不可转换,抛出ClassCastException异常,否则继续执行。 check-cast vx4,test3 检查v4寄存器中的对象引用是否可以转换为test3的实例
new-array vx,vy 根据类型id或者类型新建一个数组,vy存入数组的长度,vx存入数组的引用:例如 new-array v1,v2, char[]:表示 char v1[v2]

age开头的都跟数组获取有关系的 比如: aget vx,vy,vz 从int数组获取一个int型值到vx中,其中对象数组引用是vy,数组元素索引是vz
age后面-跟的条件 只不过是获取值的对一个条件,比如 aget-object vx,vy,xz 表示的是从对象引用数组获取一个对象引用到vx,对象数组的的引用位于vy获取元素的索引位于vz。

aput开头的都跟数组存入有关系的的 比如: aput vx,vy,vz 表示将vx的int值作为元素存入int数组,数组的引用位于vy,元素的索引位于vz。

iget开头的对寄存器实例引用的赋值 比如: iget vx,vy,字段ID 根据字段ID读取实例的int型字段到vx,vy寄存器中是该实例的引用,例如: iget v0,v1 test2.i6:I表示读取int型字段i6到v0中,其中v1寄存器是test2实例的引用。
iput开头的是将寄存器的值存入实例中:比如 iput vx,vy,字段ID 根据字段ID将Vx寄存器的值存入实例的int型字段,vy寄存器是该实例的引用。
例如: iput v0,v2 test2.i6:I 将v0寄存器的值存入实例的int型字段i6,v2寄存器中是test2实例的引用。

sget开头的是读取静态相关字段信息 sget vx 字段ID 根据字段ID读取静态int型字段到vx sput开头的是将寄存器的值赋值到静态字段中。
例如: sput vx,字段id 根据字段ID将vx寄存器中的值赋值到int型静态字段。

neg开头的表示相减的neg后面跟的是类型和寄存器 neg-int vx,vy 计算vx= -vy并将结果存入vx。

int-to-long vx,vy 转换vy寄存器中的int型值为long型值存入vx,vx+1 其中字段中间有个-to-的表示类型值的转换。