解析 dex 文件结构 - 索引区和数据区(三) - ClassDefs
ClassDefs 表示某个类的全部信息,包括类类型、访问权限、父类、接口、源文件名、注解和代码等信息。
ClassDefs 的大小和文件偏移在 DexHeader 和 map_list 中都有指定。
结构
ClassDefs 以4字节对齐,即总大小为 4 * 8 * ClassDefsSize .
|
|
classIdx
指向 typeIds 的索引,类类型,必须是一个类类型而不是数组或者基本类型。
accessFlags
类的访问标识,如 public、final 等,常量定义如下,包括类、字段和方法的访问标识:
Name | Value | For Classes (and InnerClass annotations) | For Fields | For Methods |
---|---|---|---|---|
ACC_PUBLIC | 0x1 | public: visible everywhere | public: visible everywhere | public: visible everywhere |
ACC_PRIVATE | 0x2 | * private: only visible to defining class | private: only visible to defining class | private: only visible to defining class |
ACC_PROTECTED | 0x4 | * protected: visible to package and subclasses | protected: visible to package and subclasses | protected: visible to package and subclasses |
ACC_STATIC | 0x8 | * static: is not constructed with an outer this reference | static: global to defining class | static: does not take a this argument |
ACC_FINAL | 0x10 | final: not subclassable | final: immutable after construction | final: not overridable |
ACC_SYNCHRONIZED | 0x20 | synchronized: associated lock automatically acquired around call to this method.Note: This is only valid to set when ACC_NATIVE is also set. | ||
ACC_BRIDGE | 0x40 | bridge method, added automatically by compiler as a type-safe bridge | ||
ACC_VOLATILE | 0x40 | volatile: special access rules to help with thread safety | ||
ACC_TRANSIENT | 0x80 | transient: not to be saved by default serialization | ||
ACC_VARARGS | 0x80 | last argument should be treated as a “rest” argument by compiler | ||
ACC_NATIVE | 0x100 | native: implemented in native code | ||
ACC_INTERFACE | 0x200 | interface: multiply-implementable abstract class | ||
ACC_ABSTRACT | 0x400 | abstract: not directly instantiable | abstract: unimplemented by this class | |
ACC_STRICT | 0x800 | strictfp: strict rules for floating-point arithmetic | ||
ACC_SYNTHETIC | 0x1000 | not directly defined in source code | not directly defined in source code | not directly defined in source code |
ACC_ANNOTATION | 0x2000 | declared as an annotation class | ||
ACC_ENUM | 0x4000 | declared as an enumerated type | declared as an enumerated value | |
(unused) | 0x8000 | |||
ACC_CONSTRUCTOR | 0x10000 | constructor method (class or instance initializer) | ||
ACC_DECLARED_SYNCHRONIZED | 0x20000 | declared synchronized. Note: This has no effect on execution (other than in reflection of this flag, per se). |
superclassIdx
指向 typeIds 的索引,父类类型;如果没有父类值为 NO_INDEX . 注意 NO_INDEX 值不是0,因为 0 是一个合法的索引,而且 NO_INDEX 是以 uleb128p1 编码的。
|
|
interfacesOff
指向 DexTypeList 的文件偏移(在数据段中),表示接口;如果没有接口此值为 0 。
DexTypeList 中的值必须是类类型,并且没有重复。
|
|
sourceFileIdx
指向 stringIds 的索引,表示本类所在的源文件名,如果没有这个信息值为 NO_INDEX
annotationsOff
指向 annotations_directory_item 的文件偏移,表示注解;如果没有注解,此值为 0 。
annotations_directory_item
|
|
annotation_set_item
|
|
visibility 定义:
Name | Value | Description |
---|---|---|
VISIBILITY_BUILD | 0x00 | 编译时可见 |
VISIBILITY_RUNTIME | 0x01 | 运行时可见 |
VISIBILITY_SYSTEM | 0x02 | 运行时可见, 但是只对系统可见,用户代码不可见 |
encoded_annotation 格式定义:
Name | Format | Description |
---|---|---|
type_idx | uleb128 | 注解类型. 必须是类类型. |
size | uleb128 | 注解中 name-value 键值对的个数. |
elements | annotation_element[size] | 注解的元素(不是偏移). 元素需按 string_id 的索引升序排列. |
annotation_element格式:
Name | Format | Description |
---|---|---|
name_idx | uleb128 | 代表元素名称,是指向 stringIds 的索引 . |
value | encoded_value | 元素值 |
encoded_value格式:
Name | Format | Description |
---|---|---|
(value_arg << 5) | value_type | ubyte | 表示后面 value 的类型, 可选高三位 clarifying argument .下面详述. value_arg 表示 value 的长度(size - 1), 比如 0 表示 value 需要 1字节, 7 表示 value 需要8字节,不过还是有例外,下面详述. |
value | ubyte[] | 值,不同的类型有不同的解码方式, 通常是小端存储。 |
value 的格式:
类型名 | 类型值 | value_arg Format | value Format | 描述 |
---|---|---|---|---|
VALUE_BYTE | 0x00 | 必须是0 | ubyte[1] | 有符号1字节整形值 |
VALUE_SHORT | 0x02 | size - 1 (0…1) | ubyte[size] | 有符号4字节整型值,符号扩展 |
VALUE_CHAR | 0x03 | size - 1 (0…1) | ubyte[size] | 无符号4字节整型值,0扩展 |
VALUE_INT | 0x04 | size - 1 (0…3) | ubyte[size] | 有符号4字节整型值,符号扩展 |
VALUE_LONG | 0x06 | size - 1 (0…7) | ubyte[size] | 有符号8字节整型值,符号扩展 |
VALUE_FLOAT | 0x10 | size - 1 (0…3) | ubyte[size] | 4字节位模式,0扩展为右侧,以 IEEE754 32位浮点类型解码 |
VALUE_DOUBLE | 0x11 | size - 1 (0…7) | ubyte[size] | 8字节位模式,0扩展为右侧,以 IEEE754 64位浮点类型解码 |
VALUE_STRING | 0x17 | size - 1 (0…3) | ubyte[size] | 无符号(0扩展)4字节整形,解码为指向string_ids的索引,代表字符串 |
VALUE_TYPE | 0x18 | size - 1 (0…3) | ubyte[size] | 符号(0扩展)4字节整形,解码为指向type_ids的索引,代表类型或类 |
VALUE_FIELD | 0x19 | size - 1 (0…3) | ubyte[size] | 符号(0扩展)4字节整形,解码为指向field_ids的索引,代表字段 |
VALUE_METHOD | 0x1a | size - 1 (0…3) | ubyte[size] | 符号(0扩展)4字节整形,解码为指向method_ids的索引,代表方法 |
VALUE_ENUM | 0x1b | size - 1 (0…3) | ubyte[size] | 无符号(0扩展)4字节整形,解码为指向field_ids的索引,代表枚举常量 |
VALUE_ARRAY | 0x1c | 必须是0 | encoded_array | 数组,格式为encoded_array format. |
VALUE_ANNOTATION | 0x1d | 必须是0 | encoded_annotation | 子注解, 格式为encoded_annotation format. |
VALUE_NULL | 0x1e | 必须是0 | (none) | null |
VALUE_BOOLEAN | 0x1f | boolean (0…1) | (none) | 一比特的值, 0 代表 false , 1 代表 true. |
field_annotations_item
|
|
method_annotations_item
|
|
parameter_annotations_item
|
|
|
|
常量定义
|
|
classDataOff
指向 class_data_item 的文件偏移,如果此类没有数据(如接口),值为0.
除了 DexCode 之外的结构是定义在 /dalvik/libdex/DexCLass.h 文件中的,并且采用的是 uleb128 编码方式。与之前不同
class_data_item 定义
|
|
code_item
|
|
debug_info_item
每个 debug_info_item 以一个变长的 header 开始(长度取决于方法的参数个数),接着是操作码(用来修改状态机器码的值),最后是一个结束字节 DBG_END_SEQUENCE
。
状态机器码 包含5个寄存器,同时也追踪着每个寄存器中最后一个局部变量的名字和类型,为 DBG_RESTART_LOCAL 做准备。address
寄存器代表两字节指令的偏移地址,在每个 debug_info 序列里以0开始,单调递增;line
寄存器代表下一条指令的行数,它在 header 中被初始化,可能会加减变化但绝不会小于 1;source_file
寄存器代表行数所在的源文件,它的类型是 class_def_item 中的 source_file_idx;prologue_end
和 epilogue_begin
是布尔类型的标识(初始值为false),代表下一条指令是不是函数入口或者函数结束。
header 的格式:
Name | Format | Description |
---|---|---|
line_start | uleb128 | line 寄存器的初始值,不代表真实的入口 |
parameters_size | uleb128 | 参数名字的个数. 如果是实例方法的话,不包括this. |
parameter_names | uleb128p1[parameters_size] | 方法的参数名的字符串索引. NO_INDEX 表示相关参数没有名字. 类型描述符和签名同方法的类型描述符和签名. |
debug_info 中操作的值:
名字 | 值 | 格式 | 参数 | 描述 |
---|---|---|---|---|
DBG_END_SEQUENCE | 0x00 | (none) | 代表调试信息的终止 | |
DBG_ADVANCE_PC | 0x01 | uleb128 addr_diff | addr_diff: 地址寄存器加上此值 | 使address寄存器指向下一个地址 |
DBG_ADVANCE_LINE | 0x02 | sleb128 line_diff | line_diff: line寄存器加上此值 | 使line寄存器指向新的一行 |
DBG_START_LOCAL | 0x03 | uleb128 register_num,uleb128p1 name_idx,uleb128p1 type_idx | register_num: 某个寄存器,name_idx: 指向string的索引,type_idx: 指向type的索引 | 在当前地址引入一个局部变量. name_idx 或 type_idx 可能是 NO_INDEX,代表此值未知. |
DBG_START_LOCAL_EXTENDED | 0x04 | uleb128 register_num,uleb128p1 name_idx,uleb128p1 type_idx,uleb128p1 sig_idx | register_num: 某个寄存器,name_idx: 指向string的索引,type_idx: 指向type的索引,sig_idx: 指向string的索引,表示类型签名 | 在当前地址引入一个带有类型签名的局部变量. name_idx 或 type_idx、sig_idx 可能是 NO_INDEX,代表此值未知.”dalvik.annotation.Signature” 有关于处理签名的说明. |
DBG_END_LOCAL | 0x05 | uleb128 register_num | register_num: 某个寄存器 | 代表当前地址的局部变量超出范围 |
DBG_RESTART_LOCAL | 0x06 | uleb128 register_num | register_num: 重新赋值的寄存器 | 在当前地址重新引入一个局部变量.名字和类型同上一个局部变量. |
DBG_SET_PROLOGUE_END | 0x07 | (none) | 设置 prologue_end 状态寄存器, 表示下一个入口点应该被视作方法开始的结束(可设置方法断点的合适地方). prologue_end寄存器的值可以被任何>=0x0a的特殊操作码清空. | |
DBG_SET_EPILOGUE_BEGIN | 0x08 | (none) | 设置epilogue_begin状态寄存器, 表示下一个入口点应该被视作方法结束的开始(在方法结束之前使其挂起的合适地方). epilogue_begin寄存器的值可以被任何>=0x0a的特殊操作码清空. | |
DBG_SET_FILE | 0x09 | uleb128p1 name_idx | name_idx: 指向string的索引,表示源文件名; NO_INDEX 表示未知 | 表面接下来的行号入口都与这个文件相关, 而不是 code_item 指定的默认名字. |
Special Opcodes | 0x0a…0xff | (none) | line、address寄存器加减, 开启新的入口点, 清空 prologue_end、 epilogue_begin寄存器. 公式如下. |
Special Opcodes:
使 line、address 寄存器小幅度变化/开启一个新的入口点. 范围是 0x0a ~ 0xff .
DBG_FIRST_SPECIAL = 0x0a // 最小的特殊操作码
DBG_LINE_BASE = -4 // 最小的行号增量
DBG_LINE_RANGE = 15 // 代表行号的变化值
公式:
adjusted_opcode = opcode - DBG_FIRST_SPECIAL
line += DBG_LINE_BASE + (adjusted_opcode % DBG_LINE_RANGE)
address += (adjusted_opcode / DBG_LINE_RANGE)
try_item
|
|
catch_handler_item
|
|
番外 - 关于 catch_handler_list
catch_handler_list 在源码中没有定义,下面的表格是其格式:
Name | Format | Description |
---|---|---|
size | uleb128 | handler 列表的个数 |
list | encoded_catch_handler[handlers_size] | handler 列表 |
单个 catch_handler 的格式:
Name | Format | Description |
---|---|---|
size | sleb128 | 列表中 catch 到的类型的个数. 如果不是正数,则handler列表中含有可以捕获到所有异常的handler. 比如 0 表示没有指定异常的类型,只有一个所有异常handler. 2 表示有两个指定类型的异常. -1 有一个指定类型的异常和一个可以捕获到所有异常的handler. |
handlers | DexCatchHandler[abs(size)] | abs(size) 个handler, 存放的是指定类型的异常. |
catch_all_addr | uleb128 (optional) | 可以处理所有异常的 handler 的地址. size为0或者负数时此值有效. |
staticValuesOff
指向 DexEncodedArray 的文件偏移,表示静态字段的初始值,值为0表示没有设定静态字段的初始值,静态字段被初始化为0或者null.
|
|
encoded_array 格式:
Name | Format | Description |
---|---|---|
size | uleb128 | 数组元素的个数,必须不大于静态字段的个数和对应的field_list的个数,如果是小于,剩余的静态字段按照相应的类型被初始化为0或者null. |
values | encoded_value[size] | 数组内容 |
手工查找
classDefsSize:0x02
classDefsOff:0x047c
0x047c ~ 0x04bc
DexClassDef数据及其解析
|
|
以第一个为例:
classIdx
=0x05,即第0x05个typeId,得类类型为 Lcom/shell/NativeApplication;accessFlags
=0x01,查表得权限 ACC_PUBLICsuperclassIdx
=0x0e,即第0x0e个typeId,得父类类型为 Ljava/lang/Object;interfacesOff
=0x0000,没有实现接口sourceFileIdx
=0x1f,即第0x1f个stringId,得源文件名为 NativeApplication.javaannotationsOff
=0x0000,没有注解classDataOff
=0x0fc2,得DexClassData偏移为0x0fc2staticValuesOff
=0x0000,没有静态数据的初始值
DexClassData数据及其解析
|
|
[注意DexClassData中数字的类型为uleb128]
① header:staticFieldsSize
=0x00, instanceFieldsSize
=0x00, directMethodsSize
=0x05, virtualMethodsSize
=0x00, 即0个静态字段,0个实例字段,5个直接方法,0个虚方法;
② directMethods:(共5个,以第一个为例)
methodIdx
=0x03,即第0x03个methodId,得直接方法为 void com.shell.NativeApplication.() accessFlags
=88 80 04
,解码为0x10008,得方法访问权限为 ACC_STATIC 和 ACC_CONSTRUCTORcodeOff
=90 18
,解码为0xc10,得 DexCode 偏移为 0xc10
DexCode数据及其解析
|
|
registersSize
=0x0001,得寄存器个数为1insSize
=0x0000,得参数个数为0outsSize
=0x0001,调用其他方法使用寄存器个数为1triesSize
=0x0000,try的个数为0debugInfoOff
=0x0a7c,debug信息偏移为0x0a7cinsnsSize
=0x0b,即有0xb个2字节指令,在 0x00000c20 ~ 0x00000c35 之间insns
={1a 00 00 00 71 10 20 00 00 00 1a 00 01 00 71 10 20 00 00 00 0e 00}
debug_info数据及其解析
|
|
- header:
line_start(09)=0x09,得line寄存器的初始值为0x09;
parameters_size(0x00)=0x00,得参数的名字的个数为0; - 操作码:
0x07
:DBG_SET_PROLOGUE_END,方法开始;
0x0e
:0x0e-0x0a=0x04,即行号+=DBG_LINE_BASE+(0x04%DBG_LINE_RANGE),地址+=(0x04/DBG_LINE_RANGE),得行号+0,地址+0;
0x5a
:0x5a-0x0a=0x50,即行号+=DBG_LINE_BASE+(0x50%DBG_LINE_RANGE),地址+=(0x50/DBG_LINE_RANGE),得行号+1,地址+5;
0x56
:0x56-0x0a=0x4c,即行号+=DBG_LINE_BASE+(0x4c%DBG_LINE_RANGE),地址+=(0x4c/DBG_LINE_RANGE),得行号+1,地址+5;
0x00
:DBG_END_SEQUENCE,调试结束。
insns 中的指令数据的解析
在 Dalvik bytecode 找到 OpCode 的含义及格式,在 Dalvik Executable instruction formats 找到该格式的表示方式。
第1条代码:
1a : 含义为 const-string vAA, string@BBBB
, 格式为 1a 21c
21c : 格式为 AA|op BBBB
,表示方式有三种:
1a
的含义已经为我们指明,我们应该采用第三种。1a 后面的6个字节为 00 00 00
,即 AA 是 00,BBBB 是 0000。
则可翻译为 const-string v0, string@0000
, 找到第0x0个 StringId 对应的字符串 /data/data/com.zyh.lightingbackup/.lib/libexec.so .
最终解析为 const-string v0, "/data/data/com.zyh.lightingbackup/.lib/libexec.so"
第2条代码:
71 : 含义为 invoke-static
,格式为 71 35c
35c : 格式为 A|G|op BBBB F|E|D|C
,表示方式有7种:
71 后面的 A = 1, G = 0,所以应该采用方式 [A=1] op {vC}, kind@BBBB
,
而 BBBB = 0020, F|E|D|C = 0000,可翻译为 invoke-static {v0}, kind@0020
,找到第 0x20 个methodId 对应的函数 void java.lang.System.load(java.lang.String) .
最终解析为 invoke-static {v0}, Ljava/lang/System;->load(Ljava/lang/String;)V
最后我们可以使用 apktool 将 apk 文件反编译,在 /smali/com/shell/NativeApplication.smali 文件中找到
写程序解析
|
|