转载

360加固+美团walle多渠道自动化打包

为了防止安卓应用程序被恶意破解,植入黑客病毒或修改代码用于商业竞争等,对应用程序进行加固必不可少。接下來,本篇文章会主要讲加固的过程以及一些注意事项。

前期准备

首先,了解一下何为加固, 加固的原理 是怎样的,这有利于后面分析问题。

简单来说,加固就是对源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);这个方法

。。。

Q&A

1)网上传闻,360加固后无法获取到walle打包的渠道号?

是的,360加固过程会抹去已签名release包的签名信息,假如在加固前用walle打渠道包就会造成渠道号丢失,所以我们采用的方法是先加固再多渠道打包,由于加固会破坏掉原有的签名信息,所以加固后需要重新签名。

原文  https://juejin.im/post/5c825ac6f265da2db9129bd0
正文到此结束
Loading...