1. 程式人生 > >Android逆向之旅---解析編譯之後的Resource arsc檔案格式

Android逆向之旅---解析編譯之後的Resource arsc檔案格式

               

一、前言

快過年了,先提前祝賀大家新年快樂,這篇文章也是今年最後一篇了。今天我們繼續來看逆向的相關知識,前篇文章中我們介紹瞭如何解析Android中編譯之後的AndroidManifest.xml檔案格式:http://blog.csdn.net/jiangwei0910410003/article/details/50568487

當時我說到其實後續還要繼續介紹兩個檔案一個是resource.arsc和classes.dex,今天我們就來看看resource.arsc檔案個格式解析,classes.dex的解析要等年後了。

二、準備工作

我們在使用apktool工具進行反編譯的時候,會發現有一個:res/values/public.xml

這個檔案:

我們檢視一下public.xml檔案內容:

看到了,這個檔案就儲存了apk中所有的型別和對應的id值,我們看到這裡面的每個條目內容都是:

type:型別名

name:資源名

id:資源的id

型別的話有這麼幾種:

drawable,menu,layout,string,attr,color,style等

所以我們會在反編譯之後的資料夾中看到這幾個型別的檔案xml內容。

上面我們介紹瞭如何使用apktool反編譯之後的內容,下面我們要做的事情就是如何來解析resource.arsc檔案,解析出這些檔案。

我們解壓一個apk得到對應的resource.arsc檔案。按照國際慣例,每個檔案的格式描述都是有對應的資料結構的,resource也不例外:frameworks\base\include\androidfw\ResourceTypes.h

,這就是resource中定義的所有資料結構。

下面再來看一張神圖(網上盜的圖!哈哈):

每次我們在解析檔案的時候都會有一張神圖,我們按照這張圖來進行資料解析工作。

三、資料結構定義

這個是專案工程結構,我們看到定義了很多的資料結構

第一、頭部資訊

Resources.arsc檔案格式是由一系列的chunk構成,每一個chunk均包含如下結構的ResChunk_header,用來描述這個chunk的基本資訊

package com.wjdiankong.parseresource.type;import com.wjdiankong.parseresource.Utils;/**struct ResChunk_header{    // Type identifier for this chunk.  The meaning of this value depends    // on the containing chunk.    uint16_t type;    // Size of the chunk header (in bytes).  Adding this value to    // the address of the chunk allows you to find its associated data    // (if any).    uint16_t headerSize;    // Total size of this chunk (in bytes).  This is the chunkSize plus    // the size of any data associated with the chunk.  Adding this value    // to the chunk allows you to completely skip its contents (including    // any child chunks).  If this value is the same as chunkSize, there is    // no data associated with the chunk.    uint32_t size;}; * @author
i * */
public class ResChunkHeader {  public short type; public short headerSize; public int size;  public int getHeaderSize(){  return 2+2+4; }  @Override public String toString(){  return "type:"+Utils.bytesToHexString(Utils.int2Byte(type))+",headerSize:"+headerSize+",size:"+size; }}
type:是當前這個chunk的型別

headerSize:是當前這個chunk的頭部大小

size:是當前這個chunk的大小

第二、資源索引表的頭部資訊

Resources.arsc檔案的第一個結構是資源索引表頭部。其結構如下,描述了Resources.arsc檔案的大小和資源包數量。

package com.wjdiankong.parseresource.type;/**struct ResTable_header{    struct ResChunk_header header;    // The number of ResTable_package structures.    uint32_t packageCount;}; * @author i * */public class ResTableHeader public ResChunkHeader header; public int packageCount;  public ResTableHeader(){  header = new ResChunkHeader(); }  public int getHeaderSize(){  return header.getHeaderSize() + 4; }  @Override public String toString(){  return "header:"+header.toString()+"\n" + "packageCount:"+packageCount; } }

header:就是標準的Chunk頭部資訊格式

packageCount:被編譯的資源包的個數

Android中一個apk可能包含多個資源包,預設情況下都只有一個就是應用的包名所在的資源包

例項:

圖中藍色高亮的部分就是資源索引表頭部。通過解析,我們可以得到如下資訊,這個chunk的型別為RES_TABLE_TYPE,頭部大小為0XC,整個chunk的大小為1400252byte,有一個編譯好的資源包。

第三、資源項的值字串資源池

緊跟著資源索引表頭部的是資源項的值字串資源池,這個字串資源池包含了所有的在資源包裡面所定義的資源項的值字串,字串資源池頭部的結構如下。

package com.wjdiankong.parseresource.type;/**struct ResStringPool_header{    struct ResChunk_header header;    // Number of strings in this pool (number of uint32_t indices that follow    // in the data).    uint32_t stringCount;    // Number of style span arrays in the pool (number of uint32_t indices    // follow the string indices).    uint32_t styleCount;    // Flags.    enum {        // If set, the string index is sorted by the string values (based        // on strcmp16()).        SORTED_FLAG = 1<<0,        // String pool is encoded in UTF-8        UTF8_FLAG = 1<<8    };    uint32_t flags;    // Index from header of the string data.    uint32_t stringsStart;    // Index from header of the style data.    uint32_t stylesStart;}; * @author i * */public class ResStringPoolHeader {  public ResChunkHeader header; public int stringCount; public int styleCount;  public final static int SORTED_FLAG = 1public final static int UTF8_FLAG = (1<<8);  public int flags; public int stringsStart; public int stylesStart;  public ResStringPoolHeader(){  header = new ResChunkHeader(); }  public int getHeaderSize(){  return header.getHeaderSize() + 4 + 4 + 4 + 4 + 4; }  @Override public String toString(){  return "header:"+header.toString()+"\n" + "stringCount:"+stringCount+",styleCount:"+styleCount+",flags:"+flags+",stringStart:"+stringsStart+",stylesStart:"+stylesStart; } }

header:標準的Chunk頭部資訊結構

stringCount:字串的個數

styleCount:字串樣式的個數

flags:字串的屬性,可取值包括0x000(UTF-16),0x001(字串經過排序)、0X100(UTF-8)和他們的組合值

stringStart:字串內容塊相對於其頭部的距離

stylesStart:字串樣式塊相對於其頭部的距離

例項:

圖中綠色高亮的部分就是字串資源池頭部,通過解析,我們可以得到如下資訊,這個chunk的型別為RES_STRING_POOL_TYPE,即字串資源池。頭部大小為0X1C,整個chunk的大小為369524byte,有8073條字串,72個字串樣式,為UTF-8編碼,無排序,字串內容塊相對於此chunk頭部的偏移為0X7F60,字串樣式塊相對於此chunk頭部的偏移為0X5A054。緊接著頭部的的是兩個偏移陣列,分別是字串偏移陣列和字串樣式偏移陣列。這兩個偏移陣列的大小分別等於stringCount和styleCount的值,而每一個元素的型別都是無符號整型。整個字元中資源池結構如下。

字串資源池中的字串前兩個位元組為字串長度,長度計算方法如下。另外如果字串編碼格式為UTF-8則字串以0X00作為結束符,UTF-16則以0X0000作為結束符。len = (((hbyte & 0x7F) << 8)) | lbyte;字串與字串樣式有一一對應的關係,也就是說如果第n個字串有樣式,則它的樣式描述位於樣式塊的第n個元素。 字串樣式的結構包括如下兩個結構體,ResStringPool_ref和ResStringPool_span。 一個字串可以對應多個ResStringPool_span和一個ResStringPool_ref。ResStringPool_span在前描述字串的樣式,ResStringPool_ref在後固定值為0XFFFFFFFF作為佔位符。樣式塊最後會以兩個值為0XFFFFFFFF的ResStringPool_ref作為結束。

package com.wjdiankong.parseresource.type;/** struct ResStringPool_ref {     uint32_t index; };  * @author i * */public class ResStringPoolRef public int index;  public int getSize(){  return 4; }  @Override public String toString(){  return "index:"+index; } }

例項:

圖中藍色高亮的部分就是樣式內容塊,按照格式解析可以得出,第一個字串和第二字串無樣式,第三個字串第4個字元到第7個字元的位置樣式為字串資源池中0X1F88的字元,以此類推。

第四、Package資料塊

接著資源項的值字串資源池後面的部分就是Package資料塊,這個資料塊記錄編譯包的元資料,頭部結構如下:

package com.wjdiankong.parseresource.type;/**struct ResTable_package{    struct ResChunk_header header;    // If this is a base package, its ID.  Package IDs start    // at 1 (corresponding to the value of the package bits in a    // resource identifier).  0 means this is not a base package.    uint32_t id;    // Actual name of this package, \0-terminated.    char16_t name[128];    // Offset to a ResStringPool_header defining the resource    // type symbol table.  If zero, this package is inheriting from    // another base package (overriding specific values in it).    uint32_t typeStrings;    // Last index into typeStrings that is for public use by others.    uint32_t lastPublicType;    // Offset to a ResStringPool_header defining the resource    // key symbol table.  If zero, this package is inheriting from    // another base package (overriding specific values in it).    uint32_t keyStrings;    // Last index into keyStrings that is for public use by others.    uint32_t lastPublicKey;}; * @author i * */public class ResTablePackage {  public ResChunkHeader header; public int id; public char[] name = new char[128]; public int typeStrings; public int lastPublicType; public int keyStrings; public int lastPublicKey;  public ResTablePackage(){  header = new ResChunkHeader(); }  @Override public String toString(){  return "header:"+header.toString()+"\n"+",id="+id+",name:"+name.toString()+",typeStrings:"+typeStrings+",lastPublicType:"+lastPublicType+",keyStrings:"+keyStrings+",lastPublicKey:"+lastPublicKey; }}
header:Chunk的頭部資訊資料結構

id:包的ID,等於Package Id,一般使用者包的值Package Id為0X7F,系統資源包的Package Id為0X01;這個值很重要的,在後面我們構建前面說到的那個public.xml中的id值的時候需要用到。

name:包名

typeString:型別字串資源池相對頭部的偏移

lastPublicType:最後一個匯出的Public型別字串在型別字串資源池中的索引,目前這個值設定為型別字串資源池的元素個數。在解析的過程中沒發現他的用途

keyStrings:資源項名稱字串相對頭部的偏移

lastPublicKey:最後一個匯出的Public資源項名稱字串在資源項名稱字串資源池中的索引,目前這個值設定為資源項名稱字串資源池的元素個數。在解析的過程中沒發現他的用途

例項:

圖中紫色高亮的部分就是ResTable_package,按照上面的格式解析資料,我們可以得出,此Chunk的Type為RES_TABLE_PACKAGE_TYPE,頭部大小為0X120,整個chunk的大小為1030716byte,Package Id為0X7F,包名稱為co.runner.app,型別字串資源池距離頭部的偏移是0X120,有15條字串,資源項名稱字串資源池0X1EC,有6249條字串。Packege資料塊的整體結構,可以用以下的示意圖表示:

其中Type String Pool和Key String Pool是兩個字串資源池,結構和資源項的值字串資源池結構相同,分別對應型別字串資源池和資源項名稱字串資源池。再接下來的結構體可能是型別規範資料塊或者型別資源項資料塊,我們可以通過他們的Type來識別,型別規範資料塊的Type為RES_TABLE_TYPE_SPEC_TYPE,型別資源項資料塊的Type為RES_TABLE_TYPE_TYPE。

第五、型別規範資料塊

型別規範資料塊用來描述資源項的配置差異性。通過這個差異性描述,我們就可以知道每一個資源項的配置狀況。知道了一個資源項的配置狀況之後,Android資源管理框架在檢測到裝置的配置資訊發生變化之後,就可以知道是否需要重新載入該資源項。型別規範資料塊是按照型別來組織的,也就是說,每一種型別都對應有一個型別規範資料塊。其資料塊頭部結構如下。

package com.wjdiankong.parseresource.type;/**struct ResTable_typeSpec{    struct ResChunk_header header;    // The type identifier this chunk is holding.  Type IDs start    // at 1 (corresponding to the value of the type bits in a    // resource identifier).  0 is invalid.    uint8_t id;        // Must be 0.    uint8_t res0;    // Must be 0.    uint16_t res1;        // Number of uint32_t entry configuration masks that follow.    uint32_t entryCount;    enum {        // Additional flag indicating an entry is public.        SPEC_PUBLIC = 0x40000000    };}; * @author i * */public class ResTableTypeSpec {  public final static int SPEC_PUBLIC = 0x40000000;  public ResChunkHeader header; public byte id; public byte res0; public short res1; public int entryCount;  public ResTableTypeSpec(){  header = new ResChunkHeader(); }  @Override public String toString(){  return "header:"+header.toString()+",id:"+id+",res0:"+res0+",res1:"+res1+",entryCount:"+entryCount; } }
header:Chunk的頭部資訊結構

id:標識資源的Type ID,Type ID是指資源的型別ID。資源的型別有animator、anim、color、drawable、layout、menu、raw、string和xml等等若干種,每一種都會被賦予一個ID。

res0:保留,始終為0

res1:保留,始終為0

entryCount:等於本型別的資源項個數,指名稱相同的資源項的個數。

例項:

圖中綠色高亮的部分就是ResTable_typeSpec,按照上面的格式解析資料,我們可以得出,此Chunk的Type為RES_TABLE_TYPE_SPEC_TYPE,頭部大小為0X10,整個chunk的大小為564byte,資源ID為1,本型別資源項數量為137。ResTable_typeSpec後面緊跟著的是一個大小為entryCount的uint32_t陣列,每一個數組元素都用來描述一個資源項的配置差異性的。

第六、資源型別項資料塊

型別資源項資料塊用來描述資源項的具體資訊, 這樣我們就可以知道每一個資源項的名稱、值和配置等資訊。 型別資源項資料同樣是按照型別和配置來組織的,也就是說,一個具有n個配置的型別一共對應有n個型別資源項資料塊。其資料塊頭部結構如下

package com.wjdiankong.parseresource.type;/**struct ResTable_type{    struct ResChunk_header header;    enum {        NO_ENTRY = 0xFFFFFFFF    };        // The type identifier this chunk is holding.  Type IDs start    // at 1 (corresponding to the value of the type bits in a    // resource identifier).  0 is invalid.    uint8_t id;        // Must be 0.    uint8_t res0;    // Must be 0.    uint16_t res1;        // Number of uint32_t entry indices that follow.    uint32_t entryCount;    // Offset from header where ResTable_entry data starts.    uint32_t entriesStart;        // Configuration this collection of entries is designed for.    ResTable_config config;}; * @author i * */public class ResTableType {  public ResChunkHeader header;  public final static int NO_ENTRY = 0xFFFFFFFF;  public byte id; public byte res0; public short res1; public int entryCount; public int entriesStart;  public ResTableConfig resConfig;  public ResTableType(){  header = new ResChunkHeader();  resConfig = new ResTableConfig(); } public int getSize(){  return header.getHeaderSize() + 1 + 1 + 2 + 4 + 4; }  @Override public String toString(){  return "header:"+header.toString()+",id:"+id+",res0:"+res0+",res1:"+res1+",entryCount:"+entryCount+",entriesStart:"+entriesStart; }}
header:Chunk的頭部資訊結構

id:標識資源的Type ID

res0:保留,始終為0

res1:保留,始終為0

entryCount:等於本型別的資源項個數,指名稱相同的資源項的個數。

entriesStart:等於資源項資料塊相對頭部的偏移值。

resConfig:指向一個ResTable_config,用來描述配置資訊,地區,語言,解析度等

例項:

圖中紅色高亮的部分就是ResTable_type,按照上面的格式解析資料,我們可以得出,RES_TABLE_TYPE_TYPE,頭部大小為0X44,整個chunk的大小為4086byte,資源ID為1,本型別資源項數量為137,資源資料塊相對於頭部的偏移為0X268。ResTable_type後接著是一個大小為entryCount的uint32_t陣列,每一個數組元素都用來描述一個資源項資料塊的偏移位置。 緊跟在這個偏移陣列後面的是一個大小為entryCount的ResTable_entry陣列,每一個數組元素都用來描述一個資源項的具體資訊。ResTable_entry的結構如下:

package com.wjdiankong.parseresource.type;import com.wjdiankong.parseresource.ParseResourceUtils;/**struct ResTable_entry{    // Number of bytes in this structure.    uint16_t size;    enum {        // If set, this is a complex entry, holding a set of name/value        // mappings.  It is followed by an array of ResTable_map structures.        FLAG_COMPLEX = 0x0001,        // If set, this resource has been declared public, so libraries        // are allowed to reference it.        FLAG_PUBLIC = 0x0002    };    uint16_t flags;        // Reference into ResTable_package::keyStrings identifying this entry.    struct ResStringPool_ref key;}; * @author i * */public class ResTableEntry {  public final static int FLAG_COMPLEX = 0x0001public final static int FLAG_PUBLIC = 0x0002;  public short size; public short flags;  public ResStringPoolRef key;  public ResTableEntry(){  key = new ResStringPoolRef(); }  public int getSize(){  return 2+2+key.getSize(); }  @Override public String toString(){  return "size:"+size+",flags:"+flags+",key:"+key.toString()+",str:"+ParseResourceUtils.getKeyString(key.index); }}
ResTable_entry根據flags的不同,後面跟隨的資料也不相同,如果flags此位為1,則ResTable_entry是ResTable_map_entry,ResTable_map_entry繼承自ResTable_entry,其結構如下。
package com.wjdiankong.parseresource.type;/** struct ResTable_map_entry : public ResTable_entry {     //指向父ResTable_map_entry的資源ID,如果沒有父ResTable_map_entry,則等於0。     ResTable_ref parent;     //等於後面ResTable_map的數量     uint32_t count; }; * @author i * */public class ResTableMapEntry extends ResTableEntry{  public ResTableRef parent; public int count;  public ResTableMapEntry(){  parent = new ResTableRef(); }  @Override public int getSize(){  return super.getSize() + parent.getSize() + 4; }  @Override public String toString(){  return super.toString() + ",parent:"+parent.toString()+",count:"+count; }}
ResTable_map_entry其後跟隨則count個ResTable_map型別的陣列,ResTable_map的結構如下:
package com.wjdiankong.parseresource.type;/** struct ResTable_map {     //bag資源項ID     ResTable_ref name;     //bag資源項值     Res_value value; }; * @author i * */public class ResTableMap {  public ResTableRef name; public ResValue value;  public ResTableMap(){  name = new ResTableRef();  value = new ResValue(); }  public int getSize(){  return name.getSize() + value.getSize(); }  @Override public String toString(){  return name.toString()+",value:"+value.toString(); }}

例項:

圖中顏色由深到淺就是一個完整的flags為1的資源項,現在就一起來解讀這段資料的含義,這個資源項頭部的大小為0X10,flags為1所以後面跟隨的是ResTable_map陣列,名稱沒有在資源項引用池中,沒有父map_entry,有一個ResTable_map。如果flags此位為0,則ResTable_entry其後跟隨的是一個Res_value,描述一個普通資源的值,Res_value結構如下。

package com.wjdiankong.parseresource.type;import com.wjdiankong.parseresource.ParseResourceUtils;/**struct Res_value {     //Res_value頭部大小     uint16_t size;     //保留,始終為0     uint8_t res0;      enum {         TYPE_NULL = 0x00,         TYPE_REFERENCE = 0x01,         TYPE_ATTRIBUTE = 0x02,         TYPE_STRING = 0x03,         TYPE_FLOAT = 0x04,         TYPE_DIMENSION = 0x05,         TYPE_FRACTION = 0x06,         TYPE_FIRST_INT = 0x10,         TYPE_INT_DEC = 0x10,         TYPE_INT_HEX = 0x11,         TYPE_INT_BOOLEAN = 0x12,         TYPE_FIRST_COLOR_INT = 0x1c,         TYPE_INT_COLOR_ARGB8 = 0x1c,         TYPE_INT_COLOR_ARGB8 = 0x1c,         TYPE_INT_COLOR_RGB8 = 0x1d,         TYPE_INT_COLOR_ARGB4 = 0x1e,         TYPE_INT_COLOR_RGB4 = 0x1f,         TYPE_LAST_COLOR_INT = 0x1f,         TYPE_LAST_INT = 0x1f     };     //資料的型別,可以從上面的列舉型別中獲取     uint8_t dataType;      //資料對應的索引     uint32_t data; }; * @author i * */public class ResValue {  //dataType欄位使用的常量 public final static int TYPE_NULL = 0x00public final static int TYPE_REFERENCE = 0x01public final static int TYPE_ATTRIBUTE = 0x02public final static int TYPE_STRING = 0x03public final static int TYPE_FLOAT = 0x04public final static int TYPE_DIMENSION = 0x05public final static int TYPE_FRACTION = 0x06public final static int TYPE_FIRST_INT = 0x10public final static int TYPE_INT_DEC = 0x10public final static int TYPE_INT_HEX = 0x11public final static int TYPE_INT_BOOLEAN = 0x12public final static int TYPE_FIRST_COLOR_INT = 0x1cpublic final static int TYPE_INT_COLOR_ARGB8 = 0x1cpublic final static int TYPE_INT_COLOR_RGB8 = 0x1dpublic final static int TYPE_INT_COLOR_ARGB4 = 0x1epublic final static int TYPE_INT_COLOR_RGB4 = 0x1fpublic final static int TYPE_LAST_COLOR_INT = 0x1fpublic final static int TYPE_LAST_INT = 0x1f;  public static final int    COMPLEX_UNIT_PX   =0,    COMPLEX_UNIT_DIP  =1,    COMPLEX_UNIT_SP   =2,    COMPLEX_UNIT_PT   =3,    COMPLEX_UNIT_IN   =4,    COMPLEX_UNIT_MM   =5, COMPLEX_UNIT_SHIFT  =0,    COMPLEX_UNIT_MASK  =15,    COMPLEX_UNIT_FRACTION =0,    COMPLEX_UNIT_FRACTION_PARENT=1,    COMPLEX_RADIX_23p0  =0,    COMPLEX_RADIX_16p7  =1,    COMPLEX_RADIX_8p15  =2,    COMPLEX_RADIX_0p23  =3,    COMPLEX_RADIX_SHIFT  =4,    COMPLEX_RADIX_MASK  =3,    COMPLEX_MANTISSA_SHIFT =8,    COMPLEX_MANTISSA_MASK =0xFFFFFF;   public short size; public byte res0; public byte dataType; public int data;  public int getSize(){  return 2 + 1 + 1 + 4; }  public String getTypeStr(){  switch(dataType){   case TYPE_NULL:    return "TYPE_NULL";   case TYPE_REFERENCE:    return "TYPE_REFERENCE";   case TYPE_ATTRIBUTE:    return "TYPE_ATTRIBUTE";   case TYPE_STRING:    return "TYPE_STRING";   case TYPE_FLOAT:    return "TYPE_FLOAT";   case TYPE_DIMENSION:    return "TYPE_DIMENSION";   case TYPE_FRACTION:    return "TYPE_FRACTION";   case TYPE_FIRST_INT:    return "TYPE_FIRST_INT";   case TYPE_INT_HEX:    return "TYPE_INT_HEX";   case TYPE_INT_BOOLEAN:    return "TYPE_INT_BOOLEAN";   case TYPE_FIRST_COLOR_INT:    return "TYPE_FIRST_COLOR_INT";   case TYPE_INT_COLOR_RGB8:    return "TYPE_INT_COLOR_RGB8";   case TYPE_INT_COLOR_ARGB4:    return "TYPE_INT_COLOR_ARGB4";   case TYPE_INT_COLOR_RGB4:    return "TYPE_INT_COLOR_RGB4";  }  return ""; }  /*public String getDataStr(){  if(dataType == TYPE_STRING){   return ParseResourceUtils.getResString(data);  }else if(dataType == TYPE_FIRST_COLOR_INT){   return Utils.bytesToHexString(Utils.int2Byte(data));  }else if(dataType == TYPE_INT_BOOLEAN){   return data==0 ? "false" : "true";  }  return data+""; }*/  public String getDataStr() {  if (dataType == TYPE_STRING) {   return ParseResourceUtils.getResString(data);  }  if (dataType == TYPE_ATTRIBUTE) {   return String.format("?%s%08X",getPackage(data),data);  }  if (dataType == TYPE_REFERENCE) {   return String.format("@%s%08X",getPackage(data),data);  }  if (dataType == TYPE_FLOAT) {   return String.valueOf(Float.intBitsToFloat(data));  }  if (dataType == TYPE_INT_HEX) {   return String.format("0x%08X",data);  }  if (dataType == TYPE_INT_BOOLEAN) {   return data!=0?"true":"false";  }  if (dataType == TYPE_DIMENSION) {   return Float.toString(complexToFloat(data))+    DIMENSION_UNITS[data & COMPLEX_UNIT_MASK];  }  if (dataType == TYPE_FRACTION) {   return Float.toString(complexToFloat(data))+    FRACTION_UNITS[data & COMPLEX_UNIT_MASK];  }  if (dataType >= TYPE_FIRST_COLOR_INT && dataType <= TYPE_LAST_COLOR_INT) {   return String.format("#%08X",data);  }  if (dataType >= TYPE_FIRST_INT && dataType <= TYPE_LAST_INT) {   return String.valueOf(data);  }  return String.format("<0x%X, type 0x%02X>",data, dataType); }  private static String getPackage(int id) {  if (id>>>24==1) {   return "android:";  }  return ""; }  public static float complexToFloat(int complex) {  return (float)(complex & 0xFFFFFF00)*RADIX_MULTS[(complex>>4) & 3]; }  private static final float RADIX_MULTS[]={  0.00390625F,3.051758E-005F,1.192093E-007F,4.656613E-010F };  private static final String DIMENSION_UNITS[]={  "px","dip","sp","pt","in","mm","","" };  private static final String FRACTION_UNITS[]={  "%","%p","","","","","","" };  @Override public String toString(){  return "size:"+size+",res0:"+res0+",dataType:"+getTypeStr()+",data:"+getDataStr(); }}

size:ResValue的頭部大小

res0:保留,始終為0

dataType:資料的型別,可以從上面的列舉型別中獲取

data:資料對應的索引

這裡我們看到了有一個轉化的方法,這個我們在解析AndroidManifest檔案的時候也用到了這個方法。

例項:

圖中畫紅線的部分就是一個ResTable_entry其後跟隨的是一個Res_value的例子,從中我們可以得出以下資訊,這個頭部大小為8,flags等於0,所以後面跟隨的是Res_value,在資源項名稱字串資源池中的索引為150,對應的值是badge_continue_months,Res_value的大小為8,資料的型別是TYPE_STRING,在資源項的值字串資源池的索引為1912,對應的值是res/drawable-nodpi-v4/badge_continue_months.png。當我們對arsc的檔案格式有了瞭解過後,我們就可以開始我們的探索之旅了,由於在使用Android studio除錯Apktool原始碼的時候遇到很多障礙,在前輩的指導下才能夠順利進行除錯,所以下面簡單介紹下設定Android studio除錯Apktool原始碼的方法。

四、解析程式碼分析

因為篇幅的原因,這裡就不把所有的程式碼都粘貼出來了,後面會列出來程式碼下載地址

package com.wjdiankong.parseresource;import java.io.ByteArrayOutputStream;import java.io.FileInputStream;public class ParseResourceMain {  public static void main(String[] args){    byte[] srcByte = null;  FileInputStream fis = null;  ByteArrayOutputStream bos = null;  try{   fis = new FileInputStream("resource/resources_gdt1.arsc");   bos = new ByteArrayOutputStream();   byte[] buffer = new byte[1024];   int len = 0;   while((len=fis.read(buffer)) != -1){    bos.write(buffer, 0, len);   }   srcByte = bos.toByteArray();  }catch(Exception e){   System.out.println("read res file error:"+e.toString());  }finally{   try{    fis.close();    bos.close();   }catch(Exception e){    System.out.println("close file error:"+e.toString());   }  }    if(srcByte == null){   System.out.println("get src error...");   return;  }    System.out.println("parse restable header...");  ParseResourceUtils.parseResTableHeaderChunk(srcByte);  System.out.println("++++++++++++++++++++++++++++++++++++++");  System.out.println();    System.out.println("parse resstring pool chunk...");  ParseResourceUtils.parseResStringPoolChunk(srcByte);  System.out.println("++++++++++++++++++++++++++++++++++++++");  System.out.println();    System.out.println("parse package chunk...");  ParseResourceUtils.parsePackage(srcByte);  System.out.println("++++++++++++++++++++++++++++++++++++++");  System.out.println();    System.out.println("parse typestring pool chunk...");  ParseResourceUtils.parseTypeStringPoolChunk(srcByte);  System.out.println("++++++++++++++++++++++++++++++++++++++");  System.out.println();    System.out.println("parse keystring pool chunk...");  ParseResourceUtils.parseKeyStringPoolChunk(srcByte);  System.out.println("++++++++++++++++++++++++++++++++++++++");  System.out.println();    int resCount = 0;  while(!ParseResourceUtils.isEnd(srcByte.length)){   resCount++;   boolean isSpec = ParseResourceUtils.isTypeSpec(srcByte);   if(isSpec){    System.out.println("parse restype spec chunk...");    ParseResourceUtils.parseResTypeSpec(srcByte);    System.out.println("++++++++++++++++++++++++++++++++++++++");    System.out.println();   }else{    System.out.println("parse restype info chunk...");    ParseResourceUtils.parseResTypeInfo(srcByte);    System.out.println("++++++++++++++++++++++++++++++++++++++");    System.out.println();   }  }  System.out.println("res count:"+resCount);   }}
我們看到程式碼,首先我們讀取resource.arsc檔案到一個byte陣列,然後開始解析。

第一、解析頭部資訊

/** * 解析頭部資訊 * @param src */public static void parseResTableHeaderChunk(byte[] src){ ResTableHeader resTableHeader = new ResTableHeader(); resTableHeader.header = parseResChunkHeader(src, 0); resStringPoolChunkOffset = resTableHeader.header.headerSize; //解析PackageCount個數(一個apk可能包含多個Package資源) byte[] packageCountByte = Utils.copyByte(src, resTableHeader.header.getHeaderSize(), 4); resTableHeader.packageCount = Utils.byte2int(packageCountByte);}
解析結果:

第二、解析資源字串內容

/** * 解析Resource.arsc檔案中所有字串內容 * @param src */public static void parseResStringPoolChunk(byte[] src){ ResStringPoolHeader stringPoolHeader = parseStringPoolChunk(src, resStringList, resStringPoolChunkOffset); packageChunkOffset = resStringPoolChunkOffset + stringPoolHeader.header.size;}
這裡有一個核心的方法:parseStringPoolChunk
/** * 統一解析字串內容 * @param src * @param stringList * @param stringOffset * @return */public static ResStringPoolHeader parseStringPoolChunk(byte[] src, ArrayList<String> stringList, int stringOffset){ ResStringPoolHeader stringPoolHeader = new ResStringPoolHeader(); //解析頭部資訊 stringPoolHeader.header = parseResChunkHeader(src, stringOffset); System.out.println("heade