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

Android逆向及安全》系列专栏:https://blog.csdn.net/column/manage.html?alias=23485


1、xml结构

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


简单说一下大概的结构:

  • 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中,这也是我们这篇文章讨论的重点。


2、Start Tag Chunk Header

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

02011000 74000000 00000000 FFFFFFFF FFFFFFFF 09000000 14001400 04000100 

00000000 0D000000 03000000 FFFFFFFF 08000001 3800077F 0D000000 05000000 

FFFFFFFF 08000010 FEFFFFFF 0D000000 06000000 FFFFFFFF 08000010 FEFFFFFF 

0D000000 07000000 FFFFFFFF 08000001 5300067F

下面我们一步步解析它。

从上面的图中可以清楚的看到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的默认值。


3、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。


4、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。


5、总结

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


Android逆向及安全》系列专栏:https://blog.csdn.net/column/manage.html?alias=23485


猜你喜欢

转载自blog.csdn.net/chzphoenix/article/details/80570195