Smail语法「建议收藏」

2022-11-19 17:05:58 浏览数 (1)

大家好,又见面了,我是你们的朋友全栈君。

Smail语言

首先了解什么是smail?

  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/Double)用2个寄存器表示;Dalvik字节码有两种类型:原始类型;引用类型(包括对象和数组)

基本数据类型

1.原始类型

代码语言:javascript复制
V void (只能用于返回值类型) 
Z boolean
B byte
S short
C char
I int
J long(64位)两个寄存器
F float
D double(64位)

2.对象类型

代码语言:javascript复制
Lpackage/name/ObjectName; 相当于java中的package.name.ObjectName; 
L 表示这是一个对象类型 
package/name 该对象所在的包 
ObjectName 对象名称 
; 标识对象名称的结束

3.数组类型

[I:表示一个整形的一维数组,相当于java的int[];

对于多维数组,只要增加[就行了,[[I = int[][];注:每一维最多255个;

对象数组的表示形式:

[Ljava/lang/String表示一个String的对象数组;

Smail语法

1.寄存器与变量

android变量都是存放在寄存器中的,寄存器为32位,可以支持任何类型,其中long和double是64为的,需要使用两个寄存器保存。 寄存器采用v和p来命名,v表示本地寄存器,p表示参数寄存器。

例:

代码语言:javascript复制
//Java===================================================================
private void print(String string) {
    Log.d(TAG, string);
}
//Smail===================================================================
.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了。

1.本地寄存器(local register,非参寄存器)用v开头数字结尾的符号来表示,如v0、v1、v2、…,

2.参数寄存器(parameter register)用p开头数字结尾的符号来表示,如p0、p1、p2、…,

3..registers 用来标明方法中寄存器的总数,即参数寄存器和非参寄存器的总数。

4..local 0,标明在这个函数中最少要用到的本地寄存器的个数,出现在方法中的第一行。在这里,由于只需要调用一个父类的onDestroy()处理,所以只需要用到p0,所以使用到的本地寄存器数为0,在植入代码后不要忘记可能要修改.local的值。

如 .local 4,则可以使用的寄存器是v0-v3。

5.当一个方法被调用的时候,方法的参数被置于最后N个寄存器中。

6.在实例函数中,p0代指“this”,p1表示函数的第一个参数,p2代表函数中的第二个参数…,

7.在static函数中,p1表示函数的第一个参数,p2代表函数中的第二个参数…,因为Java的static方法中没有this方法。

2.基本指令

代码语言:javascript复制
.field private isFlag:z  定义变量
.annotation    类使用了注解,那么smali中会使用
.method  方法
.parameter  方法参数
.prologue  方法开始
.line 12  此方法位于第12行
move v0, v3 把v3寄存器的值移动到寄存器v0上 
const-string v0, “MainActivity”   把字符串”MainActivity”赋值给v0寄存器 
invoke-super  调用父函数 
return-void  函数返回void 
new-instance  创建实例 
iput-object  对象赋值 
iget-object  调用对象 
invoke-static  调用静态函数 
invoke-direct  调用函数

条件跳转指令:

代码语言:javascript复制
"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_**

3.函数调用

smali中的函数调用也分为direct和virtual两种类型,direct method就是private函数,public和protected函数都属于virtual method。在调用函数时,有invoke-direct,invoke-virtual,invoke-static、invoke-super以及invoke-interface等几种不同的指令。

invoke-static:就是调用static函数的,示例:

代码语言:javascript复制
invoke-static {}, Lcom/disney/Class1;->fun()Z

上句invoke-static后面有一对大括号“{}”,内部是调用该方法的实例和参数列表,由于这是static方法也不需要参数,所以{}内为空。 invoke-super:调用父类方法,在onCreate、onDestroy等方法都能看到。 invoke-direct:调用private函数,示例:

代码语言:javascript复制
invoke-direct {p0}, Lcom/disney/Class1;->getGlobalIapHandler()Lcom/disney/config/GlobalPurchaseHandler;

上句即this->getGlobalIapHandler(),函数GlobalPurchaseHandler getGlobalIapHandler()是定义在Class1中的一个private函数。 invoke-virtual:用于调用protected或public函数,示例:

代码语言:javascript复制
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参数。

4.获取函数返回结果

在smail里调用函数和返回函数结果需要分开来完成,在调用的函数返回非void后,用move-result(返回基本数据类型)和move-result-object(返回对象)指令获取返回结果。

示例:

代码语言:javascript复制
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实例。

5.函数体

.method 和 .end method之间。 例:

代码语言:javascript复制
.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[2]工具即是通过分析这些信息将smali代码还原成Java代码的。

基本语句语法

1.if语句

代码语言:javascript复制
//Java语法
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     // 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语句

代码语言:javascript复制
//java----------------------------------------
private void forSense(){
    listStr = new ArrayList<String>(COUNT);
    for (int i = 0; i < COUNT; i  ) {
        listStr.add("现在轮到我上场乐");
    }
}

//Smail----------------------------------------

.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, "u73b0u5728u8f6eu5230u6211u4e0au573au4e50"

    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做继续判断

8.switch分支语句

代码语言:javascript复制
.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语言如下:

代码语言:javascript复制
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;
}

9.try/catch语句

代码语言:javascript复制
# 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的标号,格式如下。

代码语言:javascript复制
.catch < 异常类型> {<try起始标号> .. <try 结束标号>} <catch标号>

最后

你能否将下面的Smail代码变成Java代码呢?

代码语言:javascript复制
.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

如果可以的话,你已经基本掌握了Smail语法了!

先按顺序写出代码:

代码语言:javascript复制
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循环,修改代码:

代码语言:javascript复制
int length=0x10;
for(int i=0;i<length;i  ){
    length^=0x3b;
}
if(length<=0x28)
    return 1;
return 0;

自己写的,如有错请指正:1094093288@qq.com

注解

注1:Dalvik

注2:JD-GUI

参考链接

android逆向分析之smali语法

Smail语言语法

Android工程师,如何简单高效的学会smali语法

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/200871.html原文链接:https://javaforall.cn

0 人点赞