0%

Dalvik,smali学习笔记

Dalvik,smali学习笔记

基础了解

说到smali(对Dalvik虚拟机字节码的一种解释),首先应该提一下Google专门为Android平台涉及的用于运行Android程序的虚拟机–Dalvik虚拟机

Dalvik与Jvm主要在字节码是有差别。

在程序运行时,Jvm会频繁对栈进行操作,而Dalvik则是对寄存器

用一组各自的字节码做对比

这是原程序(通过异或交换两个数的值)

1
2
3
4
5
6
7
8
9
public class math{
public static void main(String[] args){
int a=5,b=3,c=a^b;
a=c^a;
b=c^b;
System.out.println(a);
System.out.println(b);
}
}

Jvm下的字节码

1
javap -c -classpath . math.class

image-20220629214610348

重点看main函数的代码:

可以看到,Jvm的字节码明显要比Dalvik的字节码复杂iconst_5表示申明一个整型常量5,istore_0表示栈顶int数值存入第1局部变量,等…

Dalvik下的字节码(此图不准确,请看下面的smali)

(该工具通常在Android SDK的build-tools目录下,使用的时候需要注意jdk版本问题,写这文章的时候用的jdk18,不能成功执行下面的命令,换成jdk8或者其他版本就行)

1
2
3
dx --dex --output=math.dex math.class
如果用的是d8.bat则用d8 --ouput 指定文件夹名称 math.class
dexdump -d math.dex

smali一般的指令格式为: [op]-[type] (可选) / [位宽,默认4位] [目标寄存器], [源寄存器] (可选)

诸多命令可以到这篇文章查找

(41条消息) 【Android安全】Dalvik字节码含义查询表_Walter_Jia的博客-CSDN博客

也或者是官方文档

Opcodes | Android Developers

可以看到Dalvik指令后面,都是用寄存器保存返回值,对寄存器进行频繁操作,而Jvm是对栈进行频繁操作。

实现相同的功能其,相比于Jvm,Dalvik所用到的指令更少,自然也更简洁。

我们将刚得到的dex文件用baksmali编译为smali文件

1
2
java -jar baksmali.jar d math.dex
java -jar smali.jar a -o math1.dex math.smali(这个是把smali转为dex)

只需要把baksmali和smali的jar包放在SDK的tools目录下,然后切换到该目录下执行命令就可以编译出smali文件了(默认在执行后生成的output文件夹里)。(注意以上过程都需要在同一个jdk版本的前提下进行,否则会出现一些错误)

记事本打开生成的smali文件(这里我是用IDEA的插件(java2smali)直接生成的,跟上面那张图不一样,上面是用的另一台电脑的java8编译出来的,下面这个是java18的结果)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
.class public Lmath;
.super Ljava/lang/Object;
.source "math.java"


# direct methods
.method public constructor <init>()V
.registers 1

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

return-void
.end method

.method public static main([Ljava/lang/String;)V
.registers 5
.param p0, "args" # [Ljava/lang/String;

.prologue
.line 3
const/4 v0, 0x5

.local v0, "a":I
const/4 v1, 0x3

.local v1, "b":I
xor-int v2, v0, v1

.line 4
.local v2, "c":I
xor-int/lit8 v0, v0, 0x6

.line 5
xor-int/lit8 v1, v1, 0x6

.line 6
sget-object v3, Ljava/lang/System;->out:Ljava/io/PrintStream;

invoke-virtual {v3, v0}, Ljava/io/PrintStream;->println(I)V

.line 7
sget-object v3, Ljava/lang/System;->out:Ljava/io/PrintStream;

invoke-virtual {v3, v1}, Ljava/io/PrintStream;->println(I)V

.line 8
return-void
.end method

上面 Ljava/lang/System;->out:Ljava/io/PrintStream 表示Java中的System类的out字段的PrintStream字段类型

这里因为我baksmali和smali版本(都是2.5.2)的原因,指令会跟老版本不一样,老版本可能是这样的

image-20220629211611482

再贴一张Dalvik的两种寄存器命名法

image-20220630095915424

对于有M个寄存器,N个参数的函数来说

在P命名法中p0是参数寄存器,v0是变量寄存器

在V命名法中,局部变量寄存器是v0–vn 参数寄存器则为vn–v(n+m)

(在Dalvik汇编代码较长的,且使用寄存器较多的时候,P命名法可以更清楚的阅读代码。)

对于math的代码来说,参数寄存器就是用来存放参数的,即存储println引用的字段,即被交换值过后的a (=3)。v0则是用来存放变量值3,5。

Smali简单编写尝试

首先说下,有些指令助记符后添加了jumbo后缀,这是在 Android 4.0 开始的扩展指令,增加了寄存器和指令索引的取值范围。另外,除去加了jumbo后缀的扩展指令,每个指令的字节码只占1字节范围是0x0~0xff。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
.class public Lmath;
.super Ljava/lang/Object;

.method public constructor <init>()V
.prologue
.registers 1
invoke-direct {p0}, Ljava/lang/Object;-><init>()V
return-void
.end method
.method public static main([Ljava/lang/String;)V
.registers 4

const v0, 0x5
const v1, 0x3
const v2,0x0
xor-int v2 ,v0,v1

sget-object p0, Ljava/lang/System;->out:Ljava/io/PrintStream;
xor-int v0, v0, v2
invoke-virtual {p0,v0}, Ljava/io/PrintStream;->println(I)V

sget-object p0, Ljava/lang/System;->out:Ljava/io/PrintStream;
xor-int v1, v1, v2
invoke-virtual {p0,v1}, Ljava/io/PrintStream;->println(I)V
return-void
.end method

我们用上面将smali转化为dex的命令,将其转化为math2.dex,再用d2jdex将dex转化为jar包

1
d2j-dex2jar math2.dex

然后用jd-gui打开生成的jar包,查看代码

image-20220701100839722

可以看到虽然长相不同,但其实现的功能与原来的math还是差不多的。(雾)