dex文件格式终极篇------class_ids解析

紫诺不离 10天前 6

授人与鱼不如授人与渔,作为初学者,最重要的是学会查看官方文档,自主学习。

首先放上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_PUBLIC0x1public:全部可见public:全部可见public:全部可见ACC_PRIVATE0x2*private:仅对定义类可见private:仅对定义类可见private:仅对定义类可见ACC_PROTECTED0x4*protected:对软件包和子类可见protected:对软件包和子类可见protected:对软件包和子类可见ACC_STATIC0x8*static:无法通过外部 this 引用构造static:对定义类全局可见static:不采用 this 参数ACC_FINAL0x10final:不可子类化final:构建后不可变final:不可替换ACC_SYNCHRONIZED0x20synchronized:调用此方法时自动获得关联锁定。注意:这一项仅在同时设置 ACC_NATIVE 的情况下才有效。ACC_VOLATILE0x40volatile:有助于确保线程安全的特殊访问规则ACC_BRIDGE0x40桥接方法,由编译器自动添加为类型安全桥ACC_TRANSIENT0x80transient:不会通过默认序列化保存ACC_VARARGS0x80最后一个参数应被编译器解译为“rest”参数ACC_NATIVE0x100native:在原生代码中实现ACC_INTERFACE0x200interface:可多倍实现的抽象类ACC_ABSTRACT0x400abstract:不可直接实例化abstract:不通过此类实现ACC_STRICT0x800strictfp:严格的浮点运算规则ACC_SYNTHETIC0x1000不在源代码中直接定义不在源代码中直接定义不在源代码中直接定义ACC_ANNOTATION0x2000声明为注释类ACC_ENUM0x4000声明为枚举类型声明为枚举值(未使用)0x8000ACC_CONSTRUCTOR0x10000构造函数方法(类或实例初始化块)ACCDECLARED SYNCHRONIZED0x20000声明了 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_sizeuleb128此项中定义的静态字段的数量instance_fields_sizeuleb128此项中定义的实例字段的数量direct_methods_sizeuleb128此项中定义的直接方法的数量virtual_methods_sizeuleb128此项中定义的虚拟方法的数量static_fieldsencoded_field[static_fields_size]定义的静态字段;以一系列编码元素的形式表示。这些字段必须按 field_idx 以升序进行排序。instance_fieldsencoded_field[instance_fields_size]定义的实例字段;以一系列编码元素的形式表示。这些字段必须按 field_idx 以升序进行排序。direct_methodsencoded_method[direct_methods_size]定义的直接(staticprivate 或构造函数的任何一个)方法;以一系列编码元素的形式表示。这些方法必须按 method_idx 以升序进行排序。virtual_methodsencoded_method[virtual_methods_size]定义的虚拟(非 staticprivate 或构造函数)方法;以一系列编码元素的形式表示。此列表不得包括继承方法,除非被此项所表示的类覆盖。这些方法必须按 method_idx 以升序进行排序。 虚拟方法的 method_idx 不得与任何直接方法相同。

此处只简单的说一下,需要分别计算出前四个记录数量的字节数和值,然后才能得到后边四个数组的位置。

三:static_values_off

encoded_array 格式

名称格式说明sizeuleb128数组中的元素数量valuesencoded_value[size]采用本部分所指定格式的一系列 size encoded_value 字节序列;依序连接。

encoded_value 编码

(value_arg << 5) \value_typeubyte一种字节,用于表示紧跟后面的 value 及高 3 位中可选澄清参数的类型。请参阅下文,了解各种 value 定义。在大多数情况下,value_arg 会以字节为单位将紧跟后面的 value 的长度编码为 (size - 1);例如,0 表示该值需要 1 个字节;7 表示该值需要 8 个字节;不过,也存在下述例外情况。valueubyte[]用于表示值的字节,不同 value_type 字节的长度不同且采用不同的解译方式;不过一律采用小端字节序。如需了解详情,请参阅下文中的各种值定义。

第一个字节的高三位记录字节的个数,低五位记录value_type

int values = *strtic_value & 0x1f; //计算出类型int lenght = *strtic_value >> 5; //计算出长度

value_type

VALUE_BYTE0x00(无;必须为 0)ubyte[1]有符号的单字节整数值VALUE_SHORT0x02size - 1 (0…1)ubyte[size]有符号的双字节整数值,符号扩展VALUE_CHAR0x03size - 1 (0…1)ubyte[size]无符号的双字节整数值,零扩展VALUE_INT0x04size - 1 (0…3)ubyte[size]有符号的四字节整数值,符号扩展VALUE_LONG0x06size - 1 (0…7)ubyte[size]有符号的八字节整数值,符号扩展VALUE_FLOAT0x10size - 1 (0…3)ubyte[size]四字节位模式,向右零扩展,系统会将其解译为 IEEE754 32 位浮点值VALUE_DOUBLE0x11size - 1 (0…7)ubyte[size]八字节位模式,向右零扩展,系统会将其解译为 IEEE754 64 位浮点值VALUE_METHOD_TYPE0x15size - 1 (0…3)ubyte[size]无符号(零扩展)四字节整数值,会被解译为要编入 proto_ids 区段的索引;表示方法类型值VALUE_METHOD_HANDLE0x16size - 1 (0…3)ubyte[size]无符号(零扩展)四字节整数值,会被解译为要编入 method_handles 区段的索引;表示方法句柄值VALUE_STRING0x17size - 1 (0…3)ubyte[size]无符号(零扩展)四字节整数值,会被解译为要编入 string_ids 区段的索引;表示字符串值VALUE_TYPE0x18size - 1 (0…3)ubyte[size]无符号(零扩展)四字节整数值,会被解译为要编入 type_ids 区段的索引;表示反射类型/类值VALUE_FIELD0x19size - 1 (0…3)ubyte[size]无符号(零扩展)四字节整数值,会被解译为要编入 field_ids 区段的索引;表示反射字段值VALUE_METHOD0x1asize - 1 (0…3)ubyte[size]无符号(零扩展)四字节整数值,会被解译为要编入 method_ids 区段的索引;表示反射方法值VALUE_ENUM0x1bsize - 1 (0…3)ubyte[size]无符号(零扩展)四字节整数值,会被解译为要编入 field_ids 区段的索引;表示枚举类型常量的值VALUE_ARRAY0x1c(无;必须为 0)encoded_array值的数组,采用下文“encoded_array 格式”所指定的格式。value 的大小隐含在编码中。VALUE_ANNOTATION0x1d(无;必须为 0)encoded_annotation子注释,采用下文“encoded_annotation 格式”所指定的格式。value 的大小隐含在编码中。VALUE_NULL0x1e(无;必须为 0)(无)null 引用值VALUE_BOOLEAN0x1f布尔值 (0…1)(无)一位值;0 表示 false1 表示 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和方法句柄没有写),和方法名,字节码没有进行解析。

软件界面很简陋,很单调生成的class_item文件软件我上传了两个,一个动态编译的需要vc的运行库,一个静态编译不需要运行库,根据自己需要下载任意一个即可,代码我是复制出来的,需要的自己研究。
最新回复 (7)
返回