分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow
也欢迎大家转载本篇文章。分享知识,造福人民,实现我们中华民族伟大复兴!
一、前言
快过年了,先提前祝贺大家新年快乐,这篇文章也是今年最后一篇了。今天我们继续来看逆向的相关知识,前篇文章中我们介绍了如何解析Android中编译之后的AndroidManifest.xml文件格式:http://blog.csdn.net/jiangwei0910410003/article/details/50568487
当时我说到其实后续还要继续介绍两个文件一个是resource.arsc和classes.dex,今天我们就来看看resource.arsc文件个格式解析,classes.dex的解析要等年后了。
二、准备工作
我们在使用apktool工具进行反编译的时候,会发现有一个:res/values/public.xml这个文件:
我们查看一下public.xml文件内容:
看到了,这个文件就保存了apk中所有的类型和对应的id值,我们看到这里面的每个条目内容都是:
type:类型名
name:资源名
id:资源的id
类型的话有这么几种:
drawable,menu,layout,string,attr,color,style等
所以我们会在反编译之后的文件夹中看到这几个类型的文件xml内容。
上面我们介绍了如何使用apktool反编译之后的内容,下面我们要做的事情就是如何来解析resource.arsc文件,解析出这些文件。
我们解压一个apk得到对应的resource.arsc文件。按照国际惯例,每个文件的格式描述都是有对应的数据结构的,resource也不例外:frameworks\base\include\androidfw\ResourceTypes.h,这就是resource中定义的所有数据结构。
下面再来看一张神图(网上盗的图!哈哈):
每次我们在解析文件的时候都会有一张神图,我们按照这张图来进行数据解析工作。
三、数据结构定义
这个是项目工程结构,我们看到定义了很多的数据结构
第一、头部信息
Resources.arsc文件格式是由一系列的chunk构成,每一个chunk均包含如下结构的ResChunk_header,用来描述这个chunk的基本信息
package com.wjdiankong.parseresource.type;import com.wjdiankong.parseresource.Utils;/**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;}; * @author i * */public class ResChunkHeader { public short type; public short headerSize; public int size; public int getHeaderSize(){ return 2+2+4; } @Override public String toString(){ return "type:"+Utils.bytesToHexString(Utils.int2Byte(type))+",headerSize:"+headerSize+",size:"+size; }}
type:是当前这个chunk的类型
headerSize:是当前这个chunk的头部大小
size:是当前这个chunk的大小
第二、资源索引表的头部信息
Resources.arsc文件的第一个结构是资源索引表头部。其结构如下,描述了Resources.arsc文件的大小和资源包数量。
package com.wjdiankong.parseresource.type;/**struct ResTable_header{ struct ResChunk_header header; // The number of ResTable_package structures. uint32_t packageCount;}; * @author i * */public class ResTableHeader { public ResChunkHeader header; public int packageCount; public ResTableHeader(){ header = new ResChunkHeader(); } public int getHeaderSize(){ return header.getHeaderSize() + 4; } @Override public String toString(){ return "header:"+header.toString()+"\n" + "packageCount:"+packageCount; } }
header:就是标准的Chunk头部信息格式
packageCount:被编译的资源包的个数
Android中一个apk可能包含多个资源包,默认情况下都只有一个就是应用的包名所在的资源包
实例:
图中蓝色高亮的部分就是资源索引表头部。通过解析,我们可以得到如下信息,这个chunk的类型为RES_TABLE_TYPE,头部大小为0XC,整个chunk的大小为1400252byte,有一个编译好的资源包。
第三、资源项的值字符串资源池
紧跟着资源索引表头部的是资源项的值字符串资源池,这个字符串资源池包含了所有的在资源包里面所定义的资源项的值字符串,字符串资源池头部的结构如下。
package com.wjdiankong.parseresource.type;/**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;}; * @author i * */public class ResStringPoolHeader { public ResChunkHeader header; public int stringCount; public int styleCount; public final static int SORTED_FLAG = 1; public final static int UTF8_FLAG = (1<<8); public int flags; public int stringsStart; public int stylesStart; public ResStringPoolHeader(){ header = new ResChunkHeader(); } public int getHeaderSize(){ return header.getHeaderSize() + 4 + 4 + 4 + 4 + 4; } @Override public String toString(){ return "header:"+header.toString()+"\n" + "stringCount:"+stringCount+",styleCount:"+styleCount+",flags:"+flags+",stringStart:"+stringsStart+",stylesStart:"+stylesStart; } }
header:标准的Chunk头部信息结构
stringCount:字符串的个数
styleCount:字符串样式的个数
flags:字符串的属性,可取值包括0x000(UTF-16),0x001(字符串经过排序)、0X100(UTF-8)和他们的组合值
stringStart:字符串内容块相对于其头部的距离
stylesStart:字符串样式块相对于其头部的距离
实例:
图中绿色高亮的部分就是字符串资源池头部,通过解析,我们可以得到如下信息,这个chunk的类型为RES_STRING_POOL_TYPE,即字符串资源池。头部大小为0X1C,整个chunk的大小为369524byte,有8073条字符串,72个字符串样式,为UTF-8编码,无排序,字符串内容块相对于此chunk头部的偏移为0X7F60,字符串样式块相对于此chunk头部的偏移为0X5A054。
紧接着头部的的是两个偏移数组,分别是字符串偏移数组和字符串样式偏移数组。这两个偏移数组的大小分别等于stringCount和styleCount的值,而每一个元素的类型都是无符号整型。整个字符中资源池结构如下。
字符串资源池中的字符串前两个字节为字符串长度,长度计算方法如下。另外如果字符串编码格式为UTF-8则字符串以0X00作为结束符,UTF-16则以0X0000作为结束符。
len = (((hbyte & 0x7F) << 8)) | lbyte;
字符串与字符串样式有一一对应的关系,也就是说如果第n个字符串有样式,则它的样式描述位于样式块的第n个元素。 字符串样式的结构包括如下两个结构体,ResStringPool_ref和ResStringPool_span。 一个字符串可以对应多个ResStringPool_span和一个ResStringPool_ref。ResStringPool_span在前描述字符串的样式,ResStringPool_ref在后固定值为0XFFFFFFFF作为占位符。样式块最后会以两个值为0XFFFFFFFF的ResStringPool_ref作为结束。
package com.wjdiankong.parseresource.type;/** struct ResStringPool_ref { uint32_t index; }; * @author i * */public class ResStringPoolRef { public int index; public int getSize(){ return 4; } @Override public String toString(){ return "index:"+index; } }
实例:
图中蓝色高亮的部分就是样式内容块,按照格式解析可以得出,第一个字符串和第二字符串无样式,第三个字符串第4个字符到第7个字符的位置样式为字符串资源池中0X1F88的字符,以此类推。
第四、Package数据块
接着资源项的值字符串资源池后面的部分就是Package数据块,这个数据块记录编译包的元数据,头部结构如下:
package com.wjdiankong.parseresource.type;/**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. char16_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;}; * @author i * */public class ResTablePackage { public ResChunkHeader header; public int id; public char[] name = new char[128]; public int typeStrings; public int lastPublicType; public int keyStrings; public int lastPublicKey; public ResTablePackage(){ header = new ResChunkHeader(); } @Override public String toString(){ return "header:"+header.toString()+"\n"+",id="+id+",name:"+name.toString()+",typeStrings:"+typeStrings+",lastPublicType:"+lastPublicType+",keyStrings:"+keyStrings+",lastPublicKey:"+lastPublicKey; }}
header:Chunk的头部信息数据结构
id:包的ID,等于Package Id,一般用户包的值Package Id为0X7F,系统资源包的Package Id为0X01;这个值很重要的,在后面我们构建前面说到的那个public.xml中的id值的时候需要用到。
name:包名
typeString:类型字符串资源池相对头部的偏移
lastPublicType:最后一个导出的Public类型字符串在类型字符串资源池中的索引,目前这个值设置为类型字符串资源池的元素个数。在解析的过程中没发现他的用途
keyStrings:资源项名称字符串相对头部的偏移
lastPublicKey:最后一个导出的Public资源项名称字符串在资源项名称字符串资源池中的索引,目前这个值设置为资源项名称字符串资源池的元素个数。在解析的过程中没发现他的用途
实例:
图中紫色高亮的部分就是ResTable_package,按照上面的格式解析数据,我们可以得出,此Chunk的Type为RES_TABLE_PACKAGE_TYPE,头部大小为0X120,整个chunk的大小为1030716byte,Package Id为0X7F,包名称为co.runner.app,类型字符串资源池距离头部的偏移是0X120,有15条字符串,资源项名称字符串资源池0X1EC,有6249条字符串。
Packege数据块的整体结构,可以用以下的示意图表示:
其中Type String Pool和Key String Pool是两个字符串资源池,结构和资源项的值字符串资源池结构相同,分别对应类型字符串资源池和资源项名称字符串资源池。
再接下来的结构体可能是类型规范数据块或者类型资源项数据块,我们可以通过他们的Type来识别,类型规范数据块的Type为RES_TABLE_TYPE_SPEC_TYPE,类型资源项数据块的Type为RES_TABLE_TYPE_TYPE。
第五、类型规范数据块
类型规范数据块用来描述资源项的配置差异性。通过这个差异性描述,我们就可以知道每一个资源项的配置状况。知道了一个资源项的配置状况之后,Android资源管理框架在检测到设备的配置信息发生变化之后,就可以知道是否需要重新加载该资源项。类型规范数据块是按照类型来组织的,也就是说,每一种类型都对应有一个类型规范数据块。其数据块头部结构如下。
packag