Android逆向 Smali学习


描述符和数据类型

Smali数据类型,基本类型和引用类型.对象和数组都是引用类型

Java类型 Smali类型描述符
int I
long J
double D
boolean Z
float F
short S
char C
byte B
void V
对象类型 L
数组类型 [

下面我们来看下这三种类型的代码

基本类型

Java

1
2
3
4
// 由于有9个基本类型,这里我只写出两个int和void
private int a = 1;
// 变量是没有void类型的,所以用方法表示
private void getVoid(String a, int b){}

Smali

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# instance fields
.field private a:I
# 先不看方法里面的内容,后面会说
.method private getVoid(Ljava/lang/String;I)V
.registers 3
.param p1, "a" # Ljava/lang/String;
.param p2, "b" # I
.prologue
.line 4
return-void
.end method

.field表示一个字段,紧跟着private表示该字段的可见性,然后a表示字段名,I表示字段类型为int,中间用:隔开。

.method表示一个方法,private同样是可见性,getVoid(Ljava/lang/String;I)表示方法名为getVoid,参数为Ljava/lang/String;I类型,Ljava/lang/String;为对象类型,以L开头,;结尾,后面的V表示返回值为void

对象类型

Java

1
2
3
private String a;
private static String b;
private OtherClass c;

Smali

1
2
3
4
5
6
7
8
# static fields
.field private static b:Ljava/lang/String;
# instance fields
.field private a:Ljava/lang/String;
.field private c:Lcom/example/test/OtherClass;

L即上面定义的java类类型,表示后面跟着的是类的全限定名.比如java中的java.lang.String对应的描述是Ljava/lang/String;

注意:全限定名就是,一个类的完整类名如String的完整类名是java.lang.String,并将里面的.替换成/,然后在末尾加上;结尾,这样的就叫全限定名。

数组类型

Java

1
2
private int[] a = new int[1];
private String[] b = new String[1];

Smali

1
2
3
4
# instance fields
.field private a:[I
.field private b:[Ljava/lang/String;

[表示数组类型,后面跟着基本类型或者对象类型,一维数组用一个[,二维数组用两个[表示,如int[][]对应的描述就是[[I,而对象类型数组,则是后面加上类的全限定名,如String[][]对应的是[[Ljava/lang/String;

指令

Java对应Smali描述使用指令来完成的,下面我们来一一说明指令的作用。

文件头

我们在创建一个类的时候,会有包名,会有类名,类会继承父类,这就是文件头内容。

Java

1
2
3
4
package com.example.test;
public final class TestClass extends OtherClass {
}

Smali

1
2
3
.class public final Lcom/example/test/TestClass;
.super Lcom/example/test/OtherClass;
.source "TestClass.java"

这三行是每个文件的前三行,一定会有的。
.class声明该类的全限定名,声明了该类的可见性(访问权限),声明了该类为final(非权限修饰符),格式为:

1
2
3
.class <访问权限修饰符> [非权限修饰符] <类名>
# 访问权限修饰符即public, protected, private和default.
# 非权限修饰符则指的是final, abstract.

.super表示声明该类的父类,默认父类为Ljava/lang/Object;,格式为

1
.super <父类名>

.source表示声明该类的源文件名称,这里就是TestClass.java

1
.source <源文件名称>

文件内容

讲了文件头之后,接下来的就是文件正文了,也就是类的主体部分,主体内容大致包括接口描述、注解描述、字段描述、方法描述四个部分。

接口描述

在写一个类的时候,有时候会实现一个接口,我们来看看是转换的内容

1
2
3
4
package com.example.test;
public class TestClass implements OtherInterface, OtherClass {
}

Smali

1
2
3
4
5
6
7
.class public Lcom/example/test/TestClass;
.super Ljava/lang/Object;
.source "TestClass.java"
# interfaces
.implements Lcom/example/test/OtherInterface;
.implements Lcom/example/test/OtherClass;

.implements就表示实现的接口描述,后面只能描述一个接口,多个接口用多个.implements描述。

注解描述

1
2
3
4
5
6
7
8
9
10
11
package com.example.test;
import org.junit.Test;
public class TestClass {
@Test
public void annotationFun() {
}
}

Smali

1
2
3
4
5
6
7
8
9
10
11
# virtual methods
.method public annotationFun()V
.registers 1
# 这里就是注解了
.annotation runtime Lorg/junit/Test;
.end annotation
.prologue
.line 10
return-void
.end method

字段描述

1
2
private final String fieldOne = "123";
private static String fieldTwo = "123";

Smali

1
2
3
4
5
6
# static fields
.field private static fieldTwo:Ljava/lang/String;
# instance fields
.field private final fieldOne:Ljava/lang/String;

方法描述

1
2
3
4
5
6
7
8
package com.example.test;
public class TestClass {
public void annotationFun() {
}
}

Smali

1
2
3
4
5
6
7
8
# virtual methods
.method public annotationFun()V
.registers 1
.prologue
.line 7
return-void
.end method