Android应用安全防护和逆向分析-基础篇5-6

主要描述AndroidManifest.xml和resourec.arsc这两个android文件

Android应用安全防护和逆向分析-基础篇5-6

一、 基础篇⑤-⑥

这两章主要描述AndroidManifest.xml和resourec.arsc这两个android文件。内容不是很多,下面是两章的笔记。

第五章 AndroidManifest.xml格式解析

img

AndroidManifest.xml

AndroidManifest.xml文件格式图

头部信息

  • 文件魔数:4bytes。

  • 文件大小:4bytes。

  • Chunk内容 头部相同(ChunkType(4bytes)、ChunkSize(4bytes))。

    • Sting Chunk :主要用于存放AndroidManifest.xml文件中所有的字符串信息。

      • ChunkType:类型,固定4bytes(0x001C001)。

      • ChunkSize:大小,4bytes。

      • StringCount:字符串的个数 ,4bytes。

      • StyleCount :样式的个数,4bytes。

      • Unknown :位置区域。4bytes。

      • StringPoolOffset :字符串池的偏移值。4bytes。偏移值相对于StringChunk头部的位置。

      • StylePoolOffset : 样式池的偏移值。4bytes。没有Style可忽略。

      • StringOffsets :每一个字符串的偏移值,大小为StringChunk*4。

      • StyleOffsets:每个样式的偏移值,大小为StyleChunk*4。

      如何读取这个文件?

    • Resourceld Chunk :主要用来存放AndroidManifest 中用到的系统属性值对应的资源ID

      • ChunkType:类型,固定4bytes(0x00080108)。

      • ChunkSize:大小,4bytes。

      • ResourceIds : 内容,大小为Resourceld Chunk大小除以4减去头部的8字节。

      解析?

    • Start Namespace Chunk:主要包含了AndroidMaifest文件中的命名空间的内容,android中的xml都是采用Schema格式(两种格式DTD和Schema)的,所有肯定有Prefix和URI。

      • Chunk Type:类型,固定4bytes。(0x00100100)。

      • Chunk Size:大小,4bytes。

      • Line Number :AndroidMaifest文件中行号,4bytes。

      • Unknown:未知区域,4bytes。

      • Prefix:命名空间的前缀(在字符串中的索引值),eg:android

      • Uri:命名空间的URI(在字符串中的索引值),eg:http://schemas.android.com/apk/res/android

    • Start Tag Chunk:AndroidMaifest.xml的标签信息,最核心的内容,也是最复杂的内容。

      • Chunk Type:类型,固定4bytes。(0x00100102)。

      • Chunk Size:大小,4bytes。

      • Line Number :对应AndroidMaifest中的行号,4bytes。

      • Unknown:未知区域,4bytes。

      • Namespace Uri :命名空间的Uri,4bytes。

      • Name:标签名称(在字符串中的索引值),4bytes。

      • Flags:标签的类型,4bytes。eg:是开始标签还是结束标签?

      • Attributes Counk:便签中包含的属性的个数,4bytes。

      • Class Attribute:标签包含的类属性,4bytes。

      • Attributes :属性内容,每个属性算是一个Entry,Entry是一个大小5的字节数组[Namespace,URI,Name,ValueString,Data],大小为”属性个数* 5 *4"个字节。

AXMLPrinter工具

aapt 工具

第六章 resourec.arsc文件格式解析

资源文件id格式

img

img

resourec.arsc文件格式

数据结构

上图

  • 头部信息

    resourec.arsc文件格式由一系列chunk组成,每一个chunk均包含一个ResChunk_header

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    
    public class ResChunkHeader{
        public short type;  //当前chunk的类型
        public short headerSize; //当前chunk的头部大小
        public int size;  //当前chunk的大小
    
        public int getHeaderSize(){
        	return 2+2+4    
        }
        @Override
        public String toString(){
            return "type:"+Utils.bytesToHexString(
            Utils.int2Byte(type))+",headerSize:"+headerSize+",size:"+size;
        }
    }
    
  • 资源索引表的头部信息

    resourec.arsc的第一个结构,结构描述了Resource.arsc文件的大小和资源包数量:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    
    public class ResTableHeader {
    
    	public ResChunkHeader header;  //就是标准的Chunk头部信息格式
    	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;
    	}
    
    }
    

    img

  • 资源项的值字符串资源池接着头部的的是两个偏移数组,分别是字符串偏移数组和字符串样式偏移数组。这两个偏移数组的大小分别等于stringCount和styleCount的值,而每一个元素的类型都是无符号整型。整个字符中资源池结构如下。

    包含了所有在资源包里面定义的资源项的值字符串,结构如下:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    
    public class ResStringPoolHeader {
    
    	public ResChunkHeader header;  //标准的Chunk头部信息结构
    	public int stringCount;  //字符串的个数
    	public int styleCount;  //字符串样式的个数
    
    	public final static int SORTED_FLAG = 1;
    	public final static int UTF8_FLAG = (1<<8);
    
    	public int flags;  //字符串的属性,可取值包括0x000(UTF-16),0x001(字符串经过排序)、0X100(UTF-8)和他们的组合值
    	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;
    	}
    
    }
    

    img

    字符串资源池中的字符串前两个字节为字符串长度,长度计算方法如下:

    len = (((hbyte & 0x7F) << 8)) | lbyte;

    如果字符串编码格式为UTF-8则字符串以0X00作为结束符,UTF-16则以0X0000作为结束符。

  • Package数据块

    这个数据块记录编译包的元数据,头部信息如下:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    
    public class ResTablePackage {
    
    	public ResChunkHeader header;  //Chunk的头部信息数据结构
    	public int id;  //包的ID,等于Package Id,一般用户包的值Package Id为0X7F,系统资源包的Package Id为0X01;
    	public char[] name = new char[128]; //包名
    	public int typeStrings;  //类型字符串资源池相对头部的偏移
    	public int lastPublicType;  //最后一个导出的Public类型字符串在类型字符串资源池中的索引,目前这个值设置为类型字符串资源池的元素个数。在解析的过程中没发现他的用途
    	public int keyStrings;  //资源项名称字符串相对头部的偏移
    	public int lastPublicKey; // 最后一个导出的Public资源项名称字符串在资源项名称字符串资源池中的索引,目前这个值设置为资源项名称字符串资源池的元素个数。在解析的过程中没发现他的用途
    
    	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;
    	}
    
    }
    

    img

  • 类型规范数据块

    用来描述资源项的配置差异性。每一种类型都对应有一个类型规范数据块。

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    
    public class ResTableTypeSpec {
    
    	public final static int SPEC_PUBLIC = 0x40000000;
    
    	public ResChunkHeader header;  //Chunk的头部信息结构
    	public byte id;  //标识资源的Type ID,Type ID是指资源的类型ID。资源的类型有animator、anim、color、drawable、layout、menu、raw、string和xml等等若干种,每一种都会被赋予一个ID。
    	public byte res0;  //保留,始终为0
    	public short res1;  //保留,始终为0
    	public int entryCount;  //等于本类型的资源项个数,指名称相同的资源项的个数。
    
    	public ResTableTypeSpec(){
    		header = new ResChunkHeader();
    	}
    
    	@Override
    	public String toString(){
    		return "header:"+header.toString()+",id:"+id+",res0:"+res0+",res1:"+res1+",entryCount:"+entryCount;
    	}
    
    }
    
  • 资源类型项数据块

    描述资源项的具体信息,名称、值、配置等信息

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    
    public class ResTableType {
    
    	public ResChunkHeader header;  //Chunk的头部信息结构
    
    	public final static int NO_ENTRY = 0xFFFFFFFF;
    
    	public byte id;  //标识资源的Type ID
    	public byte res0;  //保留,始终为0
    	public short res1;  //保留,始终为0
    	public int entryCount;  //等于本类型的资源项个数,指名称相同的资源项的个数。
    	public int entriesStart;  //等于资源项数据块相对头部的偏移值。
    
    	public ResTableConfig resConfig;  //指向一个ResTable_config,用来描述配置信息,地区,语言,分辨率等
    
    	public ResTableType(){
    		header = new ResChunkHeader();
    		resConfig = new ResTableConfig();
    	}
    
    	public int getSize(){
    		return header.getHeaderSize() + 1 + 1 + 2 + 4 + 4;
    	}
    
    	@Override
    	public String toString(){
    		return "header:"+header.toString()+",id:"+id+",res0:"+res0+",res1:"+res1+",entryCount:"+entryCount+",entriesStart:"+entriesStart;
    	}
    
    }
    

    ResTable_type后接着是一个大小为entryCount的uint32_t数组,每一个数组元素都用来描述一个资源项数据块的偏移位置。 紧跟在这个偏移数组后面的是一个大小为entryCount的ResTable_entry数组,每一个数组元素都用来描述一个资源项的具体信息。ResTable_entry的结构如下:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    
    public class ResTableEntry {
    
    	public final static int FLAG_COMPLEX = 0x0001;
    	public final static int FLAG_PUBLIC = 0x0002;
    
    	public short size;
    	public short flags;
    
    	public ResStringPoolRef key;
    
    	public ResTableEntry(){
    		key = new ResStringPoolRef();
    	}
    
    	public int getSize(){
    		return 2+2+key.getSize();
    	}
    
    	@Override
    	public String toString(){
    		return "size:"+size+",flags:"+flags+",key:"+key.toString()+",str:"+ParseResourceUtils.getKeyString(key.index);
    	}
    
    }
    

    ResTable_entry根据flags的不同,后面跟随的数据也不相同,如果flags此位为1,则ResTable_entry是ResTable_map_entry,ResTable_map_entry继承自ResTable_entry,其结构如下。

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    
    public class ResTableMapEntry extends ResTableEntry{
    
    	public ResTableRef parent;
    	public int count;
    
    	public ResTableMapEntry(){
    		parent = new ResTableRef();
    	}
    
    	@Override
    	public int getSize(){
    		return super.getSize() + parent.getSize() + 4;
    	}
    
    	@Override
    	public String toString(){
    		return super.toString() + ",parent:"+parent.toString()+",count:"+count;
    	}
    
    }
    

    ResTable_map_entry其后跟随则count个ResTable_map类型的数组,ResTable_map的结构如下:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    
    package com.wjdiankong.parseresource.type;
    
    /**
     struct ResTable_map
     {
         //bag资源项ID
         ResTable_ref name;
         //bag资源项值
         Res_value value;
     };
     * @author i
     *
     */
    public class ResTableMap {
    
    	public ResTableRef name;
    	public ResValue value;
    
    	public ResTableMap(){
    		name = new ResTableRef();
    		value = new ResValue();
    	}
    
    	public int getSize(){
    		return name.getSize() + value.getSize();
    	}
    
    	@Override
    	public String toString(){
    		return name.toString()+",value:"+value.toString();
    	}
    
    }
    

    如果flags此位为0,则ResTable_entry其后跟随的是一个Res_value,描述一个普通资源的值,Res_value结构如下。

      1
      2
      3
      4
      5
      6
      7
      8
      9
     10
     11
     12
     13
     14
     15
     16
     17
     18
     19
     20
     21
     22
     23
     24
     25
     26
     27
     28
     29
     30
     31
     32
     33
     34
     35
     36
     37
     38
     39
     40
     41
     42
     43
     44
     45
     46
     47
     48
     49
     50
     51
     52
     53
     54
     55
     56
     57
     58
     59
     60
     61
     62
     63
     64
     65
     66
     67
     68
     69
     70
     71
     72
     73
     74
     75
     76
     77
     78
     79
     80
     81
     82
     83
     84
     85
     86
     87
     88
     89
     90
     91
     92
     93
     94
     95
     96
     97
     98
     99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    
    public class ResValue {
    
    	//dataType字段使用的常量
    	public final static int TYPE_NULL = 0x00;
    	public final static int TYPE_REFERENCE = 0x01;
    	public final static int TYPE_ATTRIBUTE = 0x02;
    	public final static int TYPE_STRING = 0x03;
    	public final static int TYPE_FLOAT = 0x04;
    	public final static int TYPE_DIMENSION = 0x05;
    	public final static int TYPE_FRACTION = 0x06;
    	public final static int TYPE_FIRST_INT = 0x10;
    	public final static int TYPE_INT_DEC = 0x10;
    	public final static int TYPE_INT_HEX = 0x11;
    	public final static int TYPE_INT_BOOLEAN = 0x12;
    	public final static int TYPE_FIRST_COLOR_INT = 0x1c;
    	public final static int TYPE_INT_COLOR_ARGB8 = 0x1c;
    	public final static int TYPE_INT_COLOR_RGB8 = 0x1d;
    	public final static int TYPE_INT_COLOR_ARGB4 = 0x1e;
    	public final static int TYPE_INT_COLOR_RGB4 = 0x1f;
    	public final static int TYPE_LAST_COLOR_INT = 0x1f;
    	public final static int TYPE_LAST_INT = 0x1f;
    
    	public static final int
        COMPLEX_UNIT_PX			=0,
        COMPLEX_UNIT_DIP		=1,
        COMPLEX_UNIT_SP			=2,
        COMPLEX_UNIT_PT			=3,
        COMPLEX_UNIT_IN			=4,
        COMPLEX_UNIT_MM			=5,
    	COMPLEX_UNIT_SHIFT		=0,
        COMPLEX_UNIT_MASK		=15,
        COMPLEX_UNIT_FRACTION	=0,
        COMPLEX_UNIT_FRACTION_PARENT=1,
        COMPLEX_RADIX_23p0		=0,
        COMPLEX_RADIX_16p7		=1,
        COMPLEX_RADIX_8p15		=2,
        COMPLEX_RADIX_0p23		=3,
        COMPLEX_RADIX_SHIFT		=4,
        COMPLEX_RADIX_MASK		=3,
        COMPLEX_MANTISSA_SHIFT	=8,
        COMPLEX_MANTISSA_MASK	=0xFFFFFF;
    
    
    	public short size;  //ResValue的头部大小
    	public byte res0;  //保留,始终为0
    	public byte dataType;  //数据的类型,可以从上面的枚举类型中获取
    	public int data;  //数据对应的索引
    
    	public int getSize(){
    		return 2 + 1 + 1 + 4;
    	}
    
    	public String getTypeStr(){
    		switch(dataType){
    			case TYPE_NULL:
    				return "TYPE_NULL";
    			case TYPE_REFERENCE:
    				return "TYPE_REFERENCE";
    			case TYPE_ATTRIBUTE:
    				return "TYPE_ATTRIBUTE";
    			case TYPE_STRING:
    				return "TYPE_STRING";
    			case TYPE_FLOAT:
    				return "TYPE_FLOAT";
    			case TYPE_DIMENSION:
    				return "TYPE_DIMENSION";
    			case TYPE_FRACTION:
    				return "TYPE_FRACTION";
    			case TYPE_FIRST_INT:
    				return "TYPE_FIRST_INT";
    			case TYPE_INT_HEX:
    				return "TYPE_INT_HEX";
    			case TYPE_INT_BOOLEAN:
    				return "TYPE_INT_BOOLEAN";
    			case TYPE_FIRST_COLOR_INT:
    				return "TYPE_FIRST_COLOR_INT";
    			case TYPE_INT_COLOR_RGB8:
    				return "TYPE_INT_COLOR_RGB8";
    			case TYPE_INT_COLOR_ARGB4:
    				return "TYPE_INT_COLOR_ARGB4";
    			case TYPE_INT_COLOR_RGB4:
    				return "TYPE_INT_COLOR_RGB4";
    		}
    		return "";
    	}
    
    	/*public String getDataStr(){
    		if(dataType == TYPE_STRING){
    			return ParseResourceUtils.getResString(data);
    		}else if(dataType == TYPE_FIRST_COLOR_INT){
    			return Utils.bytesToHexString(Utils.int2Byte(data));
    		}else if(dataType == TYPE_INT_BOOLEAN){
    			return data==0 ? "false" : "true";
    		}
    		return data+"";
    	}*/
    
    	public String getDataStr() {
    		if (dataType == TYPE_STRING) {
    			return ParseResourceUtils.getResString(data);
    		}
    		if (dataType == TYPE_ATTRIBUTE) {
    			return String.format("?%s%08X",getPackage(data),data);
    		}
    		if (dataType == TYPE_REFERENCE) {
    			return String.format("@%s%08X",getPackage(data),data);
    		}
    		if (dataType == TYPE_FLOAT) {
    			return String.valueOf(Float.intBitsToFloat(data));
    		}
    		if (dataType == TYPE_INT_HEX) {
    			return String.format("0x%08X",data);
    		}
    		if (dataType == TYPE_INT_BOOLEAN) {
    			return data!=0?"true":"false";
    		}
    		if (dataType == TYPE_DIMENSION) {
    			return Float.toString(complexToFloat(data))+
    				DIMENSION_UNITS[data & COMPLEX_UNIT_MASK];
    		}
    		if (dataType == TYPE_FRACTION) {
    			return Float.toString(complexToFloat(data))+
    				FRACTION_UNITS[data & COMPLEX_UNIT_MASK];
    		}
    		if (dataType >= TYPE_FIRST_COLOR_INT && dataType <= TYPE_LAST_COLOR_INT) {
    			return String.format("#%08X",data);
    		}
    		if (dataType >= TYPE_FIRST_INT && dataType <= TYPE_LAST_INT) {
    			return String.valueOf(data);
    		}
    		return String.format("<0x%X, type 0x%02X>",data, dataType);
    	}
    
    	private static String getPackage(int id) {
    		if (id>>>24==1) {
    			return "android:";
    		}
    		return "";
    	}
    
    	public static float complexToFloat(int complex) {
    		return (float)(complex & 0xFFFFFF00)*RADIX_MULTS[(complex>>4) & 3];
    	}
    
    	private static final float RADIX_MULTS[]={
    		0.00390625F,3.051758E-005F,1.192093E-007F,4.656613E-010F
    	};
    
    	private static final String DIMENSION_UNITS[]={
    		"px","dip","sp","pt","in","mm","",""
    	};
    
    	private static final String FRACTION_UNITS[]={
    		"%","%p","","","","","",""
    	};
    
    	@Override
    	public String toString(){
    		return "size:"+size+",res0:"+res0+",dataType:"+getTypeStr()+",data:"+getDataStr();
    	}
    
    }
    

    以上代码来自于书的原作者的博客:https://blog.csdn.net/jiangwei0910410003/article/details/50628894

    博客里还有如何解析操作,留看。

总结

这两章讲的还是挺详细的,可以留着备用查阅,这两个文件都能加以混淆来保护应用,所以还是挺重要的。

(*╹▽╹*)
Built with Hugo
主题 StackJimmy 设计