首页
社区
课程
招聘
8
[原创]手搓函数抽取加固(上)
发表于: 2024-7-10 20:12 3634

[原创]手搓函数抽取加固(上)

2024-7-10 20:12
3634

本次研究基于安卓12

基础篇--dex结构解析

工具

对于dex文件结构的的解析,我认为c或者c++语言是比较好的

推荐工具 c语言的编辑工具----我是用的 qt 简单方便

系统:windows 11

验证工具: 010 editor

dex文件大致结构

文件头:header

索引区:stringids,typeids,protoids,fieldids,method_ids

数据区:classdefs,data,linkedata

0x1 header 解析

了解结构体

// 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解读:

  1. magic_[8]:这是一个8字节的数组,用于存储文件的魔数(magic number)。魔数通常用于标识文件的类型或格式,确保读取文件的程序能够识别出这是一个它所能处理的文件。

  2. checksum_:这是一个32位的校验和,用于验证文件内容的完整性。在文件传输或存储过程中,如果文件内容被修改,这个校验和将不再匹配,从而可以检测到文件已被篡改。

  3. signature_[20]:这是一个20字节的签名数组,具体用途可能依赖于文件的实际使用场景。在某些情况下,它可能用于身份验证或完整性检查。

  4. file_size_:整个文件的大小(以字节为单位)。这个信息对于了解文件的整体大小很有帮助,尤其是在需要预先分配内存或进行文件操作时。

  5. header_size_:头部的大小(以字节为单位),或者是头部结束后,下一个部分的起始偏移量。这个信息对于解析文件的其他部分非常关键。

  6. endian_tag_:这个字段可能用于指示文件中数据的字节序(大端或小端)。这对于跨平台的数据交换和解析非常重要。

  7. link_size_link_off_:这两个字段当前标记为未使用(unused),但在某些文件格式中,它们可能用于链接到文件的其他部分或提供额外的元数据。

  8. map_off_:映射列表的偏移量,从data_off_开始计算。映射列表可能包含了文件中各个部分的详细描述或索引。

  9. 字符串ID、类型ID、原型ID、字段ID、方法ID和类定义的相关字段(如string_ids_size_, string_ids_off_, type_ids_size_等):这些字段提供了关于文件中不同类型ID、字符串、类定义等信息的数量和偏移量。这对于解析文件内容和构建程序的内部表示非常重要。

  10. 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");
}

0x2 string_ids解析

接下来解析字符串常量池

从头部获取 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];
}

0x3 type_ids 解析

老规矩先看结构体:

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));
    }

0x4 解析proto ids

观摩结构体

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);

}

0x5 解析fields

解析成员变量

上结构体

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);
}

0x6 解析method_ids

结构体:

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));
        }

    }
}

0x7 解析class

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
};
  1. uint class_idx;
  • 这是一个索引值,指向DEX文件中的type_ids列表。type_ids列表包含了DEX文件中所有引用的类型(类、接口等)的引用。class_idx的值必须是一个指向类类型的索引,不能是数组类型或基本类型的索引。这个字段用于指定当前class_def_item所描述的类的类型。
  1. uint access_flags;
  • 这个字段包含了类的访问标志,如publicfinalstatic等。这些标志决定了类的可见性和行为。访问标志的具体含义在DEX文件格式文档中“access_flags Definitions”部分有详细描述。
  1. uint superclass_idx;
  • 类似于class_idx,这也是一个索引值,指向type_ids列表。它指定了当前类的父类类型。如果当前类没有父类(比如java.lang.Object),则此字段的值为特定的索引值(通常是type_ids列表的第一个元素,即java.lang.Object)。
  1. uint interface_off;
  • 这是一个偏移量,指向DEX文件中的type_list结构,该结构包含了当前类实现的所有接口的引用。如果当前类没有实现任何接口,则此字段的值为0。
  1. uint source_file_idx;
  • 这是一个索引值,指向DEX文件中的string_ids列表,表示定义当前类的源代码文件的名称。如果源代码文件信息缺失,则此字段的值被设置为NO_INDEX(通常是0xffffffff)。
  1. uint annotations_off;
  • 这是一个偏移量,指向DEX文件中annotations_directory_item结构的位置,该结构包含了当前类的注解信息。如果当前类没有注解,则此字段的值为0。
  1. uint class_data_off;
  • 这是一个偏移量,指向DEX文件中class_data_item结构的位置,该结构包含了当前类的详细信息,如字段(field)、方法(method)、方法的执行代码(code)等。这是理解类内部结构和行为的关键部分。如果当前类不包含这些方法级别的信息(例如,它是一个标记为abstract的接口),则此字段的值为0。
  1. 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 被逆天而行编辑 ,原因:
上传的附件:
收藏
免费 8
支持
分享
赞赏记录
参与人
雪币
留言
时间
马先越
非常支持你的观点!
2025-2-26 19:45
Shangwendada
你的帖子非常有用,感谢分享!
2025-2-24 14:19
mb_uwafaabk
你的分享对大家帮助很大,非常感谢!
2025-2-10 15:40
东方玻璃
你的帖子非常有用,感谢分享!
2025-1-6 16:14
sinker_
非常支持你的观点!
2024-9-29 04:41
zhuzhu_biu
你的帖子非常有用,感谢分享!
2024-9-26 14:18
安卓逆向test
谢谢你的细致分析,受益匪浅!
2024-7-11 19:16
你瞒我瞒
为你点赞~
2024-7-11 15:03
最新回复 (1)
雪    币: 2613
活跃值: (11170)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
TQL
2024-7-11 15:12
0
游客
登录 | 注册 方可回帖
返回

账号登录
验证码登录

忘记密码?
没有账号?立即免费注册