安全矩阵

 找回密码
 立即注册
搜索
查看: 2374|回复: 0

网络安全编程:开发Dex文件格式解析工具

[复制链接]

855

主题

862

帖子

2940

积分

金牌会员

Rank: 6Rank: 6

积分
2940
发表于 2021-7-20 09:33:38 | 显示全部楼层 |阅读模式
原文链接:网络安全编程:开发Dex文件格式解析工具
解析Dex文件的工作应该是自动化的,由工具去完成。本文通过VS2012来新建一个控制台的工程,然后完成一个Dex文件的解析工具。

对于解析Dex文件而言,需要准备一些头文件,这些头文件都可以从安卓系统的源代码中获取到,首先要有common.h、uleb128.h,因为common.h中存放了相应的数据类型(这里所说的数据类型是u1、u2),uleb128.h中存放了读取uleb128数据类型的相关函数。接着要准备的是DexFile.h、DexFile.cpp、DexClass.h和DexClass.cpp 4个文件。

为了使用方便,将这4个文件中的代码都复制到了DexParse.h中,为了能够编译通过,在函数的定义部分进行了删除,或者对某些函数的参数进行了修改,对函数体的一些内容也进行了删减。

在自己准备相关内容时,可以在编译时通过报错信息自己进行修改。在这里,将DexParse.h文件添加到了新建的控制台工程当中。

解析Dex文件也按照Dex的格式逐步进行即可,当然在解析文件前请不要忘记,对文件的操作首先是要打开文件。

1. 打开与关闭文件

打开与关闭文件的代码如下:
  1. int _tmain(int argc, _TCHAR* argv[])
  2. {
  3. HANDLE hFile = CreateFile(DEX_FILE, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_
  4. EXISTING, FILE_ACTION_ADDED, NULL);
  5. HANDLE hMap = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL);
  6. LPVOID hView = MapViewOfFile(hMap, FILE_MAP_READ, 0, 0, 0);
  7. UnmapViewOfFile(hView);
  8. CloseHandle(hMap);
  9. CloseHandle(hFile);
  10. return 0;
  11. }
复制代码

在上面的代码中,首先要打开文件,然后创建文件映射,在MapViewOfFile函数和UnmapViewOfFile函数之间,来添加关于解析DEX文件的代码。

2. Dex文件头部

在解析Dex文件时,需要对Dex文件的头部进行解析,解析Dex文件的头部时,安卓系统提供了一个函数,函数定义如下:
DexFile* dexFileParse(const u1* data, size_t length, int flags);
该函数有3个参数,第一个参数是Dex文件数据的起始位置,第二个参数是Dex文件的长度,第三个参数是用来告诉dexFileParse函数是否需要进行验证的。对于目前阶段而言,我们不需要第三个参数,因此将该函数进行删减后的代码如下:
  1. DexFile* dexFileParse(const u1* data, size_t length)
  2. {
  3.   DexFile* pDexFile = NULL;
  4.   const DexHeader* pHeader;
  5.   const u1* magic;
  6.   int result = -1;
  7.   pDexFile = (DexFile*) malloc(sizeof(DexFile));
  8.   if (pDexFile == NULL)
  9.   goto bail;
  10.   memset(pDexFile, 0, sizeof(DexFile));
  11.   /*
  12.   * 去掉优化的头部
  13.   */
  14.   if (memcmp(data, DEX_OPT_MAGIC, 4) == 0) {
  15.     magic = data;
  16.     if (memcmp(magic+4, DEX_OPT_MAGIC_VERS, 4) != 0) {
  17.     goto bail;
  18.     }
  19.     /* 忽略可选的头部和在这里追加的数据
  20.     data += pDexFile->pOptHeader->dexOffset;
  21.     length -= pDexFile->pOptHeader->dexOffset;
  22.     if (pDexFile->pOptHeader->dexLength > length) {
  23.       goto bail;
  24.     }
  25.     length = pDexFile->pOptHeader->dexLength;
  26.   }
  27.   dexFileSetupBasicPointers(pDexFile, data);
  28.   pHeader = pDexFile->pHeader;
  29.   /*
  30.   * Success!
  31.   */
  32.   result = 0;
  33.   bail:
  34.   if (result != 0 && pDexFile != NULL) {
  35.     dexFileFree(pDexFile);
  36.     pDexFile = NULL;
  37.   }
  38.   return pDexFile;
  39. }
复制代码
该函数首先判断Dex文件的合法性,然后将Dex文件的一些基础的指针进行了初始化,在dexFileParse函数中调用了另外一个函数,即dexFileSetupBasicPointers函数,该函数的函数体如下:
  1. void dexFileSetupBasicPointers(DexFile* pDexFile, const u1* data) {
  2. DexHeader *pHeader = (DexHeader*) data;
  3. pDexFile->baseAddr = data;
  4. pDexFile->pHeader = pHeader;
  5. pDexFile->pStringIds = (const DexStringId*) (data + pHeader->stringIdsOff);
  6. pDexFile->pTypeIds = (const DexTypeId*) (data + pHeader->typeIdsOff);
  7. pDexFile->pFieldIds = (const DexFieldId*) (data + pHeader->fieldIdsOff);
  8. pDexFile->pMethodIds = (const DexMethodId*) (data + pHeader->methodIdsOff);
  9. pDexFile->pProtoIds = (const DexProtoId*) (data + pHeader->protoIdsOff);
  10. pDexFile->pClassDefs = (const DexClassDef*) (data + pHeader->classDefsOff);
  11. pDexFile->pLinkData = (const DexLink*) (data + pHeader->linkOff);
  12. }
复制代码

从dexFileSetupBasicPointers函数中可以看出,对于其他各个结构体的索引及数量已经在这里全部读取出来,在后面具体解析其他数据结构时,它会很方便地被使用。
在dexFileParse中使用malloc函数申请了一块空间,这块空间在解析完成以后需要手动地进行释放,在安卓系统的源码中也定义了一个函数以方便使用,函数名是dexFileFree,函数的定义如下:
  1. void dexFileFree(DexFile* pDexFile)
  2. {
  3. if (pDexFile == NULL)
  4. return;
  5. free(pDexFile);
  6. }
复制代码

很简单的函数,判断指针是否为NULL,不为NULL则直接调用free函数释放空间。
有了上面的代码,那么就可以完成解析Dex文件的第一步了,具体代码如下:
  1. DWORD dwSize = GetFileSize(hFile, NULL);
  2. DexFile *pDexFile = dexFileParse((const u1 *)hView, (size_t)dwSize);
  3. dexFileFree(pDexFile);
复制代码
这样就得到了指向DexFile结构体的指针pDexFile,DexFile结构体的定义如下:
  1. struct DexFile {
  2. /* 直接映射的"opt"头部 */
  3. const DexOptHeader* pOptHeader;
  4. /* 指向基础 DEX 中直接映射的结构体和数组的指针 */
  5. const DexHeader* pHeader;
  6. const DexStringId* pStringIds;
  7. const DexTypeId* pTypeIds;
  8. const DexFieldId* pFieldIds;
  9. const DexMethodId* pMethodIds;
  10. const DexProtoId* pProtoIds;
  11. const DexClassDef* pClassDefs;
  12. const DexLink* pLinkData;
  13. /*
  14. * 这些不映射到"auxillary"部分,可能不包含在该文件中
  15. */
  16. const DexClassLookup* pClassLookup;
  17. const void* pRegisterMapPool; // RegisterMapClassPool
  18. /* 指向 DEX 文件开始的指针 */
  19. const u1* baseAddr;
  20. /* 跟踪辅助结构的内存开销 */
  21. int overhead;
  22. /* 与 DEX 相关联的其他数据结构 */
  23. //void* auxData;
  24. };
复制代码

对于我们而言,在写程序时只需要关心结构体中DexHeader到DexClassDef之间的字段即可。
之后解析的代码中都会使用到返回的pDexFile指针,因此之后缩写的代码都必须写在调用dexFileFree函数之前。3. 解析DexMapList相关数据
DexMapList是在DexHeader的mapOff给出的,不过在程序中不用直接从DexHeader结构体中去取,因为在安卓系统中已经给出了相关的函数,函数代码如下:
  1. DEX_INLINE const DexMapList* dexGetMap(const DexFile* pDexFile) {
  2.   u4 mapOff = pDexFile->pHeader->mapOff;
  3.   if (mapOff == 0) {
  4.     return NULL;
  5.   } else {
  6.     return (const DexMapList*) (pDexFile->baseAddr + mapOff);
  7.   }
  8. }
复制代码

dexGetMap函数通过前面返回的DexFile指针来定位DexMapList在文件中的偏移位置。
在实际的代码中,我们需要将DEX_INLINE宏删掉,或者按照安卓系统的源代码中的定义去定义一下。
通过dexGetMap函数获得了DexMapList的指针,那么接下来就可以对DexMapList进行遍历了,这里定义一个自定义函数来进行遍历,代码如下:
  1. void PrintDexMapList(DexFile *pDexFile)
  2. {
  3.   const DexMapList *pDexMapList = dexGetMap(pDexFile);
  4.   printf("DexMapList:\r\n");
  5.   printf("TypeDesc\t\t type unused size offset\r\n");
  6.   for ( u4 i = 0; i < pDexMapList->size; i ++ )
  7.   {
  8.     switch (pDexMapList->list[i].type)
  9.     {
  10.       case 0x0000:printf("kDexTypeHeaderItem");break;
  11.       case 0x0001:printf("kDexTypeStringIdItem");break;
  12.       case 0x0002:printf("kDexTypeTypeIdItem");break;
  13.       case 0x0003:printf("kDexTypeProtoIdItem");break;
  14.       case 0x0004:printf("kDexTypeFieldIdItem");break;
  15.       case 0x0005:printf("kDexTypeMethodIdItem");break;
  16.       case 0x0006:printf("kDexTypeClassDefItem");break;
  17.       case 0x1000:printf("kDexTypeMapList");break;
  18.       case 0x1001:printf("kDexTypeTypeList");break;
  19.       case 0x1002:printf("kDexTypeAnnotationSetRefList");break;
  20.       case 0x1003:printf("kDexTypeAnnotationSetItem");break;
  21.       case 0x2000:printf("kDexTypeClassDataItem");break;
  22.       case 0x2001:printf("kDexTypeCodeItem");break;
  23.       case 0x2002:printf("kDexTypeStringDataItem");break;
  24.       case 0x2003:printf("kDexTypeDebugInfoItem");break;
  25.       case 0x2004:printf("kDexTypeAnnotationItem");break;
  26.       case 0x2005:printf("kDexTypeEncodedArrayItem");break;
  27.       case 0x2006:printf("kDexTypeAnnotationsDirectoryItem");break;
  28.     }
  29.     printf("\t %04X %04X %08X %08X\r\n",
  30.       pDexMapList->list[i].type,
  31.       pDexMapList->list[i].unused,
  32.       pDexMapList->list[i].size,
  33.       pDexMapList->list[i].offset);
  34.   }
  35. }
复制代码

在main函数中调用该函数时,只要将前面得到的指向DexFile结构体的指针传给该函数即可。查看该部分解析的输出,如图1所示。

图1  DexMapList解析后的输出
4. 解析StringIds相关数据
对于StringIds的解析也非常简单,这里直接给出一个自定义函数,代码如下:
  1. void PrintStringIds(DexFile *pDexFile)
  2. {
  3.   printf("DexStringIds:\r\n");
  4.   for ( u4 i = 0; i < pDexFile->pHeader->stringIdsSize; i ++ )
  5.   {
  6.     printf("%d.%s \r\n", i, dexStringById(pDexFile, i));
  7.   }
  8. }
复制代码


在该自定义函数中,它调用了dexStringById函数,也就是通过索引值来得到字符串,该函数的定义如下:
  1. /* 通过特定的 string_id index 返回 UIF-8 编码的字符串 */
  2. DEX_INLINE const char* dexStringById(const DexFile* pDexFile, u4 idx) {
  3. const DexStringId* pStringId = dexGetStringId(pDexFile, idx);
  4. return dexGetStringData(pDexFile, pStringId);
  5. }
复制代码


/* 通过特定的 string_id index 返回 UIF-8 编码的字符串 */DEX_INLINE const char* dexStringById(const DexFile* pDexFile, u4 idx) { const DexStringId* pStringId = dexGetStringId(pDexFile, idx); return dexGetStringData(pDexFile, pStringId);}
在dexStringById函数中又调用了两个其他的函数,分别是dexGetStringId和dexGetStringData,大家可以自行查看。
在main函数中调用笔者的自定义函数,输出如图2所示。

图2  StringIds解析后的输出
5. 解析TypeIds相关数据
解析TypeIds也是非常简单的,直接上代码即可,代码如下:
  1. void PrintTypeIds(DexFile *pDexFile)
  2. {
  3.   printf("DexTypeIds:\r\n");
  4.   for ( u4 i = 0; i < pDexFile->pHeader->typeIdsSize; i ++ )
  5.   {
  6.     printf("%d %s \r\n", i, dexStringByTypeIdx(pDexFile, i));
  7.   }
  8. }
复制代码


代码中调用了一个关键的函数dexStringByTypeIdx,该函数也是安卓系统源码中提供的函数,该函数的实现如下:
  1. /*
  2. * 获取与指定的类型索引相关联的描述符字符串
  3. * 调用者不能释放返回的字符串
  4. */
  5. DEX_INLINE const char* dexStringByTypeIdx(const DexFile* pDexFile, u4 idx) {
  6. const DexTypeId* typeId = dexGetTypeId(pDexFile, idx);
  7. return dexStringById(pDexFile, typeId->descriptorIdx);
  8. }
复制代码


在dexStringByTypeIdx函数中调用了dexGetTypeId和dexStringById两个函数,请大家自行在源码中查看。
在main函数中调用自定义函数,输出如图3所示。

图3  TypeIds解析后的输出
6. 解析ProtoIds相关数据
Proto是方法的原型或方法的声明,也就是提供了方法的返回值类型、参数个数,以及参数的类型。对于ProtoIds的解析,首先是对原始数据的解析,然后再将它简单地还原为可以直接阅读的方法原型。
先来看一下代码,代码如下:
  1. void PrintProtoIds(DexFile *pDexFile)
  2. {
  3.   printf("DexProtoIds:\r\n");
  4.   // 对数据的解析
  5.   for ( u4 i = 0; i < pDexFile->pHeader->protoIdsSize; i ++ )
  6.   {
  7.     const DexProtoId *pDexProtoId = dexGetProtoId(pDexFile, i);
  8.     // 输出原始数据
  9.     printf("%08X %08X %08X \r\n", pDexProtoId->shortyIdx, pDexProtoId->returnTy
  10.       peIdx, pDexProtoId->parametersOff);
  11.     // 输出对应的 TypeId
  12.     printf("%s %s\r\n",
  13.       dexStringById(pDexFile, pDexProtoId->shortyIdx),
  14.       dexStringByTypeIdx(pDexFile, pDexProtoId->returnTypeIdx));
  15.     // 获得参数列表
  16.     const DexTypeList *pDexTypeList = dexGetProtoParameters(pDexFile, pDexProtoId);
  17.     u4 num = pDexTypeList != NULL ? pDexTypeList->size : 0;
  18.     // 输出参数
  19.     for ( u4 j = 0; j < num; j ++ )
  20.     {
  21.       printf("%s ", dexStringByTypeIdx(pDexFile, pDexTypeList->list[j].typeIdx));
  22.     }
  23.     printf("\r\n");
  24.   }
  25.   printf("\r\n");
  26.   // 对解析数据的简单还原
  27.   for ( u4 i = 0; i < pDexFile->pHeader->protoIdsSize; i ++ )
  28.   {
  29.     const DexProtoId *pDexProtoId = dexGetProtoId(pDexFile, i);
  30.     printf("%s", dexStringByTypeIdx(pDexFile, pDexProtoId->returnTypeIdx));
  31.     printf("(");
  32.     // 获得参数列表
  33.     const DexTypeList *pDexTypeList = dexGetProtoParameters(pDexFile, pDexProtoId);
  34.     u4 num = pDexTypeList != NULL ? pDexTypeList->size : 0;
  35.     // 输出参数
  36.     for ( u4 j = 0; j < num; j ++ )
  37.     {
  38.       printf("%s\b, ", dexStringByTypeIdx(pDexFile, pDexTypeList->list[j].typeIdx));
  39.     }
  40.     if ( num == 0 )
  41.     {
  42.       printf(");\r\n");
  43.     }
  44.     else
  45.     {
  46.       printf("\b\b);\r\n");
  47.     }
  48.   }
  49. }
复制代码


在该自定义函数中有两个for循环,其内容基本一致。第一个循环完成了数据的解析,第二个循环是将数据简单地解析成了方法的原型。
这里只对第一个for循环进行说明。ProtoIds是方法的原型,看一下DexProtoId的定义,定义如下:
  1. /*
  2. * Direct-mapped "proto_id_item".
  3. */
  4. struct DexProtoId {
  5. u4 shortyIdx; /* index into stringIds for shorty descriptor */
  6. u4 returnTypeIdx; /* index into typeIds list for return type */
  7. u4 parametersOff; /* file offset to type_list for parameter types */
  8. };
复制代码


第一个字段是方法原型的短描述,第二个字段是方法原型的返回值,第三个字段是指向参数列表的。因此,可以看到,在两个for循环中,仍然嵌套着一个for循环,外层的循环是用来解析方法原型的,内层的循环是用来解析方法原型中的参数的。
首先,通过dexGetProtoId函数来获得ProtoIds,然后通过dexGetProtoParameters函数来得到相应ProtoIds的参数。
在main函数中调用自定义函数,输出如图4所示。

图4  ProtoIds解析后的输出
从图4中可以看出,该Dex文件中有3个方法原型,这里来说一下ProtoIds中的shortyIdx这个简短描述的意思,用第二个方法原型来说明。
第二个方法原型是V(Ljava/lang/String);这种形式,它的简短描述是VL。V表示返回值类型,就是V,而L就是第一个参数的类型。再举个例子,如果简短描述是VII,那么返回值类型是V,然后有两个参数,第一个参数是I类型,第二个参数也是I类型。
7. 解析FieldIds相关数据
FieldIds的解析相对于ProtoIds的解析就简单了,直接上代码:
  1. void PrintFieldIds(DexFile *pDexFile)
  2. {
  3.   printf("DexFieldIds:\r\n");
  4.   for ( u4 i = 0; i < pDexFile->pHeader->fieldIdsSize; i ++ )
  5.   {
  6.     const DexFieldId *pDexFieldId = dexGetFieldId(pDexFile, i);
  7.     printf("%04X %04X %08X \r\n", pDexFieldId->classIdx, pDexFieldId->typeIdx,
  8.       pDexFieldId->nameIdx);
  9.     printf("%s %s %s\r\n",
  10.       dexStringByTypeIdx(pDexFile, pDexFieldId->classIdx),
  11.       dexStringByTypeIdx(pDexFile, pDexFieldId->typeIdx),
  12.       dexStringById(pDexFile, pDexFieldId->nameIdx));
  13.   }
  14. }
复制代码


Field是类中的属性,在DexFieldId中对于类属性有3个字段,分别是属性所属的类、属性的类型和属性的名称。
在main函数中调用自定义函数,输出如图5所示。

图5  FieldIds解析后的输出
8. 解析MethodIds相关数据
MethodIds的解析也分为两部分,第一部分是解析数据,第二部分是简单的还原方法。在DexMethodId中给出了方法所属的类、方法对应的原型,以及方法的名称。在解析ProtoIds的时候,只是方法的原型,并没有给出方法的所属的类,还有方法的名称。在还原方法时,就要借助ProtoIds才能完整地还原方法。
解析MethodIds的代码如下:
  1. void PrintMethodIds(DexFile *pDexFile)
  2. {
  3.   printf("DexMethodIds:\r\n");
  4.   // 对数据的解析
  5.   for ( u4 i = 0; i < pDexFile->pHeader->methodIdsSize; i ++ )
  6.   {
  7.     const DexMethodId *pDexMethodId = dexGetMethodId(pDexFile, i);
  8.     printf("%04X %04X %08X \r\n", pDexMethodId->classIdx, pDexMethodId->protoIdx,
  9.       pDexMethodId->nameIdx);
  10.     printf("%s %s \r\n",
  11.       dexStringByTypeIdx(pDexFile, pDexMethodId->classIdx),
  12.       dexStringById(pDexFile, pDexMethodId->nameIdx));
  13.     }
  14.     printf("\r\n");
  15.     // 根据 protoIds 来简单还原方法
  16.     for ( u4 i = 0; i < pDexFile->pHeader->methodIdsSize; i ++ )
  17.     {
  18.       const DexMethodId *pDexMethodId = dexGetMethodId(pDexFile, i);
  19.       const DexProtoId *pDexProtoId = dexGetProtoId(pDexFile, pDexMethodId->protoIdx);
  20.       printf("%s ", dexStringByTypeIdx(pDexFile, pDexProtoId->returnTypeIdx));
  21.       printf("%s\b.", dexStringByTypeIdx(pDexFile, pDexMethodId->classIdx));
  22.       printf("%s", dexStringById(pDexFile, pDexMethodId->nameIdx));
  23.       printf("(");
  24.       // 获得参数列表
  25.       const DexTypeList *pDexTypeList = dexGetProtoParameters(pDexFile, pDexProtoId);
  26.       u4 num = pDexTypeList != NULL ? pDexTypeList->size : 0;
  27.       // 输出参数
  28.       for ( u4 j = 0; j < num; j ++ )
  29.       {
  30.         printf("%s\b, ", dexStringByTypeIdx(pDexFile, pDexTypeList->list[j].typeIdx));
  31.       }
  32.       if ( num == 0 )
  33.       {
  34.         printf(");");
  35.       }
  36.       else
  37.       {
  38.         printf("\b\b);");
  39.       }
  40.       printf("\r\n");
  41.     }
  42. }
复制代码


在解析数据时,只是将数据对应的字符串进行了输出,而还原方法时,则是借助ProtoIds来完整地还原了方法。
同样,在main函数中调用自定义函数,输出如图6所示。

图6  MethodIds解析后的输出
在解析ProtoIds的时候是有3个方法原型,在解析方法时是4个方法,第一个方法与第四个方法的方法原型是相同的。
用第二个方法来进行一个简单说明,V LHelloWorld.main([Ljava/lang/String]);。V表示方法的返回值类型,LHelloWorld是方法所在的类,main是方法的名称,Ljava/lang/String是该方法参数的类型。
9. 解析DexClassDef相关数据
解析DexClassDef是最复杂的部分了,因为它会先解析类相关的内容,类相关的内容包含类所属的文件、类中的属性、类中的方法、方法中的字节码等内容。虽然复杂,但是它只是前面每个部分和其余部分的组成,因此只是代码比较多,没有什么特别难的地方,具体代码如下:
  1. void PrintClassDef(DexFile *pDexFile)
  2. {
  3.   for ( u4 i =0; i < pDexFile->pHeader->classDefsSize; i ++ )
  4.   {
  5.      const DexClassDef *pDexClassDef = dexGetClassDef(pDexFile, i);
  6.      // 类所属的源文件
  7.      printf("SourceFile : %s\r\n", dexGetSourceFile(pDexFile, pDexClassDef));
  8.      // 类和父类
  9.      // 因为我们的 Dex 文件没有接口所以这里就没写
  10.      // 具体解析的时候需要根据实际情况而定
  11.     printf("class %s\b externs %s\b { \r\n",
  12.       dexGetClassDescriptor(pDexFile, pDexClassDef),
  13.       dexGetSuperClassDescriptor(pDexFile, pDexClassDef));
  14.     const u1 *pu1 = dexGetClassData(pDexFile, pDexClassDef);
  15.     DexClassData *pDexClassData = dexReadAndVerifyClassData(&pu1, NULL);
  16.     // 类中的属性
  17.     for ( u4 z = 0; z < pDexClassData->header.instanceFieldsSize; z ++ )
  18.     {
  19.       const DexFieldId *pDexField = dexGetFieldId(pDexFile, pDexClassData->
  20.         instanceFields[z].fieldIdx);
  21.       printf("%s %s\r\n",
  22.         dexStringByTypeIdx(pDexFile, pDexField->typeIdx),
  23.         dexStringById(pDexFile, pDexField->nameIdx));
  24.     }
  25.     // 类中的方法
  26.     for ( u4 z = 0; z < pDexClassData->header.directMethodsSize; z ++ )
  27.     {
  28.       const DexMethodId *pDexMethod = dexGetMethodId(pDexFile, pDexClassData->
  29.         directMethods[z].methodIdx);
  30.       const DexProtoId *pDexProtoId = dexGetProtoId(pDexFile, pDexMethod->
  31.         protoIdx);
  32.       printf("\t%s ", dexStringByTypeIdx(pDexFile, pDexProtoId->returnTypeIdx));
  33.       printf("%s\b.", dexStringByTypeIdx(pDexFile, pDexMethod->classIdx));
  34.       printf("%s", dexStringById(pDexFile, pDexMethod->nameIdx));
  35.       printf("(");
  36.       // 获得参数列表
  37.       const DexTypeList *pDexTypeList = dexGetProtoParameters(pDexFile, pDexProtoId);
  38.       u4 num = pDexTypeList != NULL ? pDexTypeList->size : 0;
  39.       // 输出参数
  40.       for ( u4 k = 0; k < num; k ++ )
  41.       {
  42.         printf("%s\b v%d, ", dexStringByTypeIdx(pDexFile, pDexTypeList->
  43.         list[k].typeIdx), k);
  44.       }
  45.       if ( num == 0 )
  46.       {
  47.         printf(")");
  48.       }
  49.       else
  50.       {
  51.         printf("\b\b)");
  52.       }
  53.       printf("{\r\n");
  54.       // 方法中具体的数据
  55.       const DexCode *pDexCode = dexGetCode(pDexFile, (const DexMethod *)&pDex
  56.         ClassData->directMethods[z]);
  57.       printf("\t\tregister:%d \r\n", pDexCode->registersSize);
  58.       printf("\t\tinsnsSize:%d \r\n", pDexCode->insSize);
  59.       printf("\t\tinsSize:%d \r\n", pDexCode->outsSize);
  60.       // 方法的字节码
  61.       printf("\t\t// ByteCode ...\r\n\r\n");
  62.       printf("\t\t//");
  63.       for ( u2 x = 0; x < pDexCode->insnsSize; x ++ )
  64.       {
  65.         printf("%04X ", pDexCode->insns[x]);
  66.       }
  67.       printf("\r\n");
  68.       printf("\t}\r\n\r\n");
  69.     }
  70.     printf("}\r\n");
  71.   }
  72. }
复制代码


在代码中逐步地对类进行了解析,从类所属的源文件、类的名称、类的父类、类的属性,到类的方法以及类的字节码。除了方法中的数据在前面的代码中没有,其余的代码在前面都有过介绍了。对于类方法中的数据只要按照DexCode进行解析即可,这里请参考前面给出的DexCode结构体即可。
最后,在main函数中调用自定义函数,输出如图7所示。

图7  DexClassDef解析后的输出




回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|安全矩阵

GMT+8, 2024-11-29 10:37 , Processed in 0.014387 second(s), 18 queries .

Powered by Discuz! X4.0

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表