背景介紹
為了防止安卓應用程式被惡意破解,植入黑客病毒或修改程式碼用於商業競爭等,對應用程式進行加固必不可少。接下來,本篇文章會主要講加固的過程以及一些注意事項。
前期準備
首先,瞭解一下何為加固,加固的原理是怎樣的,這有利於後面分析問題。
簡單來說,加固就是對源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打渠道包就會造成渠道號丟失,所以我們採用的方法是先加固再多渠道打包,由於加固會破壞掉原有的簽名資訊,所以加固後需要重新簽名。
歡迎關注公眾號——久見先
(新開的公眾號,請大家多多支援!)
如果覺得對你有幫助,麻煩點個贊,謝謝!同時,歡迎大家評論,互相討論問題。