一、对照 Java 代码与 Smali 代码差异
1.1 方法调用差异:Java vs Smali
Java 方法分类:
方法类型 | Java 示例 | Smali 指令 | 特点说明 |
---|---|---|---|
静态方法 | Utils.print("hi") |
invoke-static |
没有 this 指针 |
实例方法 | obj.show() |
invoke-virtual |
有 this,虚函数调用 |
构造函数 | new Person() |
invoke-direct |
用于构造对象 |
私有方法 | this.hidden() |
invoke-direct |
调用私有方法 |
接口方法 | list.size() |
invoke-interface |
调用接口方法 |
父类方法 | super.toString() |
invoke-super |
调用父类方法 |
动态调用(API) | Java 8 lambda 等 | invoke-polymorphic |
用于一些特殊函数,如反射调用 |
示例对照
1)静态方法调用
Java
java
Logger.log("test");
Smali
bash
const-string v0, "test"
invoke-static {v0}, Lcom/example/Logger;->log(Ljava/lang/String;)V
2)实例方法调用
Java
java
User u = new User();
u.say("hello");
Smali
bash
new-instance v0, Lcom/example/User;
invoke-direct {v0}, Lcom/example/User;-><init>()V
const-string v1, "hello"
invoke-virtual {v0, v1}, Lcom/example/User;->say(Ljava/lang/String;)V
3)构造器调用
构造器就是特殊方法**<init>
**
Java
java
Person p = new Person();
Smali
bash
new-instance v0, Lcom/example/Person;
invoke-direct {v0}, Lcom/example/Person;-><init>()V
1.2 控制流语句差异:Java vs Smali
if-else 语句
Java
java
if (a > b) {
doA();
} else {
doB();
}
Smali
bash
# a = v0, b = v1
if-le v0, v1, :else_block
invoke-static {}, Lcom/example/Utils;->doA()V
goto :end
:else_block
invoke-static {}, Lcom/example/Utils;->doB()V
:end
常用比较指令:
指令 | 条件 |
---|---|
if-eq |
等于(==) |
if-ne |
不等(!=) |
if-lt |
小于(<) |
if-gt |
大于(>) |
if-le |
小于等于(≤) |
if-ge |
大于等于(≥) |
switch 语句
Java
java
switch (x) {
case 1: f1(); break;
case 5: f5(); break;
default: fDefault();
}
Smali
bash
sparse-switch v0, :switch_data # v0 是 switch 的变量
goto :default # 如果没匹配任何 case,跳转 default
:switch_data
.sparse-switch
0x1 -> :case1
0x5 -> :case5
.end sparse-switch
:case1
invoke-static {}, Lcom/example/Util;->f1()V
goto :end
:case5
invoke-static {}, Lcom/example/Util;->f5()V
goto :end
:default
invoke-static {}, Lcom/example/Util;->fDefault()V
:end
# 后续逻辑继续执行
while 循环
Java
java
int i = 0;
while (i < 10) {
i++;
}
Smali
bash
const/4 v0, 0x0 # i = 0
:loop_start
const/4 v1, 0xA
if-ge v0, v1, :loop_end
add-int/lit8 v0, v0, 1
goto :loop_start
:loop_end
说明:
-
**
if-ge v0, v1, :loop_end
:**当 i ≥ 10 跳出循环 -
**
goto
:**用于跳转控制流
1.3 类字段与方法修饰符差异
字段定义差异
Java
java
public int count;
private String name;
Smali
bash
.field public count:I
.field private name:Ljava/lang/String;
字段类型缩写表:
Java 类型 | Smali 类型 |
---|---|
int |
I |
boolean |
Z |
byte |
B |
char |
C |
short |
S |
long |
J |
float |
F |
double |
D |
对象 | L类路径; |
字段访问指令
Java
java
this.count = 1;
int x = this.count;
Smali
bash
const/4 v1, 0x1
iput v1, v0, Lcom/example/MyClass;->count:I
iget v2, v0, Lcom/example/MyClass;->count:I
说明:
-
iput
= instance put,写入字段 -
iget
= instance get,读取字段
方法定义与修饰符
Java
java
public void print(int n) { ... }
private int getNum() { return num; }
Smali
bash
.method public print(I)V
.locals 1
# ...代码
.end method
.method private getNum()I
.locals 1
# ...代码
.end method
Java 修饰符 | Smali 前缀 |
---|---|
public |
public |
private |
private |
protected |
protected |
static |
static |
final |
final |
abstract |
abstract |
synchronized |
synchronized |
native |
native |
1.4 总结:Java ↔ Smali 快速对照表
Java 表达式 | Smali 表达 | 说明 |
---|---|---|
静态方法调用 | invoke-static |
无 this |
实例方法调用 | invoke-virtual |
用 this |
构造函数调用 | invoke-direct |
<init>() 构造器 |
if 条件控制 | if-eq / if-lt / if-ge 等 |
条件跳转 |
switch 分支 | .sparse-switch / .packed-switch |
分支跳转表 |
字段定义 | .field public/private 名:类型 |
类成员变量 |
字段访问 | iget / iput |
实例字段读写 |
方法定义 | .method public 名(参数)返回值 |
类方法定义 |
二、方法调用
2.1 Smali 方法调用指令分类
指令 | 调用类型 | 用途(Java 等价) |
---|---|---|
invoke-static |
静态方法 | ClassName.method() |
invoke-virtual |
实例方法(常用) | obj.method() |
invoke-direct |
构造方法、私有方法 | new Obj() ,this.privateMethod() |
invoke-super |
调用父类方法 | super.method() |
invoke-interface |
接口方法 | interfaceObj.method() |
invoke-virtual/range 等 |
处理参数多的 | 用于参数超过 5 个时 |
2.2 基础格式说明
通用格式:
bash
invoke-xxx {参数寄存器列表}, L类名;->方法名(参数类型)返回类型
说明:
-
{}
中写的是使用的寄存器(如{v0, v1}
) -
L类名;
是类路径(如Ljava/lang/String;
) -
(参数类型)
:方法的参数签名 -
返回类型
:返回值类型(V
表示 void)
2.3 详细讲解每个指令
1)invoke-static
:调用静态方法
Java 中 Class.method()
,如 Math.abs(-1)
示例:
bash
invoke-static {v0}, Ljava/lang/Math;->abs(I)I
说明:
-
调用 **
Math.abs(int)
**方法 -
**
v0
**是传入的参数 -
(I)I
表示:参数是int
,返回也是int
特点:
-
不需要实例对象
-
类方法、工具方法、
public static
方法都用这个调用
2)invoke-virtual
:调用实例方法(最常用)
Java 中**obj.method()
** ,例如**str.length()
**
示例:
bash
invoke-virtual {v0}, Ljava/lang/String;->length()I
说明:
-
v0
是一个**String
**对象 -
调用它的
length()
方法 -
()
:无参数,返回**int
**
特点:
-
用于非私有 的实例方法(包括**
public
、protected
**) -
支持虚方法分发(多态)
3)invoke-direct
:调用构造方法、私有方法
Java 中**new MyObject()
** 或 this.privateMethod()
示例 1:调用构造函数
bash
new-instance v0, Lcom/example/MyClass;
invoke-direct {v0}, Lcom/example/MyClass;-><init>()V
说明:
-
v0
是要构造的对象 -
调用其构造器**
MyClass()
** -
**
<init>()V
**是构造函数签名(返回类型是 void)
示例 2:调用私有方法
bash
invoke-direct {v0}, Lcom/example/MyClass;->myPrivateMethod()V
特点:
-
只能调用当前类的 private 方法 或 构造函数
-
无法用于调用继承的或 public/protected 方法
补充:invoke-super
调用父类的方法,通常在子类中使用**super.method()
**
示例:
bash
invoke-super {v0}, Lcom/example/ChildClass;->toString()Ljava/lang/String;
补充:invoke-interface
调用接口方法(Java 接口)
示例:
bash
invoke-interface {v0}, Ljava/util/List;->size()I
2.4 几个重要的调用场景示例
调用静态方法(工具类)
java
Utils.sayHello();
bash
invoke-static {}, Lcom/example/Utils;->sayHello()V
调用实例方法(普通对象方法)
java
myObj.getName();
bash
invoke-virtual {v0}, Lcom/example/MyClass;->getName()Ljava/lang/String;
调用构造器(创建对象)
java
new MyClass();
bash
new-instance v0, Lcom/example/MyClass;
invoke-direct {v0}, Lcom/example/MyClass;-><init>()V
调用私有方法(类内部)
java
this.secret();
bash
invoke-direct {v0}, Lcom/example/MyClass;->secret()V
2.5 寄存器数量限制说明
最多可以用 5 个寄存器调用 invoke-*
,如果超过 5 个参数(例如构造函数参数很多):
- 使用
invoke-* /range
版本:
bash
invoke-static/range {v0 .. v6}, Lcom/example/MyClass;->bigMethod(IJFLD)V
2.6 小结
指令 | 用途 | 说明 |
---|---|---|
invoke-static |
静态方法 | 不需要对象实例 |
invoke-virtual |
实例方法(普通调用) | 可被 override(多态) |
invoke-direct |
构造方法/私有方法 | 无法 override,调用 <init> |
invoke-super |
父类方法 | 用于**super.xxx() ** |
invoke-interface |
接口方法 | 用于接口对象 |
三、控制流语句
3.1 if-else
结构
Java 示例:
java
if (v0 == 1) {
Log.i("TAG", "Equal");
} else {
Log.i("TAG", "Not Equal");
}
Smali 实现:
bash
# v0 已有值
const/4 v1, 0x1
if-eq v0, v1, :equal # 如果 v0 == 1 跳转到 :equal
:not_equal
const-string v2, "TAG"
const-string v3, "Not Equal"
invoke-static {v2, v3}, Landroid/util/Log;->i(Ljava/lang/String;Ljava/lang/String;)I
goto :end
:equal
const-string v2, "TAG"
const-string v3, "Equal"
invoke-static {v2, v3}, Landroid/util/Log;->i(Ljava/lang/String;Ljava/lang/String;)I
:end
Smali 中常见条件跳转指令:
指令 | 含义 |
---|---|
if-eq |
相等跳转 |
if-ne |
不等跳转 |
if-lt |
小于跳转 |
if-gt |
大于跳转 |
if-le |
小于等于跳转 |
if-ge |
大于等于跳转 |
if-eqz |
v == 0 跳转(zero) |
if-nez |
v != 0 跳转 |
3.2 switch-case
Java 示例:
java
switch (v0) {
case 1: f1(); break;
case 5: f5(); break;
default: fDefault();
}
Smali 实现(sparse-switch):
bash
sparse-switch v0, :switch_data
goto :default
:switch_data
.sparse-switch
0x1 -> :case1
0x5 -> :case5
.end sparse-switch
:case1
invoke-static {}, Lcom/example/Util;->f1()V
goto :end
:case5
invoke-static {}, Lcom/example/Util;->f5()V
goto :end
:default
invoke-static {}, Lcom/example/Util;->fDefault()V
:end
packed-switch vs sparse-switch
类型 | 用法 |
---|---|
packed-switch |
case 是连续整数时使用 |
sparse-switch |
case 是稀疏整数时使用 |
3.3 while
循环
Java 示例:
java
int i = 0;
while (i < 5) {
Log.d("loop");
i++;
}
Smali 实现:
bash
const/4 v0, 0x0 # i = 0
:loop_start
const/4 v1, 0x5
if-ge v0, v1, :loop_end # if i >= 5 -> end
const-string v2, "loop"
invoke-static {v2}, Landroid/util/Log;->d(Ljava/lang/String;)I
add-int/lit8 v0, v0, 0x1 # i++
goto :loop_start
:loop_end
3.4 for
循环(其实就是 while)
Java 示例:
java
for (int i = 0; i < 3; i++) {
Log.e("for");
}
Smali 实现:
bash
const/4 v0, 0x0 # int i = 0
:for_begin
const/4 v1, 0x3
if-ge v0, v1, :for_end # if i >= 3 -> end
const-string v2, "for"
invoke-static {v2}, Landroid/util/Log;->e(Ljava/lang/String;)I
add-int/lit8 v0, v0, 0x1 # i++
goto :for_begin
:for_end
3.5 常见跳转逻辑总结表
Java 控制结构 | Smali 表达方式 |
---|---|
if (a == b) |
if-eq a, b, :label_true |
if (a != 0) |
if-nez a, :label_true |
while (...) |
标签 :begin + 条件跳转 + goto :begin |
for (...) |
与 while 相同结构 |
switch |
sparse-switch / packed-switch + 标签 |
四、类字段
4.1 什么是类字段
在 Java 中,字段就是类的成员变量:
java
public class Person {
public String name;
private static int count;
}
在 Smali 中,这些字段会写成:
bash
.field public name:Ljava/lang/String;
.field private static count:I
4.2 字段的 Smali 语法结构
bash
.field [修饰符] 字段名:类型
常见修饰符:
修饰符 | 含义 |
---|---|
public |
公有 |
private |
私有 |
protected |
受保护 |
static |
静态字段(属于类) |
final |
常量 |
volatile |
多线程可见性(较少见) |
transient |
不序列化(与 Java 一致) |
synthetic |
编译器自动生成(反混淆重要) |
4.3 字段类型表示法(Smali 中)
Java 类型 | Smali 符号 |
---|---|
int |
I |
boolean |
Z |
byte |
B |
char |
C |
short |
S |
long |
J |
float |
F |
double |
D |
void |
V (方法返回专用) |
String |
Ljava/lang/String; |
MyClass |
Lcom/example/MyClass; |
数组 | [I (int[]),[Ljava/lang/String; (String[]) |
4.4 Smali 中访问字段
字段分为 实例字段 和 静态字段,对应不同的访问指令。
1)实例字段(每个对象一份)
操作 | 指令名 | 示例 |
---|---|---|
读取字段 | iget |
iget v1, v0, Lcom/demo/User;->name:Ljava/lang/String; |
写入字段 | iput |
iput v2, v0, Lcom/demo/User;->name:Ljava/lang/String; |
2)静态字段(类级别共享)
操作 | 指令名 | 示例 |
---|---|---|
读取字段 | sget |
sget v0, Lcom/demo/User;->count:I |
写入字段 | sput |
sput v1, Lcom/demo/User;->count:I |
4.5 例子分析:完整的字段声明与访问流程
Java 示例:
java
public class User {
public String name;
private static int count = 0;
public void setName(String n) {
this.name = n;
count++;
}
}
Smali 对应(简化版):
bash
.class public Lcom/demo/User;
.super Ljava/lang/Object;
.field public name:Ljava/lang/String;
.field private static count:I
.method public setName(Ljava/lang/String;)V
.locals 1
# 把参数 n(p1)赋值给 this.name
iput-object p1, p0, Lcom/demo/User;->name:Ljava/lang/String;
# sget 静态字段
sget v0, Lcom/demo/User;->count:I
add-int/lit8 v0, v0, 0x1
sput v0, Lcom/demo/User;->count:I
return-void
.end method
4.6 几个实战技巧与细节
判断字段是静态还是实例
特征 | 静态字段 | 实例字段 |
---|---|---|
修饰符 | 带 static |
不带 static |
读取指令 | sget / sput |
iget / iput |
所属 | 类级别共享 | 每个对象一份 |
典型用途 | 常量、计数器 | 每个对象的属性,如 name |
Frida Hook 中字段的常用操作
javascript
// 获取实例字段
Java.use("com.demo.User").name.value
// 获取静态字段
Java.use("com.demo.User").count.value
// 设置字段
Java.use("com.demo.User").name.value = "hack"
4.7 .field 高级用法
初始值:
bash
.field public static final CONST:I = 0x5
表示该字段常量为 5(int
型)
4.8 小结
类型 | 声明指令 | 访问读取 | 写入修改 |
---|---|---|---|
实例字段 | .field name:Type |
iget / iget-object |
iput / iput-object |
静态字段 | .field static name:Type |
sget / sget-object |
sput / sput-object |
五、方法访问修饰符在 Smali 中如何体现
5.1 方法的 Smali 定义格式
bash
.method [修饰符] 方法名(参数)返回类型
比如:
bash
.method public static final doSomething(I)Ljava/lang/String;
这是一个:
-
public
公有 -
static
静态 -
final
不可重写的方法,接受一个
int
参数,返回一个String
。
5.2 常见修饰符说明(对照 Java)
Smali 修饰符 | Java 对应 | 含义解释 |
---|---|---|
public |
public |
任何类都可以调用(最常见) |
private |
private |
仅类内部调用 |
protected |
protected |
同包或子类中可调用 |
static |
static |
属于类本身,不依赖实例对象 |
final |
final |
不可被子类重写(继承中常见) |
abstract |
abstract |
抽象方法(无实现体,interface、abstract class 中出现) |
synthetic |
编译器自动添加 | 编译生成的方法(如桥接方法、Lambda 方法),用于反混淆 |
constructor |
构造函数 |
表示这是构造方法(即 <init> ) |
native |
native |
本地方法,用 JNI 实现(通常没代码体) |
bridge |
编译器桥接 | 泛型擦除后生成的桥接方法(一般逆不了逻辑) |
varargs |
T... args |
可变参数(只是标记,不影响 Smali 写法) |
5.3 完整示例分析
Java 源码
java
public class Demo {
public static final void log(String msg) {
Log.d("TAG", msg);
}
private int compute(int a, int b) {
return a + b;
}
protected abstract void work(); // 抽象方法
}
对应 Smali
bash
.method public static final log(Ljava/lang/String;)V
.locals 1
const-string v0, "TAG"
invoke-static {v0, p0}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I
return-void
.end method
.method private compute(II)I
.locals 1
add-int v0, p1, p2
return v0
.end method
.method protected abstract work()V
5.4 修饰符的实际作用和实战用途
修饰符 | 实战应用举例 |
---|---|
public |
可直接用 Frida/Java 调用(常用于静态分析) |
private |
需要 Frida Bypass 或 Java.use(...).$init.overload(...) |
static |
无需创建实例,Frida 中直接 .value 或 invokeStatic |
final |
子类不能 override,意味着 Hook 只能静态覆盖 |
abstract |
没有方法体,不能直接 Hook,要 Hook 实现类 |
synthetic |
混淆后生成的自动方法,可包含核心逻辑(Frida 分析重点) |
constructor |
对应 <init> ,实例化时会调用,可以 hook $init |
native |
没有 Smali 代码体,常需要 native 层分析或 trace |
5.5 Hook 时与修饰符的注意事项
1)Hook 静态方法(static)
javascript
Java.use("com.test.Demo").log.overload('java.lang.String').implementation = function(msg) {
console.log("log hooked: " + msg);
};
2)Hook 实例方法(非 static)
javascript
Java.use("com.test.Demo").compute.implementation = function(a, b) {
return a + b + 999;
};
3)Hook 构造方法 <init>
javascript
Java.use("com.test.Demo").$init.overload('int', 'java.lang.String').implementation = function(i, s) {
console.log("构造函数拦截:" + i + ", " + s);
return this.$init(i, s);
};
5.6 总结速查表
修饰符组合 | 含义 |
---|---|
.method public |
公有方法(类外可访问) |
.method private |
私有方法(类内访问,hook 时需绕过) |
.method static |
静态方法(不依赖实例) |
.method constructor |
构造方法 <init> |
.method abstract |
抽象方法(无实现体) |
.method public static final |
常量型工具函数 |
.method synthetic |
编译器生成,可能是混淆后的关键方法 |