Android逆向:二进制xml文件解析(Start Tag Chunk)

·  阅读 562

前言

在Android中,xml文件经过编译后都是不可读的二进制文件。今天我们来解析一下这个二进制文件的内容,看看如何与我们的源码进行对应。

XML文件结构

在Android中,xml文件经过编译后都是不可读的二进制文件。它的结构如图:

image.png

简单说一下大概的结构:

  • Header Chunk:8个byte,type+headersize+fileszie

  • String Chunk:字符串池

  • ResourceId Chunk: 系统的资源id,即0x01xxxxxx这种

  • XMLContent Chunk:xml内容的结

其中XMLContent Chunk是主要部分,是一个集合,从图中可以看到它包含多种类型的数据结构,其中Start Namespace Chunk、End Namespace Chunk和Text Chunk这三种类型数据有可能不存在。而Start Tag Chunk和End Tag Chunk一定存在。

Start Tag Chunk和End Tag Chunk就是一个xml tag,如<View /><View ></>,所以它们是成对出现的。

但是要注意不是一个Start Tag Chunk后面一定跟着一个End Tag Chunk,比如嵌套<Layout ><View /></Layout>,这种情况则是Start Tag Chunk + Start Tag Chunk + End Tag Chunk + End Tag Chunk

所以End Tag Chunk中基本没有什么信息,大部分信息都在Start Tag Chunk中,这也是我们这篇文章讨论的重点。

Start Tag Chunk Header

下面是Start Tag Chunk的一个实例:

20220106113847.jpg

下面我们一步步解析它。

从上面的图中可以清楚的看到Start Tag Chunk的结构。不过没展示的是Start Tag Chunk包含header和body结构。

Header的结构如下:

struct ResXMLTree_node
    {
    struct ResChunk_header header; 
    uint32_t lineNumber;
    struct ResStringPool_ref comment;
};
复制代码

这里type(2byte)+ headersize(2byte,header大小固定是16byte)+ chunksize(4byte)+ lineNum(4byte)+ comment(4byte)

结合示例来看,其中橙色部分02011000 74000000 00000000 FFFFFFFF便是header。其中:

0201是type;1000是headersize,考虑字节就是16;74000000是chunksize,是116byte,除去header的16byte,body的大小应该是100byte,后面会验证;00000000是lineNum;FFFFFFFF是没有comment的默认值。

Start Tag Chunk Body

header后面就是body,body主要分两部分,第一部分是有关tag的基本信息,第二部分则是Atrributes。

第一部分结构如下:

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: 命名空间,在字符串池中的索引。如果没有就是0xFFFFFFFF。(4byte)

  • name: 元素名称,在字符串池中的索引 。(4byte)

  • AttributeStart: 属性段的相对body的偏移,由于这部分大小固定,所以这个偏移也是固定的20byte 。(2byte)

  • AttributeSize: 每个属性的大小,固定是20byte 。(2byte)

  • AttributeCount: 属性的总数 。(2byte)

  • idIndex: 第几个属性表示id 。(2byte)

  • classIndex: 第几个属性表示class 。(2byte)

  • styleIndex: 第几个属性表示style 。(2byte)

结合上面的例子来看,其中绿色部分FFFFFFFF 09000000 14001400 04000100 00000000便是这一部分,其中:

FFFFFFFF表示没有命名空间;09000000表示元素名是字符串池第10个;第一个1400是偏移量,即20byte;第二个1400表示每个属性大小是20byte;0400表示一共有4个属性;0100表示第一个属性是id;00000000表示没有class和style。

这个tag实际是下面的代码:

<ImageView
    android:id="@+id/image1"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:src="@drawable/fio" />
复制代码

可以看到它一共有四个属性,而第一个属性是id。如果我们去字符串池中找,会发现第10字符串是ImageView。

注意:我们可以验证上面的body大小,这部分大小固定是20byte,而每个属性大小是20byte,一共四个,所以body的大小正好是上面说过的100byte。

Atrributes

接下就是Atrributes,Atrributes是一系列Entry结构的集合,每个Entry的结构如下:

struct ResXMLTree_attribute{
     struct ResStringPool_ref ns;
 
     struct ResStringPool_ref name;
 
     struct ResStringPool_ref rawValue; 
 
     struct Res_value typedValue;
 
};
复制代码

其中:

  • Ns: 属性的命名空间,在字符串池中的索引,比如xmlns:android="http://schemas.android.com/apk/res/android这种。(4byte)

  • Name: 属性的名称,在字符串池中的索引。(4byte)

  • rawValue: 属性的值的原始 XML 文字中字符串在 string indices 中的索引。(4byte)

  • TypeValue: 是一个表示属性值的结构体。(8byte)

这里的TypeValue的结构如下

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

其中:

  • Size:表示 Res_value 的大小。可以看到这里固定是8byte大小,所以size固定是0x80。(2byte)

  • Res0: 始终为 0 (1byte)

  • dataType: 值的类型。(1byte)

  • data:属性值。(4byte)

其中dataType类型如下:

public static final int TYPE_NULL = 0x00;
public static final int TYPE_REFERENCE = 0x01;
public static final int TYPE_ATTRIBUTE = 0x02;
public static final int TYPE_STRING = 0x03;
public static final int TYPE_FLOAT = 0x04;
public static final int TYPE_DIMENSION = 0x05;
public static final int TYPE_FRACTION = 0x06;
public static final int TYPE_DYNAMIC_REFERENCE = 0x07;
 
public static final int TYPE_FIRSTINT = 0x10;          // Beginning of integer flavors...
 
public static final int TYPE_INT_DEC = 0x10;           // n..n.
public static final int TYPE_INT_HEX = 0x11;           // 0xn..n.
public static final int TYPE_INT_BOOLEAN = 0x12;       // 0 or 1, "false" or "true"
 
public static final int TYPE_FIRST_COLOR_INT = 0x1c;   // Beginning of color integer flavors...
public static final int TYPE_INT_COLOR_ARGB8 = 0x1c;   // #aarrggbb.
public static final int TYPE_INT_COLOR_RGB8 = 0x1d;    // #rrggbb.
public static final int TYPE_INT_COLOR_ARGB4 = 0x1e;   // #argb.
public static final int TYPE_INT_COLOR_RGB4 = 0x1f;    // ##rgb.
public static final int TYPE_LAST_COLOR_INT = 0x1f;    // ..end of integer flavors.
 
public static final int TYPE_LAST_INT = 0x1f;          // ...end of integer flavors.
复制代码

结合上面的例子来看,蓝色部分便是Atrributes,我们取其中一个来看,比如:

0D000000 03000000 FFFFFFFF 08000001 3800077F

其中:

0D000000表示字符串池第15个是命名空间;03000000表示字符串池第4个是属性名称;FFFFFFFF表示没有rawValue。

0800表示TypeValue的大小是8byte;00是resId,固定值;01表示属性值是一个资源索引;3800077F就是资源索引,即resId是0x7F070038。

再结合实际代码,这个属性上面代码中的第一个属性,所以是android:id="@+id/image1"。所以它的属性值是@+id/image1,是一个资源索引,在R.java中

    public static final int image1=0x7F070038;
复制代码

可以看到image1正是0x7F070038。

总结

这样Start Tag Chunk结构我们就分析完了,有些细节没有说,比如属性值不同的type的表现形式,大体上是差不多的,大家有兴趣可以自己研究一下。

关注公众号:BennuCTech,获取更多干货!
分类:
Android
标签: