android res 资源文件加密 ,不是混淆资源名称和路径,也不是对asset目录下文件加密,而是对 res目录下文件,如layout ,drawable的 xml 和png等文件加密
目前市面上对apk资源相关的安全方案
1.类似 andresguard 的,对资源文件 resource.arsc 和 apk 包内的 res/layout/xxx.xml 之类的 路径进行 混淆,缩短文件夹和文件名,以及替换/删除 resource.arsc 中某个资源的 name
2.将通过加密(简单的如对称加密:xor加密,或者使用AES之类的方法),在打包前,将资源文件(任意格式,如png图片,或obb,或xml)放入到 asset 文件夹下,在使用的时候用 AssetManager将文件读出来,在内存或/data/app/pkgname/ 下对文件解密,然后正常使用
问题:能否直接对apk包内的 /res/* 任意资源文件进行加密(如使用AES对称加密)?
我在网上简单找了一下资料,发现并没有找到对 res文件夹下对任意类型资源文件进行加密的方案
我使用了一种方案,验证后,是可行的(虽然我的代码很粗糙,文章逻辑也很粗糙,文章本身也很粗糙,但是Demo证明这种方式是可行的)
先说步骤,再放Demo代码
1.正常打包完APK后,将res下的资源加密(简单的使用xor加密)(也可以保留一些,不进行加密,如 ic_launcher.png ,否则你的应用安装的时候,系统将读不出自定义应用图标,会给显示一个默认应用图标)
2.(可选扩展步骤),将 resource.arsc 中,被加密过的资源的 value 删除,以及name 删除 (也可以将整条被加密的资源条目删除。但保证删除某些资源条目后,需要保留的资源条目的id 不要发生变化)(另外对需要加密的string 这一类的简单resouce资源,也就是不存在实际文件的资源,只存在于 resouce.arsc中的资源 进行删除 或者 简单的 xor 加密,如果是删除,那么需要在解密的时候,将被删除的条目加回到 resouce.arsc 文件中,如果是被xor加密的,则需要解析文件并找到对应位置进行解密,还有一种简便的办法,就是直接将被抽离的资源条目,重新打包进一个单独的 ext_resouce.arsc 中,然后打包的时候将这个文件放在apk中任意位置,如放在asset下,或者需要有有混淆意义的话,再对这个文件加密一次,放在res文件下,又或者直接将该文件转换成byte[] 存放再java层或 so库的代码中,动态解压出来)
3.将加密后的res资源放回到 apk中,将抽离了加密资源的,进行了资源条目加密的,保留了必要资源的(如app_name 和 ic_launcher.png 条目) resouce.arsc 文件 放回到apk中,将抽离的资源条目形成的 ext_resouce.arsc 也放入apk中(使用zipInout/Output stream进行apk包编辑)
4.在java代码中加入如下处理逻辑:app首次运行时,读取 /data/app/packagename/base.apk ,然后用zipInputStream ,读取每个条目,当遇到 resouce.arsc 时,跳过该条目,当遇到 ext_resouce.arsc 时,将它重命名为 resouce.arsc 并放到新生成的apk的根目录下,就像正常 的 resouce.arsc 一样,然后在读取该文件的byte[]时,用xor解密这个文件;当读取到 res/* 中,例如 rea/layout/main_layout.xml 时 用 xor 对byte[] 解密,然后写入到输出 apk中
5.打包这个apk,并签名
6.安装这个apk
7.该apk被安装后,因为 app_name 和 ic_launcher.png 是未加密的,所以 apkInstaler 和 framwrok 可以读取到应用的名称 和 正确的图标
8.当应用第一次被打开后,将读取 自身的 base.apk ,然后通过 zip 流程 解密 res/layout/main_activity.xml ,然后解密 ext_resource.arsc 并重命名为resource.arsc, 然后将解密后的文件 通过 zipOutPutstream 输出到 /data/data/pkgname/files/mybase2.apk 中
9.。。。到这一步后,其他的方法就很多了,这里只谈其中一个办法,(虽然和demo中的不太一致,但是这个方法通用性更好,demo中的办法比较繁琐,但是因为实现起来可以比较迅速,又因为我时间不太够,所以代码写的不太好 ,只把基本的功能实现了,请见谅)
10.在application中,通过反射,注入 AssetManger,将自己继承于AssetManger的子类,放到 (这样,当应用内任何一个 contextImpl 被实例化后,调用的 setResource() 中,任意的 Resouce对象内的 asset 都可以在这里进行中间环节处理(hook)
11.在得到的 asset的对象上,通过反射,调用 addAssetPath (demo中用的是这个方法)或者 addOverlayPath ,然后保留返回的 int值,作为 cookie,以后加载资源需要用到(如果你仿照 xapk的资源加载方式,那么可以不需要保留这个,我的demo中保留了这个,因为时间仓促(被割JC T_T),直接继承Resouce 对象 并直接强制用 该cookie读取资源)
12.当你在 activity 中使用 SetContentView(int resId) 时,会通过 resouce对象,然后会通过 asset对象,然后会通过 openXmlBlockAsset(int cookie, @NonNull String fileName) ,获取到给定 cookie 的 apk内的 给定资源文件 fileName ,得到 mybase2.apk 中的 main_layout.xml
其他资源读取方式类似,但有很多其他的实现方式和改进空间(如 实现自己的 ResourceWrapper ,可以对 loadLayout() 等方法自定义, 然后将他们替换到需要使用 mybase2.apk 中资源的上下文中 context)
可以将解密资源文件的 key(如AES的key)放到加固后的so文件或dex文件中,到运行时再解密到仅本应用可读写的安全目录下
这样可以对直接进行反编译(或直接用zip文件查看器打开apk文件,提取资源文件)的情况,经行一定程度的防御
apk文件中的 .dex文件有了加固、加壳方案,.so文件有了加固、加壳方案,其他非资源文件可以加密,res文件在此之前只能混淆文件名和资源名,现在res文件及已resource.arsc文件按照本文思路,也可以实现加密了。(demoapk的实现方式和上面说的有一些不同,因为可变更的方案很多)
本文主要是想提供一种对资源文件加密的思路和一个demo(仅此而已)
经过对称加密后的资源文件,无法在apktool之类的工具下, 直接反编译出来(因为是密文),之前也有一种解决方案,是对编译后的xml文件加入防反编译处理(基于文件格式),本文的方法与它不同
Demo:
apk: android_res_encrypt_try_decompilation_this.apk.apk
github下 https://github.com/ccyyyy1/AndroidResEncryptDemo
有兴趣可以反编译看看,main_layout.xml 文件是无法被直接反编译出来的,需要在运行时才能解密
源码:明天上传