Smali语法

基础介绍

怎么得到smali文件?

apk文件通过apktool反编译出来的都有一个smali文件夹,里面都是以.smali结尾的文件。

Smali是什么?

Smali是安卓系统里的Java虚拟机(Dalvik)所使用的一种.dex格式文件的汇编器,而Baksmali是反汇编器。其语法是一种宽松式的Jasmin/dedexer语法。

Smali语言其实就是一种面向Dalvik的汇编语言(汇编语言是一种用于电子计算机、微处理器、微控制器,或其他可编程器件的低级语言。在不同的设备中,汇编语言对应着不同的机器语言指令集。使用汇编语言编写的源代码,然后通过相应的汇编程序将它们转换成可执行的机器代码。这一过程被称为汇编过程。),我们用smali语言写的代码通过汇编器就转换成Dalvik可以执行的dex文件。

对应的有两个工具,smali.jar将Smali文件转换为dex文件,baksmali.jar则是将dex文件转换为smali文件。apktool能反编译出smali文件就是因为它里面用了baksmali工具。

为什么要学习Smali?

smali语法是逆向的基础,它能帮助我们理解别人代码的逻辑(当然我们也可以用dex2jar, 更方便查看代码),但是当我们要修改别人的代码逻辑,或者加上自己的逻辑,最终是要修改smali文件的,如果我们不懂smali语法,就无从下手。

如何学习?

smali的语法细节太多了,各种指令,看的人头疼,我们其实也不需要全都学会,因为现在有工具帮我们了。

AndroidStudio有个插件: Java2Smali,方便快捷把java代码转换为smali代码。有了这个工具,我们可以结合它在实践中学习smali语法,同时它也可以帮我们省很多事,当我们想在别人的逻辑中增加代码时,我们可以把自己的代码先用这个工具转成smali语句, 然后做微调就行了。

当然我们还是需要学习一些基础语法,了解一些常用指令。

如果想完整学习smali语法,可以看看Android软件安全与逆向分析这本书,网上的很多资料也是来源于这本书。

语法简介

Dalvik是基于寄存器结构的,在Dalvik字节码中,寄存器都是32位的,能够支持任何类型,64位类型(Long/Double)用2个寄存器表示;Dalvik字节码有两种类型:原始类型;引用类型(包括对象和数组)。

原始类型

简写 含义
B byte
C char
D double (64 bits)
F float
I int
J long (64 bits)
S short
V void 只能用于返回值类型
Z boolean

对象类型

形式:Lxxx/yyy/zzz;

L 表示这是一个对象类型
xxx/yyy是该对象所在的包
zzz是对象名称
;标识对象名称的结束

如:Ljava/lang/String;

数组类型

形式:[XXX

[I表示一个int型的一维数组,相当于int[]
增加一个维度增加一个[,如[[I表示int[][]
数组每一个维度最多255个;
对象数组表示也是类似

如:String数组的表示是[Ljava/lang/String

方法

形式:Lxxx/yyy/zzz;->methodName(Lxxx/yyy/zzz;Lxxx/yyy/zzz;I)Z

Lxxx/yyy/zzz 表示对象的类型
methodName 表示方法名
Lxxx/yyy/zzz;Lxxx/yyy/zzz;I 表示三个参数,两个对象类型,一个整型(方法的参数是一个接一个的,中间没有隔开)
Z返回值类型,这里是boolean类型

如:Log.i("xxx", msg); 转换成smali语句之后是

Landroid/util/Log;->i(Ljava/lang/String;Ljava/lang/String;)I

字段

形式:Lxxx/yyy/zzz;->FieldName:Lxxx/yyy/zzz;

Lxxx/yyy/zzz; 表示对象的类型

FieldName表示字段名称

Lxxx/yyy/zzz表示字段类型

如:ff = "aa"; 转换后是 Lcom/example/reforceapk/MyLog;->ff:Ljava/lang/String

寄存器和变量

Dalvik变量都是存放在寄存器中的,寄存器的命名方法有两种,v命名法和p命名法,一般我们都用p命名法。

p命名法:寄存器采用v和p来命名,v表示本地寄存器,p表示参数寄存器。

如果一个非静态方法有两个本地变量,有三个参数,需要的寄存器关系如下:

v0 第一个本地寄存器
v1 第二个本地寄存器
p0 // 指代调用这个方法的this对象。
p1 第一个参数
p2 第二个参数
p3 第三个参数

如果是静态方法,那么就不需要this对象了,需要的寄存器是v0, v1, p0, p1, p2。

.registers使用这个指令指定方法中寄存器的总数
.locals使用这个指定表明方法中非参寄存器的总数,放在方法的第一行。

常用指令

.field private isFlag:z — 定义变量

.method — 方法

.prologue — 方法开始

.end method — 方法结束

.parameter — 方法参数

.line 12 — 此方法位于第12行

invoke-super — 调用父函数

invoke-direct — 调用函数

invoke-static — 调用静态函数

const/high16 v0, 0x7f03 — 把0x7f03赋值给v0

return-void — 函数返回void

new-instance — 创建实例

move-result v0 — 将上一个invoke类型的指令操作的非对象结果赋值给v0寄存器

move-result-object v0 — 将上一个invoke类型指令操作的对象赋值给v0寄存器

字段操作指令

对于实例字段和静态字段有两类指令:
iget, iput 对实例字段进行读,写
sget, sput 对静态字段进行读,写

会根据不同类型添加不同的后缀:

iget,iget-object,iget-boolean,iget-byte,iget-char,iget-short
iput,iput-object,iput-boolean,iput-byte,iput-char,iput-short

获取static fields的指令示例:

sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;

上句中sget-object指令把out这个变量获取并放到v0寄存器中。

获取instance fields的指令与static fields的类似,需要指明对象所属的实例。示例:

iget-object v0, p0, Lcom/example/reforceapk/MyLog;->ff:Ljava/lang/String;

上句iget-object指令比sget-object多了一个参数p0,就是该变量所在类的实例,在这里就是p0即“this”。

条件跳转指令

“if-eq vA, vB, :cond_x” — 如果vA等于vB则跳转到:cond_x
“if-ne vA, vB, :cond_x” — 如果vA不等于vB则跳转到:cond_x
“if-lt vA, vB, :cond_x” — 如果vA小于vB则跳转到:cond_x
“if-ge vA, vB, :cond_x” — 如果vA大于等于vB则跳转到:cond_x
“if-gt vA, vB, :cond_x” — 如果vA大于vB则跳转到:cond_x
“if-le vA, vB, :cond_x” — 如果vA小于等于vB则跳转到:cond_x
“if-eqz vA, :cond_x” — 如果vA等于0则跳转到:cond_x
“if-nez vA, :cond_x” — 如果vA不等于0则跳转到:cond_x
“if-ltz vA, :cond_x” — 如果vA小于0则跳转到:cond_x
“if-gez vA, :cond_x” — 如果vA大于等于0则跳转到:cond_x
“if-gtz vA, :cond_x” — 如果vA大于0则跳转到:cond_x
“if-lez vA, :cond_x” — 如果vA小于等于0则跳转到:cond_x

你可能感兴趣的:(逆向)