Android逆向之旅---解析编译之后的Resource.arsc文件格式
一、前言
快过年了,先提前祝贺大家新年快乐,这篇文章也是今年最后一篇了。今天我们继续来看逆向的相关知识,前篇文章中我们介绍了如何解析Android中编译之后的AndroidManifest.xml文件格式
当时我说到其实后续还要继续介绍两个文件一个是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_s

