0x00: 介绍
在Android逆向中,经常会遇到Smali语言,那什么是Smali语言呢?总的来说,Smali是Android设备使用的Dalvik虚拟机的一种汇编语言,通常用于对APK文件进行反编译、分析和修改。
从实际角度出发,当创建应用程序代码时,apk 文件包含一个 .dex 文件,其中包含二进制 Dalvik 字节码。这是Android平台实际理解的格式。然而,读取或修改二进制代码并不容易。为了方便,便产生了一些工具可以使得在Dalivik字节码与人类可读的表示形式之间进行转换。Smali即是这个人类可读的表示形式。
值得一提的是,Smali并非是官方提出,提出者应该是这个仓库的主人:https://github.com/JesusFreke/smali。仓库中包含两个关键的工具:
- baksmali工具:将Dalvik字节码反编译为Smali代码;
- smali工具:将Smali代码重新编译为Dalvik字节码;
工具下载:JesusFreke / smali / Downloads — Bitbucket
本篇文章让首次接触的对smali语言有大概印象,更多更详细的内容可查阅:https://pysmali.readthedocs.io/en/latest/api/smali/language.html
0x01: Demo
Demo.java:
package com.example.smalidemo;
public class Demo {
private int num;
public Demo() {
num = 10;
}
public void Print() {
System.out.println("Hello, Smali.");
int a = 10;
int res = compute(a, num);
System.out.println(res);
}
public int compute(int a, int b) {
if (a < b) {
return a + b;
} else {
return a * b;
}
}
}
工具的基本命令:
java -jar baksmali.jar d <dex-file> -o <output-directory>
java -jar smali.jar a <input-directory> -o <dex-file>
Demo.java生成对应的Demo.smali :
.class public Lcom/example/smalidemo/Demo;
.super Ljava/lang/Object;
.source "Demo.java"
# instance fields
.field private num:I
# direct methods
.method public constructor <init>()V
.registers 2
.line 6
invoke-direct {p0}, Ljava/lang/Object;-><init>()V
.line 7
const/16 v0, 0xa
iput v0, p0, Lcom/example/smalidemo/Demo;->num:I
.line 8
return-void
.end method
# virtual methods
.method public Print()V
.registers 4
.line 11
sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;
const-string v1, "Hello, Smali."
invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
.line 12
const/16 v0, 0xa
.line 13
.local v0, "a":I
iget v1, p0, Lcom/example/smalidemo/Demo;->num:I
invoke-virtual {p0, v0, v1}, Lcom/example/smalidemo/Demo;->compute(II)I
move-result v1
.line 14
.local v1, "res":I
sget-object v2, Ljava/lang/System;->out:Ljava/io/PrintStream;
invoke-virtual {v2, v1}, Ljava/io/PrintStream;->println(I)V
.line 15
return-void
.end method
.method public compute(II)I
.registers 4
.param p1, "a" # I
.param p2, "b" # I
.line 18
if-ge p1, p2, :cond_5
.line 19
add-int v0, p1, p2
return v0
.line 21
:cond_5
mul-int v0, p1, p2
return v0
.end method
0x02: 基本定义
数据类型:
Java 数据类型 | Smali 符号 |
---|---|
byte | B |
short | S |
int | I |
long | J |
float | F |
double | D |
boolean | Z |
char | C |
class 或 interface | Lclassname; |
数组 | [类型 |
类的定义:
.class public Lcom/example/smalidemo/Demo;
.super Ljava/lang/Object;
.source "Demo.java"
.class
是定义类的关键字,public
是访问修饰符,L
表示类数据类型,后面紧接着类的全名;.super
是定义父类的关键字;.source
关键字用于定义源文件名;
(other: #
是注释)
字段的定义
在Smali中,字段(Field)是类中的成员变量
.field private num:I
iput v0, p0, Lcom/example/smalidemo/Demo;->num:I
iget v1, p0, Lcom/example/smalidemo/Demo;->num:I
.field
是定义字段的关键字,private
是访问修饰符,num
是字段名,I
即int类型;iput
用于设置字段的值,v0->p0
,v表示寄存器,p表示函数参数。p0即第一个参数,默认是this
。后面则是字段的类型和名称;iget
用于获取字段的值,v1<-p0
;
其他的字段操作指令还有:sget、sput、sget-object、sput-object
(other: .line
关键字表示java源文件中指定行)
函数的定义:
.method public Print()V
.registers 4
.local v0, "a":I
.local v1, "res":I
return-void
.end method
.method
是定义函数的关键字,Print()
是函数名,括号中会表明函数各个参数的类型,V
函数返回类型,这里是void
;.end method
表明方法结束;.registers
用于声明函数中所使用的总寄存器数量,.locals
用于声明函数中局部变量的数量;.local
用于定义局部变量,v0
是变量所关联的寄存器,"a":I
是变量名称和类型;- 函数返回:
return-void
:无返回值;return
:返回32位基本数据类型,如int
、float
、boolean
、char
等;return-wide
: 用于返回long
和double
类型的数据,这些类型占用两个寄存器;return-object
:用于返回对象引用或数组;
.param p1, "a" # I
.param p2, "b" # I
invoke-direct {p0}, Ljava/lang/Object;-><init>()V
invoke-virtual {p0, v0, v1}, Lcom/example/smalidemo/Demo;->compute(II)I
.param
:定义参数的调试信息,p1
是第一个参数,"a"
是名称,并注释着数据类invoke-direct
:调用私有方法、构造函数(<init>
)或父类中的方法;invoke-virtual
:调用对象的实例方法;
0x03: Dalvik指令集
数据传输和常量加载:
move v0, v1 # 将 v1 中的数据传输到 v0
move-wide v0, v2 # 将 v2 和 v3 中的 64 位数据传输到 v0 和 v1
move-object v0, v1 # 将 v1 中的对象引用传输到 v0
const/4 v0, 0x3 # 将 3 (0x3) 加载到 v0 中
const/16 v0, 0x1234 # 将 0x1234 加载到 v0 中
const v0, 0x12345678 # 将 0x12345678 加载到 v0 中
const-string v0, "Hello, Smali!" # 将字符串引用加载到 v0 中
const-class v0, Ljava/lang/String; # 将 java.lang.String 类引用加载到 v0 中
move
:寄存器数据传输;move-wide
:用于传输64位数据类型;move-object
:用于传输对象引用;const/4
:将4位常量加载到寄存器中;const/16
:将16位常量加载到寄存器中;const
:将32位常量加载到寄存器中;const-string
:将字符串引用加载到寄存器中;const-class
:将类引用加载到寄存器中;
算术运算:
add-int v0, v1, v2 # v0 = v1 + v2
sub-int v0, v1, v2 # v0 = v1 - v2
mul-int v0, v1, v2 # v0 = v1 * v2
div-int v0, v1, v2 # v0 = v1 / v2
add-int
/add-float
:执行整数或浮点数的加法;sub-int
/sub-float
:减法;mul-int
/mul-float
:乘法;div-int
/div-floar
:除法;
逻辑运算:
and-int v0, v1, v2 # v0 = v1 & v2
or-int v0, v1, v2 # v0 = v1 | v2
xor-int v0, v1, v2 # v0 = v1 ^ v2
and-int
:执行与操作;or-int
:或操作;xor-int
:异或操作
分支和跳转:
if-eq v0, v1, :label # if (v0 == v1) goto label
if-ne v0, v1, :label # if (v0 != v1) goto label
if-lt v0, v1, :label # if (v0 < v1) goto label
if-ge v0, v1, :label # if (v0 >= v1) goto label
if-nez v0, :label # if v0 is not zero goto labal
if-eq
:当两个寄存器的值相等时跳转;if-ne
:当两个寄存器的值不相等时跳转;if-lt
:当第一个寄存器的值小于第二个寄存器时跳转;if-ge
:当第一个寄存器的值大于或等于第二个寄存器时跳转;if-nez
:如果寄存器v0
的值不为零,则跳转到指定的标签。