移动安全Android逆向系列:Dalvik概念&破解实例
字数 6500 2025-08-09 15:23:13

Android逆向工程:Dalvik虚拟机与破解实例详解

一、Dalvik虚拟机基础

1. Dalvik概述

Dalvik是Google专为Android设计的虚拟机,具有以下特点:

  • 基于寄存器架构(不同于JVM的基于栈架构)
  • 使用专属的dex文件格式(Dalvik Executable)
  • 执行效率更高,占用空间更少

2. 寄存器命名法

Dalvik有两种寄存器命名方式:

v命名法

  • 所有寄存器从v0开始递增命名
  • 参数寄存器:v(m-n)~vm(m为总寄存器数,n为参数数)
  • 局部变量寄存器:v0~vn

p命名法

  • 参数寄存器:p0~pn
  • 局部变量寄存器:v0~vn

3. Smali代码分析

Smali是dex文件的可读形式,是Dalvik的字节码表示:

方法签名示例

static public DecryptDemo->getHelloWorld(Ljava/lang/String;I)Ljava/lang/String;
  • 方法名:getHelloWorld
  • 参数类型:Ljava/lang/String;和I
  • 返回值类型:Ljava/lang/String;

寄存器使用示例

invoke-virtual {v1, p0}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
move-result-object v1
  • 调用StringBuilder的append方法
  • 参数位于p0,结果存入v1

4. 反编译工具链

编译/反编译流程及工具:

  1. 编译流程

    • .java → .class → .dex → .smali
    • 使用dx工具打包:dx --dex --output=Test.dex com/xxx/ooo/Test.class
  2. 反编译流程

    • 使用baksmali反编译:java -jar baksmali.jar -o smali_out/ source.dex
    • 使用smali重新打包:java -jar smali.jar smali_out/ -o source.dex

5. Dalvik字节码类型

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

对象类型表示:Ljava/lang/String;(全限定名用"/"代替".")

二、Dalvik指令集详解

1. 数据定义指令

指令 描述
const/4 vA,#+B 将4位数值符号扩展为32位后赋值给vA
const-wide/16 vAA,#+BBBB 将16位数值符号扩展为64位后赋值给vAA
const/high16 vAA,#+BBBB0000 将数值右补零扩展为32位后赋给vAA
const-string vAA,string@BBBB 通过字符串索引获取字符串赋值给vAA
const-class vAA,type@BBBB 通过类型索引获取类引用赋值给vAA

2. 数据操作指令

指令 描述
move vA,vB 将vB的值赋给vA
move/from16 vAA,vBBBB 将16位vBBBB的值赋给7位vAA
move/16 vAAAA,vBBBB 将16位vBBBB的值赋给16位vAAAA
move-object vA,vB 将vB的对象引用赋给vA
move-result vAA 将上一个invoke指令的32位非对象结果赋给vAA
move-result-wide vAA 将上一个invoke指令的64位非对象结果赋给vAA
move-result-object vAA 将上一个invoke指令的对象结果赋给vAA
move-exception vAA 保存上一个运行时异常到vAA

3. 比较指令

指令 描述
cmpl-float vAA,vBB,vCC 比较单精度浮点数:vBB>vCC→-1, ==→0, <→1
cmpg-float vAA,vBB,vCC 比较单精度浮点数:vBB>vCC→1, ==→0, <→-1
cmpl-double vAA,vBB,vCC 比较双精度浮点数:vBB>vCC→-1, ==→0, <→1
cmpg-double vAA,vBB,vCC 比较双精度浮点数:vBB>vCC→1, ==→0, <→-1

4. 跳转指令

指令 描述
goto +AA 无条件跳转到指定偏移处
packed-switch vAA,+BBBBBBBB 有规律分支跳转,vAA为判断值,BBBBBBBB为偏移表索引
sparse-switch vAA,+BBBBBBBB 无规律分支跳转
if-eq vA,vB,target 等价于if(a==b)
if-ne vA,vB,target 等价于if(a!=b)
if-lt vA,vB,target 等价于if(a<b)
if-gt vA,vB,target 等价于if(a>b)
if-ge vA,vB,target 等价于if(a>=b)
if-le vA,vB,target 等价于if(a<=b)

5. 方法调用指令

指令 描述
invoke-virtual 调用实例的虚方法(普通方法)
invoke-super 调用实例的父类方法
invoke-direct 调用实例的直接方法
invoke-static 调用静态方法
invoke-interface 调用接口方法

6. 实例操作指令

指令 描述
new-instance vAA,type@BBBB 构造指定类型对象并将引用赋给vAA
instance-of vA,vB,type@CCCC 判断vB引用是否为指定类型,是则v1=1,否则v1=0
check-cast vAA,type@BBBB 将vAA引用转为指定类型,失败则抛出ClassCastException

三、Android四大组件

1. Activity

  • 功能:提供用户界面
  • 生命周期方法
    • onCreate():创建Activity
    • onStart():Activity可见
    • onResume():获取焦点,可交互
    • onPause():失去焦点
    • onStop():Activity不可见
    • onDestroy():销毁Activity
    • onRestart():从停止状态恢复

2. Service

  • 功能:提供后台服务
  • 生命周期方法
    • onCreate():首次创建服务
    • onStart():启动服务(已过时,改用onStartCommand)
    • onDestroy():销毁服务

3. BroadcastReceiver

  • 功能:接收和响应系统级广播
  • 特点:轻量级,生命周期短暂

4. ContentProvider

  • 功能:实现应用间数据共享
  • 实现方式
    • 使用现有ContentProvider访问数据
    • 创建自定义ContentProvider提供数据接口

四、破解实例:贪吃蛇APK内购破解

1. 目标分析

  • 原始行为:点击购买显示"支付失败"
  • 破解目标:修改为"支付成功"实现免费购买

2. 工具准备

  1. Jadx-gui:反编译APK查看Java代码
  2. Android Killer:反编译为smali代码并修改

3. 破解步骤

  1. 使用Jadx搜索关键字符串

    • 搜索"支付失败"定位到BuyFailed()方法
    • 搜索"支付成功"定位到BuySuccess()方法
  2. 分析smali代码

    • 使用Android Killer反编译APK
    • 定位到支付失败和成功的smali代码段
  3. 代码替换

    • 将BuySuccess()的smali代码复制
    • 替换BuyFailed()的原有代码
  4. 回编译

    • 保存修改后的smali代码
    • 使用Android Killer重新打包APK
  5. 验证效果

    • 安装修改后的APK
    • 确认点击购买显示"支付成功"

4. 关键smali代码示例

原始支付失败代码:

.method public BuyFailed()V
    ... 失败处理逻辑 ...
.end method

修改为调用成功:

.method public BuyFailed()V
    invoke-virtual {p0}, Lcom/example/game/SnakeActivity;->BuySuccess()V
    return-void
.end method

五、总结

本教程详细介绍了Android逆向工程的核心知识:

  1. Dalvik虚拟机:架构特点、寄存器命名、smali语法
  2. Dalvik指令集:数据操作、流程控制、方法调用等关键指令
  3. Android组件:四大组件及其生命周期
  4. 实战破解:通过修改smali代码实现内购破解的完整流程

掌握这些知识后,可以:

  • 分析Android应用的运行机制
  • 定位关键代码进行修改
  • 实现简单的功能破解
  • 为更复杂的逆向工程打下基础

建议进一步学习:

  • 更复杂的smali代码分析
  • 动态调试技术
  • 加固应用的脱壳技术
  • 原生库(so文件)的逆向分析
Android逆向工程:Dalvik虚拟机与破解实例详解 一、Dalvik虚拟机基础 1. Dalvik概述 Dalvik是Google专为Android设计的虚拟机,具有以下特点: 基于寄存器架构(不同于JVM的基于栈架构) 使用专属的dex文件格式(Dalvik Executable) 执行效率更高,占用空间更少 2. 寄存器命名法 Dalvik有两种寄存器命名方式: v命名法 所有寄存器从v0开始递增命名 参数寄存器:v(m-n)~vm(m为总寄存器数,n为参数数) 局部变量寄存器:v0~vn p命名法 参数寄存器:p0~pn 局部变量寄存器:v0~vn 3. Smali代码分析 Smali是dex文件的可读形式,是Dalvik的字节码表示: 方法签名示例 方法名:getHelloWorld 参数类型:Ljava/lang/String;和I 返回值类型:Ljava/lang/String; 寄存器使用示例 调用StringBuilder的append方法 参数位于p0,结果存入v1 4. 反编译工具链 编译/反编译流程及工具: 编译流程 : .java → .class → .dex → .smali 使用dx工具打包: dx --dex --output=Test.dex com/xxx/ooo/Test.class 反编译流程 : 使用baksmali反编译: java -jar baksmali.jar -o smali_out/ source.dex 使用smali重新打包: java -jar smali.jar smali_out/ -o source.dex 5. Dalvik字节码类型 | 类型描述符 | Java类型 | |------------|----------------| | V | void | | Z | boolean | | B | byte | | S | short | | C | char | | I | int | | J | long | | F | float | | D | double | | L | Java对象类型 | | [ | 数组类型 | 对象类型表示: Ljava/lang/String; (全限定名用"/"代替".") 二、Dalvik指令集详解 1. 数据定义指令 | 指令 | 描述 | |------------------------|----------------------------------------------------------------------| | const/4 vA,#+B | 将4位数值符号扩展为32位后赋值给vA | | const-wide/16 vAA,#+BBBB | 将16位数值符号扩展为64位后赋值给vAA | | const/high16 vAA,#+BBBB0000 | 将数值右补零扩展为32位后赋给vAA | | const-string vAA,string@BBBB | 通过字符串索引获取字符串赋值给vAA | | const-class vAA,type@BBBB | 通过类型索引获取类引用赋值给vAA | 2. 数据操作指令 | 指令 | 描述 | |------------------------|----------------------------------------------------------------------| | move vA,vB | 将vB的值赋给vA | | move/from16 vAA,vBBBB | 将16位vBBBB的值赋给7位vAA | | move/16 vAAAA,vBBBB | 将16位vBBBB的值赋给16位vAAAA | | move-object vA,vB | 将vB的对象引用赋给vA | | move-result vAA | 将上一个invoke指令的32位非对象结果赋给vAA | | move-result-wide vAA | 将上一个invoke指令的64位非对象结果赋给vAA | | move-result-object vAA | 将上一个invoke指令的对象结果赋给vAA | | move-exception vAA | 保存上一个运行时异常到vAA | 3. 比较指令 | 指令 | 描述 | |------------------------|----------------------------------------------------------------------| | cmpl-float vAA,vBB,vCC | 比较单精度浮点数:vBB>vCC→-1, ==→0, <→1 | | cmpg-float vAA,vBB,vCC | 比较单精度浮点数:vBB>vCC→1, ==→0, <→-1 | | cmpl-double vAA,vBB,vCC | 比较双精度浮点数:vBB>vCC→-1, ==→0, <→1 | | cmpg-double vAA,vBB,vCC | 比较双精度浮点数:vBB>vCC→1, ==→0, <→-1 | 4. 跳转指令 | 指令 | 描述 | |------------------------|----------------------------------------------------------------------| | goto +AA | 无条件跳转到指定偏移处 | | packed-switch vAA,+BBBBBBBB | 有规律分支跳转,vAA为判断值,BBBBBBBB为偏移表索引 | | sparse-switch vAA,+BBBBBBBB | 无规律分支跳转 | | if-eq vA,vB,target | 等价于if(a==b) | | if-ne vA,vB,target | 等价于if(a !=b) | | if-lt vA,vB,target | 等价于if(a <b) | | if-gt vA,vB,target | 等价于if(a>b) | | if-ge vA,vB,target | 等价于if(a>=b) | | if-le vA,vB,target | 等价于if(a <=b) | 5. 方法调用指令 | 指令 | 描述 | |------------------------|----------------------------------------------------------------------| | invoke-virtual | 调用实例的虚方法(普通方法) | | invoke-super | 调用实例的父类方法 | | invoke-direct | 调用实例的直接方法 | | invoke-static | 调用静态方法 | | invoke-interface | 调用接口方法 | 6. 实例操作指令 | 指令 | 描述 | |------------------------|----------------------------------------------------------------------| | new-instance vAA,type@BBBB | 构造指定类型对象并将引用赋给vAA | | instance-of vA,vB,type@CCCC | 判断vB引用是否为指定类型,是则v1=1,否则v1=0 | | check-cast vAA,type@BBBB | 将vAA引用转为指定类型,失败则抛出ClassCastException | 三、Android四大组件 1. Activity 功能 :提供用户界面 生命周期方法 : onCreate():创建Activity onStart():Activity可见 onResume():获取焦点,可交互 onPause():失去焦点 onStop():Activity不可见 onDestroy():销毁Activity onRestart():从停止状态恢复 2. Service 功能 :提供后台服务 生命周期方法 : onCreate():首次创建服务 onStart():启动服务(已过时,改用onStartCommand) onDestroy():销毁服务 3. BroadcastReceiver 功能 :接收和响应系统级广播 特点 :轻量级,生命周期短暂 4. ContentProvider 功能 :实现应用间数据共享 实现方式 : 使用现有ContentProvider访问数据 创建自定义ContentProvider提供数据接口 四、破解实例:贪吃蛇APK内购破解 1. 目标分析 原始行为 :点击购买显示"支付失败" 破解目标 :修改为"支付成功"实现免费购买 2. 工具准备 Jadx-gui :反编译APK查看Java代码 Android Killer :反编译为smali代码并修改 3. 破解步骤 使用Jadx搜索关键字符串 : 搜索"支付失败"定位到BuyFailed()方法 搜索"支付成功"定位到BuySuccess()方法 分析smali代码 : 使用Android Killer反编译APK 定位到支付失败和成功的smali代码段 代码替换 : 将BuySuccess()的smali代码复制 替换BuyFailed()的原有代码 回编译 : 保存修改后的smali代码 使用Android Killer重新打包APK 验证效果 : 安装修改后的APK 确认点击购买显示"支付成功" 4. 关键smali代码示例 原始支付失败代码: 修改为调用成功: 五、总结 本教程详细介绍了Android逆向工程的核心知识: Dalvik虚拟机 :架构特点、寄存器命名、smali语法 Dalvik指令集 :数据操作、流程控制、方法调用等关键指令 Android组件 :四大组件及其生命周期 实战破解 :通过修改smali代码实现内购破解的完整流程 掌握这些知识后,可以: 分析Android应用的运行机制 定位关键代码进行修改 实现简单的功能破解 为更复杂的逆向工程打下基础 建议进一步学习: 更复杂的smali代码分析 动态调试技术 加固应用的脱壳技术 原生库(so文件)的逆向分析