015 Android之可执行文件dex

文章目录

从一个hello world开始

smali代码和dex之间有着千丝万缕的联系,先从一个最简单的dex文件hello world开始,学习整个dex文件的结构

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

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

    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

以上面这段smali代码为例

D:\Android\tools>java -jar smali.jar -o classes.dex HelloWorld.smali

然后将smali代码转成dex文件,并打成压缩包,命名为HelloWorld.zip

015 Android之可执行文件dex

确保adb和模拟器连接成功

015 Android之可执行文件dex

再将打包好的zip文件上传到模拟器
015 Android之可执行文件dex

接着执行dex文件,可以看到成功打印了Hello World

Dex文件结构

Dex文件主要可以分为三大块:

  1. Dex文件头
  2. 各种数据的数组,包括字符串 类型 方法原型 字段 方法
  3. 类数据

015 Android之可执行文件dex

文件头

首先来看文件头的部分

015 Android之可执行文件dex

总共占0x70个字节大小,比较重要的字段有四个

  1. dex_magic:表示dex文件的文件标识,特征字符串
  2. checksum:校验和,对文件求了32位的哈希值(从字段3开始到文件末尾)
  3. signature:表示sha1,对文件求哈希值(从字段4开始到文件末尾)
  4. file_size:表示文件大小

除了这四个字段以外,文件头部还有其他一些字段

  1. header_size:dex文件头大小
  2. endian_tag:数据排列方式–小端方式

015 Android之可执行文件dex

各种表的大小及偏移

  1. string_ids_size和string_ids_off,字符串表的大小和偏移
  2. type_ids_size和type_ids_off,类型表的大小和偏移
  3. proto_ids_size和proto_ids_off,字段表的大小和偏移
  4. class_defs_size和class_defs_off,类数据表的大小和偏移

各种数据的数组

dex文件第二部分是各种数据的数组,包括字符串 类型 方法原型 字段 方法

字符串表

015 Android之可执行文件dex

字符串表项,是一个字符串数据的偏移,偏移指向的是一个string_data结构。string_data结构中有两个字段

字段1:字符串长度,数据类型是uleb128,安卓中特有的变长的数据类型

字段2:存储数据,字符串以0结尾

类型表

015 Android之可执行文件dex

类型表,保存的是一个索引值,指向的是字符串表

015 Android之可执行文件dex

例如:索引值为3表示的是字符串表的下标为3的位置指向的是L/java/lang/Object这个字符串。

原型表

015 Android之可执行文件dex

原型表中存储的是函数原型的各部分描述信息。包括短类型(shorty_idx),返回类型(return_type_idx),参数的类型(parameters_off),最终还是一个指向字符串表的数组下标。

注意:字段为返回类型(return_type_idx)的值,是类型表中的索引

字段表

015 Android之可执行文件dex

存储的是字段信息,包括字段所在类(class_idx),字段的类型(type_idx),字段的名称(name_idx)。

class_idx是类型表中的索引,type_idx是类型表中的索引,字段名称的索引是字符串的数组下标

方法表

015 Android之可执行文件dex

方法表中存储的是方法的信息,包括方法所在的类(class_dex),方法的原型(proto_idx),方法的名称(name_idx)。

其中class_idx是类型表的索引,proto_idx是原型表的索引,方法名称的索引(name_idx)是字符串表的数组下标

类数据

类数据也是一个数组,每一个元素就是一个类的相关信息。现在所分析的这个文件因为只有一个类,所以只有一个类的信息。

015 Android之可执行文件dex

在表项中的class_data中存储的是类数据,包括类名索引,访问属性,父类索引,接口偏移,源码索引,注解偏移,类数据偏移

015 Android之可执行文件dex

其中,整个类的数据在class_data_item这个结构中。

015 Android之可执行文件dex

method_list是类内所有方法的列表,因为当前这个文件只有一个Main方法,所以列表内只有一个结构体。结构体中有方法的基本信息,包括方法索引,访问标志,代码偏移,代码信息

015 Android之可执行文件dex

其中code_item是整个代码的信息,里面有两个字段特别重要

ins_size:指令长度

ushort insns[8]:指令数组

015 Android之可执行文件dex

这个数组存放的就是被翻译成smali代码的虚拟机指令,也就是OpCode

手工解析Smali代码

62 00 00 00 1A 01 00 00 6E 20 01 00 10 00 0E 00

接下来复制这一段十六进制,手工将这段代码解析成Smali代码。

015 Android之可执行文件dex

这里还需要借助一个文档《(中文)Dalvik操作码》,里面有所有的Opcode和对应的操作码以及示例。

015 Android之可执行文件dex

首先找到62,62代表的指令含义是根据字段ID读取静态对象引用字段到vx,接着,还需要看懂旁边的示例

6201 0C00

解析为smali代码是

sget‐object v1, Test3.os1:Lja va/lang/Object; // field@000c

读取 Object 的静态对象引用字段 os1(字段表#CH 条目)到 v1。

也就说这条指令一共4个字节

62 代表操作码 sget‐object
01 代表的是序号为1的寄存器v1
000C 代表字段表索引为0xC的字段

接着再来看我们要解析的Opcode

62 00 00 00 1A 01 00 00 6E 20 01 00 10 00 0E 00

先解析前四个字节

62 00 00 00

具体含义如下

62 代表操作码 sget‐object
00 代表的是序号为0的寄存器v0
0000 代表字段表索引为00的字段

接下来在字段表中找到第0个字段

015 Android之可执行文件dex

java.io.PrintStream java.lang.System.out

第0个字段就是out这个对象,将这个字段翻译为smali代码

Ljava.lang.System;->out:java.io.PrintStream

那么前四个字节

62 00 00 00

解析为smali代码就是

sget‐object v0, Ljava.lang.System;->out:java.io.PrintStream

接着来看1A

015 Android之可执行文件dex

1A08 0000

解析为

 const‐string v8, ""  // string @0000  

存入 string@0000(字符串表#0 条目)的引用到 v 8

需要解析的Opcode

1A 01 00 00

接着找到字符串表索引为0的字符串

015 Android之可执行文件dex

那么这条指令解析为Smali代码就是

const‐string v1, "Hello World!"

接下来查找6E

015 Android之可执行文件dex

直接看例子

6E53 0600 0421 ‐ invoke‐virtual { v4, v0,  v1, v2, v3}, Test2.method5:(IIII)V // me thod@0006  

这条指令比较复杂,一共6个字节,其中

6E---> invoke‐virtual
5---> 参数数量
3---> v3
0600--->method@0006 
0421--->v4, v0,  v1, v2

调用参数表的编译比较诡异,如果参数的数量大于4,第5个参数将编译在指令字节的下一个字节的4个最低位

那么我们要解析的Opcode

6E 20 01 00 10 00

就可以解析为

6E---> invoke‐virtual
2---> 参数数量
01 00--->method@0001
10 00--->v1 v0

翻译出来就是

invoke‐virtual {v0,v1},method@01

015 Android之可执行文件dex

找到函数表下标为1的方法

void java.io.PrintStream.println(java.lang.String)

转为Smali代码

Ljava/io/PrintStream;->println(Ljava/lang/String;)V

完整的smali代码就是

invoke‐virtual {v0,v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V

最后看0E

015 Android之可执行文件dex

表示返回值为空

62 00 00 00 1A 01 00 00 6E 20 01 00 10 00 0E 00

解析出来完整的smali代码就是

sget‐object v0, Ljava.lang.System;->out:java.io.PrintStream;

const‐string v1, "Hello World!"

invoke‐virtual {v0,v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V

return‐void 

和HelloWorld.smali的源码没有区别

上一篇:Mock.js自定义Mock


下一篇:015-poj1184聪明的打字员