前段时间在bluebox的一份android安全pdf中看到一个AndroidManifest Ambiguity方案。该方案基于android系统解析AXML的一个特色:android在解析AXML的属性的时候,是经过该属性的res id号而非属性名定位的。所谓的AXML就是AndroidManifest.xml对应的二进制文件,APK包中存储的就是AXML。好比属性:linux
<public type="attr" name="name" id="0x01010003" />android
它的属性名为name,id号为0x01010003。git
该方案的大体原理以下图所示:github
我简要归纳一下:安全
咱们在axml(注意是axml不是AndroidManifest.xml)中添加一个属性,该属性的属性名是name,属性的值是some.class,属性的ID号为0。根据前文所述,android系统对于非法的res ID号是不会解析的。因此咱们添加这个无用的属性后,并不影响该APK的正常工做(上图左下角所示),可是对于apktool之类的逆向工具而言,他们却会对这个无用的属性进行解析(上图右下角所示)。因此,若是咱们进行重打包的话,apktool就会将该属性变动为一个ID号0x01010003的能够被系统解析的属性。这样形成的后果就是:因为咱们的APK中并无实现trap.class类,因此APK启动时会报错“there is no trap.class~~”。app
该PDF虽然提出了这个方案,但并无给出实现的代码(其实它就给了上面那张图~其它什么都木有了~),google也是空白。因此当我看懂原理以后,就想本身将它实现出来。哪知事情并无我想的那么简单~~工具
遇到的第一个挑战就是:网上居然搜不到AXML文件的格式!!!当时差点就放弃了,不事后来一想,既然apktool能解析AXML那就说明它是了解AXML的文件格式的,因此就上网搜索了一下解析AXML的各类解析代码,综合事后以为Claud大大的AXML Parser代码比较利于总结AXML的文件格式。因此就以该代码问蓝本总结了一下文件格式,以下表所示:性能
0x0~0x3 magic: 0x03000800固定值ui |
0x4~0x7 filesize: 文件总体大小google |
0x8~0xb StringTag: 字符串块开始标志,0x01001c00固定值 |
0xc~0xf StringChunkSize:字符串块大小 |
0x10~0x13 count of strings:字符串个数, |
0x14~0x17 count of styles: 类型个数 |
0x18~0x1b reserve field: 保留的,为0 |
0x1c~0x1f string的起始偏移值:注意,这个偏移值是相对于stringChunk而言的! |
0x20~0x23 styles的起始偏移值:同上 |
下面存储的就是n个连续的string的偏移值,每一个偏移值占4字节,须要注意的是,这个偏移值加上string的起始偏移值和0x8才是真正的偏移值!n的大小就是0x10~0x13的大小 |
而后就是n个连续的style的偏移值,同上~ |
String数据块 ........ |
Style数据块 ........ 注意:到这里,stringchunk就算是结束了 |
下面就是ResourceChunk了,里面保存的就是资源ID号 |
ResourceTag: 0x80010800 |
ResourceChunkSize: 资源ID块的大小 |
连续 ResourceChunkSize/4 -2个res id值。-2主要是除去上面的8字节resourceChunkHeader。 每一个res id占4字节 |
ResourceChunk结束 |
下面就是一些连续的chunk块了: |
CHUNK_STARTNS: doc开始标志,0x00011000 |
CHUNK SIZE: |
line number |
unknown, 0xffffffff |
下面就是一个namespace record结构体,简称NsRecord |
NsRecord->prefix |
NsRecord->uri, |
而后就是递归地进行chunk操做,由于一个命名空间里面每每含有不少子chunk |
CHUNK_TYPE:0x02011000->0x00100102为CHUNK_STARTTAG |
CHUNK_SIZE |
line number |
unknown, 0xffffffff |
current tag's namespace's uri |
当前tag的名字 所一个string索引值 |
flags, unknown usage |
当前标签含有的attr个数,注意最后结果要&0x0000ffff |
classAttribute, unknown usage |
下面就是连续的n个attribution chunk,attribution的结构体以下: /* attribute structure within tag */ typedef struct{ uint32_t uri; /* uri of its namespace index of strings*/ uint32_t name; /*属性名,索引值 index of strings */ uint32_t string; /* attribute value if type == ATTR_STRING ,索引值*/ uint32_t type; /* attribute type, == ATTR_* * / 注意该值须要右移24位 uint32_t data; /* attribute value, encoded on type */ } Attribute_t; |
依次类推 ........ |
了解了AXML的文件格式,咱们就能够想法进行属性插入了。不过在属性插入以前,咱们必须规划好具体地实施方案,由于它涉及到的东西并不算少。
1)首先,须要对属性结构体作进一步分析。它的格式以下:
/* attribute structure within tag */ typedef struct{ uint32_t uri; /* uri of its namespace index of strings*/ uint32_t name; /*属性名,索引值 index of strings */ uint32_t string; /* attribute value if type == ATTR_STRING ,索引值*/ uint32_t type; /* attribute type, == ATTR_* * / 注意该值须要右移24位 uint32_t data; /* attribute value, encoded on type */ } Attribute_t; |
重点是name, string, data。我提取出了一个AXML中属性片断,以下所示:
Offset 0 1 2 3 4 5 6 7 8 9 A B C D E F
0C 00 00 00 05 00 00 00 FF FF FF FF 08 00 00 01 01 00 06 7F
0C 00 00 00 06 00 00 00 FF FF FF FF 08 00 00 01 00 00 05 7F
0C 00 00 00[w1] 04 00 00 00[w2] 17 00 00 00[w3] 08 00 00 03 [w4] 17 00 00 00[w5]
[w1]uri:命名空间的URI,是string的索引值
[w2]name:属性名,也是一个string的索引值
[w3]string:若是属性type为ATTR_STRING的话,此值就是属性android:name="xxx",xxx在string的索引值。其他状况均为0xffffffff
[w4]type:属性的类型,对于android:name,类型值为0x03000008
[w5]data:属性的数据值,对于ATTR_STRING而言,它的值就是string的值。
能够发现,结构体里面并无一个叫作res ID的成员,那么系统又是如何获取某个属性的ID号的呢?原来这里的name成员是身兼两职,即做为属性名的一个string索引,又做为res ID的索引。好比这里name = 4,它对应StringChunk中的字符串为"name",对应ResourceChunk中的res ID 0x01010003。因此要插入一个属性名为name,ID号又为0的属性,咱们就必须新建一个string,该string的值为name,再新建一个res ID,值为0,且二者在各自Chunk区域的索引值是相等的(这是重点)。
2)其次,就是在StringChunk中string的对齐问题(最初被弄得脑洞大开~)。
AXML中几乎全部的成员都是uint32型的,除了使用UTF-16编码的string数据块以外。因此在加入string后必须对string数据块进行4字节对齐。而若是原AXML的string数据块已经进行过4字节对齐(即人为地填充了几个0x00)的话,咱们就须要注意UTF-16编码的最后一个string的第一个字节的大小并不包含这几个填充的0x00(这个字节表示该string所占用的字节数,详情可查阅UTF-16编码相关资料)。为了绕过烦人的对齐问题,咱们使用取巧的方式获取字符串的长度:
stringLen = stringChunkSize - stringOffset; //此时的stringLen确定是4字节对齐的 |
固然,这是在没有style的状况下,若是有的话,还得采起额外的操做(实现代码中有~)。为了简便,我是直接将添加的string加在这个对齐后的字符串以后的,这样就只须要考虑添加的字符串是否须要对齐了~
3)而后,就是ResourceChunk的扩充。
在1)中已经提到插入的属性的name的值同时充当res ID索引值。而一般ResourceChunk中的res ID个数是远少于string 的个数的,那么这就须要咱们将ResourceChunk进行扩充。扩充很简单,所有赋值为0便可。
4)最后,除了须要添加数据外,还须要修改原文件的某些“计量值”,这些计量值都是与数据块大小或偏移值有关的,总结以下:
①fileSize
②StringChunkSize
③count of string
④styles的起始偏移值(若是有style的话就须要修改)
⑤ResourceChunkSize
⑥application所属chunk的chunksize
⑦applicationh含有的属性个数
1)修改StringChunk,添加UTF-16表示的字符串chouchou.class和name,并为这两个字符串添加偏移值条目。同时对StringChunkSize、count of string、styles的起始偏移值进行修复;
2)修改ResourceChunk,主要是进行res ID扩充和对ResourceChunkSize的修复
3)修改application所在的chunk,插入属性,同时对chunksize和applicationh含有的属性个数进行修复;
4)将不须要修改的部分copy到合适的位置;
5)修复fileSize
固然,具体地实现确定比上诉步骤复杂一些,不过实现源码中有较为详细的注释,你们可参照源码阅读~
AxmlParser.h/.c是Claud大大解析axml的源码,出于对做者的感谢以及让你们更详细地了解AXML的解析过程(其实,是我实在是不想本身写解析代码o(╯□╰)o),我将实现代码跟它合并到一块了。AxmlModify.c就是我写的实现AXML修改功能。
当前代码还不完善,只是初步实现了插入application.attr("name", "chouchou.class",0x0)的功能。因此并不是最终版。
代码只能在linux下运行,下载代码后make便可生成可执行文件manifestAmbiguity。而后直接运行./manifestAmbiguity能够获得完整的使用说明。
修改前:
修改后:
将修改后的xml覆盖原APK中的xml,而后删掉原来的签名文件夹再进行签名便可。这时候若是对按照此方案修改后的APK进行重打包,就会发现重打包的APK已经没法启动了。
因为目前的apk软件保护主要是基于dex代码加密和so库文件加密,对AndroidManifest.xml并无进行任何操做,而AndroidManifest.xml做为apk的入口文件,其重要性是不言而喻的。因此我想能不能在此文件中作些“手脚”,而后结合相应的处理代码实现另外一角度的软件保护。
好比,咱们彻底能够实现那个陷阱类trap.class,且这个类继承自application等等,以便被重打包的apk也可运行。只是,从一开始,该apk就运行在一个错误的环境中,至于以后的操做,那就能够尽情发挥了。
或者,咱们能够在其余tag中插入一些不会影响apk运行的属性(即新添加的属性不可被系统识别,重打包后该属性能被系统识别但又不会影响apk的运行),而后在代码中检查AndroidManifest.xml是否含有该属性,若是有就说明软件被重打包了。
等等~
若是你们有好的建议或方法,请必定不吝赐教~谢谢!
代码地址:https://github.com/wanchouchou/ManifestAmbiguity