Smail语法「建议收藏」

Smail语法「建议收藏」Smail语言首先了解什么是smail?apk文件通过apktool反编译出来的都有一个smali文件夹,里面都是以.smali结尾的文件。smali语言是Davlik的寄存器语言,语法上和汇编语言相似,DalvikVM[1]与JVM的最大的区别之一就是DalvikVM是基于寄存器的。基于寄存器的意思是,在smali里的所有操作都必须经过寄存器来进行。S…

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全家桶1年46,售后保障稳定

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.原始类型

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

Jetbrains全家桶1年46,售后保障稳定

2.对象类型

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表示参数寄存器。

例:

//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.基本指令

.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  调用函数

条件跳转指令:

"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函数的,示例:

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参数。

4.获取函数返回结果

在smail里调用函数和返回函数结果需要分开来完成,在调用的函数返回非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实例。

5.函数体

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

基本语句语法

1.if语句

//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语句

//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, "\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做继续判断

8.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;
}

9.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标号>

最后

你能否将下面的Smail代码变成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

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

先按顺序写出代码:

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;

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

注解

注1:Dalvik

注2:JD-GUI

参考链接

android逆向分析之smali语法

Smail语言语法

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

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

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

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)
blank

相关推荐

  • 毕设系列之 — 教程:单片机控制步进电机

    毕设系列之 — 教程:单片机控制步进电机文章目录1简介2步进电机介绍3A4988驱动介绍4电机启动代码5最后1简介Hi,大家好,这里是丹成学长,今天向大家介绍如何使用单片机控制步进电机大家可用于课程设计或毕业设计技术解答毕设帮助:<Q>7468760412步进电机介绍步进电机是一种将电脉冲转化为角位移的执行机构。通俗一点讲:当步进驱动器接收到一个脉冲信号,它就驱动步进电机按设定的方向转动一个固定的角度(即步进角),多用于雕刻机、3D打印机等需要精确控制的设备。本篇使用ULN2003驱动五线四相减速

  • 简洁优雅的Mac OS X软件安装体验 – homebrew-cask

    简洁优雅的Mac OS X软件安装体验 – homebrew-cask

  • DNS服务器设置正确,DNS服务器配置(DNS各属性详细介绍)[通俗易懂]

    DNS服务器设置正确,DNS服务器配置(DNS各属性详细介绍)[通俗易懂]建立好DNS服务器后,用户可以在菜单中选择【属性】选项修改其配置。下面介绍如何配置DNS服务器的选项卡。具体的步骤如下。1.【接口】选项卡的配置图15-21所示为DNS服务器属性的【接口】选项卡,默认情况下,DNS服务器将侦听所有向该DNS服务器发出的域名解析请求和转发解析的DNS消息。如果要限制DNS服务器只负责侦听特定的IP地址发出的域名解析请求,可以在该选项卡中进行设置。选中【只在下列IP地…

  • 常用矩阵范数_矩阵相减的范数

    常用矩阵范数_矩阵相减的范数(1)矩阵的核范数:矩阵的奇异值(将矩阵svd分解)之和,这个范数可以用来低秩表示(因为最小化核范数,相当于最小化矩阵的秩——低秩); (2)矩阵的L0范数:矩阵的非0元素的个数,通常用它来表示稀疏,L0范数越小0元素越多,也就越稀疏。 (3)矩阵的L1范数:矩阵中的每个元素绝对值之和,它是L0范数的最优凸近似,因此它也可以近似表示稀疏; (4)矩阵的F范数:矩阵的各个元素…

  • Java静态代理_代理ip怎么设置

    Java静态代理_代理ip怎么设置Java静态代理

    2022年10月10日
  • PHP – 二维数组合并的方式

    PHP – 二维数组合并的方式有时需要二维数组(如从数据库查询的结果集)处理,如排序,过滤后才重新合并。

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号