从 Apk 提取代码到单独 dex

在这里插入图片描述

从 Apk 提取代码到单独 dex

Android 中动态加载是指应用程序在运行时加载和执行 Dex 文件的过程,可以在运行时加载不同的代码或功能,而无需重新编译整个应用程序,动态加载 Dex 文件通常涉及以下步骤:

  • 创建 Dex 文件
    我们接触到的通常是 Android studio 等 IDE 工具将 Java 或 Kotlin 代码编译成 Dex 格式的字节码文件。
  • 将 Dex 文件打包到 APK 中
    Apk 就是一个压缩包,通常也是 IDE 将编译好的 Dex 文件打包到应用程序的 APK 文件中。
  • 运行时加载 Dex 文件
    当应用程序启动时,Android 系统会加载应用程序的代码。如果应用程序中包含 Dex 文件,系统会自动将其加载到内存中,以便应用程序可以执行其中的代码。
  • 执行 Dex 文件中的代码:
    一旦 Dex 文件被加载到内存中,应用程序就可以执行其中的代码。

通过动态加载 Dex 文件,可以实现更加灵活和可扩展的功能,可以编写插件或模块化的代码,并在运行时根据需要加载它们。DexClassLoader 是 Android 中的一个类加载器,用于动态加载包含 Dex 文件的 jar 或 apk 文件,它的工作原理大概是:加载 Dex 文件,DexClassLoader 会从指定的路径中读取 Dex 文件,并将其加载到内存中… …无序过多描述,我们知道它可以加载 dex 文件即可。

本文讲的如下图所示:

如何从 apk 中获取特定包名下的代码并保存到 dex 文件中,然后再把此 dex 放到 assets 目录下,最后重新打包成 APK。

在这里插入图片描述

本次通过 Java 实现,最终产物是一个可执行的 Jar,使用方式如下所示:

java -jar xpluginJs.jar -mapping mapping.txt -apk 1.apk -dexname hello -pkg com.primer.pay.manager,com.primer.login.manager

意思是:
根据 mapping.txt 映射文件从 1.apk 安装包中提取所有 com.primer.pay.manager,com.primer.login.manager 包下的代码,将所提取代码写入 hello.dex 文件,hello.dex 文件将被放到包体内 assets 目录下。

/*** * 1、必须参数*      -mapping:安装包的映射文件*      -apk:待处理 apk 文件*      -dexname:处理后存放在 assets 目录下的 dex 文件名*      -pkg:需要提取的包名(混淆前的包名),多个包名用英文逗号分隔* * * 2、当前目录运行环境目录结构*      tools:内置工具(apktool、smali)*      xpluginJs.jar:主程序*      mapping.txt:映射文件*      1.apk:apk 文件*      output-dex2jar:中间产物输出目录*      output-dex2jar\apk-decode\dist\***.apk:最终产物*/

在这里插入图片描述

0、线性执行

一图概述~
在这里插入图片描述

    public void run() {System.err.println("=================== 1、Initializes the output directory");initOutPutDir();mDexConfig.setspecifyPkgSmailOutputPath(OUTPUT_PATH_SMAIL);System.err.println("=================== 2、Load the local mapping configuration");mObsMapping = loadMappingConfig(mDexConfig.getMappingFilePath());System.err.println("=================== 3、Gets a list of mapping names for the specified package name");List<String> specifyMappingPkgList = ObsMappingUtils.collectionSpecifyPkgMapping(mObsMapping,mDexConfig.getSpecifiyPkgList());System.err.println("=================== 4、Decompile Apk using apktool");String decodeOutPath = decodeApk(mDexConfig.getApkFilePath(), mDexConfig.getApkDecodeOutputDirname());mDexConfig.setApkDecodeOutputDirpath(decodeOutPath);System.err.println("=================== 5、Collects and moves the smail file under the specified package name");collectionAndMoveSpecifyPkgSmail(decodeOutPath, specifyMappingPkgList);System.err.println("=================== 6、Package the smali file as dex");String smailToDexPath = encodeSmailToDex(mDexConfig.getSpecifyPkgSmailOutputPath());if (CommUtil.isEmptyOrNoExists(smailToDexPath)) {throw new IllegalArgumentException("smailToDexPath is null");}System.err.println("\tsmailToDexPath: " + smailToDexPath);System.err.println("=================== 7、dex encryption");String encodeDexPath = encodeDex(mDexConfig.getEncodeDexFilename(), smailToDexPath);System.err.println("\tencodeDexPath: " + encodeDexPath);String decodeDexPath = decodeDex(mDexConfig.getEncodeDexFilename() + "-decode", encodeDexPath);System.err.println("\tencodeDexPath: " + decodeDexPath);System.err.println("=================== 8、copy dex encryption");boolean isCopy = copyDexToAssets(encodeDexPath, decodeOutPath);if (!isCopy) {throw new IllegalArgumentException("copy dex to assets error");}System.err.println("=================== 9、Apk compile back");String unsignApkFilePath = encodeApk(decodeOutPath);System.err.println("\t [Successfully~] unsignApkFilePath: " + unsignApkFilePath);}

1、参数解析

按格式解析参数,收集必要的信息,这里参数解析或许有漏洞,但你可以根据自己的想法写出更好的参数解析,避免因外部使用的多样化导致内部解析异常。

package com.primer;import java.util.Arrays;
import com.primer.bean.CmdArgs;public class Main {private static DexToolManager mDexToolManager;private static CmdArgs mCmdArgs;public static void main(String[] args) {mCmdArgs = parserArgs(args);initDexToolManager();}//解析 java -jar 传入的参数private static CmdArgs parserArgs(String[] args) {if (args == null || args.length == 0) {throw new IllegalArgumentException(" argument not be empty");}CmdArgs cmdArgs = new CmdArgs();String current;for (int i = 0; i < args.length; i++) {current = args[i];if (current.equals("-mapping")) {if (i + 1 >= args.length) {throw new IllegalArgumentException("-mapping args error");}cmdArgs.mappingPath = args[i + 1];} else if (current.equals("-apk")) {if (i + 1 >= args.length) {throw new IllegalArgumentException("-apk args error");}cmdArgs.apkPath = args[i + 1];} else if (current.equals("-dexname")) {if (i + 1 >= args.length) {throw new IllegalArgumentException("-dexname args error");}cmdArgs.encodeDexFilename = args[i + 1];} else if (current.equals("-pkg")) {if (i + 1 >= args.length) {throw new IllegalArgumentException("-pkg args error");}String str = args[i + 1];String[] pkgList = str.split(",");if (pkgList == null || pkgList.length == 0) {throw new IllegalArgumentException("args error");}cmdArgs.specifiyPkgList = Arrays.asList(pkgList);}}cmdArgs.checkArgument();return cmdArgs;}private static void initDexToolManager() {DexConfig dexConfig = new DexConfig.Builder().setApkFilePath(mCmdArgs.apkPath).setMappingFilePath(mCmdArgs.mappingPath).setSpecifyPkgList(mCmdArgs.specifiyPkgList).setEncodeDexFilename(mCmdArgs.encodeDexFilename).setApkDecodeOutputDirname("apk-decode").build();//简单得对参数是否有效做检查dexConfig.checkConfigIllegal();mDexToolManager = new DexToolManager();mDexToolManager.setDexConfig(dexConfig);mDexToolManager.run();}
}

2、准备工作目录

  • initOutPutDir:创建输出目录、清空输出目录残留的文件

如果让自己写删除目录及目录下的所有文件,很容易让我们想起了递归遍历,可以递归删除文件。

public void traversalFile(File dirFile, FileTraversal traversal) {if (dirFile == null) {return;}for (File file : dirFile.listFiles()) {if (file.isDirectory()) {traversalFile(file, traversal);traversal.processDir(file);} else {traversal.processFile(file);}}}//使用
traversalFile(outFile, new FileTraversal() {@Overridepublic void processFile(File file) {if (file.exists()) {file.delete();}}@Overridepublic void processDir(File file) {if (file.exists()) {file.delete();}}});

3、加载映射

  • loadMappingConfig:加载 mapping.txt 映射文件

我们知道 build/output/**/ release/mapping.txt 就是 Android 开启混淆打包生成的映射文件,就是根据该文件的格式进行解析,可以从中解析获取混淆前类的全限定名(包名+类名),当然也可以拿到混淆前后的方法名等信息。

com.opos.mobad.service.tasks.a -> com.opos.mobad.service.tasks.a:java.lang.String a -> ajava.io.FileFilter b -> bjava.util.HashMap getPayMap(android.content.Context,boolean,int) -> aboolean d(android.content.Context) -> djava.lang.String j(android.content.Context) -> j

根据映射文件我们可以这样简单写出解析存储数据的 bean 类及关系。

//一个映射文件包含很多类的映射
public class ObsMapping {private LinkedList<ClassObsMapping> obsMapping;//略
}//一个类包含类名映射、多个成员变量映射、多个方法的映射(我们没有使用到成员变量,所以可以不要)
public class ClassObsMapping {//类名映射private MappingItem classMapping;//方法映射private LinkedList<MappingItem> methodsMapping;//略
}//映射的基本元素是混淆前后的名称
public class MappingItem {//混淆前名称private String originalName;//混淆后名称private String mappingName;//略
}

4、确认提取目标

  • collectionSpecifyPkgMapping:根据 -pkg 参数列表指定的包名,从映射文件中收集混淆后的包名。

如 com.primer.manager.A -> com.android.manager.AA,那么收集的是 com.android.manager,最终返回的是混淆后的类的包名。

5、apk 反编译

  • decodeApk:利用 apktool 工具反编译

简单地封装 Runtime.getRuntime(),通过调用 runtime.exec(cmdline) 执行控制台命令,这里是通过控制台执行 bat 脚本,再由脚本执行 java 命令执行 apktool。

APKTOOL_BAT_PATH 指向的一个 bat 脚本路径:

:: 参数1-apktool  参数2-apk路径  参数3-输出路径  -f:强制覆盖
java -jar %1 d %2 -o %3 -f
/*** java -jar apktool_2.7.0.jar d [apk file] -o [out name]** @param inputApkPath* @param outDirName* @return*/private String decodeApk(String inputApkPath, String outDirName) {if (inputApkPath == null || inputApkPath.isEmpty()) {throw new IllegalArgumentException("inputApkPath is null");}String cmdline;String outApkPath = OUTPUT_PATH + File.separator + outDirName;StringBuilder sb = new StringBuilder();sb.append(APKTOOL_BAT_PATH).append(" ").append(APKTOOL_JAR_PATH).append(" ").append(inputApkPath).append(" ").append(outApkPath);if (sb.toString().contains("\\") && !sb.toString().contains("\\\\")) {cmdline = sb.toString().replace("\\", "\\\\");} else {cmdline = sb.toString();}System.out.println("decodeApk cmd: " + cmdline);CommUtil.executeCmdline(cmdline, false);return outApkPath;}

6、smali 收集

  • collectionAndMoveSpecifyPkgSmail:根据上述收集到的映射,在反编译目录下查找文件并存放到额外目录下。

通过递归遍历过了 smali 找到映射文件,再把目标文件已到指定目录待下一步处理。

    private void collectionAndMoveSpecifyPkgSmail(String path, List<String> specifyPkgList) {if (CommUtil.isEmptyOrNoExists(specifyPkgList)) {return;}traversalFile(new File(path), new FileTraversal() {@Overridepublic void processFile(File file) {if (file.getName().endsWith(".smali")) {SmailFile smailFile = splitClassPkgname(file.getAbsolutePath());if (!CommUtil.isEmptyOrNoExists(smailFile.pakcgeName)) {for (String pkg : specifyPkgList) {if (pkg.equals(smailFile.pakcgeName)) {moveAndDeleteTargetFile(file, smailFile);break;}}}}}@Overridepublic void processDir(File file) {}});}
public class SmailFile {//映射后的文件名(类名)public String filename;//映射后的包名,如 com.primer.managerpublic String pakcgeName;//映射后的包路径,如 com/primer/manager//因为提取到外部目录下也应该创建相同包名的目录,再不 smali 文件存放到该目录下,确保前后一致public String pakagePath;@Overridepublic String toString() {return "SmailFile: " + filename + ", " + pakcgeName + ", " + pakagePath;}
}

7、smali 打包

  • encodeSmailToDex:使用 smali.jar 把一组 smali 文件打包成 dex

这里有一点需要注意的是,最新版 smali.jar 打包参数是 assemble ,好像以前的包版本打包参数是 b。

:: java -jar smali.jar b out_directory -o output.dex
:: 参数1:smali.jar
:: 参数2:out_directory smali 文件目录
java -jar %1 assemble %2

8、dex 加密

  • encodeDex:对 dex 文件应用自己的加密算法

dex 文件本质上是一个二进制文件,二进制文件读取出来就是一组字节数组 byte[],简单的对字节数组进行特殊操作(插入偏移量等)就是对文件的加密。

9、dex 加密文件放入包体

  • copyDexToAssets:如果你想把提取部分的代码 dex 后续通过动态加载方式执行,你可以重新把它打入包体的其他地方存储备用,也可以后续通过远程请求获取再加载等。

10、apk 打包

  • encodeApk:同样的,也是使用 apktool 工具

这里还是先调用 bat 脚本,再由 bat 执行 java 命令执行 apktool,你也可以以自己的方式处理。

java -jar apktool_2.7.0.jar b [apk decode file path]

:: 参数1-apktool  参数2-apk路径
java -jar %1 b %2

TODO:
当然,有了想法你可以做很多诸如此类的事情!

  • 你可以在 apk 打包完成功之后,完成重新签名
  • 等等等

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/647021.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Redis的五种常用数据类型详解及相关面试问题

目录 Redis的五种常用数据类型详解 简述 Redis五种基本数据类型 String字符串 常用命令 应用场景 Hash散列表 常用命令 使用场景 List链表 常用命令 应用场景 Set( 集合) 常用命令 应用场景 SortedSet( 有序集合) zset 常用命令介绍 应用场景 面试题常问的数…

【Pytorch 第一讲】 如何加载预训练模型

一. 封装Pytorch的Model 加载pre-trianed Model import torch import torchvision.models as models from torchvision import transforms# 1. 下载并加载预训练模型 model models.resnet18(pretrainedFalse) # 设置pretrainedFalse&#xff0c;表示不加载预训练权重# 2. 下载…

退货通知单下推销售退货单,无法下推问题排查

文章目录 退货通知单下推销售退货单&#xff0c;无法下推问题排查报错界面排查原因 退货通知单下推销售退货单&#xff0c;无法下推问题排查 报错界面 排查 检验单已做。 原因 合格未勾选判退。

按键协管指南针加速计陀螺仪GPS等原理图纸2

1.imu电路。 加速计包含重力感应。 到传感器芯片u8, U16, U18的信息都是用的spi接口&#xff0c;如下图所示。OSCAR_TO_IMU_SPI_SCLK_FL, IMU_TO_OSCAR_SPI_MISO_FL, OSCAR_TO_IMU_SPI_MOSI_FL接了u8, u16, u18,通过片选信号cs选择哪个芯片接收。 加速计&#xff0c;陀螺仪&…

Redash 默认key漏洞(CVE-2021-41192)复现

Redash是以色列Redash公司的一套数据整合分析解决方案。该产品支持数据整合、数据可视化、查询编辑和数据共享等。 Redash 10.0.0及之前版本存在安全漏洞&#xff0c;攻击者可利用该漏洞来使用已知的默认值伪造会话。 1.漏洞级别 中危 2.漏洞搜索 fofa "redash"…

289. 生命游戏

根据 百度百科 &#xff0c; 生命游戏 &#xff0c;简称为 生命 &#xff0c;是英国数学家约翰何顿康威在 1970 年发明的细胞自动机。 给定一个包含 m n 个格子的面板&#xff0c;每一个格子都可以看成是一个细胞。每个细胞都具有一个初始状态&#xff1a; 1 即为 活细胞 &am…

【ASP.NET Core 基础知识】--数据库连接--使用Entity Framework Core进行数据库访问

一、介绍 Entity Framework Core&#xff08;简称EF Core&#xff09;是微软推出的一个轻量级版的Entity Framework&#xff0c;它是一个开源的、跨平台&#xff08;Windows、Linux和macOS&#xff09;的对象关系映射&#xff08;ORM&#xff09;框架。EF Core 旨在提供快速的…

主播考核体系相关基础

1.主播薪资类型 2.主播考核体系 1.分为日常考核、月度考核 日常考核分为三部曲&#xff1a;播前、播中、播后 &#xff08;1&#xff09;播前 &#xff08;2&#xff09;播中 &#xff08;3&#xff09;播后 月度考核 月度考核表列举 主播等级划分要素 主播晋升考核方…

JVM篇----第六篇

系列文章目录 文章目录 系列文章目录前言一、堆(Heap-线程共享) -运行时数据区二、方法区/永久代(线程共享)三、JVM 运行时内存四、新生代前言 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站,这篇文章男女通用,看…

Java面试题(5)

22.Iterator如何使用&#xff0c;有什么特点 java中的Iterator功能比较见到你&#xff0c;并且只能单向移动 1. iterator()&#xff1a;要求容器返回一个Iterator 2. next():用于获取序列的下一个元素&#xff08;第一次调用获取第一个元素&#xff09; 3. hasNext()&#xff…

Qt6入门教程 11:父子对象关系

在上一篇中的纯手写部分&#xff0c;不管是创建菜单、工具栏还是状态栏&#xff0c;我们new完之后都未显式的调用delete进行销毁&#xff0c;这样难道不会有内存泄漏么&#xff1f; QMenuBar *menuBar new QMenuBar(this); QToolBar *toolBar new QToolBar(this); QStatusBa…

web前端之不一样的居中方式、解决tabBar选项卡居中问题、css支持嵌套、auto

MENU 前言htmlstyle效果 前言 这里不能使用justify-content: center;&#xff0c;因为在小屏幕上&#xff0c;这种方式无法显示最前面的两个tabBar。 html <div id"box" class"d_f o_a mt_50 mb_50 ml_20 mr_20"><div class"ws_n">…

【MySQL】如何通过DDL去创建和修改员工信息表

&#x1f308;个人主页: Aileen_0v0 &#x1f525;热门专栏: 华为鸿蒙系统学习|计算机网络|数据结构与算法 ​&#x1f4ab;个人格言:“没有罗马,那就自己创造罗马~” #mermaid-svg-fmKISDBsFq74ab2Z {font-family:"trebuchet ms",verdana,arial,sans-serif;font-siz…

【vue】Vue2和Vue3中的代码逻辑复用对比(mixins、自定义hook):

文章目录 一、前言&#xff1a;二、mixins&#xff1a;【1】mixins是什么&#xff1f;【2】mixins如何使用&#xff1f;【3】mixins的一些特性&#xff1a;【4】mixins的缺点&#xff1a; 三、hook&#xff1a;【1】Vue3.x中的自定义hook函数是什么&#xff1f;【2】mixins和Co…

4.【SpringBoot3】文章管理接口开发

序言 在文章管理模块&#xff0c;有以下接口需要开发&#xff1a; 新增文章文章列表&#xff08;条件分页&#xff09;获取文章详情更新文章删除文章 数据库表字段和实体类属性&#xff1a; 1. 新增文章 需求分析 当用户点击左侧菜单中的“文章管理”后&#xff0c;页面主…

[EFI]HP EliteDesk 800 G6 Mini PC电脑 Hackintosh 黑苹果efi引导文件

硬件型号驱动情况主板 HP EliteDesk 800 G6 Mini PC 处理器 Intel Core i5 10500 已驱动内存16GB 2667MHz DDR4已驱动硬盘SSD NVMe KINGSTON SNVS500G 500GB已驱动显卡Intel UHD Graphics 630已驱动声卡Realtek ALC222已驱动网卡Intel(R) Ethernet Connection (10) I219-LM已驱…

vue 点击按钮跳转另一个项目的链接地址,从另一个项目返回回来页面怎么让他刷新

如果你在 Vue 3 中点击按钮跳转到另一个项目的链接地址&#xff0c;然后从另一个项目返回时想要刷新页面&#xff0c;这就涉及到不同域的页面之间的通信问题。因为跨域的限制&#xff0c;返回的页面无法直接刷新原始页面。 一种解决方法是在跳转到另一个项目的链接地址时&…

腾讯云短信服务密钥信息

public IActionResult TestMessage() { // 腾讯云短信服务密钥信息 string secretId "-------------------------- "; string secretKey “---------------------------”; /* 必要步骤&#xff1a;* 实例化一个认证对象&#xff0c;入参需要传入腾讯云账户密钥对s…

SpringBoot_基础

学习目标 基于SpringBoot框架的程序开发步骤 熟练使用SpringBoot配置信息修改服务器配置 基于SpringBoot的完成SSM整合项目开发 一、SpringBoot简介 1. 入门案例 问题导入 SpringMVC的HelloWord程序大家还记得吗&#xff1f; SpringBoot是由Pivotal团队提供的全新框架&…

java数据结构与算法刷题-----LeetCode697. 数组的度

java数据结构与算法刷题目录&#xff08;剑指Offer、LeetCode、ACM&#xff09;-----主目录-----持续更新(进不去说明我没写完)&#xff1a;https://blog.csdn.net/grd_java/article/details/123063846 方法一&#xff1a;hash表 此方法是工作中时间可以使用的&#xff0c;因为…