Smali语法学习摘要
一、Smali 简介
首先,提到smali就先说下逆向,逆向通常是安全工程师(逆向工程师),系统分析三方APP以及做破解等恶意分子因为某些利益在做(apk二次打包插入广告、破解收费应用、恶意代码植入、剽窃api等)的一种手段。
当然技术是一把双刃剑,在于使用技术的人而不再技术本身。
回归正题,Smali是一种宽松式的Jasmin/dedexer语法,是Davlik的寄存器语言,语法上和汇编语言相似,Dalvik VM与JVM的最大的区别之一就是Dalvik VM是基于寄存器的。基于寄存器的意思是,在smali里的所有操作都必须经过寄存器来进行。
Smali,Baksmali分别是冰岛语中编译器,反编译器的叫法。也许你会问为什么是冰岛语呢,因为Dalvik是一个冰岛渔村名字。
二、Smali 语法
1. 数据类型
Davlik字节码中,寄存器都是32位的,能够支持任何类型,64位类型(Long/Double)用2个寄存器表示。
Dalvik字节码有两种类型:基本类型;引用类型(包括对象和数组)。
(1)基本类型
type | 解释 |
---|---|
V | void只能用于返回值类型 |
Z | boolean |
B | byte |
S | short |
C | char |
I | int |
J | long(64位) |
F | float |
D | double(64位) |
(2)引用类型
type | 解释 |
---|---|
L | 对象类型(Lpackage/ObjectName; 相当于java中的package.ObjectName;) |
[I | 表示一个整形的一维数组,相当于java的int[]; |
[Ljava/lang/String | 表示一个String的对象数组 |
① 对象类型:
“L“:表示这是一个对象类型
”package/ObjectName“:该对象所在的包与类名,比如Ljava/lang/String =>java.lang.String
”;“:表示对象名称的结束
② 数组的表示形式:
” [I “ :表示一个整形的一维数组,相当于java的int[];
对于多维数组,只要增加”[“ 就行了,[[I => int[][]; 注:每一维最多255个;
③ 对象数组的表示形式:
[Ljava/lang/String 表示一个String的对象数组;
2. 基础语法
2.1 表达式
Java源代码:
public void smaliExpression(){
//加法运算
int a = 1;
double b = 2.5;
double c = a + b;
//减法运算
double d = b - a;
//乘法运算
double e = a * b;
//除法运算
double f = b / a;
//异或运算
int g = 3;
int h = a ^ g;
//三目运算
int i = a > b?a:g;
}
Smali代码:
.method public smaliExpression()V
.locals 15
.line 16
const/4 v0, 0x1 #1
.line 17
.local v0, "a":I
const-wide/high16 v1, 0x4004000000000000L # 2.5
.line 18
.local v1, "b":D
int-to-double v3, v0 //将int型的 1 强转为double的 1.0
add-double/2addr v3, v1 //两个double类型相加
.line 21
.local v3, "c":D
int-to-double v5, v0
sub-double v5, v1, v5 //减法 V5 = V1-V5
.line 24
.local v5, "d":D
int-to-double v7, v0
mul-double/2addr v7, v1 //乘法
.line 27
.local v7, "e":D
int-to-double v9, v0
div-double v9, v1, v9 //除法
.line 30
.local v9, "f":D
const/4 v11, 0x3
.line 31
.local v11, "g":I
xor-int v12, v0, v11 //异或
//异或语句,代条件语句
.line 34
.local v12, "h":I
int-to-double v13, v0
cmpl-double v13, v13, v1 //cmpl-double 比较
if-lez v13, :cond_0
move v13, v0
goto :goto_0
:cond_0
move v13, v11 //将寄存器v11值给v13
.line 35
.local v13, "i":I
:goto_0
return-void
.end method
整理如下:
java运算符 | samli运算符 |
---|---|
加法 | add-double/2addr |
减法 | sub-double |
乘法 | mul-double/2addr |
除法 | div-double |
异或 | xor-int |
三目运算 | 条件语句 |
可以看到三目运算符本身就是条件语句,所以我们看下详细的条件语句是怎么样的。
2.2 条件语句
先上对比代码。
java:
public void smaliIf(){
int a = 1, b = 2;
int c = 0;
if(a > b){
c = a;
}
if(a < b){
c = b;
}
if(a>=b){
c = a;
}
if(a <= b){
c = b;
}
if(a==b){
c = a;
}
if(a != b){
c = b;
}
}
samli:
.method public smaliIf()V
.locals 3
.line 38
const/4 v0, 0x1
.local v0, "a":I
const/4 v1, 0x2
.line 39
.local v1, "b":I
const/4 v2, 0x0
.line 40
.local v2, "c":I
if-le v0, v1, :cond_0
.line 41
move v2, v0
.line 43
:cond_0
if-ge v0, v1, :cond_1
.line 44
move v2, v1
.line 46
:cond_1
if-lt v0, v1, :cond_2
.line 47
move v2, v0
.line 49
:cond_2
if-gt v0, v1, :cond_3
.line 50
move v2, v1
.line 52
:cond_3
if-ne v0, v1, :cond_4
.line 53
move v2, v0
.line 55
:cond_4
if-eq v0, v1, :cond_5
.line 56
move v2, v1
.line 58
:cond_5
return-void
.end method
归纳总结整理如下:
“if-eq vA, vB, :cond_*” 如果vA等于vB则跳转到:cond_*,否则继续向下执行
“if-ne vA, vB, :cond_*” 如果vA不等于vB则跳转到:cond_*,否则继续向下执行
“if-lt vA, vB, :cond_*” 如果vA小于vB则跳转到:cond_*,否则继续向下执行
“if-ge vA, vB, :cond_*” 如果vA大于等于vB则跳转到:cond_*,否则继续向下执行
“if-gt vA, vB, :cond_*” 如果vA大于vB则跳转到:cond_*,否则继续向下执行
“if-le vA, vB, :cond_*” 如果vA小于等于vB则跳转到:cond_*,否则继续向下执行
“if-eqz vA, :cond_*” 如果vA等于0则跳转到:cond_*,否则继续向下执行
“if-nez vA, :cond_*” 如果vA不等于0则跳转到:cond_**,否则继续向下执行
“if-ltz vA, :cond_*” 如果vA小于0则跳转到:cond_*,否则继续向下执行
“if-gez vA, :cond_*” 如果vA大于等于0则跳转到:cond_*,否则继续向下执行
“if-gtz vA, :cond_*” 如果vA大于0则跳转到:cond_*,否则继续向下执行
“if-lez vA, :cond_*” 如果vA小于等于0则跳转到:cond_*,否则继续向下执行
其中的:cond_* 中 “ * ” 是编号(代表1,2,3.。。),在一个方法里如果有多个条件则这个编号不可以重复。
2.3 循环语句
还是一样,先上对照代码:
java:
public void smaliWhile(){
//while
int a = 0;
while(a<=3){
a++;
}
//for
int b = 0;
for(int i = 0;i<3;i++){
b++;
}
//do...while
int c = 0;
do{
c++;
}while (c <= 3);
}
smali:
.method public smaliWhile()V
.locals 5
.line 62
const/4 v0, 0x0
move v1, v0
.line 63
.local v1, "a":I
:goto_0
const/4 v2, 0x3
if-gt v1, v2, :cond_0 //如果a > 3跳转 cond_0
.line 64
add-int/lit8 v1, v1, 0x1 //上面if条件不存在,a自增1
goto :goto_0 //循环主体,继续从goto_0向下执行
.line 68
:cond_0
const/4 v3, 0x0
.line 69
.local v3, "b":I
move v4, v3
move v3, v0
.local v3, "i":I
.local v4, "b":I
:goto_1
if-ge v3, v2, :cond_1 // i >= 3
.line 70
add-int/lit8 v4, v4, 0x1 // b++;
.line 69
add-int/lit8 v3, v3, 0x1 //i++;
goto :goto_1 //循环主体
.line 74
.end local v3 # "i":I
:cond_1
nop //表示空操作,什么都不干
.line 76
.local v0, "c":I
:cond_2
add-int/lit8 v0, v0, 0x1
.line 77
if-le v0, v2, :cond_2
.line 78
return-void
.end method
所以循环关键点在于循环体开始地方使用 :goto_* 标识,执行循环的地方使用 goto :goto_*,判断是否跳出循环仍然使用上面我们将的条件语句。
2.4 try-catch语句
继续上代码
java:
public void smaliTryCatch(){
Object a = null;
try {
a = null;
}catch (Exception e){
a.toString();
}finally {
a = new Object();
}
}
smali:
.method public smaliTryCatch()V
.locals 2
.line 81
const/4 v0, 0x0
.line 83
.local v0, "a":Ljava/lang/Object;
const/4 v0, 0x0
.line 87 //v1: new Object();
new-instance v1, Ljava/lang/Object;
invoke-direct {v1}, Ljava/lang/Object;-><init>()V
move-object v0, v1
.line 88
nop
.line 89
return-void
.end method
这里的try-catch很奇怪,好像catch部分执行了nop也就是空操作,是smali不会去管异常吗?我也不清楚了,如果大家有知道的可以告诉我。
这里主要就是有一个new Object过程,new一个对象在smali中是怎么样的?就是上面这样的.line87的内容。
说到这里,基本的语法就先到这里了,下面用一个简单的类文件来看下smali类文件结构。
3. 类文件结构
因为一个类文件的Smali代码比较长,所以我们分开来解释。
3.1 头信息
.class public Lcom/justart/samlidemo/MainActivity;
.super Landroid/app/Activity;
.source "MainActivity.java"
.class 表示类路径 包+类名
.super 表示父类的路径和地址
.source 表示源码文件名
3.2 构造方法
# direct methods
.method public constructor <init>()V
.locals 0
.line 6
invoke-direct {p0}, Landroid/app/Activity;-><init>()V
return-void
.end method
因为源码中我没有重写构造方法,所以默认的无参构造方法里直接调用父类Activity的无参构造方法。
3.3 其他方法
这里以Activity的onCreate方法为例:
# virtual methods //表示是一个虚方法
.method protected onCreate(Landroid/os/Bundle;)V
.locals 1
.param p1, "savedInstanceState" # Landroid/os/Bundle;
.line 10
invoke-super {p0, p1}, Landroid/app/Activity;->onCreate(Landroid/os/Bundle;)V
.line 11
const/high16 v0, 0x7f050000
invoke-virtual {p0, v0}, Lcom/justart/samlidemo/MainActivity;->setContentView(I)V
.line 12
return-void
.end method
(1)方法以 .method开始, .end method 结束;
(2)方法第一行最后 V表示返回类型为void,其他返回类型见第一节数据类型;
(3)方法参数也遵循smali数据类型,这里表示参数是一个Bundle对象类型;
(4).param 表示 方法的参数,默认参数使用p0表示;
(5)最后是方法的返回类型 这里表示返回void。
3.4 附录
下面简单总结一下类中常用的一些关键词:
关键词 | 说明 |
---|---|
.class | 定义类类型 包名+类名 |
.super | 定义父类的路径和地址 |
.source | 表示源码文件名 |
filed | 定义字段 |
.method…end method | 定义方法 |
.annotation…end annotation | 定义注解 |
.implements | 定义接口指令 |
.local | 指定了方法内局部变量的个数 |
.registers | 指定方法内使用寄存器的总数 |
.prologue | 表示方法中代码的开始处 |
.line | 表示java源文件中指定行 |
.paramter .param | 指定了方法的参数 |
三、总结
文章写得比较粗糙,希望大家给点建议,有错误的地方可以指出来,谢谢大家!
说下我的学习感受吧,梳理一遍smali语法,发现同时有助于了解java代码的执行流程,也可以了解寄存器相关的入门知识,同时熟悉smali之后可以调试三方或者修改APP。简单的修改三方APP代码可以参考我的另外一篇文章反编译三方apk并添加debug log。