android资源加固,Android apk加固实现原理

article/2025/10/6 14:38:50

apk加固是每一个app发布之前必须要做的事情;如果一个apk没有加固那么别人就很容易被别人反编译,看到这其中的原码,虽然现在有代码混淆、把业务写到native层,但是这都是治标不治本。反编译的技术在更新,那么保护Apk的技术就不能停止,虽然网上有很多加固的第三方框架,但是这加固的原理还是有必要去了解一下的,其实加固有些人认为很高深的技术,其实不然,说的简单点就是对源Apk的dex进行加密,然后在套上一层壳即可,当然这里还有一些细节需要处理,这就是本文需要介绍的内容了。

加固原理

1.在主工程中添加一个Module这个module就是用来给apk加壳用的。

2.在主工程中添加一个java的中lib库这个是用来加密主apk的dex、生成加固后的apk

e836428d61b9

工程目录.png

一个apk需要启动系统会去解压这个apk去得用dex文件,去解析这些dex文件去启动改apk,因为要把这个主apk的dex加密来达到不被反编译的效果,所以这些dex文件已经被加密了,系统去加载这些dex文件肯定是会报错的。为了能让apk能正常启动那就应该要去启动这个壳的Application。所以得在app的AndroidManifest注册壳的Application;这样才能正常的启动App。

下面的Apk加固的流程图:

e836428d61b9

加密.png

e836428d61b9

解密.png

加密的流程

先要生成Module中的aar库,生成后要解压该库得apk的壳dex包,在proxy_tools的java库中进行加密

生成apk的壳文件(jar转dex)

File aarFile = new File("proxy_core/build/outputs/aar/proxy_core-debug.aar");

File aarTemp = new File("proxy_tools/temp");

Zip.unZip(aarFile, aarTemp);

File classesJar = new File(aarTemp, "classes.jar");

File classesDex = new File(aarTemp, "classes.dex");

Process process = null;

try {

process = Runtime.getRuntime().exec("cmd /c dx --dex --output " + classesDex.getAbsolutePath()

+ " " + classesJar.getAbsolutePath());

} catch (IOException e) {

e.printStackTrace();

}

try {

process.waitFor();

} catch (InterruptedException e) {

e.printStackTrace();

}

if (process.exitValue() != 0) {

throw new RuntimeException("dex error");

}

通过这样的操作就可以把aar中的jar转换成dex包了,这样apk的壳dex就可以得到了。

加密apk中的dex文件

//下面加密码APK中所有的dex文件

File apkFile = new File("app/build/outputs/apk/debug/app-debug.apk");

File apkTemp = new File("app/build/outputs/apk/debug/temp");

//把apk解压出来

Zip.unZip(apkFile, apkTemp);

//只要dex文件拿出来加密

File dexFiles[] = apkTemp.listFiles(new FilenameFilter() {

@Override

public boolean accept(File file, String s) {

return s.endsWith(".dex");

}

});

//进行AES加密

AES.init(AES.DEFAULT_PWD);

for (File dexFile : dexFiles) {

try {

byte[] bytes = Utils.getBytes(dexFile);

byte[] encrypt = AES.encrypt(bytes);

FileOutputStream fileOutputStream = new FileOutputStream(new File(apkTemp,

"secret-" + dexFile.getName()));

fileOutputStream.write(encrypt);

fileOutputStream.flush();

fileOutputStream.close();

dexFile.delete();//删除没有加密的dex

} catch (Exception e) {

e.printStackTrace();

}

}

这样就进行了apk中的dex文件加密,加密完成后需要把壳dex文件放到apk的目录下生成新apk文件。

重新生成apk文件

//把dex放入apk解压路径中,重新生成apk文件

classesDex.renameTo(new File(apkTemp, "classes.dex"));

File unSignedApk = new File("app/build/outputs/apk/debug/app-unsigned.apk");

try {

//生未签名的apk

Zip.zip(apkTemp, unSignedApk);

} catch (Exception e) {

e.printStackTrace();

}

新的apk已经生成,下面就要签名对齐

新apk的签名对齐

//4.签名对齐

File alignedApk = new File("app/build/outputs/apk/debug/app-unsigned-aligned.apk");

try {

process = Runtime.getRuntime().exec("cmd /c zipalign -v -p 4 " + unSignedApk.getAbsolutePath()

+ " " + alignedApk.getAbsolutePath());

} catch (IOException e) {

e.printStackTrace();

}

try {

process.waitFor();

} catch (InterruptedException e) {

System.out.println("exception " + e.toString());

e.printStackTrace();

}

签名对齐后这样就可以签名生成可以运行的apk了

签名生成可以运行的apk文件

File jskFile = new File("proxy_tools/proxy.jks");

System.out.println("开始签名");

try {

process = Runtime.getRuntime().exec("cmd /c apksigner sign --ks " + jskFile.getAbsolutePath()

+ " --ks-key-alias qwert --ks-pass pass:123456 --key-pass pass:123456 --out "

+ signedApk.getAbsolutePath() + " " + alignedApk.getAbsolutePath());

System.out.println("签名完成");

} catch (IOException e) {

e.printStackTrace();

}

try {

process.waitFor();

} catch (InterruptedException e) {

e.printStackTrace();

}

if (process.exitValue() != 0) {

throw new RuntimeException("signed apk error");

}

System.out.println("生成apk成功");

}

到这里加密部分的已经完成,加密完成后就可以进行解密操作了。

解密流程

在解密之前先说一下apk安装后都会在/data/app/包名-xxx/生成一个叫base.apk的文件,这个是文件是apk中的所有dex文件一个包,所以需要拿到这个base.apk中的dex进行解密。

File apkFile = new File(getApplicationInfo().sourceDir);

Log.d(TAG,"apk的路径:"+apkFile.getAbsolutePath());

File versionDir = getDir(app_name+"_"+ app_version,MODE_PRIVATE);

File appDir = new File(versionDir,"app");

File dexFile = new File(appDir,"dexDir");

//得用我们的需要加载的dex文件

List dexFiles = new ArrayList<>();

if(MD5.verification(dexFile)){

//把apk解压出来

Zip.unZip(apkFile,appDir);

//获取目录下所有文件

File [] files = appDir.listFiles();

for(File file : files){

String name = file.getName();

Log.d(ProxyApplication.class.getName(),"dexName:"+name);

if(name.endsWith(".dex") &&!TextUtils.equals(name,"classes.dex")){

AES.init(AES.DEFAULT_PWD);

//读取文件内容

try {

byte[] bytes = Utils.getBytes(file);

//解密

byte[] decrypt = AES.decrypt(bytes);

FileOutputStream fileOutputStream = new FileOutputStream(file);

fileOutputStream.write(decrypt);

fileOutputStream.flush();

fileOutputStream.close();

dexFiles.add(file);

} catch (Exception e) {

e.printStackTrace();

}

}

}

}else {

for(File file : dexFile.listFiles()){

dexFiles.add(file);

}

}

//加载解密后的dex文件到系统

try {

loadDex(dexFiles,versionDir);

} catch (Exception e) {

e.printStackTrace();

}

这里就已经完成了dex文件的解密,接下来的就应该把解密后的dex文件交给系统处理让它去运行apk。

把dex交给系统处理

要把已经解密好的dex文件交给系统,那么首先要知道系统是如何的加载dex文件的,只有搞懂系统是如何的加载dex文件的才能把已经解密好的dex文件交给系统;getClassLoader这个方法相信我们都不陌生了,通过查看系统原码发现这个getClassLoader返回的ClassLoader是具体实现是一个叫PathClassLoader的类,这个类在系统原码中看到只有构造方法就没有其它的方法了,那再往它的父类中看,它的父类是一个叫BaseDexClassLoader通过查看里面的方法可以看到有一个findClass的方法去加载这些类。

e836428d61b9

findClass.png

findClass这个类又调用了pathList的里面的findClass方法,那个这个pathList又是什么呢?这个pathList是一个叫DexPathList的类,那再到DexPathList中查看这个findClass方法干了什么事情?下面这个就是DexPathList里面的findClass方法

e836428d61b9

DexPathList中的findClass.png

通过上面的代码可以看出这个DexPathList中的findClass方法是在遍历一个叫dexElements的一个数组来生成Class对象;接下就要找这个dexElements数组是在哪里被初始化的呢?通过查看DexPathList这个类的构造方法发现这个dexElements是通过一个叫makePathElements的方法来完成初始化的。

e836428d61b9

dexElements的初始化.png

到这里就已经知道系统是如何加载dex文件的,那么已经的解密好的dex文件要放到系统的dexElements中才能被系统加载到,系统是调用makePathElements方法去生成的dexElements数组,那么我们也可以通过反射的去调用这个方法生成一个自已的dexElements数组,再将自已的dexElement数组和系统的dexElements数组合并一个新数组设置到这个DexPathList中。

private void loadDex(List dexFiles,File versionDir) throws Exception{

//通过查看源码我们可以知道系统存放dex是用一个数组存的:dexElements,而dexElements是DexPathList的成员

Field pathListField = Utils.findField(getClassLoader(),"pathList");

Object pathList = pathListField.get(getClassLoader());

Field dexElementsFieId = Utils.findField(pathList,"dexElements");

//这个是DexPathList里面的原始数据,也就是系统中的dexElements数组

Object dexElements[] = (Object[]) dexElementsFieId.get(pathList);

//构建自已的dexElement

Method makeDexElements = Utils.findMethod(pathList,"makePathElements",List.class,File.class,List.class);

ArrayList suppressedExceptions = new ArrayList();

Object[] mCustomDexElements = (Object[]) makeDexElements.invoke(pathList,dexFiles,versionDir,suppressedExceptions);

//合并数组

Object newElements = Array.newInstance(dexElements.getClass().getComponentType(),dexElements.length+mCustomDexElements.length);

System.arraycopy(dexElements,0,newElements,0,dexElements.length);

System.arraycopy(mCustomDexElements,0,newElements,dexElements.length,mCustomDexElements.length);

//替换掉classloader中的elements数组

dexElementsFieId.set(pathList,newElements);

}

通过反射的就把解密的dex文件生成dexElements数组,再和系统的dexElements合并生成一个新数组替换掉DexPathList中的dexElements就达到交给系统处理效果。

e836428d61b9

效果图.png

总结

apk加固后主dex文件已经是看不到原码,只能看壳的dex文件中的原码,这样就达到了加固的效果,但是这样做还是不够完善,因为主App的Application已经是没有用了,这个问题的解决方法请到apk加固二中查看。

apk加固二https://www.jianshu.com/p/67feffa9bff3

如果大家在看的时候如果有错误的地方欢迎指出,我们共同进步。


http://chatgpt.dhexx.cn/article/LruUspSb.shtml

相关文章

Android中Apk加固代码实现

前言&#xff1a;上一篇博客已经把Apk加固的思路详细的介绍过了&#xff0c;也开始创建了一个空的demo进行&#xff0c;然后在项目中添加一个代理module&#xff08;解密&#xff0c;和系统源码交互功能&#xff09;和tools工具加密Java library 的module &#xff0c;这里开始…

Android APK加固原理

一、前言 Android作为开源框架&#xff0c;开放之余&#xff0c;所要面临的就是安全问题&#xff0c;世间之事&#xff0c;有正就有邪&#xff0c;有攻就有守&#xff0c;作为开发者虽然不需要进入专业安全领域&#xff0c;但还是需要掌握基本的安全常识和原理。 二、加壳 加…

APK加固原理详解

一、前言 之前使用的360加固&#xff0c;挺好用的&#xff0c;从2021年底的时候限制每天每个账号仅上传2次apk&#xff08;免费的&#xff0c;不知道VIP的是不是这样&#xff09;。通过这个事情&#xff0c;感觉技术还是掌握在自己手里稳妥点&#xff0c;不用受制于人&#xf…

Android中的Apk的加固(加壳)原理解析和实现

本文转载自&#xff1a;Android中的Apk的加固(加壳)原理解析和实现 - roccheung - 博客园 一、前言 今天又到周末了&#xff0c;憋了好久又要出博客了&#xff0c;今天来介绍一下Android中的如何对Apk进行加固的原理。现阶段。我们知道Android中的反编译工作越来越让人操作熟…

浅谈安卓apk加固原理和实现

转载本文需注明出处&#xff1a;微信公众号EAWorld&#xff0c;违者必究。 引言&#xff1a; 在安卓开发中&#xff0c;打包发布是开发的最后一个环节&#xff0c;apk是整个项目的源码和资源的结合体&#xff1b;对于懂点反编译原理的人可以轻松编译出apk的源码资源&#xff0c…

安卓逆向笔记--apk加固

安卓逆向笔记–apk加固 资料来源: 浅谈安卓apk加固原理和实现 Android中的Apk的加固(加壳)原理解析和实现 前两个太老了所以具体代码借鉴下面的 Android Apk加壳技术实战详解 一、apk常见加固方法 (1)代码层级加密–代码混淆 代码混淆是一种常见的加密方式。本质是把工程中原…

imx6ull uboot移植

以下内容来自&#xff1a;正点原子Linux驱动文档 一、简介 uboot移植主要是根据原厂的uboot移植&#xff1a;芯片厂商通常会做一块自己的评估板并发布BSP&#xff0c;当我们需要定制自己的开发板时可以根据自己的需求&#xff08;硬件上的不同&#xff09;&#xff0c;对原厂…

全志V3S嵌入式驱动开发(uboot移植)

【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing @163.com】 安装了ubuntu操作系统,有了开发板,下面就可以进行我们的开发工作了。第一步,我们要面临的问题就是uboot移植。一般来说,嵌入式的基础环境就是uboot、linux和rootfs。uboot完成一…

2021-09-14 uboot移植开发

引言&#xff1a;最近要改动uboot&#xff0c;实现像微软PC上&#xff0c;u盘一键刷机或手机上安全模式下刷机的操作 专门去好好研究了点uboot的启动过程&#xff1b;以下为总结&#xff1a; 嵌入式系统 微软-PC ——…

iTOP4412 uboot移植教程

好多刚开始学习uboot移植的同学总是觉得uboot好难&#xff0c;但是再难的问题如果把它一步步拆开&#xff0c;一个个解决&#xff0c;问题也就将迎刃而解。做uboot移植&#xff0c;我们首先就得了解uboot的编译流程&#xff0c;这里以在iTOP4412精英版2G内存的板子上移植u-boot…

<Linux开发> -之-系统移植 uboot移植过程详细记录(第二部分)

&#xff1c;Linux开发&#xff1e; -之-系统移植 uboot移植过程详细记录&#xff08;第二部分&#xff09; 第一部分链接&#xff1a;系统移植-之-uboot移植第一部分 第一部分主要讲解了&#xff0c;uboot移植过程中使用的一些工具的安装&#xff0c;以及测试nxp远程uboot&a…

X210开发板(S5PV210芯片)uboot移植DM9000驱动移植

前言 本文是介绍在uboot中如何移植DM9000的驱动&#xff0c;并不深入去讲解DM9000芯片的操作时序和内部寄存器&#xff0c;想要读懂驱动代码要仔细阅读DM9000芯片的数据手册。移植的基础是手里有DM9000芯片可以用的驱动代码&#xff0c;只需要根据开发板中DM9000芯片的接线方式…

嵌入式linux UBoot移植篇

如何在U-boot添加自己的linux板卡并启动呢&#xff1f; uboot 的移植并不是说我们完完全全的从零开始将 uboot 移植到我们现在所使用的开发板或者开发平台上。这个对于我们来说基本是不可能的&#xff0c;这个工作一般是半导体厂商做的&#xff0c; 半导体厂商负责将 uboot 移…

Uboot移植流程

linux-Bootloader&#xff08;Uboot&#xff09;移植流程 前言 最近在做ZigBee的温室大棚项目&#xff0c;将自己学习的过程和经验分享给大家。本文基于linux3.4.39内核版本&#xff0c;s5p6818开发板实现。 1、uboot启动简介 uboot启动的过程比较复杂&#xff0c;这里就只…

IMX6ULL Uboot 移植

使用的开发板&#xff1a;正点原子ALPHA V2.2 Uboot简介 在学习STM32的过程中使用过IAP在线升级就会知道&#xff0c;有引导程序APP程序&#xff0c;即bootloader程序APP。在学习嵌入式Linux的时候也一样&#xff0c;这个引导程序就是Uboot. uboot移植主要是根据原厂的uboot移…

二、uboot移植

二、uboot移植 版本作者时间备注V 1.0bug设计工程师2021/11/10创建文件软件网盘链接0交叉编译工具链接:https://pan.baidu.com/s/1yFO2NDMet9_b1E1q1rMwEA提取码:42kluboot源码同上linux源码同上文件系统工具同上tftp工具同上2.1 简单说明 uboot制作结束会生成 u-boot-etc44…

linux-uboot 移植四 uboot的移植

概述 前边的章节中介绍到如果要移植uboot的话&#xff0c;最好的参考就是由官方提供的demo。 1、移植 1.1 添加board对应的板级文件夹 uboot 中每个板子都有一个对应的文件夹来存放板级文件&#xff0c;比如开发板上外设驱动文件等等。 NXP 的 I.MX 系列芯片的所有板级文件…

[uboot 移植]uboot 移植过程

文章目录 uboot 移植1 修改顶层 Makefile2 在 board 文件夹下添加开发板对应的板级文件2.1 imximage_lpddr2.cfg 和 imximage.cfg 文件2.2 plugin.S 文件2.3 Kconfig 文件2.4 igkboard.c 文件2.5 MAINTAINERS 文件2.6 Makefile 文件 3 添加 igkboard_defconfig 配置文件4 添加开…

UBoot 移植

1 NXP官方开发板uboot编译测试 1 查找 NXP 官方的开发板默认配置文件 因为我们的开发板是参考 NXP 官方的 I.MX6ULL EVK 开发板做的硬件&#xff0c;因此我们在移植 uboot 的时候就可以以 NXP 官方的 I.MX6ULL EVK 开发板为蓝本。 在 NXP 官方 I.MX6UL/6ULL 默认配置文件中找…

大话uboot 移植

结合作者多年的移植经验&#xff0c;尽量简单的为大家描述一个uboot 的移植过程。希望通过描述&#xff0c;给初入移植行道的你带来美好的希望。接下来&#xff0c;我们通过以下几个方面来描述。 1. arm soc 的启动方式 在描述soc 前&#xff0c;我们先看下一个简单的arm soc:…