当前位置: 首页 > news >正文

Android逆向工程:Smali语法解析完整指南

完整Smali语法指南 & 一些个人理解

参考来源:CTF Wiki - Smali

目录

  • 1. Smali基础介绍
  • 2. 数据类型系统
  • 3. 字节码语法
  • 4. 寄存器概念
  • 5. 方法语法结构
  • 6. 字段语法结构
  • 7. 指令语法详解
  • 8. 完整代码示例
  • 9. 编译与运行
  • 10. 实战技巧

1. Smali基础介绍

1.1 什么是Smali

Smali 是 Android 应用程序反编译后的一种中间表示形式:

  • dex文件 → baksmali工具 → smali文件(人类可读)
    backSmail 工具按照一定格式解析的dex二进制文件,再转成的smail格式文件~
  • smali文件 → smali工具 → dex文件(虚拟机执行)

2. 数据类型系统

2.1 基础数据类型

Smali标识Java类型描述示例
Vvoid空类型方法返回值
Zboolean布尔类型true/false
Bbyte8位字节-128~127
Sshort16位短整型-32768~32767
Cchar16位字符Unicode字符
Iint32位整型标准整数
Jlong64位长整型大整数
Ffloat32位浮点单精度小数
Ddouble64位浮点双精度小数

2.2 对象类型

格式说明Java等价Smali示例
Lpackage/Class;完整类名package.ClassLjava/lang/String;
[type数组类型type[][I (int数组)
[[type二维数组type[][][[Ljava/lang/String;

2.3 方法签名格式

方法名(参数类型...)返回类型

示例对照

// Java方法
public String test(int a, boolean b) { ... }
# Smali签名
test(IZ)Ljava/lang/String;

3. 字节码语法

3.1 字段语法

.field [访问修饰符] [字段名]:[类型]

示例

.field private TAG:Ljava/lang/String;
.field private running:Z

3.2 方法语法

.method [访问修饰符] [方法名](参数类型)返回类型[.registers N]          # 寄存器声明[.parameter "参数名"]    # 参数注释[.locals N]             # 本地变量声明.prologue              # 方法开始标记[.line 行号]            # 源码行号对应# 方法体指令[指令 寄存器, 参数...][return指令]           # 返回语句
.end method

示例

.method public constructor <init>()V.locals 1.prologue.line 8# 调用Activity中的init()方法invoke-direct {p0}, Landroid/app/Activity;-><init>()V.line 10const-string v0, "MainActivity"iput-object v0, p0, Lcom/social_touch/demo/MainActivity;->TAG:Ljava/lang/String;.line 13const/4 v0, 0x0iput-boolean v0, p0, Lcom/social_touch/demo/MainActivity;->running:Zreturn-void
.end method

4. 寄存器概念

4.1 寄存器声明

.registers N    # 声明使用N个寄存器(v0 ~ vN-1.locals M       # 声明M个本地变量寄存器

4.2 寄存器类型

本地寄存器 (v0-vN)
.registers 4    # 可用寄存器:v0, v1, v2, v3
const/4 v0, 0x5    # v0 = 5
const/4 v1, 0x3    # v1 = 3
add-int v2, v0, v1 # v2 = v0 + v1 = 8
参数寄存器 (p0-pN)

静态方法

# Java: public static void test(int a, String b)
.method public static test(ILjava/lang/String;)V.registers 3# p0 = 第一参数(int a)# p1 = 第二参数(String b)
.end method

实例方法

# Java: public void test(int a, String b)
.method public test(ILjava/lang/String;)V.registers 4# p0 = this (当前对象)# p1 = 第一参数(int a)  # p2 = 第二参数(String b)
.end method

5. 方法语法结构

5.1 方法声明语法

.method [访问修饰符] [方法名](参数类型)返回类型[.registers N]          # 寄存器声明[.parameter "参数名"]    # 参数注释[.locals N]             # 本地变量声明.prologue              # 方法开始标记[.line 行号]            # 源码行号对应# 方法体指令[指令 寄存器, 参数...][return指令]           # 返回语句
.end method

5.2 访问修饰符

修饰符说明示例
public公开访问.method public test()V
private私有访问.method private test()V
protected受保护访问.method protected test()V
static静态方法.method public static test()V
final最终方法.method public final test()V
abstract抽象方法.method public abstract test()V
synchronized同步方法.method public synchronized test()V

5.3 特殊方法

方法名作用Java等价
<init>构造方法public ClassName() { ... }
<clinit>静态初始化块static { ... }

6. 字段语法结构

6.1 字段声明语法

.field [访问修饰符] [字段名]:[类型]

示例

.field private TAG:Ljava/lang/String;
.field private running:Z

6.2 访问修饰符

修饰符说明示例
public公开访问.field public TAG:Ljava/lang/String;
private私有访问.field private TAG:Ljava/lang/String;
protected受保护访问.field protected TAG:Ljava/lang/String;
static静态字段.field public static TAG:Ljava/lang/String;
final最终字段.field public final TAG:Ljava/lang/String;

7. 指令语法详解

7.1 常量指令

整数常量
const/4 vAA, #+B          # 4位整数 (-8~7)
const/16 vAA, #+BBBB      # 16位整数 (-32768~32767)
const vAA, #+BBBBBBBB     # 32位整数
const/high16 vAA, #+BBBB0000  # 高16位# 示例
const/4 v0, 0x5           # v0 = 5
const/16 v1, 0x1000       # v1 = 4096
const v2, 0x12345678      # v2 = 0x12345678
字符串常量
const-string vAA, "string"    # 字符串常量
const-class vAA, Ltype;       # 类对象常量# 示例
const-string v0, "Hello World"
const-class v1, Ljava/lang/String;

7.1.1 const/high16 指令深度解析

7.1.1.1 什么是"高位"和"低位"?

想象一个32位数字 0x12345678

高位(High Bits):值较大的部分 → 0x1234(前16位)
低位(Low Bits):值较小的部分 → 0x5678(后16位)高16位         低16位┌────────┬────────────┐│ 0x1234 │   0x5678   │  ← 32位数值└────────┴────────────┘▲                    ▲MSB                  LSB

更直观的字节视图:

高位             低位
┌────┬────┬────┬────┐
│ 12 │ 34 │ 56 │ 78 │  ← 字节序列
└────┴────┴────┴────┘
▲              ▲
MSB            LSB   (最高位 ←→ 最低位)
7.1.1.2 字节序(Endianness):数据在内存中的存储顺序

1. 小端序(Little-Endian) → Android/Intel 使用

规则:低位字节在前(低地址),高位字节在后(高地址)

示例:0x12345678 在内存中的存储:

内存地址: 0x1000  0x1001  0x1002  0x1003┌─────┬─────┬─────┬─────┐
存储内容: │ 78  │ 56  │ 34  │ 12  │  ← 低位到高位└─────┴─────┴─────┴─────┘

2. 大端序(Big-Endian) → 网络传输/部分嵌入式系统使用

规则:高位字节在前(低地址),低位字节在后(高地址)

示例:0x12345678 存储为:

内存地址: 0x1000  0x1001  0x1002  0x1003┌─────┬─────┬─────┬─────┐
存储内容: │ 12  │ 34  │ 56  │ 78  │  ← 高位到低位└─────┴─────┴─────┴─────┘
7.1.1.3 为什么需要 const/high16?

Android 指令设计需节省空间 → 用 16 位指令加载 32 位常量的高半部分:

const/high16 v0, 0x10000000  # 仅加载高16位:0x1000 → v0 = 0x10000000
const/16 v0, 0x0000         # 加载低16位:0x0000 → 需后续合并

合并逻辑(伪代码):

uint32_t value = (high16_value << 16) | low16_value;
// 0x10000000 | 0x0000 = 0x10000000

实战:小端序下的数据解析

假设内存中存储 78 56 34 12(小端序):

按字节读取

地址0: 0x78
地址1: 0x56
地址2: 0x34
地址3: 0x12

组合为32位值

value = (byte3 << 24) | (byte2 << 16) | (byte1 << 8) | byte0= (0x12 << 24) | (0x34 << 16) | (0x56 << 8) | 0x78= 0x12345678  // 正确值
7.1.1.4 32位常量加载完整原理(以 0x12345678 为例)

步骤 1:拆分高低位

原始值:0x12345678
高16位:0x1234
低16位:0x5678

步骤 2:两条指令分别加载

# 加载高16位 → 存入寄存器 v0
const/high16 v0, 0x12340000  # 注意:必须补零到32位格式!# 加载低16位 → 存入寄存器 v1
const/16 v1, 0x5678

步骤 3:合并操作(编译器自动插入)

# 编译器生成的隐藏指令(实际字节码)
or-int/lit16 v0, v0, 0x5678  # v0 = v0 | 0x56780x12345678

关键点:const/high16 加载的值末尾 16 位是 0(0x12340000),后续通过 or-int/lit16 将低16位(0x5678)合并进去。

7.1.1.5 为什么要这样的设计?

问题:高位在前,低位在后,在Android中的dex文件二进制是小端序列,也就是需要从后往前读,大端序列就是从前往后读,这一个理解了,但是为什么要这样做呢?另外读取了16位的指令加载了32位常量的前面的一半部分,那么后面剩下的一半呢?

解答

1. 指令空间压缩

  • 32 位常量需 4 字节存储 → 但 Dalvik 指令长度仅 2 字节
  • 解决方案:拆成两条 2 字节指令(const/high16 + const/16)

2. 性能平衡

  • 全量加载(如 const v0, 0x12345678)需 6 字节(操作码 2字节 + 常量 4字节)
  • 分片加载 仅需 4 字节(两条 2 字节指令) → 节省 33% 空间

核心理解

  • const/high16 不是独立操作 → 它只是 32 位常量加载的第一步
  • 后续必跟合并指令(or-int/lit16 等)→ 将低16位"焊接"到高位值的尾部
  • 设计本质:牺牲少量指令数(2→3条),换取存储空间优化(6字节→4字节)
7.1.1.6 空间优化的深度分析

疑问:这样操作好处是存储空间的优化,分批加载,然后再合并,可是合并之后的长度不依然是很大吗?

解答:加载const指令的两种方式对比 — 空间优化的本质(指令长度和常量长度)

1. 传统全量加载(const 指令)

const v0, 0x12345678  # 指令长度:6字节
  • 结构:操作码(2字节) + 常量值(4字节)
  • 总长6 字节(恒定)

2. 分片加载(const/high16 + const/16 + 合并)

const/high16 v0, 0x12340000  # 2字节
const/16 v1, 0x5678           # 2字节
or-int/lit16 v0, v0, 0x5678   # 2字节(合并指令)
  • 总长6 字节(3条指令 × 2字节)
  • 空间未减少?表面看无优势?
7.1.1.7 分片加载的隐藏收益

1. 高频小值的极致压缩

对常见小数字(如 0、1、-1):

全量加载(浪费)

const v0, 0x1  # 6字节 → 操作码2字节 + 常量4字节(其中3.75字节为0

分片加载(高效)

const/4 v0, 0x1  # 仅需2字节!压缩率 300%

2. 常量池共享优化

  • 全量加载:每个 const 指令独占 4 字节常量存储
  • 分片加载:const/16 的 16 位立即数嵌入指令内**(不占常量池)**

DEX 文件结构对比

全量加载:常量池条目 [0x12345678] → 4字节
分片加载:无额外常量池开销(立即数直接存指令)

3. 具体案例分析

案例:加载 0x0000FFFF(常见掩码)

方式指令序列总字节数常量池占用
全量加载const v0, 0x0000FFFF64
分片加载const/16 v0, 0xFFFF20
节省4字节4字节

优势:单条指令完成 → 省去合并操作(因高16位为0)

7.1.1.8 总结

分片加载的合并结果(如 0x12345678)在内存中仍是 4 字节,但:

  • DEX 文件存储:通过消除常量池条目 + 短指令嵌入,显著压缩体积
  • 内存执行:短指令提升缓存命中率 → 加速虚拟机解释执行

本质:这是一种智能的指令设计,在不同场景下自动选择最优的编码方式:

  • 小值 → 超级压缩(const/4)
  • 中值 → 适度压缩(const/16)
  • 大值 → 分片处理(const/high16 + 合并)

7.2 移动指令

move vA, vB               # 移动32位值
move-wide vA, vB          # 移动64位值
move-object vA, vB        # 移动对象引用move-result vAA           # 获取方法调用结果
move-result-wide vAA      # 获取64位方法结果
move-result-object vAA    # 获取对象方法结果move-exception vAA        # 获取异常对象# 示例
move v1, v0               # v1 = v0
move-object v2, v1        # v2 = v1 (对象引用)

7.3 返回指令

return-void               # 返回void
return vAA                # 返回32位值
return-wide vAA           # 返回64位值
return-object vAA         # 返回对象引用# 示例
return-void               # return;
return v0                 # return v0;
return-object v1          # return v1;

7.4 算术指令

# 32位整数运算
add-int vAA, vBB, vCC     # vAA = vBB + vCC
sub-int vAA, vBB, vCC     # vAA = vBB - vCC
mul-int vAA, vBB, vCC     # vAA = vBB * vCC
div-int vAA, vBB, vCC     # vAA = vBB / vCC
rem-int vAA, vBB, vCC     # vAA = vBB % vCC# 浮点运算
add-float vAA, vBB, vCC   # 浮点加法
sub-double vAA, vBB, vCC  # 双精度减法# 示例
add-int v0, v1, v2        # v0 = v1 + v2

7.5 比较指令

cmp-long vAA, vBB, vCC    # 比较long值
cmpl-float vAA, vBB, vCC  # 比较float(NaN时返回-1)
cmpg-float vAA, vBB, vCC  # 比较float(NaN时返回1)# 示例
cmp-long v0, v1, v2       # if (v1 > v2) v0=1; else if (v1==v2) v0=0; else v0=-1

7.6 条件跳转指令

# 与零比较跳转
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-ge vA, vB, :label      # if (vA >= vB) goto label
if-gt vA, vB, :label      # if (vA > vB) goto label
if-le vA, vB, :label      # if (vA <= vB) goto label# 与零比较跳转
if-eqz vAA, :label        # if (vAA == 0) goto label
if-nez vAA, :label        # if (vAA != 0) goto label
if-ltz vAA, :label        # if (vAA < 0) goto label
if-gez vAA, :label        # if (vAA >= 0) goto label
if-gtz vAA, :label        # if (vAA > 0) goto label
if-lez vAA, :label        # if (vAA <= 0) goto label# 无条件跳转
goto :label               # 直接跳转

7.7 字段操作指令

# 静态字段
sget vAA, Lclass;->field:Ltype;     # 获取静态字段
sput vAA, Lclass;->field:Ltype;     # 设置静态字段# 实例字段
iget vA, vB, Lclass;->field:Ltype;  # 获取实例字段
iput vA, vB, Lclass;->field:Ltype;  # 设置实例字段# 示例
sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;
iget-object v1, p0, Lcom/example/MainActivity;->TAG:Ljava/lang/String;

7.8 方法调用指令

# 虚方法调用
invoke-virtual {参数寄存器}, Lclass;->method(参数类型)返回类型# 静态方法调用  
invoke-static {参数寄存器}, Lclass;->method(参数类型)返回类型# 直接方法调用(构造方法、私有方法)
invoke-direct {参数寄存器}, Lclass;->method(参数类型)返回类型# 接口方法调用
invoke-interface {参数寄存器}, Linterface;->method(参数类型)返回类型# 父类方法调用
invoke-super {参数寄存器}, Lclass;->method(参数类型)返回类型# 示例
invoke-virtual {v1, v0}, Ljava/io/PrintStream;->println(I)V
invoke-static {v0}, Lcom/example/MainActivity;->log(I)V
invoke-direct {v0}, Ljava/lang/StringBuilder;-><init>()V

7.9 数组操作指令

# 创建数组
new-array vA, vB, [type   # vA = new type[vB]# 数组长度
array-length vA, vB       # vA = vB.length# 数组元素读取
aget vAA, vBB, vCC        # vAA = vBB[vCC]
aget-object vAA, vBB, vCC # 对象数组读取# 数组元素写入
aput vAA, vBB, vCC        # vBB[vCC] = vAA
aput-object vAA, vBB, vCC # 对象数组写入# 示例
new-array v0, v1, [I      # v0 = new int[v1]
array-length v2, v0       # v2 = v0.length

7.10 对象操作指令

# 创建对象实例
new-instance vAA, Ltype;  # vAA = new type()# 类型检查
instance-of vA, vB, Ltype; # vA = (vB instanceof type)# 类型转换
check-cast vAA, Ltype;     # vAA = (type)vAA# 示例
new-instance v0, Ljava/lang/StringBuilder;
invoke-direct {v0}, Ljava/lang/StringBuilder;-><init>()V

7.11 同步指令

monitor-enter vAA         # synchronized(vAA) {
monitor-exit vAA          # }# 示例
monitor-enter p0          # 进入同步块
# ... 同步代码 ...
monitor-exit p0           # 退出同步块

7.12 空指令

nop                       # 空操作指令,不执行任何操作

7.13 移动指令详细分类

# 基本移动指令
move vA, vB               # 移动32位值:vA = vB
move/from16 vAA, vBBBB    # 从16位索引移动:vAA = vBBBB
move/16 vAAAA, vBBBB      # 16位索引移动:vAAAA = vBBBB# 宽度移动指令(64位)
move-wide vA, vB          # 移动64位值:vA = vB
move-wide/from16 vAA, vBBBB    # 从16位索引移动64位
move-wide/16 vAAAA, vBBBB      # 16位索引移动64位# 对象移动指令
move-object vA, vB        # 移动对象引用:vA = vB
move-object/from16 vAA, vBBBB  # 从16位索引移动对象
move-object/16 vAAAA, vBBBB    # 16位索引移动对象# 结果移动指令
move-result vAA           # 获取方法调用结果(32位)
move-result-wide vAA      # 获取方法调用结果(64位)
move-result-object vAA    # 获取方法调用结果(对象)# 异常移动指令
move-exception vAA        # 获取异常对象

7.14 返回指令详细分类

return-void               # 返回void
return vAA                # 返回32位值
return-wide vAA           # 返回64位值
return-object vAA         # 返回对象引用

7.15 常量指令详细分类

# 4位常量(-87const/4 vA, #+B           # vA = B(4位立即数)# 16位常量(-3276832767const/16 vAA, #+BBBB      # vA = BBBB(16位立即数)# 32位常量
const vAA, #+BBBBBBBB     # vA = BBBBBBBB(32位立即数)# 高16位常量
const/high16 vAA, #+BBBB0000  # vAA = BBBB0000(高16位)# 64位常量
const-wide/16 vAA, #+BBBB     # vAA = BBBB(64位,符号扩展)
const-wide/32 vAA, #+BBBBBBBB # vAA = BBBBBBBB(64位,符号扩展)
const-wide vAA, #+BBBBBBBBBBBBBBBB  # vAA = BBBBBBBBBBBBBBBB(64位)
const-wide/high16 vAA, #+BBBB000000000000  # 高16位(64位)# 字符串常量
const-string vAA, string@BBBB      # vAA = 字符串引用
const-string/jumbo vAA, string@BBBBBBBB  # 大字符串引用# 类常量
const-class vAA, type@BBBB         # vAA = 类引用

7.16 类型检查和转换指令

# 类型检查
instance-of vA, vB, type@CCCC      # vA = (vB instanceof type)# 类型转换
check-cast vAA, type@BBBB          # vAA = (type)vAA# 数组长度
array-length vA, vB                # vA = vB.length

7.17 对象创建指令

# 创建新实例
new-instance vAA, type@BBBB        # vAA = new type()# 创建新数组
new-array vA, vB, type@CCCC        # vA = new type[vB]# 创建并填充数组
filled-new-array {vC, vD, vE, vF, vG}, type@BBBB
filled-new-array/range {vCCCC .. vNNNN}, type@BBBB# 填充数组数据
fill-array-data vAA, +BBBBBBBB     # 使用数组数据填充

7.18 数组操作指令详细分类

# 获取数组元素(aget = array get)
aget vAA, vBB, vCC                 # vAA = vBB[vCC]
aget-wide vAA, vBB, vCC            # 64位数组元素获取
aget-object vAA, vBB, vCC          # 对象数组元素获取
aget-boolean vAA, vBB, vCC         # boolean数组元素获取
aget-byte vAA, vBB, vCC            # byte数组元素获取
aget-char vAA, vBB, vCC            # char数组元素获取
aget-short vAA, vBB, vCC           # short数组元素获取# 设置数组元素(aput = array put)
aput vAA, vBB, vCC                 # vBB[vCC] = vAA
aput-wide vAA, vBB, vCC            # 64位数组元素设置
aput-object vAA, vBB, vCC          # 对象数组元素设置
aput-boolean vAA, vBB, vCC         # boolean数组元素设置
aput-byte vAA, vBB, vCC            # byte数组元素设置
aput-char vAA, vBB, vCC            # char数组元素设置
aput-short vAA, vBB, vCC           # short数组元素设置

7.19 实例字段操作指令详细分类

# 获取实例字段(iget = instance get)
iget vA, vB, field@CCCC            # vA = vB.field
iget-wide vA, vB, field@CCCC       # 64位实例字段获取
iget-object vA, vB, field@CCCC     # 对象实例字段获取
iget-boolean vA, vB, field@CCCC    # boolean实例字段获取
iget-byte vA, vB, field@CCCC       # byte实例字段获取
iget-char vA, vB, field@CCCC       # char实例字段获取
iget-short vA, vB, field@CCCC      # short实例字段获取# 设置实例字段(iput = instance put)
iput vA, vB, field@CCCC            # vB.field = vA
iput-wide vA, vB, field@CCCC       # 64位实例字段设置
iput-object vA, vB, field@CCCC     # 对象实例字段设置
iput-boolean vA, vB, field@CCCC    # boolean实例字段设置
iput-byte vA, vB, field@CCCC       # byte实例字段设置
iput-char vA, vB, field@CCCC       # char实例字段设置
iput-short vA, vB, field@CCCC      # short实例字段设置

7.20 静态字段操作指令详细分类

# 获取静态字段(sget = static get)
sget vAA, field@BBBB               # vAA =.静态字段
sget-wide vAA, field@BBBB          # 64位静态字段获取
sget-object vAA, field@BBBB        # 对象静态字段获取
sget-boolean vAA, field@BBBB       # boolean静态字段获取
sget-byte vAA, field@BBBB          # byte静态字段获取
sget-char vAA, field@BBBB          # char静态字段获取
sget-short vAA, field@BBBB         # short静态字段获取# 设置静态字段(sput = static put)
sput vAA, field@BBBB               # 类.静态字段 = vAA
sput-wide vAA, field@BBBB          # 64位静态字段设置
sput-object vAA, field@BBBB        # 对象静态字段设置
sput-boolean vAA, field@BBBB       # boolean静态字段设置
sput-byte vAA, field@BBBB          # byte静态字段设置
sput-char vAA, field@BBBB          # char静态字段设置
sput-short vAA, field@BBBB         # short静态字段设置

7.21 方法调用指令详细分类

# 虚方法调用(多态)
invoke-virtual {vC, vD, vE, vF, vG}, meth@BBBB
invoke-virtual/range {vCCCC .. vNNNN}, meth@BBBB# 父类方法调用
invoke-super {vC, vD, vE, vF, vG}, meth@BBBB
invoke-super/range {vCCCC .. vNNNN}, meth@BBBB# 直接方法调用(私有方法、构造函数)
invoke-direct {vC, vD, vE, vF, vG}, meth@BBBB
invoke-direct/range {vCCCC .. vNNNN}, meth@BBBB# 静态方法调用
invoke-static {vC, vD, vE, vF, vG}, meth@BBBB
invoke-static/range {vCCCC .. vNNNN}, meth@BBBB# 接口方法调用
invoke-interface {vC, vD, vE, vF, vG}, meth@BBBB
invoke-interface/range {vCCCC .. vNNNN}, meth@BBBB

7.22 一元操作指令(unop)

# 类型转换
int-to-long vA, vB                 # vA = (long)vB
int-to-float vA, vB                # vA = (float)vB
int-to-double vA, vB               # vA = (double)vB
long-to-int vA, vB                 # vA = (int)vB
long-to-float vA, vB               # vA = (float)vB
long-to-double vA, vB              # vA = (double)vB
float-to-int vA, vB                # vA = (int)vB
float-to-long vA, vB               # vA = (long)vB
float-to-double vA, vB             # vA = (double)vB
double-to-int vA, vB               # vA = (int)vB
double-to-long vA, vB              # vA = (long)vB
double-to-float vA, vB             # vA = (float)vB
int-to-byte vA, vB                 # vA = (byte)vB
int-to-char vA, vB                 # vA = (char)vB
int-to-short vA, vB                # vA = (short)vB# 取反操作
neg-int vA, vB                     # vA = -vB
not-int vA, vB                     # vA = ~vB
neg-long vA, vB                    # vA = -vB (long)
not-long vA, vB                    # vA = ~vB (long)
neg-float vA, vB                   # vA = -vB (float)
neg-double vA, vB                  # vA = -vB (double)

7.23 二元操作指令(binop)

# 整数运算
add-int vAA, vBB, vCC              # vAA = vBB + vCC
sub-int vAA, vBB, vCC              # vAA = vBB - vCC
mul-int vAA, vBB, vCC              # vAA = vBB * vCC
div-int vAA, vBB, vCC              # vAA = vBB / vCC
rem-int vAA, vBB, vCC              # vAA = vBB % vCC
and-int vAA, vBB, vCC              # vAA = vBB & vCC
or-int vAA, vBB, vCC               # vAA = vBB | vCC
xor-int vAA, vBB, vCC              # vAA = vBB ^ vCC
shl-int vAA, vBB, vCC              # vAA = vBB << vCC
shr-int vAA, vBB, vCC              # vAA = vBB >> vCC
ushr-int vAA, vBB, vCC             # vAA = vBB >>> vCC# 长整数运算
add-long vAA, vBB, vCC             # vAA = vBB + vCC (long)
sub-long vAA, vBB, vCC             # vAA = vBB - vCC (long)
mul-long vAA, vBB, vCC             # vAA = vBB * vCC (long)
div-long vAA, vBB, vCC             # vAA = vBB / vCC (long)
rem-long vAA, vBB, vCC             # vAA = vBB % vCC (long)
and-long vAA, vBB, vCC             # vAA = vBB & vCC (long)
or-long vAA, vBB, vCC              # vAA = vBB | vCC (long)
xor-long vAA, vBB, vCC             # vAA = vBB ^ vCC (long)
shl-long vAA, vBB, vCC             # vAA = vBB << vCC (long)
shr-long vAA, vBB, vCC             # vAA = vBB >> vCC (long)
ushr-long vAA, vBB, vCC            # vAA = vBB >>> vCC (long)# 浮点运算
add-float vAA, vBB, vCC            # vAA = vBB + vCC (float)
sub-float vAA, vBB, vCC            # vAA = vBB - vCC (float)
mul-float vAA, vBB, vCC            # vAA = vBB * vCC (float)
div-float vAA, vBB, vCC            # vAA = vBB / vCC (float)
rem-float vAA, vBB, vCC            # vAA = vBB % vCC (float)# 双精度浮点运算
add-double vAA, vBB, vCC           # vAA = vBB + vCC (double)
sub-double vAA, vBB, vCC           # vAA = vBB - vCC (double)
mul-double vAA, vBB, vCC           # vAA = vBB * vCC (double)
div-double vAA, vBB, vCC           # vAA = vBB / vCC (double)
rem-double vAA, vBB, vCC           # vAA = vBB % vCC (double)

7.24 二元操作指令(binop/2addr)

# 地址模式二元操作(目标和源操作数相同)
add-int/2addr vA, vB               # vA += vB
sub-int/2addr vA, vB               # vA -= vB
mul-int/2addr vA, vB               # vA *= vB
div-int/2addr vA, vB               # vA /= vB
rem-int/2addr vA, vB               # vA %= vB
and-int/2addr vA, vB               # vA &= vB
or-int/2addr vA, vB                # vA |= vB
xor-int/2addr vA, vB               # vA ^= vB
shl-int/2addr vA, vB               # vA <<= vB
shr-int/2addr vA, vB               # vA >>= vB
ushr-int/2addr vA, vB              # vA >>>= vB# 长整数地址模式
add-long/2addr vA, vB              # vA += vB (long)
sub-long/2addr vA, vB              # vA -= vB (long)
mul-long/2addr vA, vB              # vA *= vB (long)
div-long/2addr vA, vB              # vA /= vB (long)
rem-long/2addr vA, vB              # vA %= vB (long)
and-long/2addr vA, vB              # vA &= vB (long)
or-long/2addr vA, vB               # vA |= vB (long)
xor-long/2addr vA, vB              # vA ^= vB (long)
shl-long/2addr vA, vB              # vA <<= vB (long)
shr-long/2addr vA, vB              # vA >>= vB (long)
ushr-long/2addr vA, vB             # vA >>>= vB (long)# 浮点地址模式
add-float/2addr vA, vB             # vA += vB (float)
sub-float/2addr vA, vB             # vA -= vB (float)
mul-float/2addr vA, vB             # vA *= vB (float)
div-float/2addr vA, vB             # vA /= vB (float)
rem-float/2addr vA, vB             # vA %= vB (float)# 双精度浮点地址模式
add-double/2addr vA, vB            # vA += vB (double)
sub-double/2addr vA, vB            # vA -= vB (double)
mul-double/2addr vA, vB            # vA *= vB (double)
div-double/2addr vA, vB            # vA /= vB (double)
rem-double/2addr vA, vB            # vA %= vB (double)

7.25 字面量二元操作指令(binop/lit16)

# 16位字面量操作
add-int/lit16 vA, vB, #+CCCC       # vA = vB + CCCC
rsub-int vA, vB, #+CCCC            # vA = CCCC - vB(反向减法)
mul-int/lit16 vA, vB, #+CCCC       # vA = vB * CCCC
div-int/lit16 vA, vB, #+CCCC       # vA = vB / CCCC
rem-int/lit16 vA, vB, #+CCCC       # vA = vB % CCCC
and-int/lit16 vA, vB, #+CCCC       # vA = vB & CCCC
or-int/lit16 vA, vB, #+CCCC        # vA = vB | CCCC
xor-int/lit16 vA, vB, #+CCCC       # vA = vB ^ CCCC

7.26 字面量二元操作指令(binop/lit8)

# 8位字面量操作
add-int/lit8 vAA, vBB, #+CC        # vAA = vBB + CC
rsub-int/lit8 vAA, vBB, #+CC       # vAA = CC - vBB(反向减法)
mul-int/lit8 vAA, vBB, #+CC        # vAA = vBB * CC
div-int/lit8 vAA, vBB, #+CC        # vAA = vBB / CC
rem-int/lit8 vAA, vBB, #+CC        # vAA = vBB % CC
and-int/lit8 vAA, vBB, #+CC        # vAA = vBB & CC
or-int/lit8 vAA, vBB, #+CC         # vAA = vBB | CC
xor-int/lit8 vAA, vBB, #+CC        # vAA = vBB ^ CC
shl-int/lit8 vAA, vBB, #+CC        # vAA = vBB << CC
shr-int/lit8 vAA, vBB, #+CC        # vAA = vBB >> CC
ushr-int/lit8 vAA, vBB, #+CC       # vAA = vBB >>> CC

7.27 比较指令详细分类

# 长整数比较
cmp-long vAA, vBB, vCC             # 比较vBB和vCC(long)# 结果:vAA = 1(>), 0(=), -1(<)# 浮点比较(NaN偏向)
cmpl-float vAA, vBB, vCC           # 比较vBB和vCC(float# NaN时返回-1(less bias)
cmpg-float vAA, vBB, vCC           # 比较vBB和vCC(float# NaN时返回1(greater bias)# 双精度浮点比较
cmpl-double vAA, vBB, vCC          # 比较vBB和vCC(double# NaN时返回-1
cmpg-double vAA, vBB, vCC          # 比较vBB和vCC(double# NaN时返回1

7.28 控制流指令详细分类

# 无条件跳转
goto +AA                           # 跳转到相对地址
goto/16 +AAAA                     # 16位相对跳转
goto/32 +AAAAAAAA                 # 32位相对跳转# 打包switch语句
packed-switch vAA, +BBBBBBBB       # 连续值switch
sparse-switch vAA, +BBBBBBBB       # 稀疏值switch# 条件跳转(两寄存器比较)
if-eq vA, vB, +CCCC                # if (vA == vB) goto +CCCC
if-ne vA, vB, +CCCC                # if (vA != vB) goto +CCCC
if-lt vA, vB, +CCCC                # if (vA < vB) goto +CCCC
if-ge vA, vB, +CCCC                # if (vA >= vB) goto +CCCC
if-gt vA, vB, +CCCC                # if (vA > vB) goto +CCCC
if-le vA, vB, +CCCC                # if (vA <= vB) goto +CCCC# 条件跳转(与零比较)
if-eqz vAA, +BBBB                  # if (vAA == 0) goto +BBBB
if-nez vAA, +BBBB                  # if (vAA != 0) goto +BBBB
if-ltz vAA, +BBBB                  # if (vAA < 0) goto +BBBB
if-gez vAA, +BBBB                  # if (vAA >= 0) goto +BBBB
if-gtz vAA, +BBBB                  # if (vAA > 0) goto +BBBB
if-lez vAA, +BBBB                  # if (vAA <= 0) goto +BBBB

8. 完整代码示例

8.1 MainActivity.smali 完整示例

.class public Lcom/social_touch/demo/MainActivity;
.super Landroid/app/Activity;
.source "MainActivity.java"# instance fields
.field private TAG:Ljava/lang/String;
.field private running:Z# direct methods
.method public constructor <init>()V.locals 1.prologue.line 8# 调用Activity中的init()方法invoke-direct {p0}, Landroid/app/Activity;-><init>()V.line 10const-string v0, "MainActivity"iput-object v0, p0, Lcom/social_touch/demo/MainActivity;->TAG:Ljava/lang/String;.line 13const/4 v0, 0x0iput-boolean v0, p0, Lcom/social_touch/demo/MainActivity;->running:Zreturn-void
.end method# 静态方法log()
.method public static log(I)V.locals 3.parameter "result"    # 表示result参数.prologue.line 42# v0寄存器中赋值为"MainActivity"const-string v0, "MainActivity"# 创建StringBuilder对象,并将其引用赋值给v1寄存器new-instance v1, Ljava/lang/StringBuilder;# 调用StringBuilder中的构造方法invoke-direct {v1}, Ljava/lang/StringBuilder;-><init>()V# v2寄存器中赋值为"the result:"const-string v2, "the result:"# {v1,v2}大括号中v1寄存器中存储的是StringBuilder对象的引用# 调用StringBuilder中的append(String str)方法,v2寄存器则是参数寄存器invoke-virtual {v1, v2}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;# 获取上一个方法的执行结果,此时v1中存储的是append()方法执行后的结果# 此处之所以仍然返回v1的原因在于append()方法返回的就是自身的引用move-result-object v1# 继续调用append方法(),p0表示第一个参数寄存器,即上面提到的result参数invoke-virtual {v1, p0}, Ljava/lang/StringBuilder;->append(I)Ljava/lang/StringBuilder;# 同上move-result-object v1# 调用StringBuilder对象的toString()方法invoke-virtual {v1}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;# 获取上一个方法执行结果,toString()方法返回了一个新的String对象# 因此v1中此时存储了String对象的引用move-result-object v1# 调用Log类中的静态方法d()。因为d()是静态方法,因此{v0,v1}中的都成了参数寄存器invoke-static {v0, v1}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I.line 43# 调用返回指令,此处没有返回任何值return-void
.end method# virtual methods
.method public add(II)I.locals 1.parameter "x"    # 第一个参数.parameter "y"    # 第二个参数.prologue.line 34# 调用add-int指令求和之后将结果赋值给v0寄存器add-int v0, p1, p2# 返回v0寄存器中的值return v0
.end method.method public onClick(Landroid/view/View;)V.locals 4.parameter "view"    # 参数view.prologueconst/4 v3, 0x4    # v3寄存器中赋值为4.line 23    # java源文件中的第23const/4 v1, 0x5    # v1寄存器中赋值为5# 调用add()方法invoke-virtual {p0, v3, v1}, Lcom/social_touch/demo/MainActivity;->add(II)I# 从v0寄存器中获取add方法的执行结果move-result v0.line 24    # java源文件中的24.local v0, result:I# v1寄存器中赋值为PrintStream对象的引用outsget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream;# 执行out对象的println()方法invoke-virtual {v1, v0}, Ljava/io/PrintStream;->println(I)V.line 26const/16 v1, 0x9    # v1寄存器中赋值为9const/4 v2, 0x3     # v2寄存器中赋值为3# 调用sub()方法,{p0,v1,v2},p0指的是this,即当前对象,v1,v2则是参数invoke-virtual {p0, v1, v2}, Lcom/social_touch/demo/MainActivity;->sub(II)I# 从v0寄存器中获取sub()方法的执行结果move-result v0.line 28# 如果v0寄存器的值小于等于v3寄存器中的值,则跳转到cond_0处继续执行if-le v0, v3, :cond_0.line 29# 调用静态方法log()invoke-static {v0}, Lcom/social_touch/demo/MainActivity;->log(I)V.line 31:cond_0return-void
.end method.method protected onCreate(Landroid/os/Bundle;)V.locals 1.parameter "savedInstanceState"    # 参数savedInstanceState.prologue.line 17# 调用父类方法onCreate()invoke-super {p0, p1}, Landroid/app/Activity;->onCreate(Landroid/os/Bundle;)V.line 18const v0, 0x7f04001a    # v0寄存器赋值为0x7f04001a# 调用方法setContentView()invoke-virtual {p0, v0}, Lcom/social_touch/demo/MainActivity;->setContentView(I)V.line 19return-void
.end method# declared-synchronized表示该方法是同步方法
.method public declared-synchronized sub(II)I.locals 1.parameter "x".parameter "y".prologue.line 38monitor-enter p0    # 为该方法添加锁对象p0sub-int v0, p1, p2  # v0 = p1 - p2monitor-exit p0     # 释放锁对象return v0
.end method

8.2 对应的Java源码

public class MainActivity extends Activity {private String TAG = "MainActivity";private boolean running = false;public MainActivity() {// 构造方法}@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(0x7f04001a);}public void onClick(View view) {int result = add(4, 5);System.out.println(result);result = sub(9, 3);if (result > 4) {log(result);}}public int add(int x, int y) {return x + y;}public synchronized int sub(int x, int y) {return x - y;}public static void log(int result) {String message = "the result:" + result;Log.d("MainActivity", message);}
}

9. 编译与运行

9.1 smali2dex编译

将smali文件编译为dex文件:

# 使用smali工具编译
java -jar smali.jar assemble src.smali -o src.dex

工具下载

  • smali.jar: https://bitbucket.org/JesusFreke/smali/overview

9.2 运行smali

步骤1:推送dex文件到设备
adb push main.dex /sdcard/
步骤2:使用dalvikvm运行
adb shell dalvikvm -cp /sdcard/main.dex main

参数说明

  • dalvikvm: Dalvik虚拟机命令
  • -cp: classpath路径,指定dex文件位置
  • main: 要执行的类名

9.3 实用工具

工具功能用途
baksmalidex → smali反编译dex文件
smalismali → dex编译smali文件
apktoolapk ↔ 资源+smaliAPK完整解包/重打包
jadxdex → java直接反编译为Java代码

10. 实战技巧

10.1 常见分析模式

查找字符串
const-string v0, "关键字符串"
查找方法调用
invoke-virtual {v0, v1}, Lcom/target/Class;->targetMethod(I)V
查找跳转逻辑
if-eqz v0, :bypass_check    # 如果v0==0则跳过检查
# 正常检查逻辑
return-void:bypass_check
# 绕过检查的逻辑

10.2 修改技巧

绕过检查
# 原代码:检查失败则退出
if-eqz v0, :exit_app# 修改为:检查失败也继续
# if-eqz v0, :exit_app    # 注释掉
修改返回值
# 原代码:返回检查结果
return v0# 修改为:总是返回true
const/4 v0, 0x1
return v0

10.3 调试技巧

添加日志输出
# 在关键位置添加调试日志
const-string v1, "DEBUG"
const-string v2, "执行到此处"
invoke-static {v1, v2}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I

11. 参考资源

11.1 官方文档

  • Android官方文档
  • Dalvik字节码格式

11.2 工具资源

  • smali/baksmali工具
  • apktool
  • jadx反编译器

11.3 学习资源

  • CTF Wiki - Smali
http://www.dtcms.com/a/340066.html

相关文章:

  • [ Maven 开发工具 ] 环境搭建及配置
  • DRM驱动架构浅析-上(DRM基础概要与U-Boot阶段驱动解析)
  • 基于 OpenMV 的矩形识别与 STM32 串口通信(电子设计大赛实用教程)
  • k8s运维实践:高可用Redis Cluster(三主三从)与Proxy部署方案
  • 使用 Docker 安装长安链管理平台 + 部署区块链与示例合约
  • daily notes[3]
  • Eigen中Dense 模块简要介绍和实战应用示例(最小二乘拟合直线、协方差矩阵计算和稀疏求解等)
  • 三极管驱动led灯搭配的电阻选取方法
  • 跟随广州AI导游深度探寻广州历史底蕴​
  • 如何做一次AIMD
  • 农田扫描提速37%!基于检测置信度的无人机“智能抽查”路径规划,Coovally一键加速模型落地
  • [OWASP]智能体应用安全保障指南
  • 英伟达显卡驱动怎么更新 详细步骤教程
  • MySQL练习题50题(附带详细教程)
  • Day13_【DataFrame数据组合concat连接】【案例】
  • C5.5:VDB及后面的电路讨论
  • 决策树(2)
  • Yum使用时报错
  • Spring Boot 全局异常处理
  • 快速了解Anaconda系统
  • 08.5【C++ 初阶】实现一个相对完整的日期类--附带源码
  • implement libtime on Windows
  • MyCAT基础概念
  • Python函数总结
  • week2-[一维数组]最大元素
  • 单细胞格式转换 rds 转成 h5ad
  • transformer模型初理解
  • Transformer、BERT、BEiT等模型相关八股及代码【自用】
  • HJ4 字符串分隔
  • 神经网络训练过程详解