smali语法部分已经完结,感兴趣的可以访问下面这个链接阅读上一部分:安卓逆向入门——smali语法(上)_不会算法的小白的博客-CSDN博客

创建对象

对象的创建分多步进行:

#声明实例

new-instance + 变量名, 对象全包名路径;

#调用构造方法(如果构造方法内还定义了成员变量,那么在调用之前需要提前声明,然后在invoke的时候当作参数一并传入)

invoke-direct {变量名}, 对象全包名路径;-><init>(参数)返回类型

示例

class Test{}

new Test();			// 实例化对象

用smali代码表示为:

.class LTest;
.super Ljava/lang/Object;

new-instance v0, LTest;
invoke-direct {v0}, LTest;-><init>()V

记住:先声明,后调用构造方法

数据的定义

  • 字符串数据
  • 字节码数据
  • 数值类型数据

字符串数据

以下代码在Java中表示声明一个变量并返回:

public class Test{
    public String getHello(){
        String s = "hello";
        return s;
        // 或者表示为 return "hello";
    }
}

用smali代码表示为:

.class public LTest;
.super Ljava/lang/Object;

.method public getHello()Ljava/lang/String;
	.registers 2				# 表示寄存器,后面会详细介绍
	const-string v0, "hello"	# 定义一个字符串

	return-object v0
.end method

在smali中,想要返回一个数据,必须先声明

字节码数据

比如以下Java代码:

public class LTestActivity{
    public void jump(){
    	this.startActivity(new Intent(this, GoActivity.class))
        // Intent类的全包名路径为:android/content/Intent
        // LTestActivity类继承android/app/Activity
    }
}

用smali代码表示为:

.class public LTestActivity;
.super Landroid/app/Activity;

.method public jump()V
	.registers 3
	# 将Java由内到外拆解编写
	# 第一步 声明class对象以及当前Activity this对象
	const-class v0, LGoActivity;
	# 第二步 创建Intent对象
	new-instance v1, Landroid/content/Intent;
	# 构造方法调用
	invoke-direct {v1, p0, v0}, Landroid/content/Intent;-><init>(Landroid/app/Activity;Ljava/lang/Class;)
	# 第三步 调用startActivity方法
	invoke-virtual {p0, v1}, LTestActivity;->startActivity(Landroid/content/Intent;)V
.end method

数值类型数据拆分

第一种 const开头 占用一个容器(寄存器)

const v0, 30

  • const/4 最大只允许存放4位数值(4个二进制位)
  • const/16 最大只允许存放16位数值 第一位默认为符号位 所以计算后15位的值
  • const 最大32位
  • const/high16 v0, 0xFF7f0000 一位16进制表示二进制4位,取前4位16进制

第二种 const-wide 占用两个容器 64位

const-wide v0, 30 占用v0和v1

总结

const-string v0, "hello"	# 定义字符串 将字符串"hello"赋值给v0

const-class v0, LGoActivity;	# 定义字节码对象 将GoActivity.class对象赋值给v0

# 一下数据定义高位默认为符号位
const/4 v0, 0x2		# 定义一个容器 最大只允许存放半字节4位数据 取值范围为 -8 and 7
const/16 v0, 0xABCD		# 定义一个容器 最大只允许存放16位数据 比如short类型数据 取值范围为-32768~32767
const v0, 0xA	# 定义一个容器 最大只允许存放32位数据 比如int类型数据 将数字10赋值给v0 取值范围-2147483647~2147483647
const/high16	# 定义一个容器 最大只允许存放高16位数值 比如0xFFFF0000末四位补0 存入高四位0xFFFF

# const-wide	占用两个寄存器vx和vx+1 数值必须以L结尾 否则编译不通过
const-wide/16	# 定义两个相连容器 最大只允许存放16位数据
const-wide/31	# 定义两个相连容器 最大只允许存放32位数据
const-wide		# 定义两个相连容器 最大只允许存放64位数据
const-wide/high16	# 定义两个相连容器 只允许存放高16位数据
# 负数的二进制要按位取反再加1得到真正地结果

数据取值范围算法

1000 -> -8;

1001 -> -7;

1010 -> -6;

1011 -> -5;

1100 -> -4;

1101 -> -3;

1110 -> -2;

1111 -> -1;

0000 -> 0;

0001 -> 1;

0010 -> 2;

0011 -> 3;

0100 -> 4;

0101 -> 5;

0110 -> 6;

0111 -> 7;

算法:正数的符号位是0, 复数的符号位是1。正数的反码、补码与原码一样。负数的反码是让符号位不变,数据按位取反;补码是将反码加1。

字段的取值和赋值

静态字段赋值

分多步进行 关键代码:

sput-object # s代指static

比如以下Java代码:

public class Test{
    private static String a = "hello";
}

用smali代码表示为:

.class public LTest;
.super Ljava/lang/Object;
.source "Test.java"

.field private static a:Ljava/lang/String;

# 类初始化方法 被jvm执行 优先于构造方法
.method static constructor <clinit>()V
	const-string v0, "hello" # 定义常量值

    sput-object v0, LTest;->a:Ljava/lang/String; # 常量赋值

	return-void

.end method

类非静态字段赋值

iput-object # i代表instance

比如以下Java代码:

public class Test{
	private String a = "g";
	public Test(String a){
    
    }
	public void setAa(){
        a = "b";
    }
}

用smali代码表示为:

.class public LTest;
.super Ljava/lang/Object;

.field private a:Ljava/lang/String;

.method public constructor <init>(Ljava/lang/String;)V
	.registers 3
    # 初始化父类构造方法
	invoke-direct {p0}, Ljava/lang/Object;-><init>()V
    # 声明字符串内容
    const-string v0, "g"
    # 赋值
    iput-object v0, p0, LTest;->a:Ljava/lang/String;

	return-void
.end method

.method public setAa()V
	.registers 2

    .prologue

    const-string v0, "b"

    iput-object v0, p0, LTest;->a:Ljava/lang/String;

	return-void
.end method

静态字段取值

sget-object # s代指static

比如以下Java代码:

public class Test{
	private static String a = "hello"
	public Test(String a){
	
	}
	public void getA(){
        String aa = a;
    }
}

用smali代码表示为:

.class public LTest;
.super Ljava/lang/Object;

.field private static a:Ljava/lang/String;

.method static constructor <clinit>()V
	const-string v0, "hello"

	sput-object v0, LTest;->a:Ljava/lang/String;

	return-void
.end method

.method public getA()V
	.registers 2

    # 静态字段取值
	sget-object v0, LTest;->a:Lava/lang/String;

	return-void
.end method
	

类非静态字段取值

iget-object # i代表instance

比如以下Java代码:

public class Test{
	private String a = "hello";
	public Test(String a){
    }
	public void getA(){
        String aa = a;
    }
}

用smali代码表示为:

.class public LTest;
.super Ljava/lang/Object;

.field private a:Ljava/lang/String;

.method public constructor <init>(Ljava/lang/String;)V
	.registers 3

	.prologue

	invoke-direct {p0}, Ljava/lang/Object;-><init>()V

	const-string v0, "hello"
    # 初始化成员变量
    iput-object v0, p0, LTest;->a:Ljava/lang/String;

	return-void
.end method

.method public getA()V
	.registers 2

    # 类非静态字段取值
	iget-object v0, LTest;->a:Ljava/lang/String;

	return-void
.end method

注意:以上取值赋值方法都是以String对象举例,如果是基本数据类型,那么按照如下表处理:

smali取值赋值和值定义关键字

Java

iget-byte

iput-byte

const/4

byte

iget-short

iput-short

const/4

short

iget

iput

const/4

int

iget-wide

iput-wide

const-wide/16

long

iget

iput

const/high16

float

iget-wide

iput-wide

const/high16

double

iget-char

iput-char

const/16

char

iget-boolean

iput-boolean

const/4

boolean

逻辑语句之条件跳转分支

"if-eq vA, vB, :cond_**" 如果vA等于vB则跳转到:cond_** # equal

"if-ne vA, vB, :cond_**" 如果vA不等于vB则跳转到:cond_** # not equal

"if-lt vA, vB, :cond_**" 如果vA小于vB则跳转到:cond_** # less than

"if-ge vA, vB, :cond_" 如果vA大于等于vB则跳转到:cond_** # greater equal

"if-gt vA, vB, :cond_**" 如果vA大于vB则跳转到:cond_** # greater than

"if-le vA, vB, :cond_" 如果vA小于等于vB则跳转到:cond_** # less equal

"if-eqz vA, :cond_**" 如果vA等于0则跳转到:cond_** # zero

"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_**

寄存器

介绍

对于dalviks字节码寄存器都是32位的,它能够表示任何类型,2个寄存器用于表示64位的类型(Long and Double).

作用

声明于方法内部(必须)

.method public getName()V
	.registers 6

    return-void
.end method

.registers和locals基本区别

在一个方法(method)中有两种方式指定有多少个可用的寄存器。指令.registers指令指定了在这个方法中有多少个可用的寄存器,

指令.locals指明了在这个方法中非参(non-parameter)寄存器的数量。然而寄存器的总数也包括保存方法参数的寄存器。

关于寄存器命名规则

V命名法

.method public callMe(II)V
	.registers 5
    const-string v0, "1"
    const-string v1, "1"

    v2==>p0
    v3==>p1
    v4==>p2
    v命名法
	return-void
.end method

上面的例子中我们使用的是v命名法,也就是在本地寄存器后面依次添加参数寄存器,

但是这种命名方式存在一种问题:假如我们后期想要修改方法体的内容,涉及到增加或则删除寄存器,由于v命名法需要排序的局限性,那么会造成大量代码的改动,有没有一种办法让我们只改动registers或则locals的值就可以了呢,答案是:有的

除了v命名法之外,还有一种命名法叫做p命名法

p命名法

p命名法只能给方法参数命名,不能给本地变量命名

假如有一个非静态方法如下;

.method public print(Ljava/lang/String;Ljava/lang/String;I)V

以下是p命名法参数对应表:

p0

this

p1

第一个参数LJava/lang/String;

p2

第二个参数Ljava/lang/String;

p3

第三个参数I

如前面提到的,long和double类型都是64位,需要2个寄存器。当你引用参数的时候一定要记住,例如:你有一个非静态方法

LMyObject;->MyMethod(IJZ)V

方法的参数为int、long、bool.所以这个方法的所有参数需要5个寄存器

p0

this

p1

I

p2,p3

J

p4

Z

另外当调用方法后,必须在寄存器列表,调用指令中指明,两个寄存器保存了double-wide宽度的参数。

注意:在默认的baksmali中,参数寄存器将使用p命名方式,如果除去某种原因你要禁用p命名方式,而要强制使用v命名方式,应当使用-p/--no-parameter-registers选项

总结

  • locals和registers都可以表示寄存器数量,locals指定本地局部变量寄存器个数,registers是locals和参数寄存器数量的总数,两者使用任选其一
  • 同时,寄存器命名一共分两种,一种是v命名法,另一种是p命名法

v0

第一个局部寄存器

v1

第二个局部寄存器

v2

p0

第一个参数寄存器

v3

p1

第二个参数寄存器

v4

p2

第三个参数寄存器

smali语法关键字

.line N

表示与Java源文件代码的映射关系,比如:

.line 3 # 代表以下代码还原成Java代码在源文件第三行
const/4 v0, 0x1

iput v0, p0, LTest;->a:I

删除该关键字不影响程序执行,该关键字在反编译时能很好地帮助我们阅读smali代码,以该关键字当作代码块地分割线,方便快速阅读执行内容

:cond_N

条件分支,配合if使用

.prologue

表示程序的开始,可省略

:goto_N

goto跳转分支,配合goto关键字使用

.local

显示局部变量别名信息,作用等同.line

move-result-object v0	# 调用方法后结果存储在v0中
# 方便程序阅读,不会对执行造成影响
.local v0, "b":Ljava/lang/String;	# 局部变量v0别名为b,是一个String类型 也就是 String b = v0

.param

显示方法参数别名信息,方便程序阅读,不会对执行造成影响

.param p1, "a":Ljava/lang/String; 	# public void getName(String a)

实战环节

以下Java代码:

public class Test{
	public Test(){
    }
	public static void main(String[] args){
        System.out.println("hello world!");
    }
}

用smali代码表示为:

.class public LTest;
.super Ljava/lang/Object;

.method public constructor <init>()V
	.registers 1
	invoke-direct {p0}, Ljava/lang/Object;-><init>()V

	return-void
.end method

.method public static main([Ljava/lang/String;)V
	.registers 3

    sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;
	const-string v1, "hello world!"
    invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V

	return-void
.end method
Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐