【Android 安全】DEX 加密 ( Java 工具开发 | 解压 apk 文件 | 加密生成 dex 文件 | 打包未签名 apk 文件 | 文件解压缩相关代码 )
文章目錄
- 一、解壓 apk 文件
- 二、加密生成 dex 文件
- 三、打包未簽名 apk 文件
- 四、完整代碼示例
- 五、文件解壓縮相關代碼
- 六、執行結果
參考博客 :
- 【Android 安全】DEX 加密 ( 常用 Android 反編譯工具 | apktool | dex2jar | enjarify | jd-gui | jadx )
- 【Android 安全】DEX 加密 ( Proguard 簡介 | Proguard 相關網址 | Proguard 混淆配置 )
- 【Android 安全】DEX 加密 ( Proguard 簡介 | 默認 ProGuard 分析 )
- 【Android 安全】DEX 加密 ( Proguard keep 用法 | Proguard 默認混淆結果 | 保留類及成員混淆結果 | 保留注解以及被注解修飾的類/成員/方法 )
- 【Android 安全】DEX 加密 ( Proguard 混淆 | 混淆后的報錯信息 | Proguard 混淆映射文件 mapping.txt )
- 【Android 安全】DEX 加密 ( Proguard 混淆 | 將混淆后的報錯信息轉為原始報錯信息 | retrace.bat 命令執行目錄 | 暴露更少信息 )
- 【Android 安全】DEX 加密 ( DEX 加密原理 | DEX 加密簡介 | APK 文件分析 | DEX 分割 )
- 【Android 安全】DEX 加密 ( 多 DEX 加載 | 65535 方法數限制和 MultiDex 配置 | PathClassLoader 類加載源碼分析 | DexPathList )
- 【Android 安全】DEX 加密 ( 不同 Android 版本的 DEX 加載 | Android 8.0 版本 DEX 加載分析 | Android 5.0 版本 DEX 加載分析 )
- 【Android 安全】DEX 加密 ( DEX 加密使用到的相關工具 | dx 工具 | zipalign 對齊工具 | apksigner 簽名工具 )
- 【Android 安全】DEX 加密 ( 支持多 DEX 的 Android 工程結構 )
- 【Android 安全】DEX 加密 ( 代理 Application 開發 | multiple-dex-core 依賴庫開發 | 配置元數據 | 獲取 apk 文件并準備相關目錄 )
- 【Android 安全】DEX 加密 ( 代理 Application 開發 | 解壓 apk 文件 | 判定是否是第一次啟動 | 遞歸刪除文件操作 | 解壓 Zip 文件操作 )
- 【Android 安全】DEX 加密 ( 代理 Application 開發 | 加載 dex 文件 | 反射獲取系統的 Element[] dexElements )
- 【Android 安全】DEX 加密 ( 代理 Application 開發 | 加載 dex 文件 | 使用反射獲取方法創建本應用的 dexElements | 各版本創建 dex 數組源碼對比 )
- 【Android 安全】DEX 加密 ( Java 加密工具開發 | 加密解密算法 API | 編譯代理 Application 依賴庫 | 解壓依賴庫 aar 文件 ) )
- 【Android 安全】DEX 加密 ( Java 加密工具開發 | 生成 dex 文件 | Java 命令行執行 )
在 【Android 安全】DEX 加密 ( 支持多 DEX 的 Android 工程結構 ) 博客中介紹了 DEX 加密工程的基本結構 ,
app 是主應用 , 其 Module 類型是 “Phone & Tablet Module” ,
multiple-dex-core 是 Android 依賴庫 , 其作用是解密并加載多 DEX 文件 , 其 Module 類型是 “Android Library” ,
multiple-dex-tools 是 Java 依賴庫 , 其類型是 “Java or Kotlin Library” , 其作用是用于生成主 DEX ( 主 DEX 的作用就是用于解密與加載多 DEX ) , 并且還要為修改后的 APK 進行簽名 ;
在 【Android 安全】DEX 加密 ( 代理 Application 開發 | multiple-dex-core 依賴庫開發 | 配置元數據 | 獲取 apk 文件并準備相關目錄 ) 博客中講解了 multiple-dex-core 依賴庫開發 , 每次啟動都要解密與加載 dex 文件 , 在該博客中講解到了 獲取 apk 文件 , 并準備解壓目錄 ;
在 【Android 安全】DEX 加密 ( 代理 Application 開發 | 解壓 apk 文件 | 判定是否是第一次啟動 | 遞歸刪除文件操作 | 解壓 Zip 文件操作 ) 博客中講解了 apk 文件解壓操作 ;
在 【Android 安全】DEX 加密 ( 代理 Application 開發 | 加載 dex 文件 | 反射獲取系統的 Element[] dexElements )博客中講解了 dex 文件加載第一階段 , 獲取系統中的 Element[] dexElements ;
在 【Android 安全】DEX 加密 ( 代理 Application 開發 | 加載 dex 文件 | 使用反射獲取方法創建本應用的 dexElements | 各版本創建 dex 數組源碼對比 ) 博客中講解了講解 dex 文件加載操作 第二階段 , 創建本應用的 dex 文件數組 Element[] dexElements ;
在 【Android 安全】DEX 加密 ( 代理 Application 開發 | 加載 dex 文件 | 將系統的 dexElements 與 應用的 dexElements 合并 | 替換操作 ) 博客中講解了剩余的兩個操作 :
- 將 系統加載的 Element[] dexElements 數組 與 我們 自己的 Element[] dexElements 數組 進行合并操作 ;
- 替換 ClassLoader 加載過程中的 Element[] dexElements 數組 ( 封裝在 DexPathList 中 )
在 【Android 安全】DEX 加密 ( Java 加密工具開發 | 加密解密算法 API | 編譯代理 Application 依賴庫 | 解壓依賴庫 aar 文件 ) ) 博客中介紹 加密解密算法 API , 編譯代理 Application 依賴庫 , 解壓依賴庫 aar 文件 ;
在 【Android 安全】DEX 加密 ( Java 加密工具開發 | 生成 dex 文件 | Java 命令行執行 ) 博客中介紹 使用 SDK 中的 dx 工具生成 dex 文件 ;
本博客中講解 將 app 主應用的 apk 文件解壓 , 加密其中的 classes.dex 文件 , 并將代理 Application 依賴庫中的 classes.dex 打包到未簽名的 apk 文件中 ;
一、解壓 apk 文件
被解壓的 apk 文件位置 app/build/outputs/apk/debug/app-debug.apk ,
將該 apk 文件解壓到 app/build/outputs/apk/debug/unZipFile 目錄中 ;
// 解壓 apk 文件 , 獲取所有的 dex 文件// 被解壓的 apk 文件var apkFile = File("app/build/outputs/apk/debug/app-debug.apk")// 解壓的目標文件夾var apkUnZipFile = File("app/build/outputs/apk/debug/unZipFile")// 解壓文件unZip(apkFile, apkUnZipFile)unZip 方法中的代碼在最后一節中 ;
二、加密生成 dex 文件
將 app-debug.apk 中的 dex 文件進行加密 , 使用上篇博客中的 AES 類進行加密 ,
加密后 的 dex 文件重命名為 secret-classes.dex ,
放到 app/build/outputs/apk/debug/unZipFile 目錄中 ,
然后刪除原來的文件 ;
// 加密找到的 dex 文件var aes = AES(AES.DEFAULT_PWD)// 遍歷 dex 文件for(dexFile: File in dexFiles){// 讀取文件數據var bytes = getBytes(dexFile)// 加密文件數據var encryptedBytes = aes.encrypt(bytes)// 將加密后的數據寫出到指定目錄var outputFile = File(apkUnZipFile, "secret-${dexFile.name}")// 創建對應輸出流var fileOutputStream = FileOutputStream(outputFile)// 將加密后的 dex 文件寫出, 然后刷寫 , 關閉該輸出流fileOutputStream.write(encryptedBytes)fileOutputStream.flush()fileOutputStream.close()// 刪除原來的文件dexFile.delete()}
三、打包未簽名 apk 文件
將代理 Application 所在的 Android 依賴庫的 dex 文件拷貝到 app/build/outputs/apk/debug/unZipFile 目錄中 ,
將上述目錄打包文件重命名為 app-unsigned.apk ,
存放路徑為 app/build/outputs/apk/debug/app-unsigned.apk ;
/*3 . 將代理 Application 中的 classes.dex 解壓到上述app/build/outputs/apk/debug/unZipFile 目錄中*/// 拷貝文件到 app/build/outputs/apk/debug/unZipFile 目錄中classesDexFile.renameTo(File(apkUnZipFile, "classes.dex"))// 壓縮打包 , 該壓縮包是未簽名的壓縮包var unSignedApk = File("app/build/outputs/apk/debug/app-unsigned.apk")// 壓縮打包操作zip(apkUnZipFile, unSignedApk)
四、完整代碼示例
@ExperimentalStdlibApi fun main() {/*1 . 生成 dex 文件 , 該 dex 文件中只包含解密 其它 dex 的功能編譯工程會生成 Android 依賴庫的 aar 文件生成目錄是 module/build/outputs/aar/ 目錄下前提是需要在 菜單欄 / File / Setting / Build, Execution, Deployment / Compiler設置界面中 , 勾選 Compile independent modules in parallel (may require larger )將 D:\002_Project\002_Android_Learn\DexEncryption\multiple-dex-core\build\outputs\aar路徑下的 multiple-dex-core-debug.aar 文件后綴修改為 .zip解壓上述文件拿到 classes.jar 文件即可 ;*/// 獲取 multiple-dex-core-debug.aar 文件對象var aarFile = File("multiple-dex-core/build/outputs/aar/multiple-dex-core-debug.aar")// 解壓上述 multiple-dex-core-debug.aar 文件到 aarUnzip 目錄中// 創建解壓目錄var aarUnzip = File("multiple-dex-tools/aarUnzip")// 解壓操作unZip(aarFile, aarUnzip)// 拿到 multiple-dex-core-debug.aar 中解壓出來的 classes.jar 文件var classesJarFile = File(aarUnzip, "classes.jar")// 創建轉換后的 dex 目的文件, 下面會開始創建該 dex 文件var classesDexFile = File(aarUnzip, "classes.dex")// 打印要執行的命令println("cmd /c D:/001_Programs/001_Android/002_Sdk/Sdk/build-tools/30.0.2/dx.bat --dex --output ${classesDexFile.absolutePath} ${classesJarFile.absolutePath}")/*將 jar 包變成 dex 文件 使用 dx 工具命令注意 : Windows 命令行命令之前需要加上 "cmd /c " 信息 , Linux 與 MAC 命令行不用添加*/var process = Runtime.getRuntime().exec("cmd /c D:/001_Programs/001_Android/002_Sdk/Sdk/build-tools/30.0.2/dx.bat --dex --output ${classesDexFile.absolutePath} ${classesJarFile.absolutePath}")// 等待上述命令執行完畢process.waitFor()// 執行結果提示if(process.exitValue() == 0){println("執行成功");} else {println("執行失敗");}/*2 . 加密 apk 中的 dex 文件*/// 解壓 apk 文件 , 獲取所有的 dex 文件// 被解壓的 apk 文件var apkFile = File("app/build/outputs/apk/debug/app-debug.apk")// 解壓的目標文件夾var apkUnZipFile = File("app/build/outputs/apk/debug/unZipFile")// 解壓文件unZip(apkFile, apkUnZipFile)// 從被解壓的 apk 文件中找到所有的 dex 文件, 小項目只有 1 個, 大項目可能有多個// 使用文件過濾器獲取后綴是 .dex 的文件var dexFiles : Array<File> = apkUnZipFile.listFiles({ file: File, s: String ->s.endsWith(".dex")})// 加密找到的 dex 文件var aes = AES(AES.DEFAULT_PWD)// 遍歷 dex 文件for(dexFile: File in dexFiles){// 讀取文件數據var bytes = getBytes(dexFile)// 加密文件數據var encryptedBytes = aes.encrypt(bytes)// 將加密后的數據寫出到指定目錄var outputFile = File(apkUnZipFile, "secret-${dexFile.name}")// 創建對應輸出流var fileOutputStream = FileOutputStream(outputFile)// 將加密后的 dex 文件寫出, 然后刷寫 , 關閉該輸出流fileOutputStream.write(encryptedBytes)fileOutputStream.flush()fileOutputStream.close()// 刪除原來的文件dexFile.delete()}/*3 . 將代理 Application 中的 classes.dex 解壓到上述app/build/outputs/apk/debug/unZipFile 目錄中*/// 拷貝文件到 app/build/outputs/apk/debug/unZipFile 目錄中classesDexFile.renameTo(File(apkUnZipFile, "classes.dex"))// 壓縮打包 , 該壓縮包是未簽名的壓縮包var unSignedApk = File("app/build/outputs/apk/debug/app-unsigned.apk")// 壓縮打包操作zip(apkUnZipFile, unSignedApk)}
五、文件解壓縮相關代碼
/*** 刪除文件, 如果有目錄, 則遞歸刪除*/ private fun deleteFile(file: File) {if (file.isDirectory) {val files = file.listFiles()for (f in files) {deleteFile(f)}} else {file.delete()} }/*** 解壓文件* @param zip 被解壓的壓縮包文件* @param dir 解壓后的文件存放目錄*/ fun unZip(zip: File, dir: File) {try {// 如果存放文件目錄存在, 刪除該目錄deleteFile(dir)// 獲取 zip 壓縮包文件val zipFile = ZipFile(zip)// 獲取 zip 壓縮包中每一個文件條目val entries = zipFile.entries()// 遍歷壓縮包中的文件while (entries.hasMoreElements()) {val zipEntry = entries.nextElement()// zip 壓縮包中的文件名稱 或 目錄名稱val name = zipEntry.name// 如果 apk 壓縮包中含有以下文件 , 這些文件是 V1 簽名文件保存目錄 , 不需要解壓 , 跳過即可if (name == "META-INF/CERT.RSA" || name == "META-INF/CERT.SF" || (name== "META-INF/MANIFEST.MF")) {continue}// 如果該文件條目 , 不是目錄 , 說明就是文件if (!zipEntry.isDirectory) {val file = File(dir, name)// 創建目錄if (!file.parentFile.exists()) {file.parentFile.mkdirs()}// 向剛才創建的目錄中寫出文件val fileOutputStream = FileOutputStream(file)val inputStream = zipFile.getInputStream(zipEntry)val buffer = ByteArray(1024)var len: Intwhile (inputStream.read(buffer).also { len = it } != -1) {fileOutputStream.write(buffer, 0, len)}inputStream.close()fileOutputStream.close()}}// 關閉 zip 文件zipFile.close()} catch (e: Exception) {e.printStackTrace()} }fun zip(dir: File, zip: File) {// 如果目標壓縮包存在 , 刪除該壓縮包zip.delete()// 對輸出文件做 CRC32 校驗val cos = CheckedOutputStream(FileOutputStream(zip), CRC32())val zos = ZipOutputStream(cos)// 壓縮文件compress(dir, zos, "")zos.flush()zos.close() }private fun compress(srcFile: File, zos: ZipOutputStream, basePath: String) {if (srcFile.isDirectory) {val files = srcFile.listFiles()for (file in files) {// zip 遞歸添加目錄中的文件compress(file, zos, basePath + srcFile.name + "/")}} else {compressFile(srcFile, zos, basePath)} }private fun compressFile(file: File, zos: ZipOutputStream, dir: String) {// 拼接完整的文件路徑名稱val fullName = dir + file.name// app/build/outputs/apk/debug/unZipFile 路徑val fileNames = fullName.split("/").toTypedArray()// 正確的文件目錄名 val sb = StringBuffer()if (fileNames.size > 1) {for (i in 1 until fileNames.size) {sb.append("/")sb.append(fileNames[i])}} else {sb.append("/")}// 添加 zip 條目val entry = ZipEntry(sb.substring(1))zos.putNextEntry(entry)// 讀取 zip 條目輸出到文件中val fis = FileInputStream(file)var len: Intval data = ByteArray(2048)while (fis.read(data, 0, 2048).also { len = it } != -1) {zos.write(data, 0, len)}fis.close()zos.closeEntry() }
六、執行結果
下圖中 紅色矩形框 中是 解壓后的 apk 文件 ,
紫色矩形框 中的 secret-classes.dex 文件是加密后的 dex 文件 ,
藍色矩形框 中是生成的 未簽名的 apk 文件 ;
總結
以上是生活随笔為你收集整理的【Android 安全】DEX 加密 ( Java 工具开发 | 解压 apk 文件 | 加密生成 dex 文件 | 打包未签名 apk 文件 | 文件解压缩相关代码 )的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【Android 安全】DEX 加密 (
- 下一篇: 【Android 安全】DEX 加密 (