Android 逆向笔记 —— ARSC 文件格式解析

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: Android 逆向笔记 —— ARSC 文件格式解析

概述


我们在解压缩 APK 文件之后,会看到一个叫做 resources.arsc 的文件,它的格式称之为 ARSC 文件格式 。那么它的作用是什么呢?大家对 R 文件肯定都十分熟悉,它存储了资源的 ID。在打包过程中,但凡使用到资源的地方都是使用这个 ID 来代替的。ARSC 文件就是一个资源索引表,它可以帮助系统根据资源 ID 快速找到资源。


当我们使用 ApkTool 反编译的时候,会在 res/value 目录下生成一个 public.xml 文件,里面就记录了资源项及其对应的 ID,如下图所示:

image.png

虽然没有细看过 ApkTool 的源码,但我猜测这应该就是根据 ARSC 文件解析出来的。关于 ARSC 的文件结构,网上有一张很好的图片,拿过来给大家看一下:

image.png

如果觉得有点绕,可以对照我画的思维导图来阅读后面的文章:

image.png


ARSC 文件格式的数据结构在 AOSP 中也有相应的定义,位于 ResourceType.h 文件中 。整体上可以分为下面三大块:

  • ResTableHeader :  文件头
  • ResStringPool : 资源项值字符串池
  • ResTablePackage : 数据块

其中 ResTablePackage 项最为复杂,包含了 ARSC 文件的数据块内容。其他两块内容较为简单。下面就来一一解析。


ResTableHeader



struct ResTable_header
{
    struct ResChunk_header header;
    // The number of ResTable_package structures.
    uint32_t packageCount;
};
复制代码


这里的 header 是 ResChunk_header 类型,我们先来看一下这个类,它在 ARSC 文件的其他部分也会出现很多次。其实 ARSC 文件和 AndroidManifest.xml 文件有一些类似,也是由一个一个 Chunk 组成的。每一个 Chunk 都有固定的 ResTable_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;
};
复制代码


type 是该 Chunk 的标识符,不同的 Chunk 都有自己的标识符。headerSize 表示当前 Chunk Header 的大小。size 表示当前 Chunk 的大小。


ResChunkHeader 的结构还是很简单的,我们再回到 ResTableHeader。它除了 header 字段之外,还有一个 packageCount 字段,表示 ARSC 文件 ResTablePackage 的个数,即数据块的个数,通常是 1。


ResStringPool


ResTableHeader 后面紧接着的就是 ResStringPool,存放了 APK 中所有资源项值的字符串内容。我这里就不贴 ResStringPool 的结构图了,建议阅读的时候直接对照着我上面给的思维导图,或者对照着 010 Editor 的解析结果。


ResStringPool 也有一个头,叫做 ResStringPoolHeader,其格式如下:

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;
};
复制代码


有六个字段,来分别看一下:

  • header : ResChunkHeader,其 type 是 RES_STRING_POOL_TYPE
  • stringCount : 字符串个数
  • styleCount : 字符串样式个数
  • flags : 字符串的属性,可取值包括0x000(UTF-16),0x001(字符串经过排序)、0X100(UTF-8)和他们的组合值
  • stringsStart : 字符串内容偏移量
  • stylesStart : 字符串样式内容偏移量


ResStringPoolHeader 之后跟着的是两个偏移量数组 stringOffsetsstyleOffsets,分别是字符串内容偏移量数组和字符串样式内容偏移量数组。上面提到的偏移量都是相对整个 ResStringPool 的。根据起始偏移量和每个字符串的偏移量数组,我们就可以获取到所有字符串了。注意这里的字符串并不是纯粹的字符串,它也是有结构的。 u16lenu8len,分别代表 UTF-8UTF-16 下的字符串长度。那么如何区分呢?之前的 ResStringPoolHeader 中的 flags 属性就标记了编码格式。如果是 utf-8,则字符串以 0x00 结尾,开头前两个字节分别表示 u8len 和 u16len。如果是 utf-16,则字符串以 0x0000 结尾,开头前两个字节表示 u16len,没有 u8len 字段。


下面简单看一下解析代码:

private ResStringPoolHeader parseStringPoolType(List<String> stringPoolList) {
    int currentPosition = reader.getCurrentPosition();
    ResStringPoolHeader stringPoolHeader = new ResStringPoolHeader();
    try {
        stringPoolHeader.parse(reader);
        List<Integer> stringOffsets = new ArrayList<>(stringPoolHeader.stringCount);
        for (int i = 0; i < stringPoolHeader.stringCount; i++) {
            int offset = reader.readInt();
            stringOffsets.add(offset);
        }
        List<Integer> styleOffsets = new ArrayList<>();
        for (int i = 0; i < stringPoolHeader.styleCount; i++) {
            styleOffsets.add(reader.readInt());
        }
        int position = reader.getCurrentPosition();
            for (int i = 0; i < stringPoolHeader.stringCount; i++) {
                int length = 0;
                int skipLength = 0;
                if (stringPoolHeader.flags == ResStringPoolHeader.UTF8_FLAG) {
                    int u16len = reader.read(position + stringOffsets.get(i), 1)[0];
                    int u8len = reader.read(position + stringOffsets.get(i), 1)[0];
                    length = u8len;
                    skipLength = 1; // 如果是 utf-8,则字符串以 0x00结尾
                } else {
                    int u16len =reader.readUnsignedShort();
                    length = u16len;
                    skipLength = 2; // 如果是 utf-16,则字符串以 0x0000结尾
                }
                String string = "";
                try {
                    string = new String(reader.read(position + stringOffsets.get(i) + 2, skipLength*length));
                    reader.skip(skipLength);
                } catch (Exception e) {
                    log("   parse string[%d] error!", i);
                }
                stringPoolList.add(string);
                log("   stringPool[%d]: %s", i, string);
            }
            for (int i = 0; i < stringPoolHeader.styleCount; i++) {
                int index = reader.readInt();
                int firstChar = reader.readInt();
                int lastChar = reader.readInt();
                ResSpanStyle resSpanStyle = new ResSpanStyle(index, firstChar, lastChar);
                log(resSpanStyle.toString());
                reader.skip(4); // 0xffff
            }
            reader.moveTo(currentPosition + stringPoolHeader.resChunkHeader.size);
            return stringPoolHeader;
    } catch (IOException e) {
        log("   parse string pool type error!");
    }
    return null;
}
复制代码


我拿我自己的 Wanandroid 安装包解压得到的 ARSC 文件做测试,一共打印了 2411 个字符串,如下图所示:

image.png

看完了字符串池,接下来就是最最重要的资源数据块 ResTablePackage 了。


ResTablePackage


从文章开头给出的思维导图就可以看出来,ResTablePackage 占据了 ARSC 文件内容的大半壁江山。ResTablePackage 又可以分为五小块,如下所示:

  • ResTablePackageHeader : 头信息
  • typeStrings : 资源类型字符串池
  • keyStrings : 资源项名称字符串池
  • ResTableTypeSpec : 资源表规范
  • ResTableType : 资源表类型配置

下面来一一进行解析。


ResTablePackageHeader

其实我只是为了保持名称统一才取了个名字叫 ResTablePackageHeader,但是在 AOSP 中是叫做 ResTable_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;
};
复制代码


  • header : ResChunkHeader , 其 type 是 RES_TABLE_PACKAGE_TYPE
  • id : 包的 ID, 等于 Package Id,一般用户包的 Package Id 为 0X7F, 系统资源包的 Package Id 为 0X01
  • name : 包名
  • typeStrings :  资源类型字符串池在 ResTablePackage 中的偏移量
  • lastPublicType : 一般资源类型字符串资源池的元素个数
  • keyStrings : 资源名称字符串池在 ResTablePackage 中的偏移量
  • lastPublicKey : 一般指资源项名称字符串资源池的元素个数。
  • typeIdOffset : 未知,值为 0


typeStrings

typeStrings 是资源类型字符串池,既然是资源类型,很容易就想到 stringlayoutdrawablemipmap 等等,这些都是资源类型。说直白点,就是通常写代码时候的 R. 后面跟的东西。typeStrings 就是一个 ResStringPool,所以它的解析方式和之前是一模一样的。直接看一下解析结果:

typeStrings: 
   stringPool[0]: a n i m 
   stringPool[1]: a n i m a t o r 
   stringPool[2]: a t t r 
   stringPool[3]: b o o l 
   stringPool[4]: c o l o r 
   stringPool[5]: d i m e n 
   stringPool[6]: d r a w a b l e 
   stringPool[7]: i d 
   stringPool[8]: i n t e g e r 
   stringPool[9]: i n t e r p o l a t o r 
   stringPool[10]: l a y o u t 
   stringPool[11]: m e n u 
   stringPool[12]: m i p m a p 
   stringPool[13]: r a w 
   stringPool[14]: s t r i n g 
   stringPool[15]: s t y l e 
复制代码

这里是 utf-16 编码的。


keyStrings

keyStrings 是资源项名称字符串池,它也是 ResStringPool,就不再多说了,直接看解析结果:

keyStrings: 
   stringPool[0]: abc_fade_in
   stringPool[1]: abc_fade_out
   stringPool[2]: abc_grow_fade_in_from_bottom
   stringPool[3]: abc_popup_enter
   stringPool[4]: abc_popup_exit
   stringPool[5]: abc_shrink_fade_out_from_bottom
   stringPool[6]: abc_slide_in_bottom
   stringPool[7]: abc_slide_in_top
   stringPool[8]: abc_slide_out_bottom
   stringPool[9]: abc_slide_out_top
   stringPool[10]: abc_tooltip_enter
   stringPool[11]: abc_tooltip_exit
   stringPool[12]: btn_checkbox_to_checked_box_inner_merged_animation
   stringPool[13]: btn_checkbox_to_checked_box_outer_merged_animation
   stringPool[14]: btn_checkbox_to_checked_icon_null_animation
   ...
   ...
   ...
   stringPool[2322]: Widget.Support.CoordinatorLayout
   stringPool[2323]: leak_canary_LeakCanary.Base
   stringPool[2324]: leak_canary_Theme.Transparent
复制代码


资源项名称字符串池 keyStrings 之后是 ResTableTypeSpecResTableType ,它们是不定的交叉出现的。我们先来看看 ResTableTypeSpec


ResTableTypeSpec

ResTableTypeSpec 是资源表规范,用来描述资源项的配置差异性。系统根据不同设备的配置差异就可以加载不同的资源项。该部分数据结构对应结构体 ResTable_typeSpec :

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
    };
};
复制代码


  • header : ResChunkHeader,其 type 是 RES_TABLE_TYPE_SPEC_TYPE
  • id : 标识资源的 Type ID, Type ID 是指资源的类型 ID 。资源的类型有 animator、anim、color、drawable、layout、menu、raw、string 和 xml 等等若干种,每一种都会被赋予一个 ID
  • res0 : must be 0
  • res1 : must be 0
  • entryCount : 等于本类型的资源项个数,指名称相同的资源项的个数


紧接着后面的是 entryCount 个 uint_32 数组,数组每个元素都是用来描述资源项的配置差异性的。


ResTableType

ResTableType 是资源项的具体信息,包括资源项的名称,类型,值和配置等等。对应结构体 ResTable_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;
};
复制代码


  • header : ResChunkHeader,其 type 是 RES_TABLE_TYPE_TYPE
  • id : 标识资源的 Type ID, Type ID 是指资源的类型 ID 。资源的类型有 animator、anim、color、drawable、layout、menu、raw、string 和 xml 等等若干种,每一种都会被赋予一个 ID
  • res0 : must be 0
  • res1 : must be 0
  • entryCount : 资源项的个数
  • entryStart :资源项相对于本结构的偏移量
  • config : 资源的配置信息


config 之后是一个大小为 entryCount 的 uint32_t 数组,用于描述资源项数据库的偏移量。这个偏移量数组之后是一个 ResTableEntry[],我们再来看一下这块内容。

ResTableEntry 是资源项数据,对应结构体 ResTable_entry

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,
        // If set, this is a weak resource and may be overriden by strong
        // resources of the same name/type. This is only useful during
        // linking with other resource tables.
        FLAG_WEAK = 0x0004
    };
    uint16_t flags;
    // Reference into ResTable_package::keyStrings identifying this entry.
    struct ResStringPool_ref key;
};
复制代码


  • size : 该结构体大小
  • flags : 标志位
  • key : 资源项名称在资源项名称字符串资源池的索引


根据 flags 的不同,后面的数据结构也有所不同。如果 flags 包含 FLAG_COMPLEX(0x0001),则该数据结构是 ResTableMapEntryResTableMapEntry 是继承自 ResTableEntry 的,在原有结构上多了两个 uint32_t 字段 parentcountparent 表示父资源项的 ID。count 表示接下来有多少个 ResTableMapResTableMap 结构如下所示:

struct ResTable_map
{
    ResTable_ref name; // 资源名称
    Res_value value; // 资源值
}
复制代码


再来看看 ResValue

struct Res_value {
    uint16_t size;
    uint8_t res0;
    uint8_t dataType;
    data_type data;
复制代码


以上就是 flags 包含 FLAG_COMPLEX(0x0001)时表示的 ResTableMapEntry 的结构。如果不包含的话,就直接是 Res_value


总结


最后关于 Package 数据块的内容其实说的比较简略,因为个人觉得了解了解就可以了。如果想要深入学习的话,个人十分推荐老罗的一篇文章,Android应用程序资源的编译和打包过程分析,写的相当的详细。

最后还是给出解析代码地址,ResParser



相关文章
|
5月前
|
Java 开发工具 Android开发
Android与iOS开发环境搭建全解析####
本文深入探讨了Android与iOS两大移动操作系统的开发环境搭建流程,旨在为初学者及有一定基础的开发者提供详尽指南。我们将从开发工具的选择、环境配置到第一个简单应用的创建,一步步引导读者步入移动应用开发的殿堂。无论你是Android Studio的新手还是Xcode的探索者,本文都将为你扫清开发道路上的障碍,助你快速上手并享受跨平台移动开发的乐趣。 ####
|
23天前
|
监控 Shell Linux
Android调试终极指南:ADB安装+多设备连接+ANR日志抓取全流程解析,覆盖环境变量配置/多设备调试/ANR日志分析全流程,附Win/Mac/Linux三平台解决方案
ADB(Android Debug Bridge)是安卓开发中的重要工具,用于连接电脑与安卓设备,实现文件传输、应用管理、日志抓取等功能。本文介绍了 ADB 的基本概念、安装配置及常用命令。包括:1) 基本命令如 `adb version` 和 `adb devices`;2) 权限操作如 `adb root` 和 `adb shell`;3) APK 操作如安装、卸载应用;4) 文件传输如 `adb push` 和 `adb pull`;5) 日志记录如 `adb logcat`;6) 系统信息获取如屏幕截图和录屏。通过这些功能,用户可高效调试和管理安卓设备。
|
1月前
|
XML JavaScript Android开发
【Android】网络技术知识总结之WebView,HttpURLConnection,OKHttp,XML的pull解析方式
本文总结了Android中几种常用的网络技术,包括WebView、HttpURLConnection、OKHttp和XML的Pull解析方式。每种技术都有其独特的特点和适用场景。理解并熟练运用这些技术,可以帮助开发者构建高效、可靠的网络应用程序。通过示例代码和详细解释,本文为开发者提供了实用的参考和指导。
67 15
|
2月前
|
Java API 数据处理
深潜数据海洋:Java文件读写全面解析与实战指南
通过本文的详细解析与实战示例,您可以系统地掌握Java中各种文件读写操作,从基本的读写到高效的NIO操作,再到文件复制、移动和删除。希望这些内容能够帮助您在实际项目中处理文件数据,提高开发效率和代码质量。
58 4
|
4月前
|
人工智能 自然语言处理 Java
FastExcel:开源的 JAVA 解析 Excel 工具,集成 AI 通过自然语言处理 Excel 文件,完全兼容 EasyExcel
FastExcel 是一款基于 Java 的高性能 Excel 处理工具,专注于优化大规模数据处理,提供简洁易用的 API 和流式操作能力,支持从 EasyExcel 无缝迁移。
511 9
FastExcel:开源的 JAVA 解析 Excel 工具,集成 AI 通过自然语言处理 Excel 文件,完全兼容 EasyExcel
|
3月前
|
Serverless 对象存储 人工智能
智能文件解析:体验阿里云多模态信息提取解决方案
在当今数据驱动的时代,信息的获取和处理效率直接影响着企业决策的速度和质量。然而,面对日益多样化的文件格式(文本、图像、音频、视频),传统的处理方法显然已经无法满足需求。
150 4
智能文件解析:体验阿里云多模态信息提取解决方案
|
4月前
|
存储 Linux API
深入探索Android系统架构:从内核到应用层的全面解析
本文旨在为读者提供一份详尽的Android系统架构分析,从底层的Linux内核到顶层的应用程序框架。我们将探讨Android系统的模块化设计、各层之间的交互机制以及它们如何共同协作以支持丰富多样的应用生态。通过本篇文章,开发者和爱好者可以更深入理解Android平台的工作原理,从而优化开发流程和提升应用性能。
|
存储 XML 安全
Android逆向笔记 —— AndroidManifest.xml 文件格式解析
Android逆向笔记 —— AndroidManifest.xml 文件格式解析
Android逆向笔记 —— AndroidManifest.xml 文件格式解析
|
1月前
|
JavaScript Linux 网络安全
Termux安卓终端美化与开发实战:从下载到插件优化,小白也能玩转Linux
Termux是一款安卓平台上的开源终端模拟器,支持apt包管理、SSH连接及Python/Node.js/C++开发环境搭建,被誉为“手机上的Linux系统”。其特点包括零ROOT权限、跨平台开发和强大扩展性。本文详细介绍其安装准备、基础与高级环境配置、必备插件推荐、常见问题解决方法以及延伸学习资源,帮助用户充分利用Termux进行开发与学习。适用于Android 7+设备,原创内容转载请注明来源。
259 76
|
2月前
|
JavaScript 搜索推荐 Android开发
【01】仿站技术之python技术,看完学会再也不用去购买收费工具了-用python扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-客户的麻将软件需要下载落地页并且要做搜索引擎推广-本文用python语言快速开发爬取落地页下载-优雅草卓伊凡
【01】仿站技术之python技术,看完学会再也不用去购买收费工具了-用python扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-客户的麻将软件需要下载落地页并且要做搜索引擎推广-本文用python语言快速开发爬取落地页下载-优雅草卓伊凡
83 8
【01】仿站技术之python技术,看完学会再也不用去购买收费工具了-用python扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-客户的麻将软件需要下载落地页并且要做搜索引擎推广-本文用python语言快速开发爬取落地页下载-优雅草卓伊凡

热门文章

最新文章

推荐镜像

更多