1). 文件格式

resources.arsc文件格式.png
2). 頭部信息
/**
* Resource.arsc文件格式是由一系列的chunk構(gòu)成,每一個chunk均包含ResChunk_header
*
* 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 mazaiting
*/
public class ResChunkHeader {
/**
* 當前這個chunk的類型
*/
public short type;
/**
* 當前chunk的頭部大小
*/
public short headerSize;
/**
* 當前chunk的大小
*/
public int size;
/**
* 獲取Chunk Header所占字節(jié)數(shù)
* @return
*/
public int getHeaderSize() {
return 2 + 2 + 4;
}
@Override
public String toString(){
return "type: " + Util.bytesToHexString(Util.int2Byte(type)) + ",headerSize: " + headerSize + ",size: " + size;
}
}
3). 資源索引表的頭部信息
/**
* Resources.arsc文件的第一個結(jié)構(gòu)是資源索引表頭部
* 描述Resources.arsc文件的大小和資源包數(shù)量
*
* struct ResTable_header
{
struct ResChunk_header header;
// The number of ResTable_package structures.
uint32_t packageCount;
};
*
* @author mazaiting
*/
public class ResTableHeader {
/**
* 標準的Chunk頭部信息格式
*/
public ResChunkHeader header;
/**
* 被編譯的資源包個數(shù)
* Android 中一個apk可能包含多個資源包,默認情況下都只有一個就是應用的包名所在的資源包
*/
public int packageCount;
public ResTableHeader() {
header = new ResChunkHeader();
}
/**
* 獲取當前Table Header所占字節(jié)數(shù)
* @return
*/
public int getHeaderSize() {
return header.getHeaderSize() + 4;
}
@Override
public String toString(){
return "header:" + header.toString() + "\n" + "packageCount:"+packageCount;
}
}
4). 資源項的值字符串資源池
/**
* 資源索引表頭部的是資源項的值字符串資源池,這個字符串資源池包含了所有的在資源包里面所定義的資源項的值字符串
* 一個字符串可以對應多個ResStringPool_span和一個ResStringPool_ref
* 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 mazaiting
*/
public class ResStringPoolHeader {
/**
* 排序標記
*/
public final static int SORTED_FLAG = 1;
/**
* UTF-8編碼標識
*/
public final static int UTF8_FLAG = (1 << 8);
/**
* 標準的Chunk頭部信息結(jié)構(gòu)
*/
public ResChunkHeader header;
/**
* 字符串的個數(shù)
*/
public int stringCount;
/**
* 字符串樣式的個數(shù)
*/
public int styleCount;
/**
* 字符串的屬性,可取值包括0x000(UTF-16),0x001(字符串經(jīng)過排序),0x100(UTF-8)和他們的組合值
*/
public int flags;
/**
* 字符串內(nèi)容塊相對于其頭部的距離
*/
public int stringsStart;
/**
* 字符串樣式塊相對于其頭部的距離
*/
public int stylesStart;
public ResStringPoolHeader() {
header = new ResChunkHeader();
}
/**
* 獲取當前String Pool Header所占字節(jié)數(shù)
* @return
*/
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;
}
}
5). Package數(shù)據(jù)塊
/**
* Package數(shù)據(jù)塊記錄編譯包的元數(shù)據(jù)
*
* 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.
uint16_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;
uint32_t typeIdOffset;
};
*
* Package數(shù)據(jù)塊的整體結(jié)構(gòu)
* String Pool
* Type String Pool
* Key String Pool
* Type Specification
* Type Info
*
* @author mazaiting
*/
public class ResTablePackage {
/**
* Chunk的頭部信息數(shù)據(jù)結(jié)構(gòu)
*/
public ResChunkHeader header;
/**
* 包的ID,等于package id,一般用戶包的值Package Id為0X7F,系統(tǒng)資源包Pacage Id為0X01
* 這個值會在構(gòu)建public.xml中的id值時用到
*/
public int id;
/**
* 包名
*/
public char[] name = new char[128];
/**
* 類型字符串資源池相對頭部的偏移
*/
public int typeStrings;
/**
* 最后一個到處的public類型字符串在類型字符串資源池中的索引,目前這個值設置為類型字符串資源池的元素格爾書
*/
public int lastPublicType;
/**
* 資源項名稱字符串相對頭部的偏移
*/
public int keyStrings;
/**
* 最后一個導出的Public資源項名稱字符串在資源項名稱字符串資源池中的索引,目前這個值設置為資源項名稱字符串資源池的元素個數(shù)
*/
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;
}
}
6). 類型規(guī)范數(shù)據(jù)塊
/**
* 類型規(guī)范數(shù)據(jù)塊用來描述資源項的配置差異性。通過這個差異性,我們可以知道每個資源項的配置狀況。
* 知道了一個資源項的配置狀況之后,Android資源管理框架在檢測到設備的配置信息發(fā)生變化之后,就
* 可以知道是否需要重新加載該資源項。類型規(guī)范數(shù)據(jù)塊是按照類型來組織的,即每一種類型都對應有一個
* 類型規(guī)范數(shù)據(jù)塊。
*
* 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 mazaiting
*/
public class ResTableTypeSpec {
/**
* SPEC公共常量
*/
public final static int SPEC_PUBLIC = 0x40000000;
/**
* Chunk的頭部信息結(jié)構(gòu)
*/
public ResChunkHeader header;
/**
* 標識資源的Type ID,Type ID是指資源的類型ID。資源的類型有animator、anim、color、drawable、layout、menu、raw、string和xml等等若干種,每一種都會被賦予一個ID。
*/
public byte id;
/**
* 保留,始終為0
*/
public byte res0;
/**
* 保留,始終為0
*/
public short res1;
/**
* 本類型資源項個數(shù),即名稱相同的資源項的個數(shù)
*/
public int entryCount;
public ResTableTypeSpec() {
header = new ResChunkHeader();
}
@Override
public String toString(){
return "header: " + header.toString() + ",id: " + id + ",res0: " + res0 +
",res1: " + res1 + ",entryCount: " + entryCount;
}
}
7). 資源類型項數(shù)據(jù)塊
/**
* 類型資源項數(shù)據(jù)塊用來描述資源項的具體信息,可以知道每一個資源項的名稱、值和配置等信息。
* 類型資源項數(shù)據(jù)同樣是按照類型和配置來組織的,即一個具有n個配置的類型一共對應有n個類型
* 資源項數(shù)據(jù)塊。
*
* 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 mazaiting
*/
public class ResTableType {
/**
* NO_ENTRY常量
*/
public final static int NO_ENTRY = 0xFFFFFFFF;
/**
* Chunk的頭部信息結(jié)構(gòu)
*/
public ResChunkHeader header;
/**
* 標識資源的Type ID
*/
public byte id;
/**
* 保留,始終為0
*/
public byte res0;
/**
* 保留,始終為0
*/
public short res1;
/**
* 本類型資源項個數(shù),指名稱相同的資源項的個數(shù)
*/
public int entryCount;
/**
* 資源項數(shù)組塊相對頭部的偏移值
*/
public int entriesStart;
/**
* 指向一個ResTable_config,用來描述配置信息,地區(qū),語言,分辨率等
*/
public ResTableConfig resConfig;
public ResTableType() {
header = new ResChunkHeader();
resConfig = new ResTableConfig();
}
/**
* 獲取當前資源類型所占的字節(jié)數(shù)
* @return
*/
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;
}
}
8). 代碼解析
public class ParseResourceMain {
// private final static String FILE_PATH = "res/source.apk";
private final static String FILE_PATH = "res/resources.arsc";
public static void main(String[] args) {
// byte[] arscArray = getArscFromApk(FILE_PATH);
byte[] arscArray = getArscFromFile(FILE_PATH);
System.out.println("parse restable header ...");
ParseResourceUtil.parseResTableHeaderChunk(arscArray);
System.out.println("===================================");
System.out.println();
System.out.println("parse resstring pool chunk ...");
ParseResourceUtil.parseResStringPoolChunk(arscArray);
System.out.println("===================================");
System.out.println();
System.out.println("parse package chunk ...");
ParseResourceUtil.parsePackage(arscArray);
System.out.println("===================================");
System.out.println();
System.out.println("parse typestring pool chunk ...");
ParseResourceUtil.parseTypeStringPoolChunk(arscArray);
System.out.println("===================================");
System.out.println();
System.out.println("parse keystring pool chunk ...");
ParseResourceUtil.parseKeyStringPoolChunk(arscArray);
System.out.println("===================================");
System.out.println();
/**
* 解析正文內(nèi)容
* 正文內(nèi)容就是ResValue值,也就是開始構(gòu)建public.xml中的條目信息,和類型的分離不同的xml文件
*/
int resCount = 0;
while (!ParseResourceUtil.isEnd(arscArray.length)) {
resCount++;
boolean isSpec = ParseResourceUtil.isTypeSpec(arscArray);
if (isSpec) {
System.out.println("parse restype spec chunk ...");
ParseResourceUtil.parseResTypeSpec(arscArray);
System.out.println("===================================");
System.out.println();
} else {
System.out.println("parse restype info chunk ...");
ParseResourceUtil.parseResTypeInfo(arscArray);
System.out.println("===================================");
System.out.println();
}
}
System.out.println("res count: " + resCount);
}
/**
* 從文件中獲取resouces.arsc
* @param filePath 文件路徑
* @return
*/
private static byte[] getArscFromFile(String filePath) {
byte[] srcByte = null;
InputStream is = null;
ByteArrayOutputStream baos = null;
try {
is = new FileInputStream(filePath);
baos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len = 0;
while ((len = is.read(buffer)) != -1) {
baos.write(buffer, 0, len);
}
srcByte = baos.toByteArray();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
is.close();
baos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return srcByte;
}
/**
* 從APK中獲取resources.arsc文件
* @param filePath 文件路徑
* @return resources.arsc文件二進制數(shù)據(jù)
*/
private static byte[] getArscFromApk(String filePath) {
byte[] srcByte = null;
ZipFile zipFile = null;
InputStream is = null;
ByteArrayOutputStream baos = null;
try {
zipFile = new ZipFile(filePath);
ZipEntry entry = zipFile.getEntry("resources.arsc");
is = zipFile.getInputStream(entry);
baos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len = 0;
while ((len = is.read(buffer)) != -1) {
baos.write(buffer, 0, len);
}
srcByte = baos.toByteArray();
zipFile.close();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
is.close();
baos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return srcByte;
}
}
參考文章
Android逆向之旅---解析編譯之后的Resource.arsc文件格式