一、什么是Smali?
- Smali语言是Davlik的寄存器语言,语法上和汇编语言相似,Dalvik VM与JVM的最大的区别之一就是Dalvik VM是基于寄存器的。基于寄存器的意思是,在smali里的所有操作都必须经过寄存器来进行。
- Smali,Baksmali 分别是指安卓系统里的 Java 虚拟机(Dalvik)所使用的一种 dex 格式文件的汇编器,反汇编器。其语法是一种宽松式的 Jasmin/dedexer 语法,而且它实现了 .dex 格式所有功能(注解,调试信息,线路信息等)
- 当我们对 APK 文件进行反编译后,便会生成此类文件。在Davlik字节码中,寄存器都是32位的,能够支持任何类型,64位类型(Long/Double)用2个寄存器表示;Dalvik字节码有两种类型:原始类型;引用类型(包括对象和数组)
二、基本数据类型
1.原始类型
V void (只能用于返回值类型)
Z boolean 布尔型
B byte byte型
S short 短整型
C char 字节型
I int 整型
J long 长整型(64位)两个寄存器
F float 浮点型
D double 双精度小数(64位)
2.对象类型
Lpackage/name/ObjectName; 相当于java中的package.name.ObjectName;
L 表示这是一个对象类型
package/name 该对象所在的包
ObjectName 对象名称
; 标识对象名称的结束
3.数组类型
[I
:表示一个整形的一维数组,相当于java的int[];
对于多维数组,只要增加[
就行了,[[I = int[][];
注:每一维最多255个;
对象数组的表示形式:[Ljava/lang/String
表示一个String的对象数组;
三、Smali语法
1、寄存器与变量
Android变量都是存放在寄存器中的,寄存器为32位,可以支持任何类型,其中long和double是64为的,需要使用两个寄存器保存。寄存器采用v和p来命名,v表示本地寄存器,p表示参数寄存器。
// java原代码
private void print(String string) {
Log.d(TAG, string);
}
// Smali代码
.method private print(Ljava/lang/String;)V
.registers 3
.param p1, "string" # Ljava/lang/String;
.prologue
.line 29
const-string v0, "MainActivity"
invoke-static {v0, p1}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I
.line 30
return-void
.end method
解读:
.registers 3
说明该方法有三个寄存器,其中一个本地寄存器v0,两个参数寄存器p0,p1,细心的人可能会注意到没有看到p0,原因是p0存放的是this。如果是静态方法的话就只有2个寄存器了,不需要存this了。
-
本地寄存器(local register,非参寄存器)用v开头数字结尾的符号来表示,如v0、v1、v2、…,
-
参数寄存器(parameter register)用p开头数字结尾的符号来表示,如p0、p1、p2、…,
-
.registers
用来标明方法中寄存器的总数,即参数寄存器和非参寄存器的总数。 -
.local 0
,标明在这个函数中最少要用到的本地寄存器的个数,出现在方法中的第一行。在这里,由于只需要调用一个父类的onDestroy()处理,所以只需要用到p0,所以使用到的本地寄存器数为0,在植入代码后不要忘记可能要修改.local的值。如.local 4
,则可以使用的寄存器是v0-v3。 -
当一个方法被调用的时候,方法的参数被置于最后N个寄存器中。
-
在实例函数中,
p0
代指“this
”,p1表示函数的第一个参数,p2代表函数中的第二个参数…, -
在static函数中,p1表示函数的第一个参数,p2代表函数中的第二个参数…,因为Java的static方法中没有this方法。
2、基本指令
.field private isFlag:z 定义变量
.annotation 类使用了注解,那么smali中会使用
.method 方法
.parameter 方法参数
.prologue 方法开始
.line 12 此方法位于第12行
move v0, v3 把v3寄存器的值移动到寄存器v0上
const-string v0, "MainActivity" 把字符串"MainActivity"赋值给v0寄存器
new-instance 创建实例
iput-object 对象赋值
iget-object 调用对象
invoke-super 调用父函数
invoke-static 调用静态函数
invoke-direct 调用函数
return-void 函数返回void
3、条件跳转指令
"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-gt vA, vB, :cond_**" 如果vA大于vB则跳转到:cond_**
"if-ge 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-gtz vA, :cond_**" 如果vA大于0则跳转到:cond_**
"if-lez vA, :cond_**" 如果vA小于等于0则跳转到:cond_**
"if-gez vA, :cond_**" 如果vA大于等于0则跳转到:cond_**
4、函数调用
smali中的函数调用也分为direct
和virtual
两种类型,direct method就是private
函数,public和protected函数都属于virtual method。在调用函数时,有invoke-direct,invoke-virtual,invoke-static、invoke-super以及invoke-interface等几种不同的指令。
invoke-static:就是调用static函数的,示例:
invoke-static {}, Lcom/disney/Class1;->fun()Z
上句invoke-static后面有一对大括号“{}”,内部是调用该方法的实例和参数列表,由于这是static方法也不需要参数,所以{}内为空。
invoke-super:
调用父类方法,在onCreate、onDestroy等方法都能看到。
invoke-direct:
调用private函数,示例:
invoke-direct {p0}, Lcom/disney/Class1;->getGlobalIapHandler()Lcom/disney/config/GlobalPurchaseHandler;
上句即this->getGlobalIapHandler(),函数GlobalPurchaseHandler getGlobalIapHandler()是定义在Class1中的一个private函数。
invoke-virtual:用于调用protected或public函数,示例:
sget-object v0, Lcom/disney/Class1;->shareHandler:Landroid/os/Handler;
invoke-virtual {v0, v3}, Landroid/os/Handler;->removeCallbacksAndMessages(Ljava/lang/Object;)V
上句v0是shareHandler android/os/Handler,v3是传递给removeCallbackAndMessage方法的Ljava/lang/Object参数。
5、获取函数返回结果
在Smali里调用函数和返回函数结果需要分开来完成,在调用的函数返回非void
后,用move-result
(返回基本数据类型)和move-result-object
(返回对象)指令获取返回结果。
const/4 v2, 0x0
invoke-virtual {p0, v2}, Lcom/disney/Class1;->getPreferences(I)Landroid/content/SharedPreferences;
move-result-object v1
上句v1保存的就是调用this.getPreferences(int)方法返回的SharedPreferences实例。
6、函数体
.method
和 .end method
之间。
.method protected onDestroy()V
.locals 0
.prologue
.line 277
invoke-super {p0}, Lcom/disney/common/BaseActivity;->onDestroy()V
.line 279
return-void
.end method
上段是onDestroy()函数。
.line 277
,标注了该代码在原Java文件中的行数,它不是必须的,去掉没有编译问题。它在出错时可以指出错误位置,jd-gui工具即是通过分析这些信息将smali代码还原成Java代码的。
四、基本语句语法
1、if语句
///Java语法
private boolean ifSense(){
boolean tempFlag = ((3-2)==1)? true : false;
if (tempFlag) {
return true;
}else{
return false;
}
}
//Smali语法
.method private ifSense()Z
.locals 2
.prologue
.line 22
const/4 v0, 0x1 // v0赋值为1
.line 24
.local v0, tempFlag:Z
if-eqz v0, :cond_0 // 判断v0是否等于0, 不符合条件向下走, 符合条件执行cond_0分支
.line 25
const/4 v1, 0x1 // 符合条件分支
.line 27
:goto_0
return v1
:cond_0
const/4 v1, 0x0 // cond_0分支
goto :goto_0
.end method
###文字描述:如果符合if分支则程序往下走,最终return ; 而如果条件不符合则会走到 :cond_0分支 , 最终执行 goto :goto_0走回 :goto_0返回
2、for语句
//java----------------------------------------
private void forSense(){
listStr = new ArrayList<String>(COUNT);
for (int i = 0; i < COUNT; i++) {
listStr.add("现在轮到我上场乐");
}
}
//Smali----------------------------------------
.line 40
const/4 v0, 0x0
.local v0, i:I
:goto_0
if-lt v0, v3, :cond_0 // if-lt判断数值v0小于v3 , 如不符合往下走, 符合执行分支 :cond_0
.line 43
return-void
.line 41
:cond_0 // 标签
iget-object v1, p0, Lcom/example/smalidemo/MainActivity;->listStr:Ljava/util/List; // 引用对象
const-string v2, "\u73b0\u5728\u8f6e\u5230\u6211\u4e0a\u573a\u4e50"
invoke-interface {v1, v2}, Ljava/util/List;->add(Ljava/lang/Object;)Z // List是接口, 所以执行接口方法add
.line 40
add-int/lit8 v0, v0, 0x1 // 将第二个v0寄存器中的值,加上0x1的值放入第一个寄存器中, 实现自增长
goto :goto_0 // 回去:goto_0标签
###文字描述:设定一个标签goto_0, 判断v0小于v3, 符合执行分支:cond_0 ,然后又跑回:goto_0做继续判断
3、switch分支语句
.method private packedSwitch(I)Ljava/lang/String;
.locals 1
.parameter "i"
.prologue
.line 21
const/4 v0, 0x0
.line 22
.local v0, str:Ljava/lang/String; #v0为字符串,0表示null
packed-switch p1, :pswitch_data_0 #packed-switch分支,pswitch_data_0指定case区域
.line 36
const-string v0, "she is a person" #default分支
.line 39
:goto_0 #所有case的出口
return-object v0 #返回字符串v0
.line 24
:pswitch_0 #case 0
const-string v0, "she is a baby"
.line 25
goto :goto_0 #跳转到goto_0标号处
.line 27
:pswitch_1 #case 1
const-string v0, "she is a girl"
.line 28
goto :goto_0 #跳转到goto_0标号处
.line 30
:pswitch_2 #case 2
const-string v0, "she is a woman"
.line 31
goto :goto_0 #跳转到goto_0标号处
.line 33
:pswitch_3 #case 3
const-string v0, "she is an obasan"
.line 34
goto :goto_0 #跳转到goto_0标号处
.line 22
nop
:pswitch_data_0
.packed-switch 0x0 #case 区域,从0开始,依次递增
:pswitch_0 #case 0
:pswitch_1 #case 1
:pswitch_2 #case 2
:pswitch_3 #case 3
.end packed-switch
.end method
packed-switch 指令。p1为传递进来的 int 类型的数值,pswitch_data_0 为case 区域,在 case 区域中,第一条指令“.packed-switch”指定了比较的初始值为0 ,pswitch_0~ pswitch_3分别是比较结果为“case 0 ”到“case 3 ”时要跳转到的地址。可以发现,标号的命名采用 pswitch_ 开关,后面的数值为 case 分支需要判断的值,并且它的值依次递增。再来看看这些标号处的代码,每个标号处都使用v0 寄存器初始化一个字符串,然后跳转到了goto_0 标号处,可见goto_0 是所有的 case 分支的出口。另外,“.packed-switch”区域指定的case 分支共有4 条,对于没有被判断的 default 分支,会在代码的 packed-switch指令下面给出。
java语言如下:
private String packedSwitch(int i) {
String str = null;
switch (i) {
case 0:
str = "she is a baby";
break;
case 1:
str = "she is a girl";
break;
case 2:
str = "she is a woman";
break;
case 3:
str = "she is an obasan";
break;
default:
str = "she is a person";
break;
}
return str;
}
4、try/catch语句
# virtual methods
.method public statementTry()Z
.locals 4
.prologue
.line 14
const-wide/16 v2, 0x3e8
:try_start_0
invoke-static {v2, v3}, Ljava/lang/Thread;->sleep(J)V
:try_end_0
.catch Ljava/lang/InterruptedException; {:try_start_0 .. :try_end_0} :catch_0
.line 18
:goto_0
const/4 v1, 0x1
return v1
.line 15
:catch_0
move-exception v0
.line 16
.local v0, "e":Ljava/lang/InterruptedException;
invoke-virtual {v0}, Ljava/lang/InterruptedException;->printStackTrace()V
goto :goto_0
.end method
代码中的try语句块使用try_start_开头的标号注明,以try_end_开头的标号结束。第一个try语句的开头标号为try_start_0,结束标号为 try_end_0。使用多个try语句块时标号名称后面的数值依次递增,本实例代码中最多使用到了try_end_2。
在try_end_0 标号下面使用“.catch”指令指定处理到的异常类型与catch的标号,格式如下。
.catch < 异常类型> {<try起始标号> .. <try 结束标号>} <catch标号>
五、最后
你能否将下面的Smali代码变成Java代码呢?
.locals 4
const/4 v2, 0x1
const/16 v1, 0x10
.local v1, "length":I
if-nez v1, :cond_1
:cond_0
:goto_0
return v2
:cond_1
const/4 v0, 0x0
.local v0, "i":I
:goto_1
if-lt v0, v1, :cond_2
const/16 v3, 0x28
if-le v1, v3, :cond_0
const/4 v2, 0x0
goto :goto_0
:cond_2
xor-int/lit8 v1, v1, 0x3b
add-int/lit8 v0, v0, 0x1
goto :goto_1
如果可以的话,你已经基本掌握了Smali语法了!
先按顺序写出代码:
int length=0x10;
if(length!=0){
int i=0;
}
if(i<length){
length^=0x3b;
i+=1;
}
else if(length<=0x28){
return 1;
}
else{
return 0;
}
其中0、1用v2寄存器传值,v1是length,v3存储的是0x28,v0存的是for循环计数器i
然后观察语句特点,发现像for循环,修改代码:
int length=0x10;
for(int i=0;i<length;i++){
length^=0x3b;
}
if(length<=0x28)
return 1;
return 0;
🔗原文链接:https://blog.csdn.net/cpongo3/article/details/89708340
评论区