Apk解析之 —— AndroidManifest.xml
Manifest文件结构
一. 头部信息
- 文件魔数:4bytes 0x00080003
- 文件大小:4bytes
二、String Chunk
这个Chunk主要存放的是AndroidManifest文件中所有的字符串信息
字段名 | 含义 | 长度 |
---|---|---|
ChunkType | StringChunk的类型 | 固定四个字节:0x001C0001 |
ChunkSize | StringChunk的大小 | 四个字节 |
StringCount | StringChunk中字符串的个数 | 四个字节 |
StyleCount | StringChunk中样式的个数,在实际解析过程中,这个值一直是0x00000000 | 四个字节 |
Unknown | 位置区域,在解析的过程中需要略过 | 四个字节 |
StringPoolOffset | 字符串池的偏移值,这个偏移值是相对于StringChunk的头部位置 | 四个字节 |
StylePoolOffset | 样式池的偏移值,这里没有Style,所以这个字段可忽略 | 四个字节 |
StringOffsets | 每个字符串的偏移值 | 大小应该是:StringCount*4个字节 |
SytleOffsets | 每个样式的偏移值 | 大小应该是SytleCount*4个字节 |
字符串是utf-16的宽字符,每个字符占2个字节,每个字符串块的前两个字节标识字符串的长度,以0x0000结尾,长度不包括尾部的结束符0x0000。
三、ResourceIdChunk
这个Chunk主要是存放的是AndroidManifest中用到的系统属性值
对应的资源Id(0x01xxxxxx),比如android:versionCode中的versionCode属性
字段名 | 含义 | 长度 |
---|---|---|
ChunkType | StringChunk的类型 | 固定四个字节:0x00080180 |
ChunkSize | StringChunk的大小 | 四个字节 |
ResourceIds | 资源ID列表 | (chunkSize-8)/4 * 4bytes |
Package ID相当于是一个命名空间,限定资源的来源,0x01属于系统资源命名空间,0x7f属于应用程序资源命名空间,所有位于[0x01, 0x7f]之间的Package ID都是合法的,而在这个范围之外的都是非法的Package ID。
Type ID是指资源的类型ID。资源的类型有animator、anim、color、drawable、layout、menu、raw、string和xml等等若干种,每一种都会被赋予一个ID。
Entry ID是指每一个资源在其所属的资源类型中所出现的次序。注意,不同类型的资源的Entry ID有可能是相同的,但是由于它们的类型不同,我们仍然可以通过其资源ID来区别开来。
系统资源对应id的xml文件所在路径:frameworks\base\core\res\res\values\public.xml
,版本越高的Sdk定义的ID项越多。
ID | 资源类型type |
---|---|
0x0101xxxx | attr |
0x0102xxxx | id |
0x0103xxxx | style |
0x0104xxxx | string |
0x0105xxxx | dimen |
0x0106xxxx | color |
0x0107xxxx | array |
0x0108xxxx | drawable |
0x0109xxxx | layout |
0x010axxxx | anim |
0x010bxxxx | animator |
0x010cxxxx | interpolator |
0x010dxxxx | mipmap |
0x010exxxx | integer |
0x010fxxxx | transition |
0x0110xxxx | raw |
四、StartNamespaceChunk
这个Chunk主要包含AndroidManifest文件中的命令空间的内容,Android中的xml都是采用Schema格式的,所以肯定有Prefix和Uri的。(xml格式有两种:DTD和Schema)
字段名 | 含义 | 长度 |
---|---|---|
ChunkType | Chunk的类型 | 固定四个字节:0x00100100 |
ChunkSize | Chunk的大小 | 四个字节 |
LineNumber | 在AndroidManifest文件中的行号 | 四个字节 |
Unknown | 未知区域 | 四个字节 |
Prefix | 命名空间的前缀(在字符串中的索引值),比如:android | 四个字节 |
Uri | 命名空间的uri(在字符串中的索引值):比如:http://schemas.android.com/apk/res/android | 四个字节 |
五、StratTagChunk
这个Chunk主要是存放了AndroidManifest.xml中的标签信息了,也是最核心的内容,当然也是最复杂的内容
字段名 | 含义 | 长度 |
---|---|---|
ChunkType | Chunk的类型 | 固定四个字节:0x00100102 |
ChunkSize | Chunk的大小 | 四个字节 |
LineNumber | 在AndroidManifest文件中的行号 | 四个字节 |
Unknown | 未知区域 | 四个字节 |
NamespaceUri | 这个标签用到的命名空间的Uri,比如用到了android这个前缀,那么就需要用http://schemas.android.com/apk/res/android 这个Uri去获取 | 四个字节 |
Name | 标签名称(在字符串中的索引值) | 四个字节 |
Flags | 标签的类型,比如是开始标签还是结束标签等 | 四个字节 |
AttributeCount | 标签包含的属性个数 | 四个字节 |
ClassAtrribute | 标签包含的类属性(没什么用) | 四个字节 |
Atrributes | 属性内容,每个属性算是一个Entry,这个Entry固定大小是大小为5的字节数组 | 四个字节 |
Entry的结构:
字段名 | 含义 | 长度 |
---|---|---|
NamespaceUri | 属性命名空间的Uri下标 | 四个字节 |
Name | 属性名称 | 四个字节 |
valueString | 如果属性是String类型,这个字段保存常量池的下标 | 四个字节 |
type | 标签类型,需要右移24位再使用 | 四个字节 |
data | 标签的数据 | 四个字节 |
注意Entry.type不同,对应的Entry.data含义也不同,这些类型定义可以在AOSP源码的framework/base/include/androidfw/ResourceTypes.h
头文件中找到:
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.
根据Entry.type获取Entry.data的数据:
public static String getAttributeData(AttributeEntry entry, StringChunk stringChunk) {
String attrData;
if (entry.type == TYPE_REFERENCE) {
attrData = String.format("@%s%08x", getPackage(entry.data), entry.data);
} else if (entry.type == TYPE_ATTRIBUTE) {
attrData = String.format("?%s%08x", getPackage(entry.data), entry.data);
} else if (entry.type == TYPE_STRING) {
attrData = stringChunk.getString(entry.data);
} else if (entry.type == TYPE_FLOAT) {
attrData = String.valueOf(Float.intBitsToFloat((int)entry.data));
} else if (entry.type == TYPE_DIMENSION) {
attrData = Float.toString(Float.intBitsToFloat((int) entry.data)) + getDimenUnit(entry.data);
} else if (entry.type == TYPE_FRACTION) {
attrData = Float.toString(Float.intBitsToFloat((int) entry.data)) + getFractionUnit(entry.data);
} else if (entry.type == TYPE_DYNAMIC_REFERENCE) {
attrData = "TYPE_DYNAMIC_REFERENCE"; // To be continue
} else if (entry.type == TYPE_INT_DEC) {
attrData = String.format("%d", entry.data);
} else if (entry.type == TYPE_INT_HEX) {
attrData = String.format("0x%08x", entry.data);
} else if (entry.type == TYPE_INT_BOOLEAN) {
attrData = entry.data == 0 ? "false" : "true";
} else if (entry.type == TYPE_INT_COLOR_ARGB8) {
attrData = String.format("#%08x", entry.data);
} else if (entry.type == TYPE_INT_COLOR_RGB8) {
attrData = String.format("#ff%06x", 0xffffff & entry.data);
} else if (entry.type == TYPE_INT_COLOR_ARGB4) {
attrData = String.format("#%04x", 0xffff & entry.data);
} else if (entry.type == TYPE_INT_COLOR_RGB4) {
attrData = String.format("#f%03x", 0x0fff & entry.data);
} else {
attrData = String.format("<0x%08x, type 0x%08x>", entry.data, entry.type);
}
return attrData;
}
六、EndTagChunk
EndTagChunk的结构和StartTagChunk类似,只是少了
字段名 | 含义 | 长度 |
---|---|---|
ChunkType | Chunk的类型 | 固定四个字节:0x00100103 |
ChunkSize | Chunk的大小 | 四个字节 |
LineNumber | 在AndroidManifest文件中的行号 | 四个字节 |
Unknown | 未知区域 | 四个字节 |
NamespaceUri | 这个标签用到的命名空间的Uri,比如用到了android这个前缀,那么就需要用http://schemas.android.com/apk/res/android 这个Uri去获取 | 四个字节 |
Name | 标签名称(在字符串中的索引值) | 四个字节 |
七、EndNamespaceChunk
EndNamespaceChunk与StartNamespaceChunk对应,结构也完全相同。
字段名 | 含义 | 长度 |
---|---|---|
ChunkType | Chunk的类型 | 固定四个字节:0x00100101 |
ChunkSize | Chunk的大小 | 四个字节 |
LineNumber | 在AndroidManifest文件中的行号 | 四个字节 |
Unknown | 未知区域 | 四个字节 |
Prefix | 命名空间的前缀(在字符串中的索引值),比如:android | 四个字节 |
Uri | 命名空间的uri(在字符串中的索引值):比如:http://schemas.android.com/apk/res/android | 四个字节 |
八、格式化输出Xml文档
按读入的顺序遍历StratTagChunk和EndTagChunk就可以把文档对象格式化为Xml格式:
/**
* Convert MfFile object to XML string.
* @return xml
*/
public String toXmlString() {
StringBuilder builder = new StringBuilder(4096);
int depth = 0;
builder.append("<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"no\"?>\n");
for (TagChunk tagChunk : tagChunks) {
if (tagChunk instanceof StartTagChunk) {
builder.append(createStartTagXml((StartTagChunk) tagChunk, depth));
++depth;
} else if (tagChunk instanceof EndTagChunk) {
--depth;
builder.append(createEndTagXml((EndTagChunk) tagChunk, depth));
}
}
return builder.toString();
}
九、END
推荐阅读
-
Apk解析之 —— AndroidManifest.xml
-
Android逆向笔记 —— AndroidManifest.xml 文件格式解析
-
Android开发之使用PULL解析和生成XML
-
replugin源码解析之replugin-host-gradle(宿主的gradle插件)
-
手写一个简化版的tomcat服务器之tomcat工程目录解析
-
文件解析之CSV
-
JAVA解析Excel工具easyexcel之快速上手
-
C++11 中语法解析之虚函数与继承
-
Spring 核心思想 之反向控制概念解析(有例子) 博客分类: Spring iocspring反向控制
-
Spring 核心思想 之反向控制概念解析(有例子) 博客分类: Spring iocspring反向控制