为了防止安卓应用程序被恶意破解,植入黑客病毒或修改代码用于商业竞争等,对应用程序进行加固必不可少。接下來,本篇文章会主要讲加固的过程以及一些注意事项。
首先,了解一下何为加固, 加固的原理 是怎样的,这有利于后面分析问题。
简单来说,加固就是对源Apk进行加密,然后再套上一层壳。用加密算法对源Apk进行加密,再将壳Apk进行合并得到新的Dex文件,最后替换壳程序中的dex文件得到新的Apk,这个新的Apk已经不是一个完整意义上的Apk程序了,它的主要工作是负责解密源Apk,然后加载Apk,让其正常运行起来。
目前,各大互联网公司都会自己的应用程序进行加固保护,像360公司,腾讯都有对外开放自己的服务。另外,市场上还有一些专门加固的产品,比如爱加密和梆梆加固等。好好利用这些“轮子”,专注于业务开发,來提高工作效率。
加固工具的选择:此次使用的是 360加固
第一,从调研加固结果可见,360加固在兼容性、启动速度、体积变化上都占有优势,整体上加固效果比较好;
第二,360公司是一家安全起家的公司,在业界的影响力也很大,加固技术还是值得信赖的!
第三,看了很多加固工具的官网,加固的过程都是上传签名的APK包到官网页面或使用相应的桌面程序进行上传,这个过程需要人工进行上传,而360加固提供了一个加固工具包,我们可以编写脚本來调用其中的加固程序进行自动化加固。现在很多公司都是用Jenkins线上自动化打包,加固也是打包过程的一部分,最好也能是自动化的,这样整个打包流程是“一条龙”,没有人工干预,相当于在黑匣子中进行,程序员不用关心打包过程,也减少人工成本和出错几率。
使用 gradle脚本 实现自动化加固和多渠道打包
整个过程分成三个步骤: 加固——重签名——多渠道打包
加固过程: 浏览了360加固官网,整个加固过程其实很简单,主要有以下的三个步骤:
1)输入360加固平台的帐号、密码
2)将签名文件上传到加固平台
3)上传需要加固的apk文件进行加固
关键加固命令行代码如下:
commandLine "{命令执行符号}", "-c" ,"java -jar {加固jar包的位置} -login {360加固平台帐号} {360加固平台密码}" commandLine "{命令执行符号}", "-c" ,"java -jar {加固jar包的位置} -importsign {签名文件的位置} {签名文件存储的密码} {alias别名} {alias密码}" commandLine "{命令执行符号}", "-c" ,"java -jar {加固jar包的位置} -jiagu {所要加固的apk文件路径} {加固后的apk输出路径} -autosign" 复制代码
说明:
1)系统环境不同,命令执行符号也会不同(Linux系统:sh ;Mac系统:bash ;windows系统:powershell);
2)第二行上传签名文件信息是非必要的,加固平台加固后可以进行自动重新签名,而自动签名所需要的信息正是之前上传的签名信息。为了保证签名文件的保密性和安全性,不对第三方加固平台公开,那么不能执行第二行代码即可,因为加固时将原签名抹除,而第三方此时没办法获取到我们的签名信息,所以加固后需要我们本地重新签名,下文将会介绍对加固包重签名;
3)当选择本地加固时,第三行代码不需要加上参数-autosign,因为加固平台没办法获取到签名信息进行加固;
4)更多有关加固的命令行,请参考360官网.官网介绍中,还有关于加固后导入渠道信息的功能,此次多渠道打包并没有使用该功能,第一,项目中原先使用多渠道打包方式的是美团walle;第二,暂时不知道如何获取到360加固打包后的渠道信息,而该渠道信息会在项目中广泛被使用到,比如数据埋点,渠道统计等。
基于上面的说明和项目的具体情况,整理一下代码,以Linux系统为例:
/** * 360加固 * @param apk 加固的原始apk File * @param outputPath 输出目录 */ def reinforceApk(File apk,outputPath) { println "--- 360 reinforceApk start! ---" println "reinforce apk:" + apk if(apk == null || !apk.exists()) { throw new FileNotFoundException('apk is not exists and cannot reinforce') println "---360 reinforceApk throw exception and forced stop!---" } exec { commandLine "sh", "-c", "java -jar ${REINFORCE_JAR} -login ${REINFORCE_NAME} ${REINFORCE_PASSWORD}" commandLine "sh", "-c", "java -jar ${REINFORCE_JAR} -showsign" commandLine "sh", "-c", "java -jar ${REINFORCE_JAR} -jiagu ${apk} ${outputPath}" } println "--- 360 reinforce end! ---" } 复制代码
加固工作已经完成差不多了,剩下的工作就是对加固包重新签名
重签名的方法主要是调用AndroidSDK中的build-tools,使用工具包中对齐工具和签名工具完成签名。具体步骤如下:
1)对齐,对Apk文件进行存档对齐优化,确保所有的未压缩数据都从文件的开始位置以指定的对齐方式排列
2)签名,选择Signature V2
commandLine "{命令执行符号}","-c", "{zipalign工具的文件路径} -v -p 4 {已加固的apk文件路径} {对齐后输出的apk文件路径}" commandLine "{命令执行符号}", "-c", "{apksigner工具的文件路径} sign --ks {签名文件的位置} --ks-key-alias {alias别名} --ks-pass pass:{签名文件存储的密码} --key-pass pass:{alias密码} --out {签名后输出的apk文件} {对齐后输出的apk文件路径}" 复制代码
最后,使用walle美团的多渠道打包工具
平时使用walle多渠道打包,只需要在app/build.gradle下配置插件,指定渠道包的输出路径和渠道配置文件即可,最后在Android studio的Terminal中输入./gradlew assembleReleaseChannels,任务执行完成后在指定的输出路径下生成多个对应的渠道包。具体的流程和细节可参考 官方介绍 。
这种多渠道打包方式是全自动化构建,很难去干涉到构建流程,不符合我们的需求:
1)在app/build.gradle配置插件时,在官方介绍中并没有找到指定源APK输入路径的方式,估计打包插件默认使用的是app/build/outputs/apk/release下的apk文件,这样就没办法对不同文件路径下的已加固apk包进行多渠道打包。
2)打包任务设置在assembleRelease之后执行,这个执行依赖封装在插件内部,外部很难修改打包任务依赖于加固任务,在加固任务之后执行。
除了上面的多渠道打包方式之后,walle还提供了另外一种多渠道打包方式,用命令行执行walle提供的walle-cli-all.jar执行打包操作,只需要一条打包命令即可完成打包。
commandLine "sh", "-c", "java -jar {walle-cli-all.jar文件路径} batch -f {渠道文件路径} {要加渠道的apk文件路径} {渠道包的输出路径}" 复制代码
walle-cli-all.jar文件下载地址: walle-cli-all.jar
至此,360加固+walle多渠道打包的基本工作完成了!剩下就是构建整体流程和优化代码。
首先,将加固和打包操作封装成自动化操作,利用gradle脚本构建加固任务。为了代码解耦,我们不在app/build.gradle里面实现加固任务,而是重新建一个gradle文件來实现具体的加固和多渠道打包过程,在app/build.gradle只需要通过 apply from: '×××.gradle'
引用这个gradle文件即可,当需要修改加固的一些代码逻辑时,只需要在这个gradle文件里面修改。
引入工具包。根据自己的系统环境,在加固助手网页选择对应的加固助手工具,下载后将里面的jiagu文件夹拷贝到自己项目的根目录下;在 walle-cli-jar下载链接 下载jar包到自己项目中。
确定加固任务的时机。加固任务时机应该在release包生成之后,那么加固任务应该依赖于assembleRelease这个任务,并且设置在这个任务之后执行。
接下来就是我们的基本流程了
1)找到release包,一般在app/build/outputs/apk/release/路径下
2)执行加固命令,将release包路径设置到命令中,并指定加固apk文件的输出路径
3)找到已加固的apk文件,对已加固apk文件进行对齐、重签名。(360已加固的apk文件会在原有的release文件名后面加上"_jiagu")
4)找到重新签名的apk文件,执行多渠道打包命令。(重签名后的文件名是在原有文件名后面加上"_sign")
/** * 360加固 + 美团walle渠道打包 */ task assembleReinforceRelease() { group '360reinforce' dependsOn("assembleRelease") doLast { cleanFilesPath(CHANNEL_APKS_PATH) //清空上一次生成的渠道包 def releaseApkFile = findApkFile(SOURCE_APK_PATH,"release") //遍历文件,寻找release包 if(releaseApkFile != null) { reinforceApk(releaseApkFile, DEFAULT_APK_PATH) //执行加固 def reinforceApk = findApkFile(DEFAULT_APK_PATH, "_jiagu") //寻找已加固的apk包 if(reinforceApk != null) { signApkV2(reinforceApk) //使用V2重签名 def signatureApk = findApkFile(DEFAULT_APK_PATH, "sign") if(signatureApk != null) { buildChannelApks(signatureApk,CHANNEL_APKS_PATH) //执行多渠道打包 renameChannelApkFiles(CHANNEL_APKS_PATH) //重命名渠道包 } } } } } 复制代码
整个流程确定后,差不多接近尾声了。
代码优化:
1)将流程中每个步骤封装成一个方法,使代码更加简洁易懂;
2)任务中涉及360加固平台帐号密码等敏感信息,可以将这部分信息放到签名信息所在的文件(eg:keystore.properties)中统一管理,然后将这些信息加载到gradle文件中;
3)各种输入输出的文件路径定义为常量,便于修改和管理;
加固方法,重命名和渠道打包的方法类似:
/** * 360加固 * @param apk 加固的原始apk File * @param outputPath 输出目录 */ def reinforceApk(File apk,outputPath) { println "--- 360 reinforceApk start! ---" println "reinforce apk:" + apk if(apk == null || !apk.exists()) { throw new FileNotFoundException('apk is not exists and cannot reinforce') println "---360 reinforceApk throw exception and forced stop!---" } exec { commandLine "sh", "-c", "java -jar ${REINFORCE_JAR} -login ${REINFORCE_NAME} ${REINFORCE_PASSWORD}" commandLine "sh", "-c", "java -jar ${REINFORCE_JAR} -showsign" commandLine "sh", "-c", "java -jar ${REINFORCE_JAR} -jiagu ${apk} ${outputPath}" } println "--- 360 reinforce end! ---" } 复制代码
任务中涉及到的各种常量,各种密钥名、路径都要根据自己的实际情况修改:
/*加载keystore.properties信息到该gradle文件中*/ def keystorePropertiesFile = rootProject.file("keystore.properties") def keystoreProperties = new Properties() keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) ext { /*加固*/ REINFORCE_JAR = "${project.rootDir}/jiagu/jiagu.jar" REINFORCE_NAME = keystoreProperties['360_NAME'] //360加固账号 REINFORCE_PASSWORD = keystoreProperties['360_PASSWORD'] //360加固密码 KEY_PATH = keystoreProperties['storeFile'] //密钥路径 KEY_PASSWORD = keystoreProperties['storePassword'] //密钥密码 ALIAS = keystoreProperties['keyAlias'] //密钥别名 ALIAS_PASSWORD = keystoreProperties['keyPassword'] //别名密码 SOURCE_APK_PATH = "${project.buildDir}/bakApk" //源apk文件路径 DEFAULT_APK_PATH = "${project.buildDir}/outputs/apk/release" //默认release文件路径 /*多渠道打包*/ WALLE_JAR = "${project.rootDir}/walle-cli-all.jar" WALLE_CHANNELS_CONFIG = "../app/channel" //渠道配置文件 CHANNEL_APKS_PATH = "${project.buildDir}/outputs/channels" //渠道Apk输出路径 } 复制代码
1)对比加固前release包的签名和加固后apk的签名是否一致,两者相同说明新apk能够覆盖安装
2)用反编译工具对加固包进行反编译,看能否看到Activity这些类
3)验证是否可以获取到渠道包,代码中获取渠道号是通过WalleChannelReader.getChannel(application);这个方法
。。。
1)网上传闻,360加固后无法获取到walle打包的渠道号?
是的,360加固过程会抹去已签名release包的签名信息,假如在加固前用walle打渠道包就会造成渠道号丢失,所以我们采用的方法是先加固再多渠道打包,由于加固会破坏掉原有的签名信息,所以加固后需要重新签名。