Android 解析resources.arsc文件
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文件格式
代码下载

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
阿里云MVP第五期发布,这么多行业大牛你Pick哪一个?
回顾阿里云的发展历程,阿里巴巴集团技术委员会主席王坚博士曾感慨“是阿里云的用户教会我们怎么做云计算的!”。近十年的探索之路,离不开各行各业的云计算技术实践领袖们对阿里云的鞭策与支持。为了向贡献者表示感谢,更希望通过这些云计算技术实践领袖们将更多开发者的声音反映到我们的技术路线图上。2017年6月,阿里云总裁胡晓明在上海云栖大会上发布了阿里云MVP计划。 阿里云最有价值专家,简称 MVP(Most Valuable Professional),是专注于帮助他人充分了解和使用阿里云的技术实践领袖。截止目前,全球总共有230+位技术精英成为阿里云MVP,分布在全球16个国家及地区。今天,我们重磅发布阿里云 MVP 第五期全球名单!未来,阿里云将继续与阿里云 MVP 们共同探索前路,为中国技术创新贡献更多力量。 本期将有72位资深专家及研究者
- 下一篇
Gradle应用例子(二)(待更)
例子5 编写一个通用的利于管理依赖库版本的脚本文件 参考项目 googlesamples的BasicSample 再应用Android插件时,我们通常会配置很多版本号,例如 ... buildscript { repositories { google() jcenter() } dependencies { classpath 'com.android.tools.build:gradle:3.1.2' } } ... 其中的com.android.tools.build:gradle:3.1.2 apply plugin: 'com.android.application' android { compileSdkVersion 28 defaultConfig { applicationId "com.newtrekwang.test" minSdkVersion 18 targetSdkVersion 28 versionCode 1 versionName "1.0" testInstrumentationRunner "android.support.test.runner...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- SpringBoot2全家桶,快速入门学习开发网站教程
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- Windows10,CentOS7,CentOS8安装Nodejs环境
- Hadoop3单机部署,实现最简伪集群
- Eclipse初始化配置,告别卡顿、闪退、编译时间过长
- 设置Eclipse缩进为4个空格,增强代码规范
- CentOS7编译安装Gcc9.2.0,解决mysql等软件编译问题
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- Docker安装Oracle12C,快速搭建Oracle学习环境