授人与鱼不如授人与渔,作为初学者,最重要的是学会查看官方文档,自主学习。
首先放上google官方文档对dex解释的链接,里面相当详细的介绍了dex的格式的组成。
https://source.android.google.cn/devices/tech/dalvik/dex-format#header-item
首先声明一点,因为class_ids的内部定义很复杂,层层嵌套,这里我只研究了一部分,剩下的就要靠你们自己去研究了。
class_def_item结构
class_idx uint 此类的 type_ids 列表中的索引。此项必须是“类”类型,而不能是“数组”或“基元”类型。
access_flags uint 类的访问标记(public、final 等)。如需了解详情,请参阅“access_flags 定义”。
superclass_idx uint 父类的 type_ids 列表中的索引。如果此类没有父类(即它是根类,例如 Object),则该值为常量值 NO_INDEX。如果此类存在父类,则此项必须是“类”类型,而不能是“数组”或“基元”类型。
interfaces_off uint 从文件开头到接口列表的偏移量;如果没有接口,则该值为 0。该偏移量(如果为非零值)应该位于 data 区段,且其中的数据应采用下文中“type_list”指定的格式。该列表的每个元素都必须是“类”类型(而不能是“数组”或“基元”类型),并且不得包含任何重复项。
source_file_idx uint 文件(包含这个类(至少大部分)的原始来源)名称的 string_ids 列表中的索引;或者该值为特殊值 NO_INDEX,以表示缺少这种信息。任何指定方法的 debug_info_item 都可以替换此源文件,但预期情况是大多数类只来自一个源文件。
annotations_off uint 从文件开头到此类的注释结构的偏移量;如果此类没有注释,则该值为 0。此偏移量(如果为非零值)应该位于 data 区段,且其中的数据应采用下文中“annotations_directory_item”指定的格式,同时所有项将此类作为定义符进行引用。
class_data_off uint 从文件开头到此项的关联类数据的偏移量;如果此类没有类数据,则该值为 0(这种情况有可能出现,例如,如果此类是标记接口)。该偏移量(如果为非零值)应该位于 data 区段,且其中的数据应采用下文中“class_data_item”指定的格式,同时所有项将此类作为定义符进行引用。
static_values_off uint 从文件开头到 static 字段初始值列表的偏移量;如果没有该列表(并且所有 static 字段都将使用 0 或 null 进行初始化),则该值为 0。此偏移量应位于 data 区段,且其中的数据应采用下文中“encoded_array_item”指定的格式。该数组的大小不得超出此类所声明的 static 字段的数量,且 static 字段所对应的元素应采用相对应的 field_list 中所声明的相同顺序。每个数组元素的类型均必须与其相应字段的声明类型相匹配。 如果该数组中的元素比 static 字段中的少,则剩余字段将使用适当类型的 0 或null 进行初
定义结构体
typedef struct _IMAGE_CLASS_DEF_ITEM
{
DWORD class_idx;
DWORD access_flags;
DWORD superclass_idx;
DWORD interfaces_off;
DWORD source_file_idx;
DWORD annotations_off;
DWORD class_data_off;
DWORD static_values_off;
}IMAGE_CLASS_DEF_ITEM, * PIMAGE_CLASS_DEF_ITEM;
关于class里面的解析我只挑重点的说
一:access_flags
access_flags定义
ACC_PUBLIC |
0x1 |
public :全部可见 |
public :全部可见 |
public :全部可见 |
ACC_PRIVATE |
0x2 |
*private :仅对定义类可见 |
private :仅对定义类可见 |
private :仅对定义类可见 |
ACC_PROTECTED |
0x4 |
*protected :对软件包和子类可见 |
protected :对软件包和子类可见 |
protected :对软件包和子类可见 |
ACC_STATIC |
0x8 |
*static :无法通过外部 this 引用构造 |
static :对定义类全局可见 |
static :不采用 this 参数 |
ACC_FINAL |
0x10 |
final :不可子类化 |
final :构建后不可变 |
final :不可替换 |
ACC_SYNCHRONIZED |
0x20 |
|
|
synchronized :调用此方法时自动获得关联锁定。注意:这一项仅在同时设置 ACC_NATIVE 的情况下才有效。 |
ACC_VOLATILE |
0x40 |
|
volatile :有助于确保线程安全的特殊访问规则 |
|
ACC_BRIDGE |
0x40 |
|
|
桥接方法,由编译器自动添加为类型安全桥 |
ACC_TRANSIENT |
0x80 |
|
transient :不会通过默认序列化保存 |
|
ACC_VARARGS |
0x80 |
|
|
最后一个参数应被编译器解译为“rest”参数 |
ACC_NATIVE |
0x100 |
|
|
native :在原生代码中实现 |
ACC_INTERFACE |
0x200 |
interface :可多倍实现的抽象类 |
|
|
ACC_ABSTRACT |
0x400 |
abstract :不可直接实例化 |
|
abstract :不通过此类实现 |
ACC_STRICT |
0x800 |
|
|
strictfp :严格的浮点运算规则 |
ACC_SYNTHETIC |
0x1000 |
不在源代码中直接定义 |
不在源代码中直接定义 |
不在源代码中直接定义 |
ACC_ANNOTATION |
0x2000 |
声明为注释类 |
|
|
ACC_ENUM |
0x4000 |
声明为枚举类型 |
声明为枚举值 |
|
(未使用) |
0x8000 |
|
|
|
ACC_CONSTRUCTOR |
0x10000 |
|
|
构造函数方法(类或实例初始化块) |
ACCDECLARED SYNCHRONIZED |
0x20000 |
|
|
声明了 synchronized 。 |
了解java的应该都知道,类和方法的关键字并不是只有一个,而是有多个,这里就需要我们把数字转换成对应的关键字。
从上表中可以发现,任何关键字对应的值相加都不会等于一个关键字对应的值。
解析思路:
定义两个数组:
extern LPSTR mClassType[]{"public ","private ","protected ","static ","final ","synchronized ","volatile bridge ","transient varargs ","native ","interface ","abstract ","strict ","synthetic ","annotation ","enum ","constructor ","declared_synchronized "};
extern DWORD mClassValue[]{ 0x1,0x2,0x4,0x8,0x10,0x20,0x40,0x80,
0x100,0x200,0x400,0x800,0x1000,0x2000,0x4000,0x10000,0x20000 };
比较数值的大小,有三种情况:
1、从第一个开始比较,直到与这个数相等 -----------直接返回对应的字符串,跳出循环
2、从第一个开始比较,直到大于这个数 ---------------返回前一个索引的字符串,并把数值减去前一个索引的值,进入下一个循环
3、比较到了结尾 -------------返回当前对应的字符串,减去当前数值,进入下一个循环
此时如果直接拼接字符串,那么顺序是乱的,此处我写了一个动态数组(需要一点的数据结构基础,非必须,可用一个int数组来替代)来保存索引值,此处要理解,指针的值不一定都是地址值。
代码:
void getClassType(char** str, DWORD index)
{//这是动态数组的初始化
void* arr = Init_DynamicArray(1);
//开辟的空间
char* m_str = (char*)malloc(100);
int n = 0;
memset(m_str, 0, 100);
//循环,只要这个值大于0
while (index)
{
//循环判断 IMAGE_SIZEOF_CLASS_TYPE是我定义的一个宏,值是上面数组的长度
for (size_t i = 0; i < IMAGE_SIZEOF_CLASS_TYPE; i++)
{
if (index == mClassValue[i])
{//如果相等 给动态数组插入值 index等于0跳出循环
Insert_DynamicArray(arr, n++, (void*)i);
index = 0;
break;
}
if (index < mClassValue[i])
{//如果小于 给动态数组插入前一个的索引 index减去前一个索引的数值
Insert_DynamicArray(arr, n++, (void*)(i - 1));
index -= mClassValue[i - 1];
break;
}
if(i==IMAGE_SIZEOF_CLASS_TYPE-1)
{//到最后一个了
Insert_DynamicArray(arr, n++, (void*)i);
index -=mClassValue[i];
break;
}
}
}
int num = Size_DynamicArray(arr);
//倒序拼接
for (int i = num-1; i >= 0; i--)
{
int m = (int)getByPos_DynamicArray(arr, i);
strcat(m_str, mClassType[m]);
}
Destroy_DynamicArray(arr);
*str = m_str;
}
二:class_data_off
class_data_item格式:
static_fields_size |
uleb128 |
此项中定义的静态字段的数量 |
instance_fields_size |
uleb128 |
此项中定义的实例字段的数量 |
direct_methods_size |
uleb128 |
此项中定义的直接方法的数量 |
virtual_methods_size |
uleb128 |
此项中定义的虚拟方法的数量 |
static_fields |
encoded_field[static_fields_size] |
定义的静态字段;以一系列编码元素的形式表示。这些字段必须按 field_idx 以升序进行排序。 |
instance_fields |
encoded_field[instance_fields_size] |
定义的实例字段;以一系列编码元素的形式表示。这些字段必须按 field_idx 以升序进行排序。 |
direct_methods |
encoded_method[direct_methods_size] |
定义的直接(static 、private 或构造函数的任何一个)方法;以一系列编码元素的形式表示。这些方法必须按 method_idx 以升序进行排序。 |
virtual_methods |
encoded_method[virtual_methods_size] |
定义的虚拟(非 static 、private 或构造函数)方法;以一系列编码元素的形式表示。此列表不得包括继承方法,除非被此项所表示的类覆盖。这些方法必须按 method_idx 以升序进行排序。 虚拟方法的 method_idx 不得与任何直接方法相同。 |
此处只简单的说一下,需要分别计算出前四个记录数量的字节数和值,然后才能得到后边四个数组的位置。
三:static_values_off
encoded_array 格式
名称 |
格式 |
说明 |
size |
uleb128 |
数组中的元素数量 |
values |
encoded_value[size] |
采用本部分所指定格式的一系列 size encoded_value 字节序列;依序连接。 |
encoded_value 编码
(value_arg << 5) \ |
value_type |
ubyte |
一种字节,用于表示紧跟后面的 value 及高 3 位中可选澄清参数的类型。请参阅下文,了解各种 value 定义。在大多数情况下,value_arg 会以字节为单位将紧跟后面的 value 的长度编码为 (size - 1) ;例如,0 表示该值需要 1 个字节;7 表示该值需要 8 个字节;不过,也存在下述例外情况。 |
value |
ubyte[] |
用于表示值的字节,不同 value_type 字节的长度不同且采用不同的解译方式;不过一律采用小端字节序。如需了解详情,请参阅下文中的各种值定义。 |
第一个字节的高三位记录字节的个数,低五位记录value_type
int values = *strtic_value & 0x1f; //计算出类型
int lenght = *strtic_value >> 5; //计算出长度
value_type
VALUE_BYTE |
0x00 |
(无;必须为 0 ) |
ubyte[1] |
有符号的单字节整数值 |
VALUE_SHORT |
0x02 |
size - 1 (0…1) |
ubyte[size] |
有符号的双字节整数值,符号扩展 |
VALUE_CHAR |
0x03 |
size - 1 (0…1) |
ubyte[size] |
无符号的双字节整数值,零扩展 |
VALUE_INT |
0x04 |
size - 1 (0…3) |
ubyte[size] |
有符号的四字节整数值,符号扩展 |
VALUE_LONG |
0x06 |
size - 1 (0…7) |
ubyte[size] |
有符号的八字节整数值,符号扩展 |
VALUE_FLOAT |
0x10 |
size - 1 (0…3) |
ubyte[size] |
四字节位模式,向右零扩展,系统会将其解译为 IEEE754 32 位浮点值 |
VALUE_DOUBLE |
0x11 |
size - 1 (0…7) |
ubyte[size] |
八字节位模式,向右零扩展,系统会将其解译为 IEEE754 64 位浮点值 |
VALUE_METHOD_TYPE |
0x15 |
size - 1 (0…3) |
ubyte[size] |
无符号(零扩展)四字节整数值,会被解译为要编入 proto_ids 区段的索引;表示方法类型值 |
VALUE_METHOD_HANDLE |
0x16 |
size - 1 (0…3) |
ubyte[size] |
无符号(零扩展)四字节整数值,会被解译为要编入 method_handles 区段的索引;表示方法句柄值 |
VALUE_STRING |
0x17 |
size - 1 (0…3) |
ubyte[size] |
无符号(零扩展)四字节整数值,会被解译为要编入 string_ids 区段的索引;表示字符串值 |
VALUE_TYPE |
0x18 |
size - 1 (0…3) |
ubyte[size] |
无符号(零扩展)四字节整数值,会被解译为要编入 type_ids 区段的索引;表示反射类型/类值 |
VALUE_FIELD |
0x19 |
size - 1 (0…3) |
ubyte[size] |
无符号(零扩展)四字节整数值,会被解译为要编入 field_ids 区段的索引;表示反射字段值 |
VALUE_METHOD |
0x1a |
size - 1 (0…3) |
ubyte[size] |
无符号(零扩展)四字节整数值,会被解译为要编入 method_ids 区段的索引;表示反射方法值 |
VALUE_ENUM |
0x1b |
size - 1 (0…3) |
ubyte[size] |
无符号(零扩展)四字节整数值,会被解译为要编入 field_ids 区段的索引;表示枚举类型常量的值 |
VALUE_ARRAY |
0x1c |
(无;必须为 0 ) |
encoded_array |
值的数组,采用下文“encoded_array 格式”所指定的格式。value 的大小隐含在编码中。 |
VALUE_ANNOTATION |
0x1d |
(无;必须为 0 ) |
encoded_annotation |
子注释,采用下文“encoded_annotation 格式”所指定的格式。value 的大小隐含在编码中。 |
VALUE_NULL |
0x1e |
(无;必须为 0 ) |
(无) |
null 引用值 |
VALUE_BOOLEAN |
0x1f |
布尔值 (0…1) |
(无) |
一位值;0 表示 false ,1 表示 true 。该位在 value_arg 中表示。 |
看到上面那么多的类型,长度还都是不固定的有没有头皮发麻?其实没有那么复杂,其实计算机并不知道里面的数据是有符号还是无符号的数值,你只需要小端模式拼接一下,转换成相应的类型即可。
对于变动长度,这个转换是通用的,你只需要注意num的类型,如果是八字节的就用long long类型,因为c\c++在vs中无论是64位还是32位中都是4字节。最后再强转成需要的类型即可。
int n=0;
int m=0;
int num;
lenght是计算出来的长度,因为数组的个数是长度减1,所以此处要大于-1
while (lenght-- > -1)
{
m = *strtic_value;
m = m << (8 * n);
n++;
num += m;
strtic_value2++;
}
encoded_array是一个循环类型转换
最后附上我写的半成品mfc应用和源码,很多的细节都没有处理,点击按钮之后会在桌面上新建一个文件夹,并生成相应的文件,class我只解析了类名,变量,变量的值(array和方法句柄没有写),和方法名,字节码没有进行解析。