技术标签: Android 性能优化 # Android 安全 dex 加密
参考博客 :
在 【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 合并 | 替换操作 ) 博客中讲解了剩余的两个操作 :
在 【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 文件位置 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 方法中的代码在最后一节中 ;
将 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()
}
将代理 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: Int
while (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: Int
val 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 文件 ;
webpack中文官网英文官网webpack 基本概念为什么要学习webpackwebpack 它是 vue-cli 的底层实现原理,学习了它,可以更加方便的去使用 vue-cli(现在基本不用了)webpack 的作用vue-cli 搭建的项目有哪些功能:可以运行为一个服务器可以进行实时更新代码修改之后,保存,页面会自动更新可以解析 css 文件import 'xx.css'可以解析 .vue 文件…上述的这些功能,其实都是 webpack 来实
Photon获取App ID导入PUN2至Unity素材当App ID输入并Setup Project。完成后会使所有的连接设置可以在PhotonServerSetting中进行编辑在PhotonServerSetting中PUN Logging 调制Full 显示全部信息,以便知道是否成功连接在脚本中添加命名空间Photon.Pun,Photon.Realtime再以OnConnectedToMaster()方法连接服务器并创建房间用UI中的InputField类型创建房间名字和玩家名字在触发.
大家好,今天来分享一下如何使用ANT打包Android应用。通常我们习惯用eclipse来开发Android程序,它会自动帮我们打包当前的应用程序。如果在Navigator视图下,我们可以看到以下几个文件:在上图中,com包放置的是我们的class文件,classes.dex是class文件经过转换后的可以在dalvik上跑的精简类文件,resources.ap_是经过_安卓ant引入dex.classes
//一基本概念#include 将一个文件的全部内容拷贝到另一个文件中// 二,一般形式1,#include <文件名>直接到C语言库函数头文件所在的目录中寻找文件2,#include “文件名”系统会先在源程序当前目录下寻找,若找不到,再到操作系统的path路径中查找,最后才到C语言库函数头文件所在目录中查找//三使用注意1,#include允许嵌套包含不允许...
复旦大学计算机科学技术学院 2019-2020 学年第一学期本科优秀学生奖学金名单(公示)2016级计算机学院本科班一等奖(5 名)盛钡娜 翁泽佳 陈中钰阿瓦妮萨·麦麦提 化嘉仪二等奖(18 名)胡志峰 靳帅祥 刘晓黎陈 琳 常朝坤裴恒志 蒋艳琪 张 鑫 曹庭嘉 肖起凡王雪飞 肖 杨 张煜坤 管佳乐陈 铠李 锐 杨芗琳 ...
case 字段名称 when 1 then '一'when 2 then '二'else '其他'end_数据库存的是0123,展示要显示汉字
经常收到微信好友的各种问题咨询,今天分享一个比较有代表性的,希望对大家有所帮助。一位微信好友的提问 问题如下:问题分析先简单介绍下服务端的处理逻辑,关于登录,服务端的逻辑一般是:校验用户名、密码(可能还有验证码,但是压测环境都会特殊处理),成功后,会根据规则(比如拼接时间戳等字符串、加盐、加密),生成一个tokenId,然后存入redis;关于依赖登录的请求,服务端的逻辑一般是:会校验..._jmeter中1000个参数化数据200并发为什么会有很多重复的参数请求
系统环境:Win10Python版本:3.7.6tensorflow-gpu版本:2.0.1CUDA版本:10.0cuDNN版本:7.6.5Win10+CUDA+cuDNN+tensorflow2.0+Python3.7.6环境配置前言版本对应查驱动版本一、安装CUDA 10.0二、安装cuDNN 7.6.51.下载包(前提需要注册一个免费的NVIDIA)2.解压缩,复制文件三、下载Tensorflow2.0.1总结前言大四了,快毕业,毕业设计有关深度学习,寒假想在家跑点深度学习模型,只能_tensorflow2.0 pytorch cuda
  首行加上  就可以首行缩进两个字符。示例: 个人分类与专栏管理合并啦!双击分类名称,即可编辑,输入“#”+“空格”+“分类名称”可将一级分类改成二级分类。..._csdn首行缩进
对这一方面的知识有一定的了解,不过还是有些混淆,最早认识的数据库就是Access关系数据库,那是Microsoft Office上最常用的用来保存数据的数据库,在NCRE考试的时候也有过接触,学过C++语言和C#语言后就知道基于C#语言的SQL Server是最强大和最全面的关系数据库。 数据库 数据库(DB)是一个以某种有组织的方式存储的数据集合。当人们收集了大量
1.打开控制面板2.打开网络共享中心3.4.5.双击标红处6.点击标红处。确认
1、如图,双击订单号输入框时弹出销售记录查询子窗口 2、输入订单号查询可以查询到相应记录,查询到后双击就可以将对应的信息显示到父页面了 那么是如何实现的呢? 1、双击订单号input框 2、action对应的方法 3、getorders方法对应页面 注意:父页面的name值必须要与这里的field1~field...