欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

第四章 常见的 Android 文件格式(三)(AndroidManifest.xml)

程序员文章站 2024-03-24 09:50:52
...

AndroidManifest.xml

  • 其中存放了 APK 的大量配置信息:软件名称、图标、主题、包名、组件配置等
  • 合理、安全地配置组件是安全开发中最重要的一课

AndroidManifest.xml 文件的格式

  • 采用 XML 文本格式,在开发阶段,所有的配置信息都可直接以可视化的方式编辑。Crackme0201 的内容:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.droider.crackme0201">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>
android:allowBackup="true"
  • android:allowBackup 允许系统在进行备份操作时备份程序的应用数据,典型的操作是在终端执行 adb backup 命令,或点击手机设置界面上的“备份操作”按钮。对数据安全比较敏感的话,可设置为“false”
android:supportsRtl="true"
  • 这个标签的作用:让 APK 支持 RTL(Right-to-Left)视图。将此值设为“true”,并将 targetSdkVersion 的值设为 17 及以上,即可开启 RTL 视图支持

AXML 文件格式

  • AS 在编译 APK 时,会将 AndroidManifest.xml 处理后打包进去。打包进去的 AndroidManifest.xml 被编译成二进制格式的文件。解压 APK 后,用文本编辑器打开它,会发现内容是乱码。这个打包后的 AndroidManifest.xml 称“AXML”,其格式称“AXML 文件格式”
  • APK 用 AXML 而非纯文本格式 XML 存放数据,主要目的应是解决 APK 加载时的性能问题。在 Android 设备内存资源与能耗极其有限的情况下,二进制的 AXML 在分析处理速度和内存占用方面都比纯文本的 XML 有明显优势。但 AXML 的内容不能直接显示,因此,逆向分析 APK 时,对其格式有所了解,才能知道它原来的内容
  • Android 官方没明确给出 AXML 的二进制布局规范,可通过阅读 APK 打包流程和系统加载 APK 的代码掌握它的文件格式。在 Android 系统源码文件 frameworks/base/include/androidfw/ResourceType.h 中列举了 AXML 使用的大部分数据结构和常量定义
  • 学习 AXML 文件格式过程中,在了解数据结构的同时,可使用 010 Editor 辅助分析
  • AXML 文件格式模板可通过 010 Editor 的模板库来安装,如下:
    第四章 常见的 Android 文件格式(三)(AndroidManifest.xml)
    第四章 常见的 Android 文件格式(三)(AndroidManifest.xml)
  • 尽管 Android 官方提供了 AndroidManifest.bt 模板,但其结构化显示效果略差,且 Android 官方模板使用的数据结构定义与 Android 官方定义的结构有所出入。可下载链接中给出的模板(随书提供),链接: https://pan.baidu.com/s/1hr0sH5UWvzvz5wqUenARPw 提取码: r9c4
  • 下图是一幅 AXML 文件格式简图。在 AXML 中,数据块用 chunk 表示。从整体结构看,一个 AXML 由文件头 ResFileHeader、字符串池 ResStringPool、资源 ID 块 ResIDs、XML 数据内容块 ResXMLTree 四部分线性地组成
    第四章 常见的 Android 文件格式(三)(AndroidManifest.xml)
  • ResFileHeader 表示文件的头部,这里用 ResChunk_header 表示。ResChunk_header 除了在文件开头表示文件头,还用来表示其他 chunk 的头部信息。ResChunk_header 定义:
struct ResChunk_header {
    uint16_t type;
    uint16_t headerSize;
    uint32_t size;
};
  • type 字段描述了 chunk 所属结构体的类型,其取值如下:
enum {
    RES_NULL_TYPE            = 0x0000,
    RES_STRING_TYPE            = 0x0001,
    RES_TABLE_TYPE            = 0x0002,
    RES_XML_TYPE            = 0x0003,
    
    // Chunk types in RES_XML_TYPE
    RES_XML_FIRST_CHUNK_TYPE        = 0x0100,
    RES_XML_START_NAMESPACE_TYPE    = 0x0100,
    RES_XML_END_NAMESPACE_TYPE        = 0x0101,
    RES_XML_START_ELEMENT_TYPE        = 0x0102,
    RES_XML_END_ELEMENT_TYPE        = 0x0103,
    RES_XML_CDATA_TYPE                = 0x0104,
    RES_XML_LAST_CHUNK_TYPE            = 0x017f,
    // This contains a uint32_t array mapping strings
    // in the string pool back to resource identifiers.
    // It is optional.
    RES_XML_RESOURCE_MAP_TYPE        = 0x0180,
    
    // Chunk types in RES_TABLE_TYPE
    RES_TABLE_PACKAGE_TYPE            = 0x0200,
    RES_TABLE_TYPE_TYPE                = 0x0201,
    RES_TABLE_TYPE_SPEC_TYPE        = 0x0202
};
  • 对文件头来说,type 字段的值固定为 0x3,即 RES_XML_TYPE,表示这是一个 AXML 文件;headerSize 字段表示当前 ResChunk_header 结构的大小,它的值固定为 0x8。在一些 AXML 文件格式的描述文档中,使用一个 4 字节的字段 magic 表示 type 和 headerSize 字段(效果一样);size 字段表示该 chunk 结构体数据的长度,它包含当前 ResChunk_header 结构体前两个字段的 8 字节,因此在实际计算数据大小时要减去 8 字节(对第一个 ResChunk_header 结构来说,它表示文件的总长度)
  • 在文件头后是字符串池 ResStringPool,其包含 AXML 中使用的所有字符串。字符串池由字符串池头 ResStringPool_header、字符串列表、样式列表三部分组成。定义如下:
struct ResStringPool_header {
    struct ResChunk_header header;
    uint32_t stringCount;
    uint32_t styleCount;
    
    // Flags.
    enum {
        // If set, the string index is sorted
        // by the string values (based on strcmp16()).
        SORTED_FLAG = 1 << 0,
        UTF8_FLAG = 1 << 8        // String pool is encoded in UTF-8
    };
    uint32_t flags;
    uint32_t stringsStart;
    uint32_t stylesStart;
};
  • header 字段的结构和文件中第一个 ResChunk_header 一样,只是这里的 type 不同。其取值固定为 0x1,即 RES_STRING_POLL_TYPE,表示这个 chunk 是一个字符串池。stringCount 与 styleCount 字段分别表示这个池中字符串的数目和样式的数目。flags 字段用于标识字符串的类型是 UTF-8 还是 16 位编码。stringsStart 与 stylesStart 字段分别表示字符串列表与样式列表在文件中的偏移量(在计算取字符串时要跳过文件头与 ResChunk_header 大小的 8 字节)
  • 接着是具体的字符串数据和样式数据。字符串数据是一个字符串索引列表,每条索引都使用 ResStringPool_string 结构体来表示,定义如下:
struct ResStringPool_string {
    uint32_t index;
};
  • index 字段指向字符串在文件中的具体偏移量,其指向的内容可能是一个 UTF-8 字符串,也可能是一个 16 位编码字符串(这依赖于前面的 flags 字段)。目前 AXML 没有使用样式,所有关于样式的实际数据部分都为 0 或空值,而字符串数据使用的是 16 位编码字符串,字符串中的每个字符使用双字节表示,在字符串开头使用 16 位表示字符串的长度
  • 接着是 资源 ID 块 ResIDs。这部分主要用于存放 AndroidManifest.xml 使用的系统属性值所对应的资源 ID,结构定义:
typedef struct {
    struct ResChunk_header header;
    int count;
    uint ids[count];
} ResIDs;
  • header 字段的 type 在这里是 0x180,即 RES_XML_RESOURCE_MAP_TYPE,表示这是一个资源表。count 字段表示资源 ID 的个数。ids 字段中存放一个个资源 ID。每个资源 ID 都是一个 32 位整型值,由三部分组成,使用十六进制表示为“0xpptteeee”。“pp”“tt”“eeee”分别表示资源 ID 的 Package ID index(包 ID 索引)、Type ID index(类型 ID 索引)、Entry ID index(条目 ID 索引)
  • Package ID 相当于一个命名空间,用于限定资源的来源。Android 系统目前定义了两个资源命名空间:系统资源命名空间,它的 Package ID 等于 0x01,如 Application 标签的 android:allowBackup 属性的 Package ID 就是 0x01;应用程序资源命名空间,它的 Package ID 等于 0x7f,如程序中 R.java 中的资源 ID
  • Type ID 表示资源的类型 ID。资源的类型包括 attr、id、style、anim、color、drawable、layout、menu、raw、string、xml 等,每种类型都会赋予一个 ID。ID 的取值从 1 开始,0 表示无效,如 0x1 表示 attr、0x2 表示 id、0x9 表示 layout
  • Entry ID 指明了每一个资源在其所属的资源类型中的索引位置
  • 所有系统资源命名空间的资源 ID 都可在 Android 源码文件 frameworks/base/core/res/res/values/public.xml 中找到。如,0x01010280 在 public.xml 中的定义如下,表示它是一个名为“allowBackup”的属性值
    <public type="attr" name="allowBackup" id="0x01010280"/>
  • 接着是 ResXMLTree,它用于表示 XML 的具体内容。他是一个线性的 XML 字节数据集合,由多个 XML 节点数据组成,每个 XML 节点数据由基本结构体 ResXMLTree_node 和扩展结构体组成
  • ResXMLTree_node 定义:
struct ResXMLTree_node {
    struct ResChunk_header header;
    uint32_t lineNumber;
    struct ResStringPool_ref comment;
};
  • 对第一个节点来说,header 的 type 字段必须是 RES_XML_START_NAMESPACE_TYPE,表示这是一个 namespace 开始节点。与此对应的是 ResXMLTree 部分的最后一个 ResXMLTree_node,它的 header 的 type 字段必须是 RES_XML_END_NAMESPACE_TYPE,表示 namespace 节点的结束。lineNumber 字段表示节点数据在 AndroidManifest.xml 中的行号,占用 4 字节。comment 字段表示节点数据关联的注释内容,它的结构是 ResStringPool_ref,定义:
struct ResStringPool_ref {
    uint32_t index;
};
  • index 字段是字符串在字符串池中的偏移索引。若节点数据没有对应的注释,由 comment 字段取值 -1。对类型为 RES_XML_START_NAMESPACE_TYPE 与 RES_XML_END_NAMESPACE_TYPE 的节点数据来说,它的扩展结构体用 ResXMLTree_namespaceExt 表示,定义:
struct ResXMLTree_namespaceExt {
    struct ResStringPool_ref prefix;
    struct ResStringPool_ref uri;
};
  • prefix 字段表示 namespace 的前缀,对 AXML 来说,它的值通常为“android”。uri 字段表示 namespace 的 URI,对 AXML 来说,它的值通常为“http://schemas.android.com/apk/res/android”
  • 在 RES_XML_START_NAMESPACE_TYPE 和 RES_XML_END_NAMESPACE_TYPE 类型的节点数据中间是一系列以 RES_XML_START_ELEMENT_TYPE 开头且以 RES_XML_END_ELEMENT_TYPE 结束的成对的 ResXMLTree_node 节点数据。这些节点数据虽然可嵌套,但必须成对出现,与 XML 的语法格式一样
  • RES_XML_START_ELEMENT_TYPE 类型的节点数据表示一个节点 TAG 的开始。它除了可包含多个子 TAG,还可包含多个属性值。它的扩展结构部分用 ResXMLTree_attrExt 表示,定义:
struct ResXMLTree_attrExt {
    struct ResStringPool_ref ns;
    struct ResStringPool_ref name;
    uint16_t attributeStart;
    uint16_t attributeSize;
    uint16_t attributeCount;
    uint16_t idIndex;
    uint16_t classIndex;
    uint16_t styleIndex;
};
  • ns 和 name 字段分别表示节点数据所在的 namespace 与节点的名称。attributeStart 字段表示属性的初始地址,它的位置是相对于本结构体的文件偏移。attributeSize 字段表示单个属性的大小。attributeCount 字段表示属性的总个数。idIndex、classIndex、styleIndex 字段分别表示 id 属性、class 属性、style 属性的索引(以 1 为下标,为 0 表示空值)
  • 若 attributeStart 字段指向的偏移量不为 -1,且 attributeCount 字段指定的个数大于 0,接下来就是具体的属性数据。属性数据由 ResXMLTree_attribute 表示,定义:
struct ResXMLTree_attribute {
    struct ResStringPool_ref ns;
    struct ResStringPool_ref name;
    struct ResStringPool_ref rawValue;
    struct Res_value typedValue;
};
  • ns 和 name 字段分别表示属性所在的 namespace 与属性的名称。rawValue 字段表示该属性的原始字符串值。typedValue 字段的类型是 Res_value。Res_value 是一个复杂的类型,可存放各种类型的属性值。Res_value 定义如下,data_type 字段表示可存储的数据类型
struct Res_value {
    uint16_t size;
    uint8_t res0;
    uint8_t dataType;
    typedef uint32_t data_type;
    data_type data;
};
  • size 字段描述了属性占用的总字节数。res0 字段的值目前必须是 0。dataType 字段表示数据的类型,它的取值可以是如下形式:
enum {
    TYPE_NULL        = 0x00,
    TYPE_REFERENCE    = 0x01,
    TYPE_ATTRIBUTE    = 0x02,
    TYPE_STRING        = 0x03,
    TYPE_FLOAT        = 0x04,
    TYPE_DIMENSION    = 0x05,
    TYPE_FRACTION    = 0x06,
    TYPE_DYNAMIC_REFERENCE    = 0x07,
    TYPE_DYNAMIC_ATTRIBUTE    = 0x08,
    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_AGB8        = 0x1d,
    TYPE_INT_COLOR_ARGB4    = 0x1e,
    TYPE_INT_COLOR_AGB4        = 0x1f,
    TYPE_LAST_COLOR_INT        = 0x1f,
    TYPE_LAST_INT            = 0x1f
};
  • data 字段中存放具体的数据,根据 dataType 指定的不同的数据类型,它的值的类型也不同。如,当 dataType 为 TYPE_STRING 时,data 字段中存放的是字符串的索引,前面的 rawValue 字段指向的也是字符串的索引值
  • 对 RES_XML_END_ELEMENT_TYPE 类型的节点来说,它表示要给节点 TAG 的结束,它的扩展结构用 ResXMLTree_endElementExt 表示,定义:
struct ResXMLTree_endElementExt {
    struct ResStringPool_ref ns;
    struct ResStringPool_ref name;
};
  • ns 和 name 字段同前面的 ResXMLTree_attrExt 结构体所对应的字段,分别表示属性所在的 namespace 和属性的名称
  • 至此,AXML 的格式介绍完毕。完整分析一个 AXML 的内容,关键在于解析后面一系列以 RES_XML_START_ELEMENT_TYPE 开始且以 RES_XML_END_ELEMENT_TYPE 结束的 ResXMLTree_node 节点数据

AXML 文件的修改

  • 部分 APK 保护工具及一些厂商的加固方案利用 Android 系统解析 AXML 的漏洞,在编译 APK 时构造畸形的 AXML,是系统能正常安装 APK,但无法运行 ApkTool 等反编译工具。这时就要对 AXML 进行修改。最直接的修改方式:配合使用 010 Editor 及 AXML 模板查看文件格式,找到异常部分进行修改
  • 对一些已出现的 AXML 加固方案,可用现成的工具修改:
  • AmBinaryEditor:https://github.com/ele7enxxh/AmBinaryEditor
  • AndroidManifestFix:https://github.com/zylc369/AndroidManifestFix