Android反编译下的smali语法学习

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


导语:学习一门新技术不仅要知其然,更要知其所然。在学习Smali语法之前,首先要明白其学习目的,学习用途,怎么样才能从能达到事半功倍的效果。所以本文章打算用循序渐进的方式去讲述该知识点。

一、了解基础知识

图片来源处
在这里插入图片描述
命令行形式表示:

  • 使用 javac 命令将 Java 源文件编译成 .class 文件:
javac YourJavaFile.java
  • 使用 Android SDK 中的 dx 工具将 .class 文件打包成 DEX 文件:
java -jar dx.jar --dex --output=YourApp.dex YourJavaFile.class
  • 使用 baksmali 工具将 DEX 文件反编译成 smali 代码:
java -jar baksmali.jar YourApp.dex

APK的基础知识

  • 在学习Smali语法之前,建议先学习掌握Android应用程序的APK构建流程和Android应用程序的打包流程。APK构建流程可以看这一篇
    Android应用程序的打包流程建议看这一篇
  • Smali语法的学习是为了更好的反编译这一块的内容,而Android反编译不得不提及到Dalvik汇编语言(SmaliDalvik 汇编语言的一种文本表示形式。DalvikAndroid 操作系统用于执行 .apk 文件中的应用程序代码的中间表示形式,而 Smali 提供了一种人类可读的方式来查看和编辑 Dalvik 代码),包括其与Java字节码的区别。Dalvik汇编语言的基本概念

Dalvik的基础知识

DalvikAndroid 操作系统中使用的虚拟机,专为 Android 设计。它有自己的指令集和执行环境,与 Java 虚拟机(JVM)不同。

Dalvik 虚拟机基于寄存器架构,这意味着它使用一组寄存器来存储数据,而不是像 JVM 那样基于

1 .Dalvik的表示方法

注:Dalvik字节码有自己的类型方法字段的表示方法,这些内容与Dalvik虚拟机指令集一起组成了Dalvik汇编代码

1.Dalvik类型

Dalvik 字节码只有两种类型,分别是基本类型引用类型Dalvik 使用这两种类型来表示 Java 语言的全部类型。除了对象和数组属于引用对象,其它的 Java 类型都属于基本类型Java 语言的类型与 Dalvik 字节码类型描述符的对应关系如下表所示:

Smali类型描述符 Java类型
V   void
Z   Boolean
B   byte
S   string
C   char
I   int
J   long
F   float
D   double
L   Java对象类型
[   数组类型

对于上述的类型对照表,这里通过下表中几个实际的例子进一步来理解对应关系:

Dalvik汇编代码Java代码
Lpackage/name/ObjectName;package.name.ObjectName
Ljava/lang/String;java.lang.String
[Iint[ ]
[[int[ ] [ ](最大维数为255)
[Ljava/lang/String;int [ ]

每个Dalvik寄存器都是32位的。对长度小于或等于32位的类型来说,只用一个寄存器就可以存放该类型的值,而对 J 、D 等64位的类型来说,它们的值要使用相邻的两个寄存器来存储,例如 v0 与 v1、v3 与 v4。

2.Dalvik方法

Dalvik使用方法名、类型参数与返回值来描述一个方法。格式如下:

在这里插入图片描述在这里插入图片描述

二、学习Smali语法(重点)

前言:学习一门技术,最好的方式就是拿现有的知识和技术,在此基础上去研究新内容,把自己现有的知识体系作为切入点,最好的实现方式就是做一个入门demo,扩展自己的思维和动手能力。
Smali语法主要从三个方面进行学习,分别是smali文件解读Smali基础语法Smali指令集
Java源代码
demo的话,就以MainActivity类进行讲解
smali文件
在这里插入图片描述

1.smali文件解读

1.1头文件

.class public Lcom/cxj/smalitest/MainActivity;
.super Landroidx/appcompat/app/AppCompatActivity;
.source "MainActivity.java"

第一行表示的含义:当前class的包名和类名
第二行表示的是:父类的包名和类名
第三行表示的是:源文件的名称
头文件格式:

.class <访问权限修饰符> [非权限修饰符] <类名>
.super <父类名>
.source <源文件名称>

1.2静态字段

Smali文件类的主体部分:一个类由字段方法组成。.field字段声明,字段分两类:静态字段和实例字段
Java静态字段:

    private static final String STR = "hello"; //常量
    private static final int TAKE_PHOTO = 1; //常量
    
    private static int a;

Smali静态字段:

# static fields   #是Davlik自动生成的注释,表明以下为静态字段,
# 可以看出final常量需要额外用等于号赋值
.field private static final STR:Ljava/lang/String; = "hello" 
.field private static final TAKE_PHOTO:I = 0x1
.field private static a:I

Smali静态字段格式:

#static fields
.field <访问权限> static [修饰词] <字段名>:<变量类型>
.field <访问权限> static final <常量名>:<常量类型> = 常量值

1.3实例字段

实例字段相比于静态字段少了一个static的静态声明,其他均相同
Java实例字段:
在这里插入图片描述
Smali实例字段:
在这里插入图片描述
Smali实例字段格式:

#instance fields
.field <访问权限修饰符> [非权限修饰符] <变量名>:<变量类型>

1.4直接方法

smali的方法声明使用的.method指令,方法分为直接方法和虚方法两种。
直接方法就是不能被覆写的方法,包括用static,private修饰的方法
虚方法表示可以被覆写的方法,包括public,protected修饰的方法。

Java直接方法:

public int add(int a, int b) {
    return a + b;
}

Smali直接方法:

#direct methods
.method public add(II)I  
    .locals 2
    .prologue
    iadd v0, p1, p2
    return v0
.end method

解读:(大家有不懂的可以借助GPT)
在这里插入图片描述

Smali直接方法格式:
两者在smali中的注释分别是直接方法(#direct methods),虚方法(#virtual methods),一般直接方法在smali文件的前半部分,虚方法在后半部分

#direct methods/#virtual methods
.method <访问权限修饰符> [非访问权限修饰符] <方法名> (Para-Type1Para-Type2Para-Type3...)Return-Type
      <.locals>
      [.parameter]
      [.prologue]
      [.line]     #对应Java源码的一行代码
      <代码逻辑>
.end method

1.5虚方法

虚方法:是指从父类中继承的方法或者实现的接口的方法,它的声明跟直接方法相同,只是起始的初始为virtual methods
Java虚方法:

public int getNumber() {
    return 42;
}

Java 中的虚方法可以被继承的子类重写(override)
Smali虚方法:

#virtual methods
.method public virtual getNumber()I
    .locals 1
    .prologue

    # 返回一个整数
    const v0, 42
    return v0

.end method

# 调用一个虚方法
invoke-virtual {v1}, Lcom/example/MyClass;->getNumber()I

解读:
在这里插入图片描述
在这里插入图片描述

Smali虚方法格式:

#virtual methods
.method <访问权限修饰符> [非访问权限修饰符] <方法名> (Para-Type1Para-Type2Para-Type3...)Return-Type
      <.locals>
      [.parameter]
      [.prologue]
      [.line]     #对应Java源码的一行代码
      <代码逻辑>
.end method

1.6接口

Java接口:

public interface Vehicle {
    int WHEELS = 4; // 常量
    void start(); // 抽象方法
    default void stop() { // 默认方法
        System.out.println("Stopping the vehicle.");
    }
}

Smali接口:

# 类 MyClass 实现了接口 Vehicle
.class public Lcom/example/MyClass;
.super Ljava/lang/Object;

# 实现接口 Vehicle
.implements Lcom/example/Vehicle;

# 抽象方法 start 必须在实现类中提供具体实现
.method public start()V
    .locals 0

    # 实现细节...
    return-void
.end method

# 默认方法 stop 可以选择在实现类中重写或使用接口中的默认实现
.method public stop()V
    .locals 0

    # 如果不重写,将使用 Vehicle 接口中的默认实现
    invoke-interface {p0}, Lcom/example/Vehicle;->stop()V

    # 或者,如果提供了自己的实现
    # 实现细节...
    return-void
.end method

在这里插入图片描述

Smali接口格式:
如果一个类实现了一个接口,那么会在smali文件中用.implements指令指出,而且在文件前面就已经指出
在这里插入图片描述

#interfaces
.implements <接口名称>

2.smali文件基础语法

2.1寄存器

Android 的变量都存储于寄存器中。变量分为 v 与 p 两种格式
对于dalviks字节码寄存器都是32位的,它能够表示任何类型,2个寄存器用于表示64位的类型**(Long and Double)**

  • 寄存器的命名方法有两种:v命名法p命名法

​ v命名法:局部变量寄存器用v开头数字结尾的符号来表示,如v0、 v1、v2。

​ p命名法:函数参数寄存器则使用p开头数字结尾的符号来表示,如p0、p1、p2。

v命名法:局部变量寄存器V0-Vn,参数寄存器是Vn-Vn+m。

p命名法:(局部和成员)变量寄存器V0-Vn,参数寄存器P0-Pn,

特别注意的是,p0不一定是函数中的第一个参数,在非static函数中,p0代指“this",p1表示函数的第一个 参数,p2代表函数中的第二个参数。而在static函数中p0才对应第一个参数(因为Java的static方法中没有this方法)
在这里插入图片描述

2.2数据类型

Smali的数据类型其实就是Davikd的数据类型
Dalvik 字节码只有两种类型,分别是基本类型引用类型Dalvik 使用这两种类型来表示 Java 语言的全部类型。除了对象和数组属于引用对象,其它的 Java 类型都属于基本类型Java 语言的类型与 Dalvik 字节码类型描述符的对应关系如下表所示:
基本数据类型

Smali类型描述符 Java类型
V   void
Z   Boolean
B   byte
S   string
C   char
I   int
J   long
F   float
D   double
L   Java对象类型
[   数组类型

数组类型

[.... --- array

在基本类型前加上前中括号“[”即表示该类型的数组,如: [B 表示byte数组 , [i 表示int数组

对象类型的数组为”[“加上对象类型表示符来表示,如String类型表示为:[Ljava/lang/String

对象类型

L ..../....; --- object

如果是对象类型,则以L作为开头,格式是LpackageName / objectName; packageName/表示该对象所在的包,ObjectName 是对象的名字,“;” 表示对象名称的结束。相当于 java 中的 packageName.ObjectName

如:Ljava/lang/String; :表示 String 对象。其中 java/lang 对应 java.lang 包, String 是该包的一个对象。类对象中的内部类则使用“$”来连接

2.3字段和方法的表示

字段描述

在JAVA中假设有一个类:com.test.Person,类有一个属性:int id,在smali中则用下面的方法表示:

Lcom/test/Person;->id:I

语法为:类名->属性名:属性类型

方法描述

在JAVA中假设有一个类:com.test.Person,类有一个方法:void setId(int id);,在smali中则用下面的方法表示:

Lcom/test/Person;->setId(I)V

语法为:完整类名->方法名(参数类型)返回类型

2.4 Smali文件关键词

在这里插入图片描述
.registers 和 .locals 基本区别
指令.registers指定了在这个方法中有多少个可用的寄存器(包括存方法参数的寄存器与非参寄存器
指令.locals指明了在这个方法中非参(non-parameter) 寄存器的数量

3.smali文件指令集

3.1空操作指令


空操作指令的助记符为nop,它的值为00,通常nop指令被用来作对齐代码之用,无实际操作。

3.2数据操作指令

在这里插入图片描述
字符串数据

在这里插入图片描述
格式:

const-string 变量名,”字符串”

字节码数据
在这里插入图片描述
在这里插入图片描述
格式:

const-class 变量名,字节码对象;

数值类型数据 (默认定义最高位为符号位)

#占用一个寄存器(32位)
const/4 变量名,#十六进制数

const/16 变量名,#十六进制数

const 变量名,#十六进制数

const/high16 变量名,#十六进制数

#占用相邻两个容器(64位)
const-wide 变量名,#十六进制数

const-wide/16 变量名,#十六进制数

const-wide/32 变量名,#十六进制数

const-wide/high16 变量名,#十六进制数

1.const-wide只用写出一个寄存器,另一个寄存器默认为其下一个,即const-wide vx,寄存器为 vx 与 v(x+1)如const-wide v2,#FF763D33,其寄存器为 v2 和 v3
2.在使用const/high16时,数值补齐32位,即十六进制8位,不足的末尾+0
如用const/ high16给 v0 赋0xFF7F,代码为const/ high16 v0,0xFF7F0000(补满8位)只取最高16位,即将 #0xFF7F 赋给v0

3.3变量操作指令

用于将源寄存器的值移动到目标寄存器中,此类操作常用于赋值。
基础格式:move 目标寄存器 源寄存器
在这里插入图片描述

3.4返回指令

在这里插入图片描述
在这里插入图片描述

3.5方法调用指令

invoke-virtual或invoke-virtual/range :调用实例的虚方法(普通方法)
invoke-super或invoke-super/range :调用实例的父类/基类方法
invoke-direct或invoke-direct/range :调用实例的直接方法(私有方法和构造方法)
invoke-static或invoke-static/range :调用实例的静态方法
invoke-interface或invoke-interface/range :调用实例的接口方法
在这里插入图片描述
格式如下:

invoke-xxxxx {参数}, 方法所属类(全包名路径);->方法名称(方法参数描述符)方法返回值类型描述符

xxxxx 为direct,virtual,static,super,interface其中一种

例1:

Java格式
在这里插入图片描述
Smali格式

.class public LTest;	#声明类
.super Ljava/lang/Object;	#声明父类

.method public constructor <init>(Ljava/lang/String;)v

	invoke-direct {p0}, Ljava/lang/Object;-><init>()v	#调用父类构造方法
	invoke-virtual {p0}, LTest;->getName()Ljava/lang/String;	#调用getName方法
	
	return-void
.end method

#声明getName方法
.method public getName()Ljava/lang/String;
	
	const-String v0,"hello"	 #定义局部字符常量
	
	return-object v0	#返回常量
.end method

例2:
Java格式

public class Test(){
    public Test(String a){
        String b=getName();
        System.out.println(b);
    }
    private static String getName(){
        return "hello";
    }
}

Smali格式

# 类定义
.class public Lcom/example/Test; # 假设包名为 com.example

# 构造函数
.method public <init>(Ljava/lang/String;)V
    .locals 1
    .prologue
    .line 1
    invoke-direct {p0, p1}, Ljava/lang/Object;-><init>()V # 调用父类构造函数

    .line 2
    const-string v0, "hello" # 调用静态方法前,需要先获取字符串 "hello"
    invoke-static {}, Lcom/example/Test;->getName()Ljava/lang/String; # 调用静态方法 getName

    move-result-object v0 # 将静态方法的返回值移动到 v0 寄存器

    .line 3
    invoke-virtual {p0, v0}, Lcom/example/Test;->print(Ljava/lang/String;)V # 假设有一个 print 方法

    return-void
.end method

# 静态方法定义
.method private static getName()Ljava/lang/String;
    .locals 1
    .prologue

    const-string v0, "hello" # 静态方法返回字符串 "hello"
    return-object v0
.end method

在这里插入图片描述
其他指令可以参考本章博客进行详细学习

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值