019 Android加固之APK加固的原理和实现

article/2025/10/6 14:36:00

文章目录

    • 前言
    • 加载Activity遇到的问题
      • APK的启动过程
      • 替换ClassLoader流程
        • 获取ActivityThread类对象
        • 获取AppBindData类对象mBoundApplication
        • 获取LoadedApk类对象info
        • 获取info对象中的ClassLoader
    • 设计傀儡dex文件
    • 手工加固APK
    • 代码实现APK加固
      • 实现步骤
    • 总结

前言

动态加载dex之后,我们会想说,能不能将整个程序的dex都进行动态加载。如果将加载的dex事先加密,加载前解密,这样就完成了对程序完整的解密了。但这里面遇到一个问题,那就是Android中很多组件其实是事先在清单文件中注册过的,我们需要在不多修改清单文件的前提下,完成对藏匿在资源中加密的dex文件。完成了这个过程,也就完成了apk的加固

那做到在不过多修改清单文件的前提下,完成对藏匿在资源中加密的dex文件,我们将分为以下几步完成:

  1. 完成对已注册的Activity的加载
  2. 找寻apk启动时最开始启动的代码,插入自己的代码
  3. 设计傀儡dex文件,启动apk之后,将傀儡dex代码替换为目标dex代码

接下来就是按照这个思路,开始加载Activity

加载Activity遇到的问题

我们已经学会了如何动态加载一个类,那动态加载一个Activity呢?为了能让Activity免除资源困扰,我们在资源中先创建一个Activity类,资源同样也建立好。

先看Activity类代码

package com.example.apkdemo2;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;public class MainActivity2 extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main2);}
}

再看资源文件

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:orientation="vertical"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".MainActivity2"><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="第二个Activity"></TextView>
</LinearLayout>

接着将这个Activity反编译后再转成dex文件

在这里插入图片描述

然后将生成的dex放到assets目录下。

在这里插入图片描述

然后删除MainActivity2这个类。

我们在这个项目中去加载Activity,因为当前项目已经有Activity2的资源文件了,这样就可以不受资源的困扰,难度会低很多。

接着完成动态加载dex的步骤:

  1. 拷贝自定义资源中的dex到程序中
  2. 创建一个DexClassLoader,加载dex
  3. 调用加载dex中的class方法

这个步骤我们之前已经完成了

区别在于第三步,这里需要获取类类型,设置Intent信息,启动Activity,代码如下:

Class clz=null;try {clz=dexClassLoader.loadClass("com.example.apkdemo2.MainActivity2");} catch (ClassNotFoundException e) {e.printStackTrace();}Intent intent=new Intent(this,clz);startActivity(intent);

完成后我们在界面中增加按钮,然后在按钮中调用上面的方法。

在这里插入图片描述

运行程序,点击按钮,发现出现了错误,报错信息如下:

unable to instantiate activity

无法实例化Activity。

我们使用创建了DexClassLoader加载器,加载了我们需要的类,但有一个问题,那就是程序当前的ClassLoader是哪个?答案是PathClassLoader,但是PathClassLoader没有我们加载的类。

针对这个问题有两个解决方案

  1. 直接使用PathClassLoader加载我们需要的类
  2. 使用我们自己的ClassLoader替换掉PathClassLoader

第一种方法显然不行。除非我们能先将dex文件安装到apk中,既然都能安装到apk中了,那就不能做到我们想要做的隐藏效果了。所以排除第一种方案

第二种方法,替换掉PathClassLoader,这种方法应该是可行的,因为只有ClassLoader换了,用对应的ClassLoader肯定能加载对应的类

那怎么才能替换掉ClassLoader呢?需要我们进一步分析APK的启动过程,找到保存ClassLoader的变量,变量所在的类,使用反射修改。

接下来从分析APK的启动过程开始

APK的启动过程

关于APK的启动过程,这里推荐一篇文章:

《Android应用程序启动过程源代码分析》https://blog.csdn.net/Luoshengyang/article/details/6689748

这篇文章详细的分析了Android应用程序的启动过程。这里就不再赘述,做一个简单总结

Step 1. Launcher.startActivitySafely
Step 2. Activity.startActivity
Step 3. Activity.startActivityForResult
Step 4. Instrumentation.execStartActivity
Step 5. ActivityManagerProxy.startActivity
Step 6. ActivityManagerService.startActivity
Step 7. ActivityStack.startActivityMayWait
Step 8. ActivityStack.startActivityLocked
Step 9. ActivityStack.startActivityUncheckedLocked
Step 10. Activity.resumeTopActivityLocked
Step 11. ActivityStack.startPausingLocked
Step 12. ApplicationThreadProxy.schedulePauseActivity
Step 13. ApplicationThread.schedulePauseActivity
Step 14. ActivityThread.queueOrSendMessage
Step 15. H.handleMessage
Step 16. ActivityThread.handlePauseActivity
Step 17. ActivityManagerProxy.activityPaused
Step 18. ActivityManagerService.activityPaused
Step 19. ActivityStack.activityPaused
Step 20. ActivityStack.completePauseLocked
Step 21. ActivityStack.resumeTopActivityLokced
Step 22. ActivityStack.startSpecificActivityLocked
Step 23. ActivityManagerService.startProcessLocked
Step 24. ActivityThread.main
Step 25. ActivityManagerProxy.attachApplication
Step 26. ActivityManagerService.attachApplication
Step 27. ActivityManagerService.attachApplicationLocked
Step 28. ActivityStack.realStartActivityLocked
Step 29. ApplicationThreadProxy.scheduleLaunchActivity
Step 30. ApplicationThread.scheduleLaunchActivity
Step 31. ActivityThread.queueOrSendMessage
Step 32. H.handleMessage
Step 33. ActivityThread.handleLaunchActivity
Step 34. ActivityThread.performLaunchActivity
Step 35. MainActivity.onCreate

apk的启动过程相对比较复杂,我们的分析目的是为了找到PathClassLoader,所以不需要对每一个步骤进行详细了解,整个启动过程可以简化为下面的步骤

Step 2. Activity.startActivity--->CreateProcess
......
Step 24. ActivityThread.main----->入口点
......
Step 35. MainActivity.onCreate--->main函数

用Windows系统来对比参照的话,Step 2可以看作是创建进程,一直到Step 24中间的步骤就等于是创建进程的准备工作,而真正的程序入口则是ActivityThread.main,相当于Windows的程序入口,接着Step 35才是真正执行用户代码的地方,相当于是main函数了

然后就可以进入Android源码网站http://androidxref.com/,开始分析app启动过程

在这里插入图片描述

另外一种分析的方法就是在onCreate函数下断,然后查看调用堆栈

替换ClassLoader流程

获取ActivityThread类对象

这个源码分析起来还是比较吃力的,这里直接看结论。替换ClassLoader的流程如下

在这里插入图片描述

首先获取ActivityThread类类型,然后调用这个类的currentActivityThread方法,目的是为了拿到sCurrentActivityThread对象

在这里插入图片描述

 private static ActivityThread sCurrentActivityThread;

sCurrentActivityThread是ActivityThread类的类对象,拿到了这个类的类对象,我们就可以操作整个类的数据

获取AppBindData类对象mBoundApplication

在这里插入图片描述

然后.通过类对象获取成员变量mBoundApplication

获取LoadedApk类对象info

在这里插入图片描述

获取到了AppBindData的类对象以后,就可以拿到AppBindData的类对象内的成员变量info,也就是LoadedApk类对象

获取info对象中的ClassLoader

在这里插入图片描述

接着就能获取到LoadedApk类对象内的ClassLoader。最后需要将这一段替换ClassLoader的逻辑转换成代码,就解决了这个问题。

完整代码如下:

 //替换app启动时的classloader为加载的dexclassloaderprivate void replaceClassLoader(DexClassLoader dexClassLoader) {try {//1.获取ActivityThread类对象//获取类类型Class clzActivityThread=Class.forName("android.app.ActivityThread");//获取类方法Method methodcurrentActivityThread=clzActivityThread.getDeclaredMethod("currentActivityThread");//调用方法Object objectActivityThread = methodcurrentActivityThread.invoke(null,new Object[]{});//2.通过类对象获取成员变量mBoundApplication//获取字段Field fieldmBoundApplication=clzActivityThread.getDeclaredField("mBoundApplication");//取消访问检查fieldmBoundApplication.setAccessible(true);//获取字段的值Object objBoundApplication= fieldmBoundApplication.get(objectActivityThread);//3.获取mBoundApplication对象中的成员变量info//获取类类型Class clzAppBindData=Class.forName("android.app.ActivityThread$AppBindData");//获取字段Field fieldInfo=clzAppBindData.getDeclaredField("info");fieldInfo.setAccessible(true);//获取字段的值Object objInfo = fieldInfo.get(objBoundApplication);//4.获取info对象中的mClassLoader//获取类类型Class clzLoadedApk=Class.forName("android.app.LoadedApk");//获取字段Field fieldmClassLoader=clzLoadedApk.getDeclaredField("mClassLoader");fieldmClassLoader.setAccessible(true);//设置字段 替换ClassLoaderfieldmClassLoader.set(objInfo,dexClassLoader);} catch (Exception e) {e.printStackTrace();}}

设计傀儡dex文件

经过对APK启动的分析,可知在APK启动时在创建MainActivity前,会创建Application对象,调用其attachBaseContext方法,以及onCreate方法 。所以我们设计的傀儡dex只需要先原APK清单文件中添加Application的节点,创建一个MyApplication类,实现attachBaseContext方法以及onCreate方法,在这两个方法中初始化原dex文件,加载dex文件即可

我们可以在attachBaseContext方法,加载源dex文件返回dex加载器,替换系统默认的加载器,然后剩下的交给系统处理即可

先编写傀儡Application代码,继承自Application,重写attachBaseContext方法和onCreate方法

package com.example.dummydex;import android.app.Application;
import android.content.Context;public class DummyApplication extends Application {@Overrideprotected void attachBaseContext(Context base) {super.attachBaseContext(base);//code}@Overridepublic void onCreate() {super.onCreate();//code}
}

在attachBaseContext方法中添加加载源dex文件和替换ClassLoader的代码

protected void attachBaseContext(Context base) {super.attachBaseContext(base);//拷贝自定义资源中的dex文件到程序目录下String path=CopyDex("src.dex");//创建一个DexClassLoader 加载dexDexClassLoader dexClassLoader=GetLoader(path);//替换ClassLoaderreplaceClassLoader(dexClassLoader);}

代码中的三个方法和之前的一致,到此DummyApplication类已经写完。注意需要将DummyApplication类的完整类(带包名)写入待加密的清单文件

使用smali.jar将DummyApplication类编译成class.dex

在这里插入图片描述

至此,傀儡dex文件生成完毕

手工加固APK

有了以上的准备,我们基本上可以手工完成对一个APK的加固,大致步骤:

  1. 获取待加固的apk,随便写一个hello world
  2. 使用apktool反编译apk,修改AndroidManifest.xml,添加DummyApplication类信息,让程序运行的第一个类为DummyApplication类
  3. 将源classes.dex放入assets目录,修改文件名为src.dex
  4. 使用apktool重打包修改后的信息
  5. 替换重打包后的classes.dex为傀儡dex
  6. 为新打包的apk进行签名

实际操作如下:

在这里插入图片描述

java -jar .\apktool.jar d .\test.apk

首先用apktool将需要加固的apk进行反编译,反编译后会生成文件夹

在这里插入图片描述

然后修改清单文件,在application中添加name属性,类名为dummydex的完整包名+类名

在这里插入图片描述

修改完成之后 新建assets文件夹,将apk原本的dex文件放到assets文件夹下,然后修改名称为src.dex

在这里插入图片描述

java -jar .\apktool.jar b .\test -o app.apk

再将apk进行回编译。如果回编译的时候报了下面的错误

No resource identifier found for attribute ‘compileSdkVersion’ in package ‘android’

那么需要执行一下这条命令

java -jar apktool.jar empty-framework-dir --force

清空一下framework目录

在这里插入图片描述

然后打开压缩包,将原来的classes.dex删除,然后把dummy.dex放到apk里面

在这里插入图片描述

并修改为classes.dex,最近将apk打上签名,整个加固过程就完成了

在这里插入图片描述

最后安装,运行成功。那么整个加固过程就是成功的。

在这里插入图片描述

此时我们再用AndroidKiller进行反编译,MainActivity已经无法找到

在这里插入图片描述

工程文件中只有DummyApplication.smali文件。到此就完成了整个手工加固的过程

代码实现APK加固

实现步骤

根据手工加固APK的步骤得出将其转出代码的步骤:

  1. 获取待加密的apk路径
  2. 调用apktool,反编译目标apk
  3. 修改AndroidManifest.xml添加DummyApplication的信息
  4. 将源classes.dex复制到assets目录,修改文件名为src.dex
  5. 调用apktool重新打包,生成新的apk
  6. 修改新的apk中的calsses.dex将其替换为傀儡dex或者将反编译后的smali代码替换为傀儡dex的smali代码
  7. 为新的apk进行签名

主要流程代码如下:

public static void Pack(){//需要反编译的apk文件名String fileName="test.apk";//获取当前路径String currentdir=System.getProperty("user.dir");//构造全路径String filepath=currentdir+ File.separator+fileName;//去掉扩展名String NoExtenDir=StringsUtils.getFileNameNoEx(filepath);//运行apktool 反编译apkSystem.err.println("1.反编译apk...");CMDUtils.runCMD("java -jar apktool.jar d "+filepath);System.err.println("1.反编译apk完成...");//修改xml文件String xmlPath=NoExtenDir+File.separator+"AndroidManifest.xml";System.err.println("2.修改清单文件...");XMLUtils.ChagenApplication(xmlPath);System.err.println("2.修改清单文件完成...");//拷贝源dex到assets目录String assetsDir=NoExtenDir+ File.separator+"assets";System.err.println("3.拷贝源dex到assets目录...");FileUtils.copyFileFromZip(filepath,assetsDir);System.err.println("3.拷贝源dex到assets目录完成...");//删除smali文件 拷贝dummydex的smaliString smaliDir=NoExtenDir+ File.separator+"smali";System.err.println("4.删除smali文件 拷贝dummydex的smali...");FileUtils.deleteFolder(smaliDir);String oldDir=currentdir+ File.separator+"dummySmali";FileUtils.copyFolder(oldDir,smaliDir);System.err.println("4.删除smali文件 拷贝dummydex的smali完成...");//重打包System.err.println("5.重打包apk...");CMDUtils.runCMD("java -jar apktool.jar b "+NoExtenDir+" -o pack.apk");System.err.println("5.重打包apk完成...");//签名System.err.println("6.对apk进行签名...");String outpath =currentdir+File.separator+"pack.apk";String signPath =currentdir+File.separator+"pack_sigin.apk";CMDUtils.runCMD("java -jar signapk.jar testkey.x509.pem testkey.pk8 "+outpath+" "+signPath,"sign");System.err.println("6.对apk进行签名完成...");//收尾工作System.err.println("7.收尾工作...");FileUtils.deleteFolder(NoExtenDir);File file=new File(outpath);file.delete();System.err.println("7.收尾工作完成...");//输出文件路径System.out.println("8. 已加固文件:"+signPath);}

运行效果如图:

在这里插入图片描述

运行完成之后,

在这里插入图片描述

APK加固也成功了。完整工程代码如下:

https://download.csdn.net/download/qq_38474570/23560575?spm=1001.2014.3001.5503

总结

通过回顾思路,写代码对APK加固有了一定的认识,在完全实现自动化的那一刻,感叹程序的魅力。不过这只是一个Demo,还有很多可以完善的地方,比如内存加载dex文件,合并源dex和傀儡dex等等。


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

相关文章

【Android 安全】Android 应用 APK 加固总结 ( 加固原理 | 应用加固完整的实现方案 | 源码资源 )

文章目录 一、 APK 加固原理1、 Android 应用反编译2、 ProGuard 混淆3、 多 dex 加载原理4、 代理 Application 开发5、Java 工具开发6、Application 替换 二、 应用加固完整的实现方案1、 代理 Application( 1 ) ProxyApplication( 2 ) OpenSSL 解码 Kotlin 类( 3 ) 反射工具…

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

apk加固是每一个app发布之前必须要做的事情;如果一个apk没有加固那么别人就很容易被别人反编译&#xff0c;看到这其中的原码&#xff0c;虽然现在有代码混淆、把业务写到native层&#xff0c;但是这都是治标不治本。反编译的技术在更新&#xff0c;那么保护Apk的技术就不能停止…

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 添加开…