smali

一、什么是 Smali?

你可以把 Smali 理解为**Android 应用的 “汇编语言”**。就像电脑程序的 C 语言会被编译成汇编语言一样,Android 的 Java 代码会被编译成.dex文件,而 Smali 就是.dex文件反编译后的 “可读形式”。

  • 作用:修改 Smali 代码 = 直接修改 Android 应用的逻辑(比如破解付费功能、去除广告)
  • 特点:基于 “寄存器” 工作(所有数据都存在寄存器里,类似我们用的变量)
  • 工具:通过 APKTool 等工具可以把 APK 里的.dex文件转成 Smali 代码,修改后再转回去

二、最基础的结构:寄存器

Smali 里没有 “变量名”,所有数据都存在寄存器里,就像一个个小盒子,每个盒子有编号。

2.1 寄存器的两种类型

类型 命名 作用 例子
局部寄存器 v0, v1, v2… 存方法里的临时数据(类似 Java 的局部变量) v0 存一个数字,v1 存一个字符串
参数寄存器 p0, p1, p2… 存方法的参数(包括特殊的this 调用setName("张三")时,p1 存 “张三”

2.2 关键区别:静态方法 vs 非静态方法的参数寄存器

  • 非静态方法(普通方法):p0 固定是this(当前对象),p1 才是第一个参数
    例:Java 的void eat(String food),Smali 中 p0 是 “这个对象”,p1 是 food 参数
  • 静态方法(带 static 的方法):没有this,p0 直接是第一个参数
    例:Java 的static int add(int a, int b),Smali 中 p0 是 a,p1 是 b

三、类的声明(对应 Java 的 class)

3.1 基本格式(必须掌握)

1
2
3
4
.class 权限修饰符 类的全路径;  // 声明类(类似Java的"public class 包名.类名"
.super 父类全路径; // 声明父类(类似Java的"extends 父类"
.implements 接口全路径; // 声明实现的接口(类似Java的"implements 接口"
.source "Java文件名.java" // 对应Java源码文件(可选,混淆后可能没有)

3.2 实例(对照 Java 看更清楚)

Java 代码

1
2
3
public class Student extends Person implements Study {
// 类内容
}

对应的 Smali 代码

1
2
3
4
.class public Lcom/example/Student;  // L开头+包名/类名+;结尾(固定格式)
.super Lcom/example/Person; // 父类是Person
.implements Lcom/example/Study; // 实现Study接口
.source "Student.java" // 对应Java文件

3.3 特别注意

  • 所有类 / 接口的全路径必须以L开头,以;结尾(比如Ljava/lang/String;对应java.lang.String
  • 如果类在某个类内部(内部类),用$连接,比如Student$Score对应 Smali 的Lcom/example/Student$Score;

四、字段(对应 Java 的成员变量)

字段就是类里的变量(包括静态变量和非静态变量),声明格式很固定。

4.1 基本格式

1
.field 权限修饰符 静态修饰符 变量名:变量类型;
  • 权限修饰符:public/private/protected(和 Java 一样)
  • 静态修饰符:static(静态变量加,非静态不加)
  • 变量类型:Smali 的特殊写法(后面会详细讲)

4.2 实例(对照 Java)

Java 代码

1
2
3
4
5
public class Student {
private String name; // 非静态字段
public static int age; // 静态字段
protected boolean isMale; // 非静态字段
}

对应的 Smali 代码

1
2
3
4
5
6
7
8
9
10
11
.class public Lcom/example/Student;
.super Ljava/lang/Object;

# 非静态字段:private String name;
.field private name:Ljava/lang/String; // 类型是String(Ljava/lang/String;

# 静态字段:public static int age;
.field public static age:I // 类型是int(I)

# 非静态字段:protected boolean isMale;
.field protected isMale:Z // 类型是boolean(Z)

五、数据类型(Smali 的 “密码本”)

Smali 的类型写法和 Java 完全不同,必须死记!这是看懂 Smali 的基础。

5.1 基本类型(简单类型)

Smali 类型 对应 Java 类型 例子
I int(整数) 123、-45
Z boolean(布尔) true(1)、false(0)
B byte(字节) 0~255 的数
C char(字符) ‘A’、’ 中’
S short(短整数) -32768~32767
J long(长整数) 超过 int 范围的数(占 2 个寄存器)
F float(单精度浮点数) 3.14f
D double(双精度浮点数) 3.1415926(占 2 个寄存器)

5.2 引用类型(对象 / 数组)

类型 格式 对应 Java 例子
类类型 L 包名 / 类名; 类对象 Ljava/lang/String;String
数组类型 [元素类型 数组 [Iint[][Ljava/lang/String;String[]
多维数组 [[元素类型(几维就加几个 [) 多维数组 [[Iint[][][[[Bbyte[][][]

5.3 方法签名(方法的 “身份证”)

Smali 用 “方法签名” 表示方法,格式:方法名(参数类型列表)返回值类型
参数之间没有分隔符,直接连写!

例子

Smali 签名 对应 Java 方法
getName()Ljava/lang/String; String getName()
setAge(I)V void setAge(int age)
add(IF)F float add(int a, float b)
show(String[] arr, int num)Z → 签名是show([Ljava/lang/String;I)Z

六、方法的声明与实现

方法是 Smali 的核心,所有逻辑都在这里写。

6.1 方法的基本结构

1
2
3
4
5
6
7
8
9
.method 权限修饰符 静态修饰符 方法签名  // 方法声明(类似Java的"public static int add(int a)"
.locals N // 局部寄存器的数量(v0到vN-1)
.prologue // 代码开始的标记(固定加)
.line 10 // 对应Java源码的行号(可选,删除不影响运行)

# 方法体(具体逻辑)

返回指令 // 比如return-void(返回空)
.end method // 方法结束标记

6.2 实例:简单方法(对照 Java)

Java 代码

1
2
3
4
5
6
7
8
public class Student {
private String name;

// 非静态方法:设置名字
public void setName(String newName) {
this.name = newName;
}
}

对应的 Smali 代码

1
2
3
4
5
6
7
8
9
10
11
.method public setName(Ljava/lang/String;)V  // 方法签名:参数是String,返回void
.locals 0 // 不需要局部寄存器(v0都不用)
.prologue // 代码开始
.line 8 // 对应Java第8行

# 核心逻辑:this.name = newName
# p0是this(当前Student对象),p1是newName参数
iput-object p1, p0, Lcom/example/Student;->name:Ljava/lang/String;

return-void // 返回空
.end method

6.3 特殊方法:构造方法(

构造方法是创建对象时调用的方法,Smali 中固定叫<init>,必须调用父类的构造方法!

Java 代码

1
2
3
4
5
6
7
8
public class Student {
private String name;

// 构造方法
public Student(String name) {
this.name = name; // 给name赋值
}
}

对应的 Smali 代码

1
2
3
4
5
6
7
8
9
10
11
12
13
.method public <init>(Ljava/lang/String;)V  // 构造方法固定叫<init>
.locals 0
.prologue
.line 5

# 第一步:必须调用父类的构造方法(Object的构造方法)
invoke-direct {p0}, Ljava/lang/Object;-><init>()V // p0是this

# 第二步:给当前对象的name赋值(this.name = 参数name)
iput-object p1, p0, Lcom/example/Student;->name:Ljava/lang/String;

return-void
.end method

6.4 特殊方法:静态代码块(

Java 的静态代码块(static { ... })在 Smali 中对应<clinit>方法,用于初始化静态字段。

Java 代码

1
2
3
4
5
6
7
8
public class Student {
public static String school;

// 静态代码块
static {
school = "阳光小学";
}
}

对应的 Smali 代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
.field public static school:Ljava/lang/String;  // 静态字段school

# 静态代码块对应<clinit>方法(静态构造方法)
.method static constructor <clinit>()V // 固定写法
.locals 1 // 需要1个局部寄存器v0
.prologue
.line 4

# 给静态字段school赋值
const-string v0, "阳光小学" // v0存字符串"阳光小学"
sput-object v0, Lcom/example/Student;->school:Ljava/lang/String; // 存入静态字段

return-void
.end method

七、常用指令(Smali 的 “动词”)

指令是 Smali 的动作,比如 “赋值”、”调用方法”、”跳转” 等,必须掌握常用的。

7.1 赋值指令(给寄存器存数据)

指令 作用 例子
const/4 vA, 0xN 存一个小整数(-8 到 7)到 vA const/4 v0, 0x1 → v0=1
const/16 vA, 0xN 存中等整数(-32768 到 32767)到 vA const/16 v0, 0x100 → v0=256
const-string vA, “内容” 存字符串到 vA const-string v0, "你好" → v0=”你好”
const-class vA, L 类名; 存类的 Class 对象到 vA const-class v0, Ljava/lang/String; → v0=String.class

7.2 字段操作指令(存 / 取字段的值)

指令 作用 例子
iget-object vA, vB, 字段 从 vB 对象中取 “对象类型字段” 到 vA iget-object v0, p0, LStudent;->name:LString; → v0 = this.name
iput-object vA, vB, 字段 把 vA 的对象存入 vB 对象的 “对象类型字段” iput-object v0, p0, LStudent;->name:LString; → this.name = v0
sget-object vA, 静态字段 取 “静态对象字段” 到 vA sget-object v0, LStudent;->school:LString; → v0 = Student.school
sput-object vA, 静态字段 把 vA 的对象存入 “静态对象字段” sput-object v0, LStudent;->school:LString; → Student.school = v0
iget vA, vB, 字段 取 “基本类型字段”(int 等)到 vA iget v0, p0, LStudent;->age:I → v0 = this.age
iput vA, vB, 字段 存 “基本类型字段”(int 等) iput v0, p0, LStudent;->age:I → this.age = v0

7.3 方法调用指令(执行另一个方法)

指令 作用 例子
invoke-virtual {参数}, 方法签名 调用普通方法(非私有、非静态) invoke-virtual {p0}, LStudent;->getName()LString; → 调用 this.getName ()
invoke-static {参数}, 方法签名 调用静态方法 invoke-static {v0}, LInteger;->parseInt(LString;)I → 调用 Integer.parseInt (v0)
invoke-direct {参数}, 方法签名 调用私有方法或构造方法 invoke-direct {p0}, LObject;-><init>()V → 调用父类构造方法

调用后取返回值

  • 若方法返回对象(如 String):move-result-object vA → 把结果存到 vA
  • 若方法返回基本类型(如 int):move-result vA → 把结果存到 vA

例子:调用Integer.parseInt("123")并获取结果

1
2
3
4
const-string v0, "123"  // v0 = "123"
# 调用静态方法:Integer.parseInt("123")
invoke-static {v0}, Ljava/lang/Integer;->parseInt(Ljava/lang/String;)I
move-result v1 // 把返回的int值存到v1(v1=123)

7.4 条件跳转指令(做判断)

条件跳转就像 Java 的if语句,满足条件就跳到指定标签(如:cond_0)。

指令 作用(满足条件则跳转) 例子
if-eq vA, vB, :label vA == vB if-eq v0, v1, :cond_0 → 若 v0 等于 v1,跳去:cond_0
if-ne vA, vB, :label vA != vB if-ne v0, v1, :cond_0 → 若 v0 不等于 v1,跳去:cond_0
if-lt vA, vB, :label vA < vB if-lt v0, v1, :cond_0 → 若 v0 小于 v1,跳去:cond_0
if-gt vA, vB, :label vA > vB if-gt v0, v1, :cond_0 → 若 v0 大于 v1,跳去:cond_0

例子:判断年龄是否大于 18

1
2
3
4
5
6
7
8
9
10
11
12
13
# 假设v0存年龄(比如20)
const/4 v1, 0x12 # v1 = 18(0x12是18的十六进制)
if-gt v0, v1, :is_adult # 若v0>v1(20>18),跳去:is_adult

# 未成年人逻辑
const-string v2, "未成年"
goto :end # 跳过成年人逻辑

:is_adult # 成年人标签
const-string v2, "成年人"

:end # 结束标签
# 最终v2的值是"成年人"

7.5 数组操作指令(数组的增删改查)

指令 作用 例子
new-array vA, vB, [类型 创建长度为 vB 的数组,存到 vA new-array v0, v1, [I → 创建 int 数组,长度 v1,存到 v0
aget-object vA, vB, vC 取数组 vB 中索引 vC 的对象到 vA aget-object v0, v1, v2 → v0 = v1 [v2](v1 是 String [])
aput-object vA, vB, vC 把 vA 的对象存入数组 vB 的索引 vC aput-object v0, v1, v2 → v1[v2] = v0
array-length vA, vB 取数组 vB 的长度到 vA array-length v0, v1 → v0 = v1.length

例子:创建 String 数组并赋值

1
2
3
4
5
6
7
8
9
10
const/4 v0, 0x2  # v0=2(数组长度)
new-array v1, v0, [Ljava/lang/String; # 创建长度2的String数组,存到v1

const-string v2, "苹果" # v2="苹果"
aput-object v2, v1, 0x0 # 数组[0] = "苹果"

const-string v2, "香蕉" # v2="香蕉"
aput-object v2, v1, 0x1 # 数组[1] = "香蕉"

# 此时v1是["苹果", "香蕉"]

八、异常处理(try-catch)

和 Java 的try-catch一样,Smali 用.try.catch处理异常。

Java 代码

1
2
3
4
5
6
7
8
9
public void readFile() {
try {
// 可能出错的代码
FileReader fr = new FileReader("test.txt");
} catch (FileNotFoundException e) {
// 异常处理
e.printStackTrace();
}
}

对应的 Smali 代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
.method public readFile()V
.locals 2 # 局部寄存器v0、v1
.prologue
.line 6

# try块开始
.try_start_0
# 创建FileReader对象(可能抛异常)
const-string v0, "test.txt"
new-instance v1, Ljava/io/FileReader;
invoke-direct {v1, v0}, Ljava/io/FileReader;-><init>(Ljava/lang/String;)V
.try_end_0
# 捕获FileNotFoundException,跳去:catch_0
.catch Ljava/io/FileNotFoundException; {:try_start_0 .. :try_end_0} :catch_0

# 没有异常就直接返回
return-void

# catch块:处理异常
:catch_0
move-exception v0 # v0 = 捕获到的异常对象
# 调用printStackTrace()
invoke-virtual {v0}, Ljava/lang/Throwable;->printStackTrace()V
return-void
.end method

九、工具使用(动手实操必备)

学完语法,必须会用工具才能真正修改 Smali:

  1. APKTool(核心工具):

    • 功能:把 APK 反编译成 Smali 代码和资源文件
    • 命令:
      • 反编译:apktool d 你的应用.apk(会生成一个文件夹,里面的smali目录就是 Smali 代码)
      • 重新打包:修改完 Smali 后,apktool b 反编译的文件夹(生成的 APK 在dist目录)
  2. 辅助工具

    • AndroidKiller:可视化界面,集成反编译、编辑、打包(新手推荐)

    • JEB:把 Smali 转成类似 Java 的代码(方便分析逻辑,但修改仍需改 Smali)

Dalvik 字节码格式 | Android Open Source Project]

[smali介绍](Home · JesusFreke/smali Wiki)

  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2025 唐小唐
  • 访问人数: | 浏览次数: