Android逆向之旅---解析编译之后的Dex文件格式
一、前言
新的一年又开始了,大家是否还记得去年年末的时候,我们还有一件事没有做,那就是解析Android中编译之后的classes.dex文件格式,我们在去年的时候已经介绍了:
如何解析编译之后的xml文件格式:
http://blog.csdn.net/jiangwei0910410003/article/details/50568487
如何解析编译之后的resource.arsc文件格式:
http://blog.csdn.net/jiangwei0910410003/article/details/50628894
那么我们还剩下一个文件格式就是classes.dex了,那么今天我们就来看看最后一个文件格式解析,关于Android中的dex文件的相关知识这里就不做太多的解释了,网上有很多资料可以参考,而且,我们在之前介绍的一篇加固apk的那篇文章中也介绍了一点dex的格式知识点:http://blog.csdn.net/jiangwei0910410003/article/details/48415225,我们按照之前的解析思路来,首先还是来一张神图:
有了这张神图,那么接下来我们就可以来介绍dex的文件结构了,首先还是来看一张大体的结构图:
二、准备工作
我们在讲解数据结构之前,我们需要先创建一个简单的例子来帮助我们来解析,我们需要得到一个简单的dex文件,这里我们不借助任何的IDE工具,就可以构造一个dex文件出来。借助的工具很简单:javac,dx命令即可。
创建 java 源文件 ,内容如下
代码:
public class Hello
{
public static void main(String[] argc)
{
System.out.println("Hello, Android!\n");
}
}
在当前工作路径下 , 编译方法如下 :
(1) 编译成 java class 文件
执行命令 : javac Hello.java
编译完成后 ,目录下生成 Hello.class 文件 。可以使用命令 java Hello 来测试下 ,会输出代码中的 “Hello, Android!” 的字符串 。
(2) 编译成 dex 文件
编译工具在 Android SDK 的路径如下 ,其中 19.0.1 是Android SDK build_tools 的版本 ,请按照在本地安装的 build_tools 版本来 。建议该路径加载到 PATH 路径下 ,否则引用 dx 工具时需要使用绝对路径 :./build-tools/19.0.1/dx
执行命令 :dx --dex --output=Hello.dex Hello.class
编译正常会生成 Hello.dex 文件 。
3. 使用 ADB 运行测试
测试命令和输出结果如下 :
$ adb root
$ adb push Hello.dex /sdcard/
$ adb shell
root@maguro:/ # dalvikvm -cp /sdcard/Hello.dex Hello
Hello, Android!
4. 重要说明
(1) 测试环境使用真机和 Android 虚拟机都可以的 。核心的命令是
dalvikvm -cp /sdcard/Hello.dex Hello
-cp 是 class path 的缩写 ,后面的 Hello 是要运行的 Class 的名称 。网上有描述说输入 dalvikvm --help
可以看到 dalvikvm 的帮助文档 ,但是在 Android4.4 的官方模拟器和自己的手机上测试都提示找不到
Class 路径 ,在Android 老的版本 ( 4.3 ) 上测试还是有输出的 。
(2) 因为命令在执行时 , dalvikvm 会在 /data/dalvik-cache/ 目录下创建 .dex 文件 ,因此要求 ADB 的
执行 Shell 对目录 /data/dalvik-cache/ 有读、写和执行的权限 ,否则无法达到预期效果 。
三、讲解数据结构
下面我们按照这张大体的思路图来一一讲解各个数据结构
第一、头部信息Header结构
dex文件里的header。除了描述.dex文件的文件信息外,还有文件里其它各个区域的索引。header对应成结构体类型,逻辑上的描述我用结构体header_item来理解它。先给出结构体里面用到的数据类型ubyte和uint的解释,然后再是结构体的描述,后面对各种结构描述的时候也是用的这种方法。
代码定义:
package com.wjdiankong.parsedex.struct; import com.wjdiankong.parsedex.Utils; public class HeaderType { /** * struct header_item { ubyte[8] magic; unit checksum; ubyte[20] siganature; uint file_size; uint header_size; unit endian_tag; uint link_size; uint link_off; uint map_off; uint string_ids_size; uint string_ids_off; uint type_ids_size; uint type_ids_off; uint proto_ids_size; uint proto_ids_off; uint method_ids_size; uint method_ids_off; uint class_defs_size; uint class_defs_off; uint data_size; uint data_off; } */ public byte[] magic = new byte[8]; public int checksum; public byte[] siganature = new byte[20]; public int file_size; public int header_size; public int endian_tag; public int link_size; public int link_off; public int map_off; public int string_ids_size; public int string_ids_off; public int type_ids_size; public int type_ids_off; public int proto_ids_size; public int proto_ids_off; public int field_ids_size; public int field_ids_off; public int method_ids_size; public int method_ids_off; public int class_defs_size; public int class_defs_off; public int data_size; public int data_off; @Override public String toString(){ return "magic:"+Utils.bytesToHexString(magic)+"\n" + "checksum:"+checksum + "\n" + "siganature:"+Utils.bytesToHexString(siganature) + "\n" + "file_size:"+file_size + "\n" + "header_size:"+header_size + "\n" + "endian_tag:"+endian_tag + "\n" + "link_size:"+link_size + "\n" + "link_off:"+Utils.bytesToHexString(Utils.int2Byte(link_off)) + "\n" + "map_off:"+Utils.bytesToHexString(Utils.int2Byte(map_off)) + "\n" + "string_ids_size:"+string_ids_size + "\n" + "string_ids_off:"+Utils.bytesToHexString(Utils.int2Byte(string_ids_off)) + "\n" + "type_ids_size:"+type_ids_size + "\n" + "type_ids_off:"+Utils.bytesToHexString(Utils.int2Byte(type_ids_off)) + "\n" + "proto_ids_size:"+proto_ids_size + "\n" + "proto_ids_off:"+Utils.bytesToHexString(Utils.int2Byte(proto_ids_off)) + "\n" + "field_ids_size:"+field_ids_size + "\n" + "field_ids_off:"+Utils.bytesToHexString(Utils.int2Byte(field_ids_off)) + "\n" + "method_ids_size:"+method_ids_size + "\n" + "method_ids_off:"+Utils.bytesToHexString(Utils.int2Byte(method_ids_off)) + "\n" + "class_defs_size:"+class_defs_size + "\n" + "class_defs_off:"+Utils.bytesToHexString(Utils.int2Byte(class_defs_off)) + "\n" + "data_size:"+data_size + "\n" + "data_off:"+Utils.bytesToHexString(Utils.int2Byte(data_off)); } }
查看Hex如下:
里面一对一对以_size和_off为后缀的描述:data_size是以Byte为单位描述data区的大小,其余的
_size都是描述该区里元素的个数;_off描述相对与文件起始位置的偏移量。其余的6个是描述.dex文件信
息的,各项说明如下:
(1) magic value
这 8 个 字节一般是常量 ,为了使 .dex 文件能够被识别出来 ,它必须出现在 .dex 文件的最开头的
位置 。数组的值可以转换为一个字符串如下 :
{ 0x64 0x65 0x78 0x0a 0x30 0x33 0x35 0x00 }= "dex\n035\0"
中间是一个 ‘\n' 符号 ,后面 035 是 Dex 文件格式的版本 。
(2) checksum 和 signature
文件校验码 ,使用alder32 算法校验文件除去 maigc ,checksum 外余下的所有文件区域 ,用于检
查文件错误 。
signature , 使用 SHA-1 算法 hash 除去 magic ,checksum 和 signature 外余下的所有文件区域 ,
用于唯一识别本文件 。
(3) file_size
Dex 文件的大小 。
(4) header_size
header 区域的大小 ,单位 Byte ,一般固定为 0x70 常量 。
(5) endian_tag
大小端标签 ,标准 .dex 文件格式为 小端 ,此项一般固定为 0x1234 5678 常量 。
(6) link_size和link_off
这个两个字段是表示链接数据的大小和偏移值
(7) map_off