当前位置: 首页 > news >正文

手写 Android Dex VMP 壳:指令流 AES 加密 + 动态加载全流程

版权归作者所有,如有转发,请注明文章出处:https://cyrus-studio.github.io/blog/

前言

在上一篇《手写 Android Dex VMP 壳:自定义虚拟机 + 指令解释执行全流程》中,我们从零实现了一个简易的 Dex VMP 壳,通过自定义虚拟机和指令解释执行,让应用代码在运行时以“虚拟指令流”的方式运行,大大增加了逆向分析的难度。

如果攻击者能够直接读取 Dex 中的虚拟指令流,那么依然可能进行还原。为了解决这一问题,本篇将进一步升级:在 Dex VMP 基础上引入指令流加密与动态加载

通过 AES 算法 对指令流进行加密,运行时再解密并交给虚拟机执行,从而让静态分析几乎无从下手。

Dex VMP 指令流加密 + 动态加载完整流程

Dex VMP 指令流加密 + 动态加载执行完整流程大概如下:

 ┌──────────────────────┐│     原始 Dex 指令流   │└──────────┬───────────┘│▼┌─────────────────────────┐│ 保存指令流到文件 / 内存   │└──────────┬──────────────┘│▼┌─────────────────────────┐│       使用 AES 加密      ││ (key/iv 固定或动态生成)   │└──────────┬──────────────┘│▼┌─────────────────────────┐│    得到加密后的指令流文件 ││ (静态分析无法直接还原)  │└──────────┬──────────────┘│┌────────▼──────────────┐│   Android 运行时启动   │└────────┬──────────────┘│▼┌────────────────────────┐│  动态读取加密指令流文件  │└──────────┬─────────────┘│▼┌────────────────────────┐│       AES 解密恢复      ││  (内存中得到明文指令流)│└──────────┬─────────────┘│▼┌──────────────────────────┐│   将解密后的指令流交给 VMP ││   → 自定义虚拟机解释执行   │└──────────┬───────────────┘│▼┌─────────────────────────┐│    App 正常运行业务逻辑   ││ (逆向者难以静态还原逻辑) │└─────────────────────────┘

核心思路

  1. 编译/打包阶段:把 Dex 指令流抽取出来,AES 加密,存储到文件中。

  2. 运行时阶段:App 启动时,从文件中加载 → AES 解密 → 交给虚拟机解释执行。

  3. 保护效果:静态分析拿到的 Dex 是“假代码”,真正的逻辑被加密隐藏,只有运行时内存里才会出现明文。

保存指令流到文件

在 010Editor 中搜索找到 sign 方法的字节码并复制

word/media/image1.png

新建 Hex 文件

word/media/image2.png

把 sign 方法字节码粘贴到新建的文件保存文件为 sign

word/media/image3.png

AES加解密

编写一个 kotlin 语言 AES 加解密算法工具类

package com.cyrus.vmpimport java.io.ByteArrayOutputStream
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import javax.crypto.Cipher
import javax.crypto.KeyGenerator
import javax.crypto.SecretKey
import javax.crypto.spec.SecretKeySpecobject AESUtils {private const val ALGORITHM = "AES"private const val TRANSFORMATION = "AES/ECB/PKCS5Padding" // AES 加密模式// 生成一个 128 位的 AES 密钥fun generateSecretKey(): SecretKey {val keyGenerator = KeyGenerator.getInstance(ALGORITHM)keyGenerator.init(128) // AES 128 位return keyGenerator.generateKey()}// 使用给定的密钥加密数据fun encrypt(data: ByteArray, key: SecretKey): ByteArray {val cipher = Cipher.getInstance(TRANSFORMATION)cipher.init(Cipher.ENCRYPT_MODE, key)return cipher.doFinal(data)}// 使用给定的密钥解密数据fun decrypt(data: ByteArray, key: SecretKey): ByteArray {val cipher = Cipher.getInstance(TRANSFORMATION)cipher.init(Cipher.DECRYPT_MODE, key)return cipher.doFinal(data)}// 将文件内容加密并导出到新文件fun encryptFile(inputFile: File, outputFile: File, keyFile: File) {// 读取文件内容val fileData = readFile(inputFile)// 生成密钥val secretKey = generateSecretKey()// 加密文件内容val encryptedData = encrypt(fileData, secretKey)// 保存加密后的数据到新文件(.vmp 文件)writeFile(outputFile, encryptedData)// 保存密钥到文件saveKeyToFile(secretKey, keyFile)}// 解密文件内容并导出到新文件fun decryptFile(inputFile: File, outputFile: File, keyFile: File) {// 从文件加载密钥val secretKey = loadKeyFromFile(keyFile)// 读取加密后的文件内容val encryptedData = readFile(inputFile)// 解密文件内容val decryptedData = decrypt(encryptedData, secretKey)// 保存解密后的数据到文件writeFile(outputFile, decryptedData)}// 读取文件内容并返回字节数组fun readFile(file: File): ByteArray {val fis = FileInputStream(file)val baos = ByteArrayOutputStream()val buffer = ByteArray(1024)var bytesRead: Intwhile (fis.read(buffer).also { bytesRead = it } != -1) {baos.write(buffer, 0, bytesRead)}fis.close()return baos.toByteArray()}// 将字节数组写入到文件fun writeFile(file: File, data: ByteArray) {val fos = FileOutputStream(file)fos.write(data)fos.close()}// 保存密钥到文件private fun saveKeyToFile(key: SecretKey, keyFile: File) {val fos = FileOutputStream(keyFile)fos.write(key.encoded)fos.close()}// 从文件加载密钥fun loadKeyFromFile(keyFile: File): SecretKey {val keyBytes = ByteArray(keyFile.length().toInt())val fis = FileInputStream(keyFile)fis.read(keyBytes)fis.close()return SecretKeySpec(keyBytes, ALGORITHM)}}

指令流加密

把 sign 文件放到工程中如下路径

word/media/image4.png

调用 AESUtils 类中方法对 sign 进行加密并输出加密文件和密钥

package com.cyrus.vmpimport java.io.Filefun main() {// 获取工程根目录路径val projectRoot = System.getProperty("user.dir")// 设置相对路径val encryptedFile = File(projectRoot, "vmp/sign/sign.vmp") // 相对路径val keyFile = File(projectRoot, "vmp/sign/sign.key") // 相对路径// 输入文件路径val inputFile = File(projectRoot, "vmp/sign/sign") // 需要加密的文件try {// 使用 AES 加密文件AESUtils.encryptFile(inputFile, encryptedFile, keyFile)println("File encryption completed, saved as: ${encryptedFile.absolutePath}")println("Key saved as: ${keyFile.absolutePath}")} catch (e: Exception) {e.printStackTrace()}
}

指令流解密

package com.cyrus.vmpimport com.cyrus.vmp.AESUtils.loadKeyFromFile
import com.cyrus.vmp.AESUtils.readFile
import com.cyrus.vmp.AESUtils.writeFile
import java.io.Filefun main() {// 获取工程根目录路径val projectRoot = System.getProperty("user.dir")// 输入加密文件路径val encryptedFile = File(projectRoot, "vmp/sign/sign.vmp")// 密钥文件路径val keyFile = File(projectRoot, "vmp/sign/sign.key")// 输出解密文件路径val decryptedFile = File(projectRoot, "vmp/sign/sign_")try {// 从文件加载密钥val secretKey = loadKeyFromFile(keyFile)// 解密文件val encryptedData = readFile(encryptedFile)val decryptedData: ByteArray = AESUtils.decrypt(encryptedData, secretKey)// 保存解密后的文件writeFile(decryptedFile, decryptedData)println("File decryption completed, saved as: ${decryptedFile.absolutePath}")} catch (e: Exception) {e.printStackTrace()}
}

Android 中运行时解密并执行指令流

将 .vmp 和 .key 文件放在 Android 应用的 assets 目录下

word/media/image5.png

编写工具类,用于读取 assets 文件并解密

package com.cyrus.example.vmpimport android.content.Context
import java.io.InputStream
import javax.crypto.Cipher
import javax.crypto.SecretKey
import javax.crypto.spec.SecretKeySpecobject AESUtils {private const val ALGORITHM = "AES"private const val TRANSFORMATION = "AES/ECB/PKCS5Padding"// 从 assets 中读取文件并解密fun decryptFileFromAssets(context: Context, vmpFileName: String, keyFileName: String): ByteArray? {// 读取密钥文件val key = loadKeyFromAssets(context, keyFileName)// 读取加密的 vmp 文件val encryptedData = readFileFromAssets(context, vmpFileName)// 解密return decrypt(encryptedData, key)}// 读取文件内容为字节数组private fun readFileFromAssets(context: Context, fileName: String): ByteArray {val inputStream: InputStream = context.assets.open(fileName)return inputStream.readBytes()}// 从 assets 中加载密钥文件private fun loadKeyFromAssets(context: Context, keyFileName: String): SecretKey {val keyBytes = readFileFromAssets(context, keyFileName)return SecretKeySpec(keyBytes, ALGORITHM)}// 解密private fun decrypt(data: ByteArray, key: SecretKey): ByteArray {val cipher = Cipher.getInstance(TRANSFORMATION)cipher.init(Cipher.DECRYPT_MODE, key)return cipher.doFinal(data)}
}

调用解密方法并读取指令流

private fun readInstructionFromAssets(): ByteArray? {// 文件名:在 assets 中放置的加密文件和密钥文件val vmpFileName = "sign.vmp"val keyFileName = "sign.key"// 解密文件val decryptedData = AESUtils.decryptFileFromAssets(this, vmpFileName, keyFileName)return decryptedData
}

得到解密后的指令流后调用 VMP 执行指令流对 input 参数加密

val input = "example"// 解密并执行指令流
val bytecode = readInstructionFromAssets()// 通过 VMP 解析器执行指令流
if (bytecode != null) {val result = SimpleVMP.execute(bytecode, input)// 显示 ToastToast.makeText(this, result, Toast.LENGTH_SHORT).show()
}

测试

执行结果如下

word/media/image6.png

和原来的 sign 算法对比是结果是一样的。

word/media/image7.png

完整源码

开源地址:https://github.com/CYRUS-STUDIO/AndroidExample

http://www.dtcms.com/a/395390.html

相关文章:

  • 视频融合平台EasyCVR国标GB28181视频诊断功能详解与实践
  • ORACLE adg 备库也能单独提取AWR报告
  • Angular由一个bug说起之十九:Angular 实现可拓展 Dropdown 组件
  • Kafka核心架构与高效消息处理指南
  • flink1.18配置多个上游source和下游sink
  • 快速查看自己电脑的ip地址:一个命令见本机私网ip,一步查询本地网络公网ip,附内网ip让外网访问的通用方法
  • 插件化(Plugin)设计模式——Python 的动态导入和参数解析库 argparse 的高级用法
  • 【JavaSE】【网络原理】UDP和TCP原理
  • 高防IP真的能抵御DDoS攻击吗?
  • 93. 复原 IP 地址
  • 智能排班系统,促进人岗匹配提升人效
  • PostgreSQL介绍和PostgreSQL包安装
  • 分享“泰迪杯”数据挖掘挑战赛全新升级——赛题精准对标,搭建 “白名单” 赛事进阶通道
  • 对接文档:快递鸟取件码API,实现物流末端服务自动化
  • GIS学习:GIS认知与开发初步入门
  • 9. NVME与SSD之间的通信
  • Navicat连接PostgreSQL报错:authentication method 10 not supported
  • Diffusion 模型解读
  • 【寰宇光锥舟】 数学模型讨论
  • Further inference in the multiple linear regression model
  • Turtlebot: 开源机器人开发平台 SLAM硬件搭建(激光雷达+IMU+相机+移动底盘)
  • Java 线程的几种状态
  • 在线ps修改图片中的文字
  • Hadoop 保姆级搭建手册:突出教程的细致和易上手
  • 使用gsettings修改命令ubuntu快捷键
  • Linux线程互斥与同步
  • 【AI扣子生成测试用例】自动生成测试用例工作流
  • Hive建表实战
  • Ethernaut Level 5: Token - 整数下溢攻击详解
  • 正向代理 vs 反向代理