1). 文件格式
2). 头部信息
/**
* Resource.arsc文件格式是由一系列的chunk构成,每一个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所占字节数
* @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文件的第一个结构是资源索引表头部
* 描述Resources.arsc文件的大小和资源包数量
*
* 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;
/**
* 被编译的资源包个数
* Android 中一个apk可能包含多个资源包,默认情况下都只有一个就是应用的包名所在的资源包
*/
public int packageCount;
public ResTableHeader() {
header = new ResChunkHeader();
}
/**
* 获取当前Table Header所占字节数
* @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头部信息结构
*/
public ResChunkHeader header;
/**
* 字符串的个数
*/
public int stringCount;
/**
* 字符串样式的个数
*/
public int styleCount;
/**
* 字符串的属性,可取值包括0x000(UTF-16),0x001(字符串经过排序),0x100(UTF-8)和他们的组合值
*/
public int flags;
/**
* 字符串内容块相对于其头部的距离
*/
public int stringsStart;
/**
* 字符串样式块相对于其头部的距离
*/
public int stylesStart;
public ResStringPoolHeader() {
header = new ResChunkHeader();
}
/**
* 获取当前String Pool Header所占字节数
* @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数据块
/**
* Package数据块记录编译包的元数据
*
* 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数据块的整体结构
* String Pool
* Type String Pool
* Key String Pool
* Type Specification
* Type Info
*
* @author mazaiting
*/
public class ResTablePackage {
/**
* Chunk的头部信息数据结构
*/
public ResChunkHeader header;
/**
* 包的ID,等于package id,一般用户包的值Package Id为0X7F,系统资源包Pacage Id为0X01
* 这个值会在构建public.xml中的id值时用到
*/
public int id;
/**
* 包名
*/
public char[] name = new char[128];
/**
* 类型字符串资源池相对头部的偏移
*/
public int typeStrings;
/**
* 最后一个到处的public类型字符串在类型字符串资源池中的索引,目前这个值设置为类型字符串资源池的元素格尔书
*/
public int lastPublicType;
/**
* 资源项名称字符串相对头部的偏移
*/
public int keyStrings;
/**
* 最后一个导出的Public资源项名称字符串在资源项名称字符串资源池中的索引,目前这个值设置为资源项名称字符串资源池的元素个数
*/
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). 类型规范数据块
/**
* 类型规范数据块用来描述资源项的配置差异性。通过这个差异性,我们可以知道每个资源项的配置状况。
* 知道了一个资源项的配置状况之后,Android资源管理框架在检测到设备的配置信息发生变化之后,就
* 可以知道是否需要重新加载该资源项。类型规范数据块是按照类型来组织的,即每一种类型都对应有一个
* 类型规范数据块。
*
* 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的头部信息结构
*/
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;
/**
* 本类型资源项个数,即名称相同的资源项的个数
*/
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). 资源类型项数据块
/**
* 类型资源项数据块用来描述资源项的具体信息,可以知道每一个资源项的名称、值和配置等信息。
* 类型资源项数据同样是按照类型和配置来组织的,即一个具有n个配置的类型一共对应有n个类型
* 资源项数据块。
*
* 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的头部信息结构
*/
public ResChunkHeader header;
/**
* 标识资源的Type ID
*/
public byte id;
/**
* 保留,始终为0
*/
public byte res0;
/**
* 保留,始终为0
*/
public short res1;
/**
* 本类型资源项个数,指名称相同的资源项的个数
*/
public int entryCount;
/**
* 资源项数组块相对头部的偏移值
*/
public int entriesStart;
/**
* 指向一个ResTable_config,用来描述配置信息,地区,语言,分辨率等
*/
public ResTableConfig resConfig;
public ResTableType() {
header = new ResChunkHeader();
resConfig = new ResTableConfig();
}
/**
* 获取当前资源类型所占的字节数
* @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();
/**
* 解析正文内容
* 正文内容就是ResValue值,也就是开始构建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文件二进制数据
*/
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文件格式