smail语法教程小白版

smail语法教程小白版

smail语法教程小白版

本教程参考,许多源码也引自CSDN。

前面由于我教程原因,导致编写错误,应该是smali,而不是smail,后面如果有为smail的,请自动将其过滤为smali,对大家造成的困扰,我深表歉意。

序言:
首先,上段代码来看看smali是什么东西

const/4 v0, 0x1

我想许多对这句代码都及其熟悉,但对它的含义却不太明确
而本教程就是教你基本读懂它们。
首先我们先解释一下,上面那句代码是什么意思
const 的含义是为寄存器赋值(可以先将寄存器理解成变量)/4 /可以理解成一个标识,而后面的数字,只是表示一个类型,而整体含义则表示为将要为寄存器赋的值为4字节的,而v0则是寄存器,而,号后面的就是要赋的值,为0x1,而他的值就是0×4+1=1,所以v1的值就是1,而0x0就是0×4+0=0,为0。同样的还有const/16 v0, 0x10。按照上面的讲解可知,16表示16字节,v0代表寄存器,0x10是赋值,值为1×16+0=16,同样的const还有const-string,const-class,const-wide等等,
而后我们的正文会进行详细讲解。

正文开始
首先我们讲解一下,smail是什么
讲smail就必须知道一下Dalvik,下面我将用百科的一段介绍让你们稍稍了解一下Dalvik就行。
/下文引自百度百科/ Dalvik是Google公司自己设计用于Android平台的虚拟机。Dalvik虚拟机是Google等厂商合作开发的Android移动设备平台的核心组成部分之一。它可以支持已转换为 .dex(即DalvikExecutable)格式的Java应用程序的运行,.dex格式是专为Dalvik设计的一种压缩 格式,适合内存和处理器速度有限的系统。Dalvik 经过优化,允许在有限的内存中同时运行多个虚拟机的实例,并且每一个Dalvik应用作为 一个独立的Linux 进程执行。独立的进程可以防止在虚拟机崩溃的时候所有程序都被关闭。 了解完Dalvik,我们在来看下smali /smali介绍/
1.apk文件通过apktool反编译出来的都有一个smali文件夹,
里面都是以.smali结尾的文件。
2.smali语言是Davlik的寄存器语言,语法上和汇编语言相似,Dalvik VM[1]与JVM的最大的区别之一就是Dalvik VM是基于寄存器的。基于寄存器的意思是,在smali里的所有操作都必须经过寄存器来进行。
3.Smali,Baksmali 分别是指安卓系统里的 Java 虚拟机(Dalvik)所使用的一种 dex 格式文件的汇编器,反汇编器。其语法是一种宽松式的
Jasmin/dedexer 语法,而且它实现了 .dex 格式所有功能(注解,调试信息,线路信息等)
4.当我们对 APK 文件进行反编译后,便会生成此类文件。在Davlik字节码中,寄存器都是32位的,能够支持任何类型,64位类型(Long/Dou
ble)用2个寄存器表示;Dalvik字节码有两种类型:原始类型;
引用类型(包括对象和数组)
引自CSDN

——————————分割线—————————————

下面,是一些需要先了解的知识点
基本数据类型:
1.原始数据类型
V void (只能用于返回值类型)
Z boolean
B byte
S short
C char
I int
J long(64位)两个寄存器
F float
D double(64位)
L 对象类型
数组类型
/上面这些原始类型,大家应该都明白,就不多说了*/

2.对象类型
Lpackage/name/ObjectName;
相当于java中的package.name.ObjectName;
L 表示这是一个对象类型
package/name 该对象所在的包
ObjectName 对象名称
; 标识对象名称的结束
/参考自CSDN某大佬文章/ /请记住这些,后面会及其常见的/

下面用段简单的代码来讲解

/*
这是一段Java代码,它是一个名为test的方法
方法的返回值是boolean内型的
方法内创建了一个局部变量a,并为a负值true
然后返回a
**/
public boolean test(){
boolean a = true;
return a;
}

//而下面则是他的编译成dex文件后的smail语法内容
.method public test(I)Z //方法标准段,相当于public那段
.registers 9 //标明寄存器数量

.prologue  //方法开始
.line 16  //标准.line16-17都属于原java代码16行的内容
move-object v0, p0 //将p0寄存器对象值赋给v0寄存器

move v1, p1 //将p1寄存器的值赋给v0寄存器

const/4 v6, 0x1 //为v6寄存器赋值,值为0x1,也就是1

move v3, v6 //把v6寄存器值赋给v3

.line 17  //同上
move v6, v1  //将v1寄存器值赋给v1

move v4, v6  //将v6寄存器值赋给v4

.line 18  //同上
move v6, v3  //将v3值赋给v6

move v0, v6  //将v6值赋给v0

return v0  //返回v0的值

.end method //方法结束,相当于上面java代码都}
我们可以看到,上面我们创建了一个方法,方法的名字是test,类型是boolean型,传人了int型参数i,然后在方法里面创建了局部boolbean型变
量a,并赋值true,创建int型变量b,并将参数i的值赋给了b,然后返回变量a的值。
然后,编译后的代码里,我们发现了一些相同的东西,就是test的名称没变,public也没变,但是其他都变了,但是通过观察,我们发现了public前面有一个.method,而代码的末尾有个.end method,由此,我们就可以推出:
在smali里,方法名和修饰是不变的,而且方法是包裹在.method和.end method里的。
同时,我们发现,test()里的参数变成了一个I,而且返回值类型的声明boolean不见了,而且()后面多了个Z,看了前面基本类型的估计都猜到了,那就是,test(I)里的I就是表示我们传入的int类型参数,而Z就是声明返回值类型的,而Z表示的类型就是boolean类型,同理,返回值为int型,括号后面就是I。
而且smali还有一个特别的地方,那就是我们标明参数,但却并不会显示参数名,只会在()里显示参数类型,也就是说,方法如果传了两个参数,也就是这样,test(int a,int b),而编译成smail后,就是这样的test(II),然后我们继续往下看,在test(I)Z下面一句就是,.registers 9,而这句代码的意思是,声明了该方法有9个寄存器。
而寄存器是什么呢,这是个重点内容。
寄存器就是smali里存储值的东西,你们可以理解为变量,什么类型的值都能存的变量,就像js里的变量。
寄存器的类型有两种:
本地寄存器(v开头的寄存器,如v0,v1,v2…)
参数寄存器(p开头的,如p0,p1,p2….)
本地寄存器可以有65535个,也就是v0-v65535
参数寄存器好像也是,我没查到相关资料,只查到本地的
然后,我们观察上面的代码,就会发现有v1,v2,p1,p0,这些都是寄存器
而后,我们往下走,发现有一句.prologue,他的意思是标注方法开始
然后是.line,这个上面都注释了,就不讲了。
再往下,就出现了许多的move,还有move-object,而move的用法就是将一个寄存器的值赋给另一个寄存器。如:move v1, v2,就是将v2的寄存器的值赋给v1,而move-object则是对象赋值。move还有其他后缀,会在本章最后补上。
然后是const/v4 v6, 0x1,这个序里讲了,是为v6赋值,值为1,而在java代码中,我们为布尔型变量a赋值了true,而true对应的值就是1,所已可以推出,a所对应的寄存器是v6,然后是一段乱七的转值,最终将v6的值赋给了v0,然后返回了v0,也就是1,true。同时我们也发现smail的
return是不变的,同时还有return-void,也就是返回空值。
补充,在参数寄存器中,p0指的是this,如果是静态方法就没有p0的。而
v0就没有特指。还有,为啥a的寄存先是v6,然后一路乱传,传到了v0,而不是直接是v0,我也不清楚,也许是因为我是用aide编译的,而我看别人用as编译的就不会有这种情况,所以下节我将借别人的代码来给大家讲解一下。
最后,我们讲下如何将本节知识用到破解上。
我们破解VIP,一般都会搜isVip这个关键字,而isVip一般是返回值为布尔
的方法,所以假如他在最后,返回的值是,return v0,那我们可以暴力的
直接在return v0上直接加一句const/4 v0, 0x1 直接将v0改为true。
也可以在上面改,更多思路请自行思考,其他复杂的将在后面的教程中讲述
const/4 p1, -0x1
//给寄存器p1 长度4位 赋值十六进制-1
const-string v0, “1111”
//字符串

上节补充

invoke-static {v0}, Lscala/f/aa;->e(Ljava/lang/Object;)I
调用静态方法
move-result v0
//将上一步操作结果赋值到v0
move v0,v1
将v1的值赋给v0 ,两个寄存器都为4位
move-result v0
将上一个invoke类型指令操作的单字非对象结果
赋给v0
move-result-object v0
将上一个invoke类型指令操作的对象结果(返
回值)赋给v0

————————分割线——————————————

上面,因为是aide编译的,感觉出现了许多乱七八糟的转值,好像也有些东西没有,今天就在偷些代码来为大家讲解一下。
//java代码
public int add(int a,int b){
int c = a+b;
return c;
}

编译后生成的smail语句

.method public add(II)I
.registers 4
.param p1, “a” # I
.param p2, “b” # I

.prologue
.line 6
add-int v0, p1, p2

.line 7
.local v0, "c":I
return v0

.end method

我们可以看出,Java代码是一个方法,方法名为add,返回值类型为int,并传入了两个int型参数a,b。然后方法内定义了一个int型变量c,并为其赋值,值为两个传参相加之和,最后将c的值返回。
然后再拉回smali代码,上次的教程已经讲了,smail的方法是在.method和.end method之中的,而方法的修饰符和方法名都不会变,这些上个教程都讲了,就不讲了。
然后方法里第一句是.registers 4,这个是为此方法注册是个寄存器,根据上面的smali代码,我们可以看出,这四个寄存器分别是v0,p0,p1,p2,而v0是本地寄存器,存储的是局部变量,也就是说,v0存的就是变量c,p开头的是参数寄存器,而p0只有在非静态方法中有,代表的是this,而静态方法没有this,所以也就没有p0。p1,p2则是两个传参,a,b。
在.registers 4下面有两句代码,.param p1, “a” #I
.param p2, “b” #I,这两句的意思很容易看出来,就是为p1寄存器绑定参数a,为p2绑定参数b,而后面的#I不知道是不是声明参数类型,我看有些代码有,有些没有,所以请知道的大佬回复下。
在往下就是方法开始标志,.prologue,然后是.line,标志行,下面就是这个方法的重点之一
add-int v0, p1, p2。这句的意识是将p1和p2寄存器里的值想加赋给v0,而v0代表的就是变量c。同样,smail有加就有减,乘,除,这些会在本节最后放出
在往下走,到.line 7下,有一句.local v0 , “c”:I,这句的意思就是为将变量c绑定在寄存器v0,:I则是表明变量c是int类型的。最后返回v0,.end mothod结束方法。
可以说,这还是很好理解的

smail加减乘除

add-int v0, p0, p1
:v0 = p0 + p1(static函数参数 从p0 开始)
sub-int v0, p1, p2
:v0 = p1 + p2(普通成员函数参数从 p1 开始)
mul-int v0, p1, p2
:v0 = p1 * p2
div-int v0, p1, p2
:v0 = p1 / p2
add-int/2addr v0, v1
:v0+v1放到 v0所在的地址
sub-int/2addr v0, p2
:v0-p2放到 v0所在的地址
——————————分割线—————————————
if-eqz讲解
在教程开始之前,我们先了解一下smail里的if语句有哪些,意思又是什么。
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_**

这些就是smali的判断语句
我有尝试了一波,用aide编译出来的代码还是感觉好乱,不适合用了讲解,所以我只能继续偷代码来讲了。
先上代码,后面讲解
private boolean ifSense(){
boolean tempFlag = ((3-2)==1)? true : false;
if (tempFlag) {
return true;
}else{
return false;
}
}
//Smail语法
.method private ifSense()Z
.locals 2
.prologue
.line 22
const/4 v0, 0x1
.line 24
.local v0, tempFlag:Z
if-eqz v0, :cond_0
.line 25
const/4 v1, 0x1
.line 27
:goto_0
return v1
:cond_0
const/4 v1, 0x0
goto :goto_0
.end method

//Java语法
private boolean ifSense(){
boolean tempFlag =
((3-2)==1)? true : false;
if (tempFlag) {
return true;
}else{
return false;
}
}
/*
本段java代码表示定义方法ifSense,无参数返
回值类型为boolean,方法内定义布尔类型变量
tempFlag,赋值并判断3-2是否等于1,是就返
回true,不是返回false。然后用if判断tempF
lag是true还是false,然后返回true和false
**/
//Smail语法
.method private ifSense()Z

定义方法,不多说

.locals 2

标明有两个本地寄存器

.prologue
#方法开始
.line 22
const/4 v0, 0x1
#为v0赋值1
.line 24
.local v0, tempFlag:Z
#将tempFlag存入v0
if-eqz v0, :cond_0
#下面是本节的重点if-eqz,根据上面我们

知道,if-eqz是判断某个值是否为0,如果为0,
就跳转到:cond_*标签,运行这个标签后面的代
码,不符合就继续往下走。而这里的代码,就是
判断v0是否为0,也就是0x0,是就跳到cond_0
标签后的代码。 而false的值就是0,true为1,
所以,这段代码并不会跳转到:cond_0,而是直
接往下走。要注意的是,带z的if代码,只能指定
一个值,如eqz,nez,ltz等,而不带z的就要
指定两个值。
.line 25
const/4 v1, 0x1
#将v1复制0x1,也就是1,true。
.line 27
:goto_0
#:goto标签,和cond标签作用相同,都是用
来跳转的,单cond是给if跳转的,:goto是给go
to语句跳转的。(我是这样理解的)
return v1
#返回v1的值
:cond_0
#cond标签,上面的if判断就跳转到这里,
如果为零,就到这里继续往下走
const/4 v1, 0x0
#为v1赋值0x0,也就是0,false
goto :goto_0
#重点之一,goto语法,他的作用是用来跳转
标签的,就像这里,他的含义是跳转到goto_
0标签处,然后我们发现,goto_0标签下有一
句return v0,而goto就是跳回goto_0返回
v0
.end method
#方法结束

摘自CSDN

对上面的代码进行总结一下。代码主要部分就是if
代码。而if用的是if-eqz,也就是判断值v0是
否为0,而v0表示的是变量tempFlag,而我们为
tempFlag赋的值是true,所以就有了const/4
v0 0x1,然后if-eqz判断v0是否为零,是就跳
转到cond_0,答案是不为零,所以没跳转,就继
续执行下去了,直到return v1。可以说,在
smail里,一个方法里,执行遇到return,就可
以说是执行完毕了。
本节到此结束,下节将为大家演示直接在dex中修改代码,从而直接影响的app。

————————分割线———————————————
本节内容为smali中的创建对象,调用方法。
1.方法的调用
//在smali中,invoke-***的都是调用方法的
invoke-static 调用静态方法
invoke-direct 调用private方法
invoke-virtual 调用public,protected
invoke-super 调用父类方法
然后,他们都具体用法如下。
invoke-virtual {v1,v2} L类路径;->方法名(参数类型)返回值类型;
比如说,我们调用TextView的setText()方法。
那么v1就是TextView的实例对象,而setText()方法有一个string参数。
所以v2就是一个string类型的变量值。
类路径就是android/widget/TextView
参数是java/lang/String
返回值是V
如果调用本类方法的话,v1就变成了p0,也就是this
注意:除静态方法外{}里的参数第一个都是类的实例化变量。
调用本类的方法,第一个参数就是p0。
而invoke-static调用静态方法,就不需要对象参数。
如果调用的静态方法不需要传参的话,那{}里就是空的
许多方法都有返回值,而上面只是调用方法,而获得方法
返回值,则需要使用move-result或move-result-object
来返回结果,move-result只能返回基本数据类型
而其他类型得用move-result-object。
2.创建对象
//java代码
StringBuffer strb = new StringBuffer();

//smali代码
new-instance v1, Ljava/lang/StringBuffer;
invoke-direct {v1}, Ljava/lang/StringBuffer;->()V
.local v1, “strb” :Ljava/lang/StringBuffer;

#

没错,java一句代码,在smali里是三句代码。
更准确滴说是两句。是new-instance和invoke-direct。
new-instance创建一个类型为java/lang/StringBuffer
的寄存器,然后再用invoke-direct调用StrringBuffer类
的构造方法,将对象实例话。
类;->(构造方法的参数)V,这个就是构造方法
这两句我们可以这样理解,在smali里面,创建一个对象的
过程,是先创建一个只有构造方法的半实例化对象,
也就是new-instance,然后再用这个对象调用对象的构造方法,
使对象完全示例化,(个人这样理解的,因为我发现调用构造方法
时,他那上面new-instance里的寄存器v1传进去了,所以才产生
这样的想法)。
实在不行,你可以把new-instance看作StringBuffer strb
invoke-direct{v1}看作new StringBuffer()
3.调用/写入类中的变量

在smail中,定义全局变量的方法是:
.field 修饰符 变量名:变量类型
例:
protected int a;
public static boolean b;
上面两句代码编译成smail就是:
.field protected a:I
.field public static b:Z

而读取,写入的方法是:
sget-object 读取静态类型变量
sput-object 写入静态类型变量
iget-object 读取普通类型变量
iput-object 写入普通类型变量
用法如下:
sget/sput-object v, L类地址;->变量名:变量类型 iget/iput-object v,类实例, L类地址;->变量名:变量类型
可以看到,读取普通类型变量比读取静态变量多了一个实例化对象。
如果是本类调用,则为p0。
好了,这个教程也告一段落了,由于我也是小白,写出来的很差,然后大家却能看到这里,我真的非常感谢各位。
最后留一些课后作业[滑稽]
1.自行研究for循环在smali里的结构
2.自行研究switch语句在smali里的结构
以后有时间会发一些smali的使用示例的。

发表回复