《電子技術(shù)應(yīng)用》
您所在的位置:首頁(yè) > 通信與網(wǎng)絡(luò) > 業(yè)界動(dòng)態(tài) > 網(wǎng)絡(luò)安全編程:開發(fā)Dex文件格式解析工具

網(wǎng)絡(luò)安全編程:開發(fā)Dex文件格式解析工具

2021-07-30
來源:計(jì)算機(jī)與網(wǎng)絡(luò)安全
關(guān)鍵詞: 安全 編程 Dex文件

  解析Dex文件的工作應(yīng)該是自動(dòng)化的,由工具去完成。本文通過VS2012來新建一個(gè)控制臺(tái)的工程,然后完成一個(gè)Dex文件的解析工具。

  對(duì)于解析Dex文件而言,需要準(zhǔn)備一些頭文件,這些頭文件都可以從安卓系統(tǒng)的源代碼中獲取到,首先要有common.h、uleb128.h,因?yàn)閏ommon.h中存放了相應(yīng)的數(shù)據(jù)類型(這里所說的數(shù)據(jù)類型是u1、u2),uleb128.h中存放了讀取uleb128數(shù)據(jù)類型的相關(guān)函數(shù)。接著要準(zhǔn)備的是DexFile.h、DexFile.cpp、DexClass.h和DexClass.cpp 4個(gè)文件。

  為了使用方便,將這4個(gè)文件中的代碼都復(fù)制到了DexParse.h中,為了能夠編譯通過,在函數(shù)的定義部分進(jìn)行了刪除,或者對(duì)某些函數(shù)的參數(shù)進(jìn)行了修改,對(duì)函數(shù)體的一些內(nèi)容也進(jìn)行了刪減。

  在自己準(zhǔn)備相關(guān)內(nèi)容時(shí),可以在編譯時(shí)通過報(bào)錯(cuò)信息自己進(jìn)行修改。在這里,將DexParse.h文件添加到了新建的控制臺(tái)工程當(dāng)中。

  解析Dex文件也按照Dex的格式逐步進(jìn)行即可,當(dāng)然在解析文件前請(qǐng)不要忘記,對(duì)文件的操作首先是要打開文件。

  1. 打開與關(guān)閉文件

  打開與關(guān)閉文件的代碼如下:

  int _tmain(int argc, _TCHAR* argv[])

  {

  HANDLE hFile = CreateFile(DEX_FILE, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_

  EXISTING, FILE_ACTION_ADDED, NULL);

  HANDLE hMap = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL);

  LPVOID hView = MapViewOfFile(hMap, FILE_MAP_READ, 0, 0, 0);

  UnmapViewOfFile(hView);

  CloseHandle(hMap);

  CloseHandle(hFile);

  return 0;

  }

  在上面的代碼中,首先要打開文件,然后創(chuàng)建文件映射,在MapViewOfFile函數(shù)和UnmapViewOfFile函數(shù)之間,來添加關(guān)于解析DEX文件的代碼。

  2. Dex文件頭部

  在解析Dex文件時(shí),需要對(duì)Dex文件的頭部進(jìn)行解析,解析Dex文件的頭部時(shí),安卓系統(tǒng)提供了一個(gè)函數(shù),函數(shù)定義如下:

  DexFile* dexFileParse(const u1* data, size_t length, int flags);

  該函數(shù)有3個(gè)參數(shù),第一個(gè)參數(shù)是Dex文件數(shù)據(jù)的起始位置,第二個(gè)參數(shù)是Dex文件的長(zhǎng)度,第三個(gè)參數(shù)是用來告訴dexFileParse函數(shù)是否需要進(jìn)行驗(yàn)證的。對(duì)于目前階段而言,我們不需要第三個(gè)參數(shù),因此將該函數(shù)進(jìn)行刪減后的代碼如下:

  DexFile* dexFileParse(const u1* data, size_t length)

  {

  DexFile* pDexFile = NULL;

  const DexHeader* pHeader;

  const u1* magic;

  int result = -1;

  pDexFile = (DexFile*) malloc(sizeof(DexFile));

  if (pDexFile == NULL)

  goto bail;

  memset(pDexFile, 0, sizeof(DexFile));

  /*

  * 去掉優(yōu)化的頭部

  */

  if (memcmp(data, DEX_OPT_MAGIC, 4) == 0) {

  magic = data;

  if (memcmp(magic+4, DEX_OPT_MAGIC_VERS, 4) != 0) {

  goto bail;

  }

  /* 忽略可選的頭部和在這里追加的數(shù)據(jù)

  data += pDexFile->pOptHeader->dexOffset;

  length -= pDexFile->pOptHeader->dexOffset;

  if (pDexFile->pOptHeader->dexLength > length) {

  goto bail;

  }

  length = pDexFile->pOptHeader->dexLength;

  }

  dexFileSetupBasicPointers(pDexFile, data);

  pHeader = pDexFile->pHeader;

  /*

  * Success!

  */

  result = 0;

  bail:

  if (result != 0 && pDexFile != NULL) {

  dexFileFree(pDexFile);

  pDexFile = NULL;

  }

  return pDexFile;

  }

  該函數(shù)首先判斷Dex文件的合法性,然后將Dex文件的一些基礎(chǔ)的指針進(jìn)行了初始化,在dexFileParse函數(shù)中調(diào)用了另外一個(gè)函數(shù),即dexFileSetupBasicPointers函數(shù),該函數(shù)的函數(shù)體如下:

  void dexFileSetupBasicPointers(DexFile* pDexFile, const u1* data) {

  DexHeader *pHeader = (DexHeader*) data;

  pDexFile->baseAddr = data;

  pDexFile->pHeader = pHeader;

  pDexFile->pStringIds = (const DexStringId*) (data + pHeader->stringIdsOff);

  pDexFile->pTypeIds = (const DexTypeId*) (data + pHeader->typeIdsOff);

  pDexFile->pFieldIds = (const DexFieldId*) (data + pHeader->fieldIdsOff);

  pDexFile->pMethodIds = (const DexMethodId*) (data + pHeader->methodIdsOff);

  pDexFile->pProtoIds = (const DexProtoId*) (data + pHeader->protoIdsOff);

  pDexFile->pClassDefs = (const DexClassDef*) (data + pHeader->classDefsOff);

  pDexFile->pLinkData = (const DexLink*) (data + pHeader->linkOff);

  }

  從dexFileSetupBasicPointers函數(shù)中可以看出,對(duì)于其他各個(gè)結(jié)構(gòu)體的索引及數(shù)量已經(jīng)在這里全部讀取出來,在后面具體解析其他數(shù)據(jù)結(jié)構(gòu)時(shí),它會(huì)很方便地被使用。

  在dexFileParse中使用malloc函數(shù)申請(qǐng)了一塊空間,這塊空間在解析完成以后需要手動(dòng)地進(jìn)行釋放,在安卓系統(tǒng)的源碼中也定義了一個(gè)函數(shù)以方便使用,函數(shù)名是dexFileFree,函數(shù)的定義如下:

  void dexFileFree(DexFile* pDexFile)

  {

  if (pDexFile == NULL)

  return;

  free(pDexFile);

  }

  很簡(jiǎn)單的函數(shù),判斷指針是否為NULL,不為NULL則直接調(diào)用free函數(shù)釋放空間。

  有了上面的代碼,那么就可以完成解析Dex文件的第一步了,具體代碼如下:

  DWORD dwSize = GetFileSize(hFile, NULL);

  DexFile *pDexFile = dexFileParse((const u1 *)hView, (size_t)dwSize);

  dexFileFree(pDexFile);

  這樣就得到了指向DexFile結(jié)構(gòu)體的指針pDexFile,DexFile結(jié)構(gòu)體的定義如下:

  struct DexFile {

  /* 直接映射的“opt”頭部 */

  const DexOptHeader* pOptHeader;

  /* 指向基礎(chǔ) DEX 中直接映射的結(jié)構(gòu)體和數(shù)組的指針 */

  const DexHeader* pHeader;

  const DexStringId* pStringIds;

  const DexTypeId* pTypeIds;

  const DexFieldId* pFieldIds;

  const DexMethodId* pMethodIds;

  const DexProtoId* pProtoIds;

  const DexClassDef* pClassDefs;

  const DexLink* pLinkData;

  /*

  * 這些不映射到“auxillary”部分,可能不包含在該文件中

  */

  const DexClassLookup* pClassLookup;

  const void* pRegisterMapPool; // RegisterMapClassPool

  /* 指向 DEX 文件開始的指針 */

  const u1* baseAddr;

  /* 跟蹤輔助結(jié)構(gòu)的內(nèi)存開銷 */

  int overhead;

  /* 與 DEX 相關(guān)聯(lián)的其他數(shù)據(jù)結(jié)構(gòu) */

  //void* auxData;

  };

  對(duì)于我們而言,在寫程序時(shí)只需要關(guān)心結(jié)構(gòu)體中DexHeader到DexClassDef之間的字段即可。

  之后解析的代碼中都會(huì)使用到返回的pDexFile指針,因此之后縮寫的代碼都必須寫在調(diào)用dexFileFree函數(shù)之前。

  3. 解析DexMapList相關(guān)數(shù)據(jù)

  DexMapList是在DexHeader的mapOff給出的,不過在程序中不用直接從DexHeader結(jié)構(gòu)體中去取,因?yàn)樵诎沧肯到y(tǒng)中已經(jīng)給出了相關(guān)的函數(shù),函數(shù)代碼如下:

  DEX_INLINE const DexMapList* dexGetMap(const DexFile* pDexFile) {

  u4 mapOff = pDexFile->pHeader->mapOff;

  if (mapOff == 0) {

  return NULL;

  } else {

  return (const DexMapList*) (pDexFile->baseAddr + mapOff);

  }

  }

  dexGetMap函數(shù)通過前面返回的DexFile指針來定位DexMapList在文件中的偏移位置。

  在實(shí)際的代碼中,我們需要將DEX_INLINE宏刪掉,或者按照安卓系統(tǒng)的源代碼中的定義去定義一下。

  通過dexGetMap函數(shù)獲得了DexMapList的指針,那么接下來就可以對(duì)DexMapList進(jìn)行遍歷了,這里定義一個(gè)自定義函數(shù)來進(jìn)行遍歷,代碼如下:

  void PrintDexMapList(DexFile *pDexFile)

  {

  const DexMapList *pDexMapList = dexGetMap(pDexFile);

  printf(“DexMapList:\r\n”);

  printf(“TypeDesc\t\t type unused size offset\r\n”);

  for ( u4 i = 0; i < pDexMapList->size; i ++ )

  {

  switch (pDexMapList->list[i].type)

  {

  case 0x0000:printf(“kDexTypeHeaderItem”);break;

  case 0x0001:printf(“kDexTypeStringIdItem”);break;

  case 0x0002:printf(“kDexTypeTypeIdItem”);break;

  case 0x0003:printf(“kDexTypeProtoIdItem”);break;

  case 0x0004:printf(“kDexTypeFieldIdItem”);break;

  case 0x0005:printf(“kDexTypeMethodIdItem”);break;

  case 0x0006:printf(“kDexTypeClassDefItem”);break;

  case 0x1000:printf(“kDexTypeMapList”);break;

  case 0x1001:printf(“kDexTypeTypeList”);break;

  case 0x1002:printf(“kDexTypeAnnotationSetRefList”);break;

  case 0x1003:printf(“kDexTypeAnnotationSetItem”);break;

  case 0x2000:printf(“kDexTypeClassDataItem”);break;

  case 0x2001:printf(“kDexTypeCodeItem”);break;

  case 0x2002:printf(“kDexTypeStringDataItem”);break;

  case 0x2003:printf(“kDexTypeDebugInfoItem”);break;

  case 0x2004:printf(“kDexTypeAnnotationItem”);break;

  case 0x2005:printf(“kDexTypeEncodedArrayItem”);break;

  case 0x2006:printf(“kDexTypeAnnotationsDirectoryItem”);break;

  }

  printf(“\t %04X %04X %08X %08X\r\n”,

  pDexMapList->list[i].type,

  pDexMapList->list[i].unused,

  pDexMapList->list[i].size,

  pDexMapList->list[i].offset);

  }

  }

  在main函數(shù)中調(diào)用該函數(shù)時(shí),只要將前面得到的指向DexFile結(jié)構(gòu)體的指針傳給該函數(shù)即可。查看該部分解析的輸出,如圖1所示。

  圖1  DexMapList解析后的輸出

  4. 解析StringIds相關(guān)數(shù)據(jù)

  對(duì)于StringIds的解析也非常簡(jiǎn)單,這里直接給出一個(gè)自定義函數(shù),代碼如下:

  void PrintStringIds(DexFile *pDexFile)

  {

  printf(“DexStringIds:\r\n”);

  for ( u4 i = 0; i < pDexFile->pHeader->stringIdsSize; i ++ )

  {

  printf(“%d.%s \r\n”, i, dexStringById(pDexFile, i));

  }

  }

  在該自定義函數(shù)中,它調(diào)用了dexStringById函數(shù),也就是通過索引值來得到字符串,該函數(shù)的定義如下:

  /* 通過特定的 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函數(shù)中又調(diào)用了兩個(gè)其他的函數(shù),分別是dexGetStringId和dexGetStringData,大家可以自行查看。

  在main函數(shù)中調(diào)用筆者的自定義函數(shù),輸出如圖2所示。

  圖2  StringIds解析后的輸出

  5. 解析TypeIds相關(guān)數(shù)據(jù)

  解析TypeIds也是非常簡(jiǎn)單的,直接上代碼即可,代碼如下:

  void PrintTypeIds(DexFile *pDexFile)

  {

  printf(“DexTypeIds:\r\n”);

  for ( u4 i = 0; i < pDexFile->pHeader->typeIdsSize; i ++ )

  {

  printf(“%d %s \r\n”, i, dexStringByTypeIdx(pDexFile, i));

  }

  }

  代碼中調(diào)用了一個(gè)關(guān)鍵的函數(shù)dexStringByTypeIdx,該函數(shù)也是安卓系統(tǒng)源碼中提供的函數(shù),該函數(shù)的實(shí)現(xiàn)如下:

  /*

  * 獲取與指定的類型索引相關(guān)聯(lián)的描述符字符串

  * 調(diào)用者不能釋放返回的字符串

  */

  DEX_INLINE const char* dexStringByTypeIdx(const DexFile* pDexFile, u4 idx) {

  const DexTypeId* typeId = dexGetTypeId(pDexFile, idx);

  return dexStringById(pDexFile, typeId->descriptorIdx);

  }

  在dexStringByTypeIdx函數(shù)中調(diào)用了dexGetTypeId和dexStringById兩個(gè)函數(shù),請(qǐng)大家自行在源碼中查看。

  在main函數(shù)中調(diào)用自定義函數(shù),輸出如圖3所示。

  圖3  TypeIds解析后的輸出

  6. 解析ProtoIds相關(guān)數(shù)據(jù)

  Proto是方法的原型或方法的聲明,也就是提供了方法的返回值類型、參數(shù)個(gè)數(shù),以及參數(shù)的類型。對(duì)于ProtoIds的解析,首先是對(duì)原始數(shù)據(jù)的解析,然后再將它簡(jiǎn)單地還原為可以直接閱讀的方法原型。

  先來看一下代碼,代碼如下:

  void PrintProtoIds(DexFile *pDexFile)

  {

  printf(“DexProtoIds:\r\n”);

  // 對(duì)數(shù)據(jù)的解析

  for ( u4 i = 0; i < pDexFile->pHeader->protoIdsSize; i ++ )

  {

  const DexProtoId *pDexProtoId = dexGetProtoId(pDexFile, i);

  // 輸出原始數(shù)據(jù)

  printf(“%08X %08X %08X \r\n”, pDexProtoId->shortyIdx, pDexProtoId->returnTy

  peIdx, pDexProtoId->parametersOff);

  // 輸出對(duì)應(yīng)的 TypeId

  printf(“%s %s\r\n”,

  dexStringById(pDexFile, pDexProtoId->shortyIdx),

  dexStringByTypeIdx(pDexFile, pDexProtoId->returnTypeIdx));

  // 獲得參數(shù)列表

  const DexTypeList *pDexTypeList = dexGetProtoParameters(pDexFile, pDexProtoId);

  u4 num = pDexTypeList != NULL ? pDexTypeList->size : 0;

  // 輸出參數(shù)

  for ( u4 j = 0; j < num; j ++ )

  {

  printf(“%s ”, dexStringByTypeIdx(pDexFile, pDexTypeList->list[j].typeIdx));

  }

  printf(“\r\n”);

  }

  printf(“\r\n”);

  // 對(duì)解析數(shù)據(jù)的簡(jiǎn)單還原

  for ( u4 i = 0; i < pDexFile->pHeader->protoIdsSize; i ++ )

  {

  const DexProtoId *pDexProtoId = dexGetProtoId(pDexFile, i);

  printf(“%s”, dexStringByTypeIdx(pDexFile, pDexProtoId->returnTypeIdx));

  printf(“(”);

  // 獲得參數(shù)列表

  const DexTypeList *pDexTypeList = dexGetProtoParameters(pDexFile, pDexProtoId);

  u4 num = pDexTypeList != NULL ? pDexTypeList->size : 0;

  // 輸出參數(shù)

  for ( u4 j = 0; j < num; j ++ )

  {

  printf(“%s\b, ”, dexStringByTypeIdx(pDexFile, pDexTypeList->list[j].typeIdx));

  }

  if ( num == 0 )

  {

  printf(“);\r\n”);

  }

  else

  {

  printf(“\b\b);\r\n”);

  }

  }

  }

  在該自定義函數(shù)中有兩個(gè)for循環(huán),其內(nèi)容基本一致。第一個(gè)循環(huán)完成了數(shù)據(jù)的解析,第二個(gè)循環(huán)是將數(shù)據(jù)簡(jiǎn)單地解析成了方法的原型。

  這里只對(duì)第一個(gè)for循環(huán)進(jìn)行說明。ProtoIds是方法的原型,看一下DexProtoId的定義,定義如下:

  /*

  * Direct-mapped “proto_id_item”.

  */

  struct DexProtoId {

  u4 shortyIdx; /* index into stringIds for shorty descriptor */

  u4 returnTypeIdx; /* index into typeIds list for return type */

  u4 parametersOff; /* file offset to type_list for parameter types */

  };

  第一個(gè)字段是方法原型的短描述,第二個(gè)字段是方法原型的返回值,第三個(gè)字段是指向參數(shù)列表的。因此,可以看到,在兩個(gè)for循環(huán)中,仍然嵌套著一個(gè)for循環(huán),外層的循環(huán)是用來解析方法原型的,內(nèi)層的循環(huán)是用來解析方法原型中的參數(shù)的。

  首先,通過dexGetProtoId函數(shù)來獲得ProtoIds,然后通過dexGetProtoParameters函數(shù)來得到相應(yīng)ProtoIds的參數(shù)。

  在main函數(shù)中調(diào)用自定義函數(shù),輸出如圖4所示。

  圖4  ProtoIds解析后的輸出

  從圖4中可以看出,該Dex文件中有3個(gè)方法原型,這里來說一下ProtoIds中的shortyIdx這個(gè)簡(jiǎn)短描述的意思,用第二個(gè)方法原型來說明。

  第二個(gè)方法原型是V(Ljava/lang/String);這種形式,它的簡(jiǎn)短描述是VL。V表示返回值類型,就是V,而L就是第一個(gè)參數(shù)的類型。再舉個(gè)例子,如果簡(jiǎn)短描述是VII,那么返回值類型是V,然后有兩個(gè)參數(shù),第一個(gè)參數(shù)是I類型,第二個(gè)參數(shù)也是I類型。

  7. 解析FieldIds相關(guān)數(shù)據(jù)

  FieldIds的解析相對(duì)于ProtoIds的解析就簡(jiǎn)單了,直接上代碼:

  void PrintFieldIds(DexFile *pDexFile)

  {

  printf(“DexFieldIds:\r\n”);

  for ( u4 i = 0; i < pDexFile->pHeader->fieldIdsSize; i ++ )

  {

  const DexFieldId *pDexFieldId = dexGetFieldId(pDexFile, i);

  printf(“%04X %04X %08X \r\n”, pDexFieldId->classIdx, pDexFieldId->typeIdx,

  pDexFieldId->nameIdx);

  printf(“%s %s %s\r\n”,

  dexStringByTypeIdx(pDexFile, pDexFieldId->classIdx),

  dexStringByTypeIdx(pDexFile, pDexFieldId->typeIdx),

  dexStringById(pDexFile, pDexFieldId->nameIdx));

  }

  }

  Field是類中的屬性,在DexFieldId中對(duì)于類屬性有3個(gè)字段,分別是屬性所屬的類、屬性的類型和屬性的名稱。

  在main函數(shù)中調(diào)用自定義函數(shù),輸出如圖5所示。

  圖5  FieldIds解析后的輸出

  8. 解析MethodIds相關(guān)數(shù)據(jù)

  MethodIds的解析也分為兩部分,第一部分是解析數(shù)據(jù),第二部分是簡(jiǎn)單的還原方法。在DexMethodId中給出了方法所屬的類、方法對(duì)應(yīng)的原型,以及方法的名稱。在解析ProtoIds的時(shí)候,只是方法的原型,并沒有給出方法的所屬的類,還有方法的名稱。在還原方法時(shí),就要借助ProtoIds才能完整地還原方法。

  解析MethodIds的代碼如下:

  void PrintMethodIds(DexFile *pDexFile)

  {

  printf(“DexMethodIds:\r\n”);

  // 對(duì)數(shù)據(jù)的解析

  for ( u4 i = 0; i < pDexFile->pHeader->methodIdsSize; i ++ )

  {

  const DexMethodId *pDexMethodId = dexGetMethodId(pDexFile, i);

  printf(“%04X %04X %08X \r\n”, pDexMethodId->classIdx, pDexMethodId->protoIdx,

  pDexMethodId->nameIdx);

  printf(“%s %s \r\n”,

  dexStringByTypeIdx(pDexFile, pDexMethodId->classIdx),

  dexStringById(pDexFile, pDexMethodId->nameIdx));

  }

  printf(“\r\n”);

  // 根據(jù) protoIds 來簡(jiǎn)單還原方法

  for ( u4 i = 0; i < pDexFile->pHeader->methodIdsSize; i ++ )

  {

  const DexMethodId *pDexMethodId = dexGetMethodId(pDexFile, i);

  const DexProtoId *pDexProtoId = dexGetProtoId(pDexFile, pDexMethodId->protoIdx);

  printf(“%s ”, dexStringByTypeIdx(pDexFile, pDexProtoId->returnTypeIdx));

  printf(“%s\b.”, dexStringByTypeIdx(pDexFile, pDexMethodId->classIdx));

  printf(“%s”, dexStringById(pDexFile, pDexMethodId->nameIdx));

  printf(“(”);

  // 獲得參數(shù)列表

  const DexTypeList *pDexTypeList = dexGetProtoParameters(pDexFile, pDexProtoId);

  u4 num = pDexTypeList != NULL ? pDexTypeList->size : 0;

  // 輸出參數(shù)

  for ( u4 j = 0; j < num; j ++ )

  {

  printf(“%s\b, ”, dexStringByTypeIdx(pDexFile, pDexTypeList->list[j].typeIdx));

  }

  if ( num == 0 )

  {

  printf(“);”);

  }

  else

  {

  printf(“\b\b);”);

  }

  printf(“\r\n”);

  }

  }

  在解析數(shù)據(jù)時(shí),只是將數(shù)據(jù)對(duì)應(yīng)的字符串進(jìn)行了輸出,而還原方法時(shí),則是借助ProtoIds來完整地還原了方法。

  同樣,在main函數(shù)中調(diào)用自定義函數(shù),輸出如圖6所示。

  圖6  MethodIds解析后的輸出

  在解析ProtoIds的時(shí)候是有3個(gè)方法原型,在解析方法時(shí)是4個(gè)方法,第一個(gè)方法與第四個(gè)方法的方法原型是相同的。

  用第二個(gè)方法來進(jìn)行一個(gè)簡(jiǎn)單說明,V LHelloWorld.main([Ljava/lang/String]);。V表示方法的返回值類型,LHelloWorld是方法所在的類,main是方法的名稱,Ljava/lang/String是該方法參數(shù)的類型。

  9. 解析DexClassDef相關(guān)數(shù)據(jù)

  解析DexClassDef是最復(fù)雜的部分了,因?yàn)樗鼤?huì)先解析類相關(guān)的內(nèi)容,類相關(guān)的內(nèi)容包含類所屬的文件、類中的屬性、類中的方法、方法中的字節(jié)碼等內(nèi)容。雖然復(fù)雜,但是它只是前面每個(gè)部分和其余部分的組成,因此只是代碼比較多,沒有什么特別難的地方,具體代碼如下:

  void PrintClassDef(DexFile *pDexFile)

  {

  for ( u4 i =0; i < pDexFile->pHeader->classDefsSize; i ++ )

  {

  const DexClassDef *pDexClassDef = dexGetClassDef(pDexFile, i);

  // 類所屬的源文件

  printf(“SourceFile : %s\r\n”, dexGetSourceFile(pDexFile, pDexClassDef));

  // 類和父類

  // 因?yàn)槲覀兊?Dex 文件沒有接口所以這里就沒寫

  // 具體解析的時(shí)候需要根據(jù)實(shí)際情況而定

  printf(“class %s\b externs %s\b { \r\n”,

  dexGetClassDescriptor(pDexFile, pDexClassDef),

  dexGetSuperClassDescriptor(pDexFile, pDexClassDef));

  const u1 *pu1 = dexGetClassData(pDexFile, pDexClassDef);

  DexClassData *pDexClassData = dexReadAndVerifyClassData(&pu1, NULL);

  // 類中的屬性

  for ( u4 z = 0; z < pDexClassData->header.instanceFieldsSize; z ++ )

  {

  const DexFieldId *pDexField = dexGetFieldId(pDexFile, pDexClassData->

  instanceFields[z].fieldIdx);

  printf(“%s %s\r\n”,

  dexStringByTypeIdx(pDexFile, pDexField->typeIdx),

  dexStringById(pDexFile, pDexField->nameIdx));

  }

  // 類中的方法

  for ( u4 z = 0; z < pDexClassData->header.directMethodsSize; z ++ )

  {

  const DexMethodId *pDexMethod = dexGetMethodId(pDexFile, pDexClassData->

  directMethods[z].methodIdx);

  const DexProtoId *pDexProtoId = dexGetProtoId(pDexFile, pDexMethod->

  protoIdx);

  printf(“\t%s ”, dexStringByTypeIdx(pDexFile, pDexProtoId->returnTypeIdx));

  printf(“%s\b.”, dexStringByTypeIdx(pDexFile, pDexMethod->classIdx));

  printf(“%s”, dexStringById(pDexFile, pDexMethod->nameIdx));

  printf(“(”);

  // 獲得參數(shù)列表

  const DexTypeList *pDexTypeList = dexGetProtoParameters(pDexFile, pDexProtoId);

  u4 num = pDexTypeList != NULL ? pDexTypeList->size : 0;

  // 輸出參數(shù)

  for ( u4 k = 0; k < num; k ++ )

  {

  printf(“%s\b v%d, ”, dexStringByTypeIdx(pDexFile, pDexTypeList->

  list[k].typeIdx), k);

  }

  if ( num == 0 )

  {

  printf(“)”);

  }

  else

  {

  printf(“\b\b)”);

  }

  printf(“{\r\n”);

  // 方法中具體的數(shù)據(jù)

  const DexCode *pDexCode = dexGetCode(pDexFile, (const DexMethod *)&pDex

  ClassData->directMethods[z]);

  printf(“\t\tregister:%d \r\n”, pDexCode->registersSize);

  printf(“\t\tinsnsSize:%d \r\n”, pDexCode->insSize);

  printf(“\t\tinsSize:%d \r\n”, pDexCode->outsSize);

  // 方法的字節(jié)碼

  printf(“\t\t// ByteCode …\r\n\r\n”);

  printf(“\t\t//”);

  for ( u2 x = 0; x < pDexCode->insnsSize; x ++ )

  {

  printf(“%04X ”, pDexCode->insns[x]);

  }

  printf(“\r\n”);

  printf(“\t}\r\n\r\n”);

  }

  printf(“}\r\n”);

  }

  }

  在代碼中逐步地對(duì)類進(jìn)行了解析,從類所屬的源文件、類的名稱、類的父類、類的屬性,到類的方法以及類的字節(jié)碼。除了方法中的數(shù)據(jù)在前面的代碼中沒有,其余的代碼在前面都有過介紹了。對(duì)于類方法中的數(shù)據(jù)只要按照DexCode進(jìn)行解析即可,這里請(qǐng)參考前面給出的DexCode結(jié)構(gòu)體即可。

  最后,在main函數(shù)中調(diào)用自定義函數(shù),輸出如圖7所示。

  圖7  DexClassDef解析后的輸出




電子技術(shù)圖片.png

本站內(nèi)容除特別聲明的原創(chuàng)文章之外,轉(zhuǎn)載內(nèi)容只為傳遞更多信息,并不代表本網(wǎng)站贊同其觀點(diǎn)。轉(zhuǎn)載的所有的文章、圖片、音/視頻文件等資料的版權(quán)歸版權(quán)所有權(quán)人所有。本站采用的非本站原創(chuàng)文章及圖片等內(nèi)容無(wú)法一一聯(lián)系確認(rèn)版權(quán)者。如涉及作品內(nèi)容、版權(quán)和其它問題,請(qǐng)及時(shí)通過電子郵件或電話通知我們,以便迅速采取適當(dāng)措施,避免給雙方造成不必要的經(jīng)濟(jì)損失。聯(lián)系電話:010-82306118;郵箱:aet@chinaaet.com。