当前位置: 代码网 > it编程>编程语言>Java > Java 字节码与Smali 语法基础实战案例

Java 字节码与Smali 语法基础实战案例

2026年01月26日 Java 我要评论
3.1 java 字节码与 dex 字节码3.1.1 java 字节码(.class)简介java 字节码是 java 源代码编译后的中间表示形式,运行在 java 虚拟机(jvm)上。编译流程:ja

3.1 java 字节码与 dex 字节码

3.1.1 java 字节码(.class)简介

java 字节码是 java 源代码编译后的中间表示形式,运行在 java 虚拟机(jvm)上。

编译流程:

java 源码 (.java) 
    ↓ javac
java 字节码 (.class)
    ↓ jvm
机器码执行

java 字节码特点:

  • 平台无关的中间代码
  • 基于栈的虚拟机(stack-based vm)
  • 指令集相对简单
  • 面向对象设计

3.1.2 dex 字节码(.dex)简介

dex(dalvik executable)是 android 平台上的字节码格式,针对移动设备进行了优化。

转换流程:

java 源码 (.java)
    ↓ javac
java 字节码 (.class)
    ↓ dx/d8
dex 字节码 (.dex)
    ↓ dalvik/art vm
机器码执行

dex 字节码特点:

  • 多个 .class 文件合并为一个 .dex
  • 基于寄存器的虚拟机(register-based vm)
  • 指令集更紧凑
  • 优化了内存使用

3.1.3 dalvik 虚拟机与 jvm 的区别

特性jvmdalvik vm
字节码格式.class.dex
虚拟机类型栈式虚拟机寄存器虚拟机
文件组织每个类一个文件多个类合并到一个文件
指令集基于栈操作基于寄存器操作
内存占用较大较小(优化后)
执行方式解释执行/jit解释执行/jit/aot

3.1.4 指令集映射关系

java 字节码示例:

// java 源码
int a = 10;
int b = 20;
int c = a + b;

对应的 java 字节码(javap -c):

0: bipush        10    // 将 10 压入栈
2: istore_1           // 弹出栈顶值,存储到局部变量 1 (a)
3: bipush        20    // 将 20 压入栈
5: istore_2           // 弹出栈顶值,存储到局部变量 2 (b)
6: iload_1            // 加载局部变量 1 (a) 到栈
7: iload_2            // 加载局部变量 2 (b) 到栈
8: iadd               // 弹出两个值,相加,结果压入栈
9: istore_3           // 弹出栈顶值,存储到局部变量 3 (c)

对应的 smali 代码:

const/16 v0, 0xa      # 将 10 加载到寄存器 v0
const/16 v1, 0x14     # 将 20 加载到寄存器 v1
add-int v2, v0, v1    # v2 = v0 + v1

关键区别:

  • jvm:使用栈,需要 push/pop 操作
  • dalvik:使用寄存器,直接操作寄存器

3.2 smali 语法基础

3.2.1 寄存器系统

smali 使用寄存器来存储局部变量和方法参数,这是与 java 字节码最大的区别。

寄存器命名规则

局部变量寄存器:

  • v0, v1, v2, … vn:局部变量寄存器
  • 从 0 开始编号
  • 通常用于存储方法内的局部变量

参数寄存器:

  • p0, p1, p2, … pn:参数寄存器
  • p0 在非静态方法中通常是 this 引用
  • p1, p2, … 是方法的实际参数

寄存器数量限制:

  • 单个方法最多可以使用 65536 个寄存器(理论值)
  • 实际使用中,寄存器数量受方法复杂度限制
  • 寄存器数量过多会导致编译失败

寄存器使用示例

java 代码:

public int add(int a, int b) {
    int result = a + b;
    return result;
}

对应的 smali 代码:

.method public add(ii)i
    .registers 4        # 使用 4 个寄存器
    # 寄存器分配:
    # p0 = this (非静态方法的 this 引用)
    # p1 = a (第一个参数)
    # p2 = b (第二个参数)
    # v0 = result (局部变量)
    .prologue
    .line 1
    add-int v0, p1, p2  # v0 = p1 + p2
    return v0           # 返回 v0
.end method

⚠️ 重要提示:

  • 参数寄存器从 p0 开始,不是 p1
  • 在非静态方法中,p0this,实际参数从 p1 开始
  • 在静态方法中,参数从 p0 开始

3.2.2 数据类型

基本类型

java 类型smali 类型大小说明
booleanz1 字节布尔值
byteb1 字节字节
shorts2 字节短整型
charc2 字节字符
inti4 字节整型
longj8 字节长整型
floatf4 字节单精度浮点
doubled8 字节双精度浮点
voidv-无返回值

引用类型

类类型:

ljava/lang/string;        # string 类
landroid/app/activity;   # activity 类

数组类型:

[i                       # int[]
[[i                      # int[][]
[ljava/lang/string;      # string[]

完整类型示例:

// java 代码
string[] names;
int[][] matrix;
# smali 代码
.field names:[ljava/lang/string;
.field matrix:[[i

3.2.3 方法调用约定

smali 中有多种方法调用指令,用于不同的调用场景。

invoke-virtual(虚方法调用)

用途: 调用实例方法,支持多态

语法:

invoke-virtual {参数寄存器}, 类名;->方法名(参数类型)返回类型

示例:

// java 代码
string str = "hello";
int len = str.length();
# smali 代码
const-string v0, "hello"
invoke-virtual {v0}, ljava/lang/string;->length()i
move-result v1  # 将返回值存储到 v1

invoke-static(静态方法调用)

用途: 调用静态方法

语法:

invoke-static {参数寄存器}, 类名;->方法名(参数类型)返回类型

示例:

// java 代码
int max = math.max(10, 20);
# smali 代码
const/16 v0, 0xa      # 10
const/16 v1, 0x14     # 20
invoke-static {v0, v1}, ljava/lang/math;->max(ii)i
move-result v2        # max 结果存储到 v2

invoke-direct(直接方法调用)

用途: 调用构造函数、私有方法、final 方法

语法:

invoke-direct {参数寄存器}, 类名;->方法名(参数类型)返回类型

示例:

// java 代码
string str = new string("hello");
# smali 代码
new-instance v0, ljava/lang/string;
const-string v1, "hello"
invoke-direct {v0, v1}, ljava/lang/string;-><init>(ljava/lang/string;)v

invoke-interface(接口方法调用)

用途: 调用接口方法

语法:

invoke-interface {参数寄存器}, 接口名;->方法名(参数类型)返回类型

invoke-super(父类方法调用)

用途: 调用父类方法

语法:

invoke-super {参数寄存器}, 父类名;->方法名(参数类型)返回类型

调用指令对比表:

指令用途多态支持性能
invoke-virtual实例方法较慢
invoke-static静态方法
invoke-direct构造函数/私有方法
invoke-interface接口方法最慢
invoke-super父类方法

3.2.4 条件跳转指令

条件跳转指令用于实现 if-else、循环等控制流。

基本条件跳转

相等比较:

if-eq va, vb, :label    # if (va == vb) goto label
if-ne va, vb, :label    # if (va != vb) goto label

大小比较:

if-lt va, vb, :label    # if (va < vb) goto label
if-le va, vb, :label    # if (va <= vb) goto label
if-gt va, vb, :label    # if (va > vb) goto label
if-ge va, vb, :label    # if (va >= vb) goto label

零值比较:

if-eqz va, :label       # if (va == 0) goto label
if-nez va, :label       # if (va != 0) goto label
if-ltz va, :label       # if (va < 0) goto label
if-lez va, :label       # if (va <= 0) goto label
if-gtz va, :label       # if (va > 0) goto label
if-gez va, :label       # if (va >= 0) goto label

示例:

// java 代码
if (a == b) {
    return true;
} else {
    return false;
}
# smali 代码
if-eq p1, p2, :equal    # if (a == b) goto equal
const/4 v0, 0x0          # v0 = false
return v0                # return false

:equal
const/4 v0, 0x1          # v0 = true
return v0                # return true

3.3 smali 指令集详解

3.3.1 invoke-* 系列(方法调用)

invoke-virtual

完整示例:

// java 代码
public void test() {
    string str = "hello";
    int len = str.length();
    system.out.println(len);
}
.method public test()v
    .registers 3
    .prologue
    const-string v0, "hello"        # v0 = "hello"
    invoke-virtual {v0}, ljava/lang/string;->length()i
    move-result v1                  # v1 = str.length()
    sget-object v0, ljava/lang/system;->out:ljava/io/printstream;
    invoke-virtual {v0, v1}, ljava/io/printstream;->println(i)v
    return-void
.end method

invoke-static

// java 代码
int result = math.max(10, 20);
const/16 v0, 0xa      # v0 = 10
const/16 v1, 0x14     # v1 = 20
invoke-static {v0, v1}, ljava/lang/math;->max(ii)i
move-result v2        # v2 = math.max(10, 20)

3.3.2 if-* 系列(条件判断)

完整 if-else 示例

// java 代码
public int compare(int a, int b) {
    if (a > b) {
        return 1;
    } else if (a < b) {
        return -1;
    } else {
        return 0;
    }
}
.method public compare(ii)i
    .registers 3
    .param p1, "a"    # i
    .param p2, "b"    # i
    .prologue
    if-gt p1, p2, :check_less    # if (a > b) goto check_less
    const/4 v0, 0x1              # v0 = 1
    return v0                     # return 1
    :check_less
    if-lt p1, p2, :equal         # if (a < b) goto equal
    const/4 v0, -0x1             # v0 = -1
    return v0                     # return -1
    :equal
    const/4 v0, 0x0              # v0 = 0
    return v0                     # return 0
.end method

3.3.3 move-* 系列(数据移动)

常用 move 指令:

move va, vb              # va = vb (32位)
move-wide va, vb         # va = vb (64位,用于 long/double)
move-object va, vb       # va = vb (对象引用)
move-result va           # va = 方法返回值 (32位)
move-result-wide va      # va = 方法返回值 (64位)
move-result-object va    # va = 方法返回值 (对象)
move-exception va        # va = 异常对象

示例:

// java 代码
int a = 10;
int b = a;
int c = getvalue();
const/16 v0, 0xa         # v0 = 10 (a)
move v1, v0              # v1 = v0 (b = a)
invoke-static {}, lcom/example/test;->getvalue()i
move-result v2           # v2 = getvalue() (c)

3.3.4 const-* 系列(常量加载)

常用 const 指令:

const/4 va, #+b          # va = 符号扩展的 4 位立即数 (-8 到 7)
const/16 va, #+b         # va = 符号扩展的 16 位立即数 (-32768 到 32767)
const va, #+b            # va = 32 位立即数
const-wide/16 va, #+b    # va = 符号扩展的 16 位立即数 (long)
const-wide/32 va, #+b    # va = 符号扩展的 32 位立即数 (long)
const-wide va, #+b       # va = 64 位立即数 (long)
const/high16 va, #+b     # va = 0xbbbb0000 (高16位)
const-string va, string  # va = 字符串引用
const-class va, type     # va = 类对象引用

示例:

// java 代码
int a = 10;
int b = 1000;
long c = 100000l;
string str = "hello";
const/16 v0, 0xa         # v0 = 10
const/16 v1, 0x3e8       # v1 = 1000
const-wide/32 v2, 0x186a0  # v2 = 100000l
const-string v4, "hello"   # v4 = "hello"

3.3.5 其他常用指令

算术运算

add-int va, vb, vc       # va = vb + vc
sub-int va, vb, vc       # va = vb - vc
mul-int va, vb, vc       # va = vb * vc
div-int va, vb, vc       # va = vb / vc
rem-int va, vb, vc       # va = vb % vc

逻辑运算

and-int va, vb, vc       # va = vb & vc
or-int va, vb, vc        # va = vb | vc
xor-int va, vb, vc       # va = vb ^ vc
shl-int va, vb, vc       # va = vb << vc
shr-int va, vb, vc       # va = vb >> vc
ushr-int va, vb, vc      # va = vb >>> vc

数组操作

new-array va, vb, type   # va = new type[vb]
array-length va, vb       # va = vb.length
aget va, vb, vc          # va = vb[vc]
aput va, vb, vc          # vb[vc] = va

字段操作

iget va, vb, field       # va = vb.field
iput va, vb, field       # vb.field = va
sget va, field           # va = static_field
sput va, field           # static_field = va

3.4 smali 中的类、方法和字段

3.4.1 类定义

java 代码:

package com.example;
public class myclass {
    // ...
}

smali 代码:

.class public lcom/example/myclass;
.super ljava/lang/object;
.source "myclass.java"

类修饰符:

.class public final lcom/example/myclass;    # public final class
.class public abstract lcom/example/myclass;  # public abstract class
.class public lcom/example/myclass;          # public class

3.4.2 方法定义

java 代码:

public int add(int a, int b) {
    return a + b;
}

smali 代码:

.method public add(ii)i
    .registers 4
    .param p1, "a"    # i
    .param p2, "b"    # i
    .prologue
    .line 1
    add-int v0, p1, p2
    return v0
.end method

方法修饰符:

java 修饰符smali 表示
public.method public
private.method private
protected.method protected
static.method public static
final.method public final
synchronized.method public synchronized

3.4.3 字段定义

java 代码:

public class myclass {
    private int value;
    public static string name;
}

smali 代码:

.class public lcom/example/myclass;
.super ljava/lang/object;
# 实例字段
.field private value:i
# 静态字段
.field public static name:ljava/lang/string;

字段修饰符:

java 修饰符smali 表示
public.field public
private.field private
protected.field protected
static.field public static
final.field public final

3.4.4 异常处理

java 代码:

try {
    int result = 10 / 0;
} catch (arithmeticexception e) {
    e.printstacktrace();
}

smali 代码:

.method public test()v
    .registers 3
    .prologue
    .line 1
    :try_start_0
    const/16 v0, 0xa
    const/4 v1, 0x0
    div-int v0, v0, v1
    :try_end_0
    .catch ljava/lang/arithmeticexception; {:try_start_0 .. :try_end_0} :catch_0
    return-void
    :catch_0
    move-exception v0
    invoke-virtual {v0}, ljava/lang/arithmeticexception;->printstacktrace()v
    return-void
.end method

⚠️ 重要提示:

  • try_start_xtry_end_x 标记 try 块的开始和结束
  • catch 指令指定捕获的异常类型和处理位置
  • move-exception 将异常对象移动到寄存器

3.5 工具使用:字节码转换与分析

3.5.1 使用 javac 编译 java 源码

基本用法:

# 编译单个文件
javac helloworld.java
# 编译多个文件
javac *.java
# 指定输出目录
javac -d build/ src/**/*.java
# 指定 classpath
javac -cp libs/*.jar src/**/*.java

示例:

# 创建测试文件
cat > test.java << 'eof'
public class test {
    public static void main(string[] args) {
        system.out.println("hello world");
    }
}
eof
# 编译
javac test.java
# 查看生成的 .class 文件
ls -la test.class

3.5.2 使用 dx/d8 转换为 dex

dx 工具(旧版,android sdk build tools < 28):

# 转换单个 .class 文件
dx --dex --output=classes.dex test.class
# 转换整个目录
dx --dex --output=classes.dex build/classes/
# 包含外部库
dx --dex --output=classes.dex --libs=android.jar build/classes/

d8 工具(新版,android sdk build tools >= 28):

# 转换 .class 文件
d8 test.class --output .
# 转换整个目录
d8 build/classes/**/*.class --output .
# 指定 android api 级别
d8 --lib android.jar --output . build/classes/**/*.class

验证 dex 文件:

# 使用 dexdump 查看 dex 内容
dexdump -d classes.dex | head -50

3.5.3 使用 baksmali 反编译为 smali

安装 baksmali:

# 下载 baksmali
wget https://github.com/jesusfreke/smali/releases/download/v2.5.2/baksmali-2.5.2.jar
# 或使用 homebrew (macos)
brew install smali

基本用法:

# 反编译 dex 文件
java -jar baksmali-2.5.2.jar d classes.dex -o smali_output/
# 指定 api 级别(重要!)
java -jar baksmali-2.5.2.jar d classes.dex -o smali_output/ --api-level 28
# 反编译 apk 中的 dex
java -jar baksmali-2.5.2.jar d app.apk -o smali_output/

查看反编译结果:

# 查看目录结构
tree smali_output/
# 查看特定类的 smali 代码
cat smali_output/com/example/test.smali

3.5.4 使用 smali 编译回 dex

基本用法:

# 编译 smali 目录为 dex
java -jar smali-2.5.2.jar a smali_output/ -o classes_new.dex
# 指定 api 级别
java -jar smali-2.5.2.jar a smali_output/ -o classes_new.dex --api-level 28

验证编译结果:

# 使用 dexdump 验证
dexdump -d classes_new.dex | head -50

3.5.5 使用文本编辑器阅读和修改 smali

推荐编辑器:

  1. vs code + smali 语法高亮插件
  2. sublime text + smali 插件
  3. android studio(内置 smali 支持)

vs code 配置:

// .vscode/settings.json
{
    "files.associations": {
        "*.smali": "smali"
    }
}

安装 smali 插件:

  1. 打开 vs code
  2. 搜索 “smali” 插件
  3. 安装 “smali” 或 “smali language support”

3.6 代码对照:java → 字节码 → smali

3.6.1 示例 1:字符串拼接

java 源码:

public class stringtest {
    public static string concat(string a, string b) {
        return a + b;
    }
}

java 字节码(javap -c):

public static java.lang.string concat(java.lang.string, java.lang.string);
  code:
     0: new           #2  // class java/lang/stringbuilder
     3: dup
     4: invokespecial #3  // method java/lang/stringbuilder."<init>":()v
     7: aload_0
     8: invokevirtual #4  // method java/lang/stringbuilder.append:(ljava/lang/string;)ljava/lang/stringbuilder;
    11: aload_1
    12: invokevirtual #4  // method java/lang/stringbuilder.append:(ljava/lang/string;)ljava/lang/stringbuilder;
    15: invokevirtual #5  // method java/lang/stringbuilder.tostring:()ljava/lang/string;
    18: areturn

smali 代码:

.class public lstringtest;
.super ljava/lang/object;
.method public static concat(ljava/lang/string;ljava/lang/string;)ljava/lang/string;
    .registers 4
    .param p0, "a"    # ljava/lang/string;
    .param p1, "b"    # ljava/lang/string;
    .prologue
    .line 3
    new-instance v0, ljava/lang/stringbuilder;
    invoke-direct {v0}, ljava/lang/stringbuilder;-><init>()v
    invoke-virtual {v0, p0}, ljava/lang/stringbuilder;->append(ljava/lang/string;)ljava/lang/stringbuilder;
    move-result-object v0
    invoke-virtual {v0, p1}, ljava/lang/stringbuilder;->append(ljava/lang/string;)ljava/lang/stringbuilder;
    move-result-object v0
    invoke-virtual {v0}, ljava/lang/stringbuilder;->tostring()ljava/lang/string;
    move-result-object v0
    return-object v0
.end method

逐行解释:

# 1. 创建 stringbuilder 实例
new-instance v0, ljava/lang/stringbuilder;
# v0 = new stringbuilder()
# 2. 调用构造函数
invoke-direct {v0}, ljava/lang/stringbuilder;-><init>()v
# stringbuilder(v0).<init>()
# 3. 调用 append 方法,添加第一个字符串
invoke-virtual {v0, p0}, ljava/lang/stringbuilder;->append(ljava/lang/string;)ljava/lang/stringbuilder;
# v0.append(p0)
move-result-object v0
# v0 = 返回值(stringbuilder 对象)
# 4. 调用 append 方法,添加第二个字符串
invoke-virtual {v0, p1}, ljava/lang/stringbuilder;->append(ljava/lang/string;)ljava/lang/stringbuilder;
# v0.append(p1)
move-result-object v0
# v0 = 返回值
# 5. 调用 tostring 方法
invoke-virtual {v0}, ljava/lang/stringbuilder;->tostring()ljava/lang/string;
# v0.tostring()
move-result-object v0
# v0 = 返回的字符串
# 6. 返回结果
return-object v0
# return v0

3.6.2 示例 2:条件判断

java 源码:

public class conditiontest {
    public static int max(int a, int b) {
        if (a > b) {
            return a;
        } else {
            return b;
        }
    }
}

java 字节码:

public static int max(int, int);
  code:
     0: iload_0
     1: iload_1
     2: if_icmple     7
     5: iload_0
     6: ireturn
     7: iload_1
     8: ireturn

smali 代码:

.method public static max(ii)i
    .registers 2
    .param p0, "a"    # i
    .param p1, "b"    # i
    .prologue
    .line 3
    if-gt p0, p1, :cond_0    # if (a > b) goto cond_0
    return p0                 # return a
    :cond_0
    return p1                 # return b
.end method

逐行解释:

# 1. 比较 a 和 b,如果 a > b,跳转到 :cond_0
if-gt p0, p1, :cond_0
# if (p0 > p1) goto :cond_0
# 2. 如果条件不满足(a <= b),执行这里,返回 a
return p0
# return p0
# 3. 标签:如果 a > b,跳转到这里
:cond_0
# 4. 返回 b
return p1
# return p1

3.6.3 示例 3:循环

java 源码:

public class looptest {
    public static int sum(int n) {
        int result = 0;
        for (int i = 0; i < n; i++) {
            result += i;
        }
        return result;
    }
}

smali 代码:

.method public static sum(i)i
    .registers 3
    .param p0, "n"    # i
    .prologue
    .line 3
    const/4 v0, 0x0        # v0 = result = 0
    const/4 v1, 0x0        # v1 = i = 0
    :goto_0
    if-ge v1, p0, :cond_0  # if (i < n) goto cond_0
    return v0              # return result
    :cond_0
    add-int/2addr v0, v1   # result += i
    add-int/lit8 v1, v1, 0x1  # i++
    goto :goto_0           # goto 循环开始
.end method

逐行解释:

# 1. 初始化 result = 0
const/4 v0, 0x0
# v0 = 0
# 2. 初始化 i = 0
const/4 v1, 0x0
# v1 = 0
# 3. 循环标签
:goto_0
# 4. 检查循环条件:if (i < n)
if-ge v1, p0, :cond_0
# if (v1 >= p0) goto :cond_0 (如果 i >= n,退出循环)
# 注意:if-ge 是 "if greater or equal",所以这里是反向逻辑
# 5. 如果循环结束,返回 result
return v0
# return v0
# 6. 循环体标签
:cond_0
# 7. 执行循环体:result += i
add-int/2addr v0, v1
# v0 = v0 + v1
# 8. i++
add-int/lit8 v1, v1, 0x1
# v1 = v1 + 1
# 9. 跳回循环开始
goto :goto_0
# goto :goto_0

3.7 实战案例:修改 smali 代码绕过验证

3.7.1 创建测试应用

步骤 1:编写 java 代码

创建 loginactivity.java

package com.example.test;
import android.app.activity;
import android.os.bundle;
import android.widget.edittext;
import android.widget.toast;
public class loginactivity extends activity {
    private static final string correct_password = "admin123";
    @override
    protected void oncreate(bundle savedinstancestate) {
        super.oncreate(savedinstancestate);
        // 简化:直接验证
        if (checkpassword("admin123")) {
            showmessage("login success!");
        } else {
            showmessage("login failed!");
        }
    }
    private boolean checkpassword(string password) {
        return password.equals(correct_password);
    }
    private void showmessage(string message) {
        toast.maketext(this, message, toast.length_short).show();
    }
}

步骤 2:编译为 apk

# 使用 android studio 或命令行编译
# 这里假设已经编译为 app.apk

3.7.2 反编译 apk

步骤 1:使用 apktool 反编译

apktool d app.apk -o app_decompiled

步骤 2:查看 smali 代码

cat app_decompiled/smali/com/example/test/loginactivity.smali

反编译后的 smali 代码:

.class public lcom/example/test/loginactivity;
.super landroid/app/activity;
.field private static final correct_password:ljava/lang/string; = "admin123"
.method protected oncreate(landroid/os/bundle;)v
    .registers 3
    .param p1, "savedinstancestate"    # landroid/os/bundle;
    .prologue
    .line 12
    invoke-super {p0, p1}, landroid/app/activity;->oncreate(landroid/os/bundle;)v
    .line 15
    const-string v0, "admin123"
    invoke-direct {p0, v0}, lcom/example/test/loginactivity;->checkpassword(ljava/lang/string;)z
    move-result v0
    if-eqz v0, :cond_0
    .line 16
    const-string v0, "login success!"
    invoke-direct {p0, v0}, lcom/example/test/loginactivity;->showmessage(ljava/lang/string;)v
    :goto_0
    return-void
    :cond_0
    .line 18
    const-string v0, "login failed!"
    invoke-direct {p0, v0}, lcom/example/test/loginactivity;->showmessage(ljava/lang/string;)v
    goto :goto_0
.end method
.method private checkpassword(ljava/lang/string;)z
    .registers 3
    .param p1, "password"    # ljava/lang/string;
    .prologue
    .line 23
    const-string v0, "admin123"
    invoke-virtual {p1, v0}, ljava/lang/string;->equals(ljava/lang/object;)z
    move-result v0
    return v0
.end method
.method private showmessage(ljava/lang/string;)v
    .registers 3
    .param p1, "message"    # ljava/lang/string;
    .prologue
    .line 27
    const/4 v0, 0x0
    invoke-static {p0, p1, v0}, landroid/widget/toast;->maketext(landroid/content/context;ljava/lang/charsequence;i)landroid/widget/toast;
    move-result-object v0
    invoke-virtual {v0}, landroid/widget/toast;->show()v
    return-void
.end method

3.7.3 分析验证逻辑

关键代码分析:

.method private checkpassword(ljava/lang/string;)z
    .registers 3
    .param p1, "password"    # ljava/lang/string;
    .prologue
    .line 23
    const-string v0, "admin123"    # v0 = "admin123"
    # 调用 password.equals("admin123")
    invoke-virtual {p1, v0}, ljava/lang/string;->equals(ljava/lang/object;)z
    move-result v0    # v0 = 返回值(true/false)
    return v0        # return v0
.end method

在 oncreate 中的调用:

invoke-direct {p0, v0}, lcom/example/test/loginactivity;->checkpassword(ljava/lang/string;)z
move-result v0    # v0 = checkpassword 的返回值
if-eqz v0, :cond_0    # if (v0 == 0) goto cond_0
# 如果返回 false (0),跳转到失败分支
# 如果返回 true (非0),继续执行成功分支

3.7.4 修改 smali 代码绕过验证

方法 1:修改 checkpassword 方法,始终返回 true

.method private checkpassword(ljava/lang/string;)z
    .registers 3
    .param p1, "password"    # ljava/lang/string;
    .prologue
    .line 23
    # 原始代码(注释掉)
    # const-string v0, "admin123"
    # invoke-virtual {p1, v0}, ljava/lang/string;->equals(ljava/lang/object;)z
    # move-result v0
    # return v0
    # 修改:直接返回 true
    const/4 v0, 0x1    # v0 = true
    return v0          # return true
.end method

方法 2:修改条件判断,反转逻辑

oncreate 方法中:

# 原始代码
if-eqz v0, :cond_0    # if (v0 == 0) goto cond_0
# 修改为:始终跳转到成功分支
# if-eqz v0, :cond_0
goto :cond_1          # 直接跳转到成功分支(需要添加标签)
# 或者修改条件判断
if-nez v0, :cond_0    # if (v0 != 0) goto cond_0 (反转逻辑)

方法 3:直接修改 oncreate,移除验证

.method protected oncreate(landroid/os/bundle;)v
    .registers 3
    .param p1, "savedinstancestate"    # landroid/os/bundle;
    .prologue
    .line 12
    invoke-super {p0, p1}, landroid/app/activity;->oncreate(landroid/os/bundle;)v
    # 移除所有验证代码,直接显示成功消息
    const-string v0, "login success!"
    invoke-direct {p0, v0}, lcom/example/test/loginactivity;->showmessage(ljava/lang/string;)v
    return-void
.end method

3.7.5 回编译并测试

步骤 1:回编译 apk

apktool b app_decompiled -o app_modified.apk

步骤 2:签名 apk

# 生成密钥库(如果还没有)
keytool -genkey -v -keystore my-release-key.jks \
    -keyalg rsa -keysize 2048 -validity 10000 \
    -alias my-key-alias
# 签名 apk
apksigner sign --ks my-release-key.jks \
    --ks-key-alias my-key-alias \
    app_modified.apk

或者使用 jarsigner(旧方法):

jarsigner -verbose -sigalg sha1withrsa -digestalg sha1 \
    -keystore my-release-key.jks \
    app_modified.apk my-key-alias

步骤 3:对齐 apk(可选但推荐)

zipalign -v 4 app_modified.apk app_modified_aligned.apk

步骤 4:安装测试

# 卸载旧版本(如果已安装)
adb uninstall com.example.test
# 安装修改后的 apk
adb install app_modified_aligned.apk
# 运行应用,验证绕过效果

3.7.6 验证修改效果

预期结果:

  • ✅ 无论输入什么密码,都应该显示 “login success!”
  • ✅ checkpassword 方法始终返回 true
  • ✅ 应用正常运行,无崩溃

如果修改失败:

  1. 检查 smali 语法是否正确
  2. 检查寄存器使用是否正确
  3. 检查方法签名是否匹配
  4. 查看 logcat 日志排查错误

3.8 常见问题与解决方案

3.8.1 寄存器相关问题

问题 1:寄存器数量限制导致编译失败

症状:

error: invalid register: v65536

原因:

  • 寄存器数量超过了限制
  • 寄存器编号错误

解决方案:

  1. 检查 .registers 指令声明的寄存器数量
  2. 确保使用的寄存器编号在声明范围内
  3. 减少局部变量的使用

示例:

# 错误:声明了 3 个寄存器,但使用了 v3
.method test()v
    .registers 3
    const/4 v3, 0x1    # 错误!v3 超出范围
.end method
# 正确:声明足够的寄存器
.method test()v
    .registers 4
    const/4 v3, 0x1    # 正确
.end method

问题 2:方法参数寄存器编号错误

症状:
方法调用时参数传递错误

原因:

  • 在非静态方法中,p0this,参数从 p1 开始
  • 在静态方法中,参数从 p0 开始

解决方案:

# 非静态方法
.method public test(ljava/lang/string;i)v
    .registers 3
    .param p1, "str"    # 第一个参数是 p1(p0 是 this)
    .param p2, "num"    # 第二个参数是 p2
.end method
# 静态方法
.method public static test(ljava/lang/string;i)v
    .registers 2
    .param p0, "str"    # 第一个参数是 p0
    .param p1, "num"    # 第二个参数是 p1
.end method

3.8.2 异常处理问题

问题:try-catch 块表示方式

java 代码:

try {
    // code
} catch (exception e) {
    // handle
}

smali 表示:

:try_start_0
    # try 块代码
:try_end_0
.catch ljava/lang/exception; {:try_start_0 .. :try_end_0} :catch_0
# 正常流程继续
return-void
:catch_0
move-exception v0
# catch 块代码
return-void

⚠️ 重要提示:

  • try_start_xtry_end_x 必须配对
  • catch 指令必须在 try_end_x 之后
  • move-exception 必须在 catch 块的第一条指令

3.8.3 编译和回编译问题

问题 1:baksmali 反编译失败

症状:

error: invalid dex file

解决方案:

  1. 检查 dex 文件是否损坏
  2. 使用正确的 api 级别:--api-level 28
  3. 更新 baksmali 到最新版本

问题 2:smali 编译失败

症状:

error: invalid register
error: invalid instruction

解决方案:

  1. 检查 smali 语法是否正确
  2. 检查寄存器使用是否超出范围
  3. 检查指令格式是否正确
  4. 查看详细的错误信息定位问题

问题 3:回编译后 apk 无法安装

症状:

failure [install_parse_failed_no_certificates]

解决方案:

  1. apk 必须签名才能安装
  2. 使用 apksignerjarsigner 签名
  3. 使用 zipalign 对齐 apk
# 完整流程
apktool b app_decompiled -o app_unsigned.apk
apksigner sign --ks keystore.jks app_unsigned.apk
zipalign -v 4 app_unsigned.apk app_final.apk

3.8.4 代码修改问题

问题:修改后逻辑错误

症状:
应用崩溃或行为异常

解决方案:

  1. 仔细分析原始逻辑
  2. 确保修改后的逻辑完整
  3. 注意寄存器的一致性
  4. 使用 logcat 查看错误日志

调试技巧:

# 添加日志输出(需要导入 log 类)
const-string v0, "tag"
const-string v1, "debug message"
invoke-static {v0, v1}, landroid/util/log;->d(ljava/lang/string;ljava/lang/string;)i

3.9 本章总结

3.9.1 知识点回顾

  • java 字节码 vs dex 字节码
    • jvm 使用栈,dalvik 使用寄存器
    • dex 格式更紧凑,适合移动设备
  • smali 语法基础
    • 寄存器系统(v0-vn, p0-pn)
    • 数据类型表示
    • 方法调用约定
  • smali 指令集
    • invoke-* 系列:方法调用
    • if-* 系列:条件跳转
    • move-* 系列:数据移动
    • const-* 系列:常量加载
  • 类、方法、字段定义
    • 访问修饰符
    • 异常处理

3.9.2 实践要点

  • ✅ 理解寄存器系统是学习 smali 的关键
  • ✅ 掌握指令集,能够阅读 smali 代码
  • ✅ 能够修改 smali 代码实现逻辑绕过
  • ✅ 注意参数寄存器的编号规则

3.9.3 下一步学习

  • 第 4 章:使用 apktool 和 jadx 进行静态分析
  • 第 6 章:学习动态调试技术
  • 第 7 章:使用 frida 进行动态 hook

附录:smali 指令速查表

数据移动指令

指令说明示例
move va, vb移动 32 位值move v0, v1
move-wide va, vb移动 64 位值move-wide v0, v2
move-object va, vb移动对象引用move-object v0, v1
move-result va移动方法返回值move-result v0

常量加载指令

指令说明示例
const/4 va, #+b加载 4 位常量const/4 v0, 0x5
const/16 va, #+b加载 16 位常量const/16 v0, 0x100
const-string va, string加载字符串const-string v0, "hello"

方法调用指令

指令说明示例
invoke-virtual虚方法调用invoke-virtual {v0}, ljava/lang/string;->length()i
invoke-static静态方法调用invoke-static {}, ljava/lang/system;->currenttimemillis()j
invoke-direct直接方法调用invoke-direct {v0}, ljava/lang/string;-><init>()v

条件跳转指令

指令说明示例
if-eq va, vb, :label相等跳转if-eq v0, v1, :equal
if-ne va, vb, :label不等跳转if-ne v0, v1, :not_equal
if-lt va, vb, :label小于跳转if-lt v0, v1, :less
if-gt va, vb, :label大于跳转if-gt v0, v1, :greater

本章完成! 🎉

现在你已经掌握了 smali 语法的基础知识,能够阅读和修改 smali 代码。在下一章中,我们将学习如何使用工具进行静态分析实战。

到此这篇关于java 字节码与 smali 语法基础的文章就介绍到这了,更多相关java 字节码与 smali 语法内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2026  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com