文章目录
- 一、判定是否是第一次启动
- 二、递归删除文件操作
- 三、解压 Zip 文件操作
- 四、解压操作相关代码
参考博客 :
- 【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 加密 ( 支持多 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 文件 , 并准备解压目录 ;
本博客中主要讲解 解压 dex 文件操作 ;
一、判定是否是第一次启动
应用启动后 , 获取 apk 文件 , 解压该文件 , 并 解密其中的 dex 文件 , 然后进行 加载 ;
应用每次启动前 , 都要执行上述操作 ;
现在讨论解压文件的细节操作 ;
如果应用是 第一次启动 , 则需要解压该 apk 文件 , 并进行解密 ;
如果应用 不是第一次启动 , 则直接获取之前已经 解压 apk 并解密好的 dex 文件即可 ;
先获取 dexDir 目录中的文件 , 该目录的作用是存 解压后 并 解密 的 dex 文件 ;
代码语言:javascript复制 // app 中存放的是解压后的所有的 apk 文件
// app 下创建 dexDir 目录 , 将所有的 dex 目录移动到该 deDir 目录中
// dexDir 目录存放应用的所有 dex 文件
// 这些 dex 文件都需要进行解密
var dexDir : File = File(appDir, "dexDir")
// 遍历解压后的 apk 文件 , 将需要加载的 dex 放入如下集合中
var dexFiles : ArrayList<File> = ArrayList<File>()
如果该 dexDir 目录不存在 , 并且获取的目录子元素数组大小为
, 说明这是第一次启动 ;
代码语言:javascript复制 // 如果该 dexDir 存在 , 并且该目录不为空 , 并进行 MD5 文件校验
if( !dexDir.exists() || dexDir.list().size == 0){
// 将 apk 中的文件解压到了 appDir 目录
}else{
// 已经解密完成, 此时不需要解密, 直接获取 dexDir 中的文件即可
}
}
二、递归删除文件操作
解压的目标目录 , 如果存在 , 则闪出去该目录 , 注意 递归删除 其 子目录 中的文件 ; ( 该方法一般情况下不会调用 )
代码语言:javascript复制 /**
* 删除文件, 如果有目录, 则递归删除
*/
private fun deleteFile(file: File) {
if (file.isDirectory) {
val files = file.listFiles()
for (f in files) {
deleteFile(f)
}
} else {
file.delete()
}
}
三、解压 Zip 文件操作
解压操作主要使用 java.util.zip 包下的 api ;
首先 创建 zip 文件 , 获取 zip 文件中的条目 ;
在最后解压完毕后 , 关闭该 zip 文件 ;
代码语言:javascript复制 // 获取 zip 压缩包文件
val zipFile = ZipFile(zip)
// 获取 zip 压缩包中每一个文件条目
val entries = zipFile.entries()
...
// 关闭 zip 文件
zipFile.close()
遍历压缩包中的文件 ,
如果 apk 压缩包中含有以下文件 , 这些文件是 V1 签名文件保存目录 , 不需要解压 , 跳过即可 ,
如果该文件条目 , 不是目录 , 说明就是文件 ,
向刚才创建的目录中写出文件 ;
代码语言:javascript复制 // 遍历压缩包中的文件
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: Int
while (inputStream.read(buffer).also { len = it } != -1) {
fileOutputStream.write(buffer, 0, len)
}
inputStream.close()
fileOutputStream.close()
}
}
四、解压操作相关代码
代码语言:javascript复制 /**
* 解压文件
* @param zip 被解压的压缩包文件
* @param dir 解压后的文件存放目录
*/
fun unZipApk(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: Int
while (inputStream.read(buffer).also { len = it } != -1) {
fileOutputStream.write(buffer, 0, len)
}
inputStream.close()
fileOutputStream.close()
}
}
zipFile.close()
} catch (e: Exception) {
e.printStackTrace()
}
}
/**
* 删除文件, 如果有目录, 则递归删除
*/
private fun deleteFile(file: File) {
if (file.isDirectory) {
val files = file.listFiles()
for (f in files) {
deleteFile(f)
}
} else {
file.delete()
}
}