本次研究基于安卓12
对于dex文件结构的的解析,我认为c或者c++语言是比较好的
推荐工具 c语言的编辑工具----我是用的 qt 简单方便
系统:windows 11
验证工具: 010 editor
文件头:header
索引区:stringids,typeids,protoids,fieldids,method_ids
数据区:classdefs,data,linkedata
了解结构体
// Raw header_item.
struct Header {
uint8_t magic_[8] = {};
uint32_t checksum_ = 0; // See also location_checksum_
uint8_t signature_[20] = {};
uint32_t file_size_ = 0; // size of entire file
uint32_t header_size_ = 0; // offset to start of next section
uint32_t endian_tag_ = 0;
uint32_t link_size_ = 0; // unused
uint32_t link_off_ = 0; // unused
uint32_t map_off_ = 0; // map list offset from data_off_
uint32_t string_ids_size_ = 0; // number of StringIds
uint32_t string_ids_off_ = 0; // file offset of StringIds array
uint32_t type_ids_size_ = 0; // number of TypeIds, we don't support more than 65535
uint32_t type_ids_off_ = 0; // file offset of TypeIds array
uint32_t proto_ids_size_ = 0; // number of ProtoIds, we don't support more than 65535
uint32_t proto_ids_off_ = 0; // file offset of ProtoIds array
// file offset of data section
// Decode the dex magic version
// uint32_t GetVersion() const;
};
头部结构体AI解读:
magic_[8]
:这是一个8字节的数组,用于存储文件的魔数(magic number)。魔数通常用于标识文件的类型或格式,确保读取文件的程序能够识别出这是一个它所能处理的文件。
checksum_
:这是一个32位的校验和,用于验证文件内容的完整性。在文件传输或存储过程中,如果文件内容被修改,这个校验和将不再匹配,从而可以检测到文件已被篡改。
signature_[20]
:这是一个20字节的签名数组,具体用途可能依赖于文件的实际使用场景。在某些情况下,它可能用于身份验证或完整性检查。
file_size_
:整个文件的大小(以字节为单位)。这个信息对于了解文件的整体大小很有帮助,尤其是在需要预先分配内存或进行文件操作时。
header_size_
:头部的大小(以字节为单位),或者是头部结束后,下一个部分的起始偏移量。这个信息对于解析文件的其他部分非常关键。
endian_tag_
:这个字段可能用于指示文件中数据的字节序(大端或小端)。这对于跨平台的数据交换和解析非常重要。
link_size_
和 link_off_
:这两个字段当前标记为未使用(unused),但在某些文件格式中,它们可能用于链接到文件的其他部分或提供额外的元数据。
map_off_
:映射列表的偏移量,从data_off_
开始计算。映射列表可能包含了文件中各个部分的详细描述或索引。
字符串ID、类型ID、原型ID、字段ID、方法ID和类定义的相关字段(如string_ids_size_
, string_ids_off_
, type_ids_size_
等):这些字段提供了关于文件中不同类型ID、字符串、类定义等信息的数量和偏移量。这对于解析文件内容和构建程序的内部表示非常重要。
data_size_
和 data_off_
:分别表示数据部分的大小和偏移量。数据部分可能包含了文件的主要执行内容或数据。
解析代码:
int main()
{
FILE *fp = fopen("C:\\Users\\Administrator\\Desktop\\dex\\classes4.dex","rb");
if(fp == NULL){
log("失败! error! fopen");
}{
log("fopen success!");
}
Header *header =(Header *) malloc(sizeof(Header));
fread((void *)header,sizeof(char),sizeof(Header),fp);
showHeader(header);
return 0;
}
void showHeader(Header *header){
printf("******************print begin*****************\n");
printf("magic: \t\t %s \n",header->magic_);
printf("checksum: \t\t %lx\n",header->checksum_);
printf("file size: \t\t %x\n",header->file_size_);
printf("head size: \t\t %x\n",header->header_size_);
printf("endian tag: \t\t %x\n",header->endian_tag_);
printf("link size: \t\t %x\n",header->link_size_);
printf("link off: \t\t %x\n",header->link_off_);
printf("map off: \t\t %x\n",header->link_off_);
printf("string ids size: \t %x\n",header->string_ids_size_);
printf("string ids off: \t %x\n",header->string_ids_off_);
printf("type ids size : \t %x\n",header->type_ids_size_);
printf("type ids off:\t \t %x\n",header->type_ids_off_);
printf("proto ids size: \t %x\n",header->proto_ids_size_);
printf("proto ids off: \t\t %x\n",header->proto_ids_off_);
printf("field ida size: \t %x\n",header->field_ids_size_);
printf("field ida off:\t \t %x\n",header->field_ids_off_);
printf("method ids size: \t %x\n",header->method_ids_size_);
printf("method ids off: \t %x\n",header->method_ids_off_);
printf("class defs size: \t %x\n",header->class_defs_size_);
printf("class defs off: \t %x\n",header->class_defs_off_);
printf("data size:\t \t %x\n",header->data_size_);
printf("data off: \t\t %x\n",header->data_off_);
printf("*****************header over print************\n");
}
接下来解析字符串常量池
从头部获取 stringidsoff 与 size
即可解析 先看这个结构体吧:
struct DexStringId {
uint32_t stringDataOff; /* 字符串数据偏移 */
};
这个结构体告诉你字符串的地址
DexStringId * string_ids(Header *header);
FILE *fp;
DexStringId * strings;
char * buffer;
Header *header;
void init_string(){
strings = string_ids(header);
buffer = (char *)malloc(header->file_size_);
fseek(fp,0,0);
fread((void*)buffer,sizeof(char),header->file_size_,fp);
}
char * getString(int index){
return &buffer[strings[index].stringDataOff+1];
}
老规矩先看结构体:
struct DexTypeId {
uint32_t descriptorIdx; /* 指向DexStringId列表的索引 */
};
结构体指向的数据 是字符串常量池的索引
char * gettypestring(int index){
return getString(types[index].descriptorIdx);
}
void init_type(){
types =(DexTypeId *) malloc(header->type_ids_size_*sizeof(DexTypeId));
fseek(fp,header->type_ids_off_,0);
fread((void *)types,sizeof(DexTypeId),header->type_ids_size_,fp);
}
打印全部type的代码:
//初始化
init_type();
//打印所有types
for(int i = 0;itype_ids_size_;i++){
printf("%s\n",gettypestring(i));
}
观摩结构体
struct DexProtoId {
uint32_t shortyIdx; /* 指向DexStringId列表的索引 */
uint32_t returnTypeIdx; /* 指向DexTypeId列表的索引 */
uint32_t parametersOff; /* 指向DexTypeList的偏移 */
};
struct DexTypeList {
uint32_t size; /* 接下来DexTypeItem的个数 */
DexTypeItem list[1]; /* DexTypeItem结构 */
};
struct DexTypeItem {
uint16_t typeIdx; /* 指向DexTypeId列表的索引 */
};
解析protos
void show_protos(){
for(int i=0;iproto_ids_size_;i++){
printf("%s \n",getString(protos[i].shortyIdx));
printf("%s \n",gettypestring(protos[i].returnTypeIdx));
if(protos[i].parametersOff != 0){
DexTypeList *list =(DexTypeList *) (buffer+protos[i].parametersOff) ;
for(int a=0;asize;a++){
printf("%s \n",gettypestring(list->list[a].typeIdx));
}
}
}
}
void init_protos(){
//初始化DexProtoId
protos=(DexProtoId *) malloc(header->proto_ids_size_*sizeof(DexProtoId));
fseek(fp,header->proto_ids_off_,0);
fread((void *)protos,sizeof(DexProtoId),header->proto_ids_size_,fp);
}
解析成员变量
上结构体
struct DexFieldId {
uint16_t classIdx; /* 类的类型,指向DexTypeId列表的索引 */
uint16_t typeIdx; /* 字段类型,指向DexTypeId列表的索引 */
uint32_t nameIdx; /* 字段名,指向DexStringId列表的索引 */
};
解析打印
void show_fields(){
for(int i=0;ifield_ids_size_;i++){
printf("所在类:%s,成员类型 %s,变量名 %s \n",gettypestring(fields[i].classIdx),gettypestring(fields[i].typeIdx),getString(fields[i].nameIdx));
}
}
void init_fields(){
fields = (DexFieldId *)malloc (header->field_ids_size_*sizeof(DexFieldId));
fseek(fp,header->field_ids_off_,0);
fread((void*)fields,sizeof(DexFieldId),header->field_ids_size_,fp);
}
结构体:
struct DexMethodId {
uint16_t classIdx; /* 类的类型,指向DexTypeId列表的索引 */
uint16_t protoIdx; /* 声明类型,指向DexProtoId列表的索引 */
uint32_t nameIdx; /* 方法名,指向DexStringId列表的索引 */
};
解析代码:
void show_methodids(){
for(int i=0;imethod_ids_size_;i++){
printf("%s \n",gettypestring(methodids[i].classIdx));
getprotosstring(methodids[i].protoIdx);
printf("%s \n",getString(methodids[i].nameIdx));
}
}
void init_methodids(){
methodids=(DexMethodId *)malloc(header->method_ids_size_*sizeof(DexMethodId));
fseek(fp,header->method_ids_off_,0);
fread((void *)methodids,sizeof(DexMethodId),header->method_ids_size_,fp);
}
void getprotosstring(int i){
printf("%s ",getString(protos[i].shortyIdx));
printf("%s ",gettypestring(protos[i].returnTypeIdx));
if(protos[i].parametersOff != 0){
DexTypeList *list =(DexTypeList *) (buffer+protos[i].parametersOff) ;
for(int a=0;asize;a++){
printf("%s ",gettypestring(list->list[a].typeIdx));
}
}
}
struct class_def_item
{
uint32_t class_idx; //描述具体的 class 类型,值是 type_ids 的一个 index 。值必须是一个 class 类型,不能是数组类型或者基本类型。
uint32_t access_flags; //描述 class 的访问类型,诸如 public , final , static 等。在 dex-format.html 里 “access_flags Definitions” 有具体的描述
uint32_t superclass_idx; //描述 supperclass 的类型,值的形式跟 class_idx 一样
uint32_t interface_off; //值为偏移地址,指向 class 的 interfaces,被指向的数据结构为 type_list 。class 若没有 interfaces 值为 0
uint32_t source_file_idx; //表示源代码文件的信息,值是 string_ids 的一个 index。若此项信息缺失,此项值赋值为 NO_INDEX=0xffff ffff
uint32_t annotations_off; //值是一个偏移地址,指向的内容是该 class 的注释,位置在 data 区,格式为 annotations_direcotry_item。若没有此项内容,值为 0
uint32_t class_data_off; //值是一个偏移地址,指向的内容是该 class 的使用到的数据,位置在 data 区,格式为 class_data_item。若没有此项内容值为 0。该结构里有很多内容,详细描述该 class 的 field、method, method 里的执行代码等信息,后面会介绍 class_data_item
uint32_t static_value_off; //值是一个偏移地址 ,指向 data 区里的一个列表 (list),格式为 encoded_array_item。若没有此项内容值为 0
};
uint class_idx;
- 这是一个索引值,指向DEX文件中的
type_ids
列表。type_ids
列表包含了DEX文件中所有引用的类型(类、接口等)的引用。class_idx
的值必须是一个指向类类型的索引,不能是数组类型或基本类型的索引。这个字段用于指定当前class_def_item
所描述的类的类型。
uint access_flags;
- 这个字段包含了类的访问标志,如
public
、final
、static
等。这些标志决定了类的可见性和行为。访问标志的具体含义在DEX文件格式文档中“access_flags Definitions”部分有详细描述。
uint superclass_idx;
- 类似于
class_idx
,这也是一个索引值,指向type_ids
列表。它指定了当前类的父类类型。如果当前类没有父类(比如java.lang.Object
),则此字段的值为特定的索引值(通常是type_ids
列表的第一个元素,即java.lang.Object
)。
uint interface_off;
- 这是一个偏移量,指向DEX文件中的
type_list
结构,该结构包含了当前类实现的所有接口的引用。如果当前类没有实现任何接口,则此字段的值为0。
uint source_file_idx;
- 这是一个索引值,指向DEX文件中的
string_ids
列表,表示定义当前类的源代码文件的名称。如果源代码文件信息缺失,则此字段的值被设置为NO_INDEX
(通常是0xffffffff
)。
uint annotations_off;
- 这是一个偏移量,指向DEX文件中
annotations_directory_item
结构的位置,该结构包含了当前类的注解信息。如果当前类没有注解,则此字段的值为0。
uint class_data_off;
- 这是一个偏移量,指向DEX文件中
class_data_item
结构的位置,该结构包含了当前类的详细信息,如字段(field)、方法(method)、方法的执行代码(code)等。这是理解类内部结构和行为的关键部分。如果当前类不包含这些方法级别的信息(例如,它是一个标记为abstract
的接口),则此字段的值为0。
uint static_values_off;
- 这是一个偏移量,指向DEX文件中
encoded_array_item
结构的位置,该结构包含了当前类的静态字段的初始值。如果当前类没有静态字段或没有为静态字段提供初始值,则此字段的值为0。
在我们重点关注class_data_off
指向的结构吧
先看看 class_data_off
指向的结构体吧
struct class_data_item
{
uleb128 static_fields_size; //静态成员变量的个数
uleb128 instance_fields_size; //实例成员变量个数
uleb128 direct_methods_size; //直接函数个数
uleb128 virtual_methods_size; // 虚函数个数
encoded_field static_fields[static_fields_size];
encoded_field instance_fields[instance_fields_size];
encoded_method direct_methods[direct_methods_size];
encoded_method virtual_methods[virtual_methods_size];
}
这里使用了uleb128的方式存储 那我们在使用结构体直接覆盖得到数据的方式就不在适用了,那这个结构体不导入进项目!
那自己设计一个函数 要求获取读取的数据值,还有在内存中的大小 就可以比较方便的读取了
int read_uleb128(uint8_t * addr,int *size){
int result= 0;
*size =0;
result = addr[0] & 0x7f;
*size++;
if(addr[0] & 0x80 ){
result = result+(addr[1] & 0x7f)<<7;
*size++;
if((addr[1]& 0x80)){
result = result+(addr[2] & 0x7f)<<14;
*size++;
if((addr[2]& 0x80)){
result = result+(addr[3] & 0x7f)<<21;
*size++;
}
}
}
return result;
}
有了这个工具就可以更好的解析了!
我们通过uleb128读取获取了
staticfieldssize; //静态成员变量的个数
instancefieldssize; //实例成员变量个数
directmethodssize; //直接函数个数
virtualmethodssize; // 虚函数个数
for(int i=0;iclass_defs_size_;i++){
printf("data_off %d \n",classitems[i].class_data_off);
void *addr = (void *)(&buffer[classitems[i].class_data_off]);
//printf(" %2x %2x %2x %2x \n",(int8_t)buffer[classitems[i].class_data_off],(int8_t)buffer[classitems[i].class_data_off+1],(int8_t)buffer[classitems[i].class_data_off+2],(int8_t)buffer[classitems[i].class_data_off+3]);
int size=0;
int static_fields_size = read_uleb128((uint8_t *)addr,&size);//静态成员变量的个数
addr+=size;
int instance_fields_size = read_uleb128((uint8_t *)addr,&size);//实例成员变量个数
addr+=size;
int direct_methods_size = read_uleb128((uint8_t *)addr,&size); //直接函数个数
addr+=size;
int virtual_methods_size = read_uleb128((uint8_t *)addr,&size);// 虚函数个数
addr+=size;
printf("静态成员变量:%d ,实例成员变量个数%d,直接函数个数%d.虚函数个数%d\n ",static_fields_size,instance_fields_size,direct_methods_size,virtual_methods_size);
}
void init_class_def_item(){
classitems = (class_def_item *)malloc(header->class_defs_size_*sizeof(class_def_item));
fseek(fp,header->class_defs_off_,0);
fread((void *)classitems,sizeof(class_def_item),header->class_defs_size_,fp);
}
然后按顺序解析
field 与 method
这里的结构体也是uleb128
struct DexField{
uleb128 fieldIdx;//指向DexFieldId索引
uleb128 axxessFlags;//字段访问标志
}
struct DexMethod{
uleb128 methodIdx;//指向DexMethodId索引
uleb128 accessFlags;//方法访问标志
uleb128 codeOff;//指向DexCode结构偏移
}
继续在原来的方法上解析
放解析代码:
void jx_class_data(){
for(int i=0;iclass_defs_size_;i++){
printf("data_off %d \n",classitems[i].class_data_off);
void *addr = (void *)(&buffer[classitems[i].class_data_off]);
//printf(" %2x %2x %2x %2x \n",(int8_t)buffer[classitems[i].class_data_off],(int8_t)buffer[classitems[i].class_data_off+1],(int8_t)buffer[classitems[i].class_data_off+2],(int8_t)buffer[classitems[i].class_data_off+3]);
int size=0;
int static_fields_size = read_uleb128((uint8_t *)addr,&size);//静态成员变量的个数
addr+=size;
int instance_fields_size = read_uleb128((uint8_t *)addr,&size);//实例成员变量个数
addr+=size;
int direct_methods_size = read_uleb128((uint8_t *)addr,&size); //直接函数个数
addr+=size;
int virtual_methods_size = read_uleb128((uint8_t *)addr,&size);// 虚函数个数
addr+=size;
printf("静态成员变量:%d ,实例成员变量个数%d,直接函数个数%d.虚函数个数%d\n ",static_fields_size,instance_fields_size,direct_methods_size,virtual_methods_size);
//开始遍历static_fields_size
for(int i=0;iclass_defs_size_*sizeof(class_def_item));
fseek(fp,header->class_defs_off_,0);
fread((void *)classitems,sizeof(class_def_item),header->class_defs_size_,fp);
}
这里我们就把方法的offcode找到了
接下来解析 code item
struct code_item
{
ushort registers_size; //本段代码使用到的寄存器数目
ushort ins_size; //method 传入参数的数目
ushort outs_size; //本段代码调用其它 method 时需要的参数个数
ushort tries_size;//try_item 结构的个数
uint debug_info_off;//偏移地址,指向本段代码的 debug 信息存放位置,是一个 debug_info_item 结构
uint insns_size;
//ushort insns [insns_size];
//ushort paddding; // optional
//try_item tries [tyies_size]; // optional
//encoded_catch_handler_list handlers; // optional
}
void jx_codeitem(void * addr)
{
code_item * code = (code_item *)addr;
printf("寄存器数量:%d 参数数量:%d 需要的外部参数:%d try个数%d debug偏移%d 代码大小%d\n",code->registers_size,code->ins_size,code->outs_size,code->tries_size,code->debug_info_off,code->insns_size);
}
在insns_size后面就是数组 code
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课
最后于 2024-7-10 20:25
被逆天而行编辑
,原因: