前提
直接将apk解压,查看其中的AndroidManifest.xml文件,会发现无法正常显示,是因为在打包的时候,aapt将资源文件编译成二进制文件,包括AndroidManifest.xml。所以修改apk中的AndroidManifest.xml的思路就是反编译AndroidManifest.xml->修改->回编译AndroidManifest.xml->打包签名。
修改apk的AndroidManifest.xml的用处多多,例如可以修改名称、修改横竖屏、插入android:debuggable=”true”属性等等。
尝试
apktool
反编译apk后修改内容再打包,首先想到的是使用apktool
- apktool d test.apk
- 修改smali、修改xml、替换资源文件
- apktool b test
- zipalign
- sign
这样就可以实现修改现有apk的目的(未被加固的apk,并且修改后签名会改变)。
不过使用apktool有个缺点,就是无论是否需要改到代码逻辑,都需要将dex反编译成smali,然后回编译的时候再整个包回编译,包括将smali回编译成dex,而反编译和回编译代码逻辑这是整个过程中最耗时的。如果我们不需要改到代码逻辑,只想要在AndroidManifest.xml中修改一些属性,例如插入android:debuggable=”true”属性,使用这个方法就将耗费大量时间在无意义的事情上。MacBook Pro 15款对一个极其简单的apk执行apktool b指令都需要耗费10秒左右的时间。
apktool+aapt
apktool在执行apktool d test.apk命令反编译的时候,能在将AndroidManifest.xml文件反编译后替换其中所有资源文件索引的十六进制索引为可读的@string/xxx这样的形式;aapt是用来编译资源文件的,能编译APP中的所有资源文件。其实apktool本身在回编译的时候就是使用aapt来编译资源文件。
那就有了另一种可能性
- unzip test.apk
- apktool d test.apk
- 修改apktool反编译出来的xml
- 对修改后的xml执行aapt package
- 提取aapt编译出来的二进制AndroidManifest.xml,替换掉unzip中的二进制AndroidManifest.xml
- 对unzip并替换了AndroidManifest.xml的文件夹执行zip
- zipalign
- sign
有人可能会觉得使用apktool d命令来反编译整个apk只是为了提取AndroidManifest.xml有点浪费,能不能直接使用AXMLPrinter2来反编译AXMLPrinter2?AXMLPrinter2是能实现单独反编译AndroidManifest.xml文件,但是问题是其中所有对资源文件的引用依然保留R.java中对应的十六进制索引,而不是可读的@string/xxx这样的形式,在后续使用aapt编译资源文件的时候明显会出错。
那能不能直接使用apktool+aapt来饶过对代码逻辑的回编译从而直接反编译和回编译AXMLPrinter2呢?不能。
使用aapt生成R.java rem 测试的工程目录下必须得有gen文件夹,否则会提示:Unable to open class file R.java:No such file or directory %aapt% package -f -m -J %GEN% -S %RES% -I %ANDROID_JAR% -M %ANDROID_MANIFEST_XML% 使用aapt生成资源包文件 %aapt% package -f -M %ANDROID_MANIFEST_XML% -S %RES% -A %ASSETS% -I %ANDROID_JAR% -F %RESOURCE% %GEN%:存放的R.java文件夹路径。 %RES%:res文件夹路径。 %ANDROID_JAR%:引用的android.jar路径。 %ANDROID_MANIFEST_XML%:工程AndroidManifest.xml绝对路径。 %ASSETS%:asset文件夹路径。 %RESOURCE%:生成的resouces.arsc存放路径。
aapt在编译打包资源文件的时候,必须引入res、assets资源文件夹,在编译之后AndroidManifest.xml中@string/xxx这样的形式将被替换成生成的R.java中的十六进制索引,那这时候生成的索引会跟原始apk中的索引一致吗?经过实验证明,这个索引并不一致,所以这就意味着,如果要用这个方法,必须修改原来apk文件中的dex文件,替换掉R.java中的资源索引。先不管用什么方法可以实现这个目的,首先这需要对dex的编译和反编译就违背了我们快速修改AndroidManifest.xml文件而不去动代码逻辑的初衷。
方法
既然二进制的AndroidManifest.xml文件结构是已知的,那肯定是可以将(通过AXMLPrinter2提取并修改后的)可读AndroidManifest.xml直接反编译成二进制,而不用像aapt一样让所有资源文件也参和进来,全程对资源文件的引用都保留十六进制索引。
https://github.com/hzw1199/xml2axml
该项目就是为了实现这个功能存在的。在release中直接下载jar就可以使用。该项目同时还支持像AXMLPrinter2那样反编译二进制AndroidManifest.xml。
该项目是在另一个项目的基础上改过来的,原始项目是我在github搜索相关开源项目时无意中找到的,加上了我的star竟然也才10个star,真是酒香也怕巷子深。我对原始项目的改造包括替换原来API Level 21的android.jar为API Level 28,解决了不支持高Target的attribute,导致高Target的apk使用后出现无法连接http等异常的问题,并且通过添加maven plugin打包了jar文件,方便直接下载使用。
encode java -jar xml2axml e [AndroidManifest-readable-in.xml] [AndroidManifest-bin-out.xml] decode java -jar xml2axml d [AndroidManifest-bin-in.xml] [AndroidManifest-readable-out.xml]
于是整个流程就变成这样
- unzip test.apk
- xml2axml d AndroidManifest.xml AndroidManifest-dec.xml
- 修改xml2axml反编译出来的xml:AndroidManifest-dec.xml
- xml2axml e AndroidManifest-dec.xml AndroidManifest.xml
- 对unzip并修改了AndroidManifest.xml的文件夹执行zip
- zipalign
- sign
结论
本文的方法全程不对dex进行任何反编译和回编译,对AndroidManifest.xml的反编译和回编译也是计算量极小,唯一计算量稍大的地方就是unzip和zip,但是跟使用apktool的方法中需要反编译和回编译dex比起来,效率有了非常大的提升。而由于AndroidManifest.xml中的所有对资源文件的引用全程维持R.java中的十六进制索引,所以无需使用aapt重新编译资源文件,从而无需担心R.java中对资源的索引改变导致需要修改dex。
参考文献
https://blog.csdn.net/qq_35159110/article/details/82751928
https://blog.csdn.net/qq_35159110/article/details/82223723
https://www.jb51.net/article/101656.htm
https://blog.csdn.net/byhook/article/details/83060836
https://www.jianshu.com/p/839969887e2c
https://code.google.com/archive/p/android4me/downloads
https://stackoverflow.com/questions/16777049/encode-decode-androidmanifest-xml-inside-apk
https://ibotpeaches.github.io/Apktool/
https://github.com/fourbrother/AXMLEditor