首页
社区
课程
招聘
[原创]Android从整体加固到抽取加固的实现及原理
发表于: 2025-5-21 23:34 4465

[原创]Android从整体加固到抽取加固的实现及原理

2025-5-21 23:34
4465

虽然网上针对整体加固和抽取加固已经有不少文章讨论如何实现以及脱壳,也有很多成熟的加固方案

但是在学习这方面技术时发现涉及到的理论知识很多,不能局限于基本流程和代码实现而不明白底层原理

身为初学者最大的痛苦是知识体系庞大不知道如何下手,所以希望本文能抛砖引玉,对于入门Android加固的师傅有所帮助

本文主要内容如下:

由于涉及的知识较多,分为了3部分:

阅读本文的正确姿势:

刚开始不要试图把握每一个细节,先把握整体再逐步学习细节部分,可以多看看流程图

了解相关理论基础知识后,再看一到三代加固的实现,如果部分原理和代码不明白则阅读详解部分

原理部分强烈推荐阅读Android进阶解密和Andorid系统源码

附件attachments.zip (31.7MB)内容如下:

AndroidShell

Android壳程序项目,包括一代到三代加固的代理类,其中整体加固已注释,默认为三代抽取加固

ReadDex

Dex文件解析器项目,支持抽取指定dex文件所有DexClassDef的代码,过滤系统类

ShellScripts

一代到三代的加壳程序自动化脚本

ShellScripts\tools目录下提供以下工具

JavaReflectionDemo

java反射的demo

LoadDexDemo

加载dex文件并调用其中指定类的demo

ShellTestResults.zip (76.7MB 单独压缩)

一代到三代加固的测试文件,包括源程序,壳程序,加壳后的程序

附件链接: AndroidShell 83eK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6H3j5h3&6Q4x3X3g2T1j5h3W2V1N6g2)9J5k6h3y4G2L8g2)9J5c8Y4y4Q4x3V1j5I4y4r3A6J5P5e0p5I4g2%4m8f1L8s2A6i4d9e0k6v1g2K6y4J5y4p5S2f1N6#2)9K6c8Y4m8%4k6q4)9K6c8s2q4K6P5i4M7`.

References: 列举了本文参考的部分资料,常用脱壳工具和第四代加固——Dex2C和DexVMP相关项目

由于本人知识水平有限,如有不足之处望各位师傅指出

加密壳

落地指释放Dex文件到文件系统,不落地则不释放Dex文件,直接在内存中加载

四代壳

DexVMP Dex2C

混淆壳

OLLVM

在学习Android加固之前,一定要了解以下知识:

参考 java反射技术学习 Java反射机制-十分钟搞懂

示例文件: attachments\JavaReflectionDemo\ReflectionDemo.java

反射的原理:

Java反射机制的核心是在程序运行时动态加载类并获取类的详细信息,从而操作类或对象的属性和方法

本质是JVM得到class对象之后,再通过class对象进行反编译,从而获取对象的各种信息

Java属于先编译再运行的语言

程序中对象的类型在编译期就确定下来了,而当程序在运行时可能需要动态加载某些类,这些类因为之前用不到,所以没有被加载到JVM.通过反射,可以在运行时动态地创建对象并调用其属性,不需要提前在编译期知道运行的对象是谁.

反射是实现动态加载的技术之一

反射的优缺点:

优点

在运行时获得类的各种内容,进行反编译,对于Java这种先编译再运行的语言,能够让我们很方便的创建灵活的代码,这些代码可以在运行时装配,无需在组件之间进行源代码的链接,更加容易实现面向对象.

缺点

反射会消耗一定的系统资源,因此,如果不需要动态地创建一个对象,那么就不需要用反射;

反射调用方法时可以忽略权限检查,因此可能会破坏封装性而导致安全问题.

反射的作用: 反射机制在实现壳程序动态加载被保护程序时非常关键,它可以突破默认的权限访问控制,访问安卓系统默认情况下禁止用户代码访问的以及不公开的部分.

正常类的加载过程:

对于同一个类而言,无论有多少个实例,都只对应一个Class对象

Java反射的本质: 获取Class对象后,反向访问实例对象

1-JVM创建Class对象

反射相关文件

JDK中,主要由以下类来实现Java反射机制,这些类都位于java.lang.reflect包中

Class类: 代表一个类

Constructor 类: 代表类的构造方法

Field 类: 代表类的成员变量(属性)

Method类: 代表类的成员方法

反射的入口-Class类:

Class类的对象称为类对象

Class类是Java反射机制的起源和入口

用于获取与类相关的各种信息

提供了获取类信息的相关方法

Class类继承自Object类

Class类是所有类的共同的图纸

每个类有自己的对象,好比图纸和实物的关系

每个类也可看做是一个对象,有共同的图纸Class,存放类的结构信息,比如类的名字、属性、方法、构造方法、父类和接口,能够通过相应方法取出相应信息

示例类,用于后续操作

非动态加载时,可通过.class属性或实例.getClass()方法获取Class类

动态加载时,可使用Class.forName()和ClassLoader.loadClass()加载并获取类对象

输出如下

输出如下

输出如下

输出如下

输出如下

输出如下

输出如下

封装Reflection.java反射类便于后续使用,提供以下功能:

热修复和插件化技术依赖于ClassLoader,JVM虚拟机运行class文件,而Dalvik/ART运行dex文件,所以它们的ClassLoader有部分区别

Java的ClassLoader分为两种:

系统类加载器

BootstrapClassLoader, ExtensionsClassLoader, ApplicationClassLoader

自定义类加载器

Custom ClassLoader, 通过继承java.lang.ClassLoader实现

具体分析如下

Bootstrap ClassLoader (引导类加载器)

是使用C/C++实现的加载器(不能被Java代码访问),用于加载JDK的核心类库,例如java.lang和java.util等系统类

会加载JAVA_HOME/jre/lib和-Xbootclasspath参数指定的目录

JVM虚拟机的启动就是通过BootstrapClassLoader创建的初始类完成的

可通过如下代码得出其加载的目录(java8)

效果如下

Extensions ClassLoader (拓展类加载器)

该类在Java中的实现类为ExtClassLoader,用于加载java的拓展类,提供除系统类之外的额外功能

用于加载JAVA_HOME/jre/lib/ext和java.ext.dir指定的目录

以下代码可以打印ExtClassLoader的加载目录

Application ClassLoader (应用程序类加载器)

对应的实现类为AppClassLoader,又可以称作System ClassLoader(系统类加载器),因为它可以通过ClassLoader.getSystemClassLoader()方法获取

用于加载程序的Classpath目录和系统属性java.class.path指定的目录

Custom ClassLoader (自定义加载器)

除了以上3个系统提供的类加载器之外,还可以通过继承java.lang.ClassLoader实现自定义类加载器

Extensions和Application ClassLoader也继承了该类

运行一个Java程序需要几种类型的类加载器呢?可以使用如下代码验证

打印出了AppClassLoader和ExtClassLoader,由于BootstrapClassLoader由C/C++编写,并非Java类,所以无法在Java中获取它的引用

注意:

系统提供的类加载器有3种类型,但是系统提供的ClassLoader不止3个

并且,AppClassLoader的父类加载器为ExtClassLoader,不代表AppClassLoader继承自ExtClassLoader

ClassLoader的继承关系如图所示:

ClassLoader 是一个抽象类,定义了ClassLoader的主要功能

SecureClassLoader 继承自ClassLoader,但并不是ClassLoader的实现类,而是拓展并加入了权限管理方面的功能,增强了安全性

URLClassLoader 继承自SecureClassLoader 可通过URL路径从jar文件和文件夹中加载类和资源

ExtClassLoader和AppClassLoader都继承自URLClassLoader

它们都是Launcher的内部类,而Launcher是JVM虚拟机的入口应用,所以ExtClassLoader和AppClassLoader都在Launcher中初始化

类加载器查找Class采用了双亲委托模式:

双亲委托机制的优点:

避免重复加载

如果已经加载过Class则无需重复加载,只需要读取加载的Class即可

更加安全

保证无法使用自定义的类替代系统类,并且只有两个类名一致并且被同一个加载器加载的类才会被认为是同一个类

ClassLoader.loadClass方法源码如下(Java17)

注释1处 检查传入的类是否被加载, 如果已经加载则不执行后续代码

注释2处 若父类加载器不为null则调用父类loadClass方法加载Class

注释3处 如果父类加载器为null则调用findBootstrapClassOrNull方法

该方法内部调用了Native方法findBootstrapClass,最终用Bootstrap ClassLoader检查该类是否被加载

注意: 在Android中,该方法直接返回null,因为Android中没有BootstrapClassLoader

注释4处 调用自身的findClass查找类

以上流程示意图如下

3-ClassLoader-LoadClass

Java中的ClassLoader可以加载jar和class文件(本质都是加载class文件)

而在Android中,无论DVM还是ART加载的文件都是dex文件,所以需要重新设计ClassLoader的相关类.

Android中的ClassLoader分为系统类加载器和自定义加载器:

系统类加载器

包括 BootClassLoader, PathClassLoader, DexClassLoader等

自定义加载器

通过继承BaseDexClassLoader实现,它们的继承关系如图所示:

各个ClassLoader的作用:

ClassLoader

抽象类,定义了ClassLoader的主要功能.

BootClassLoader

继承自ClassLoader,用于Android系统启动时预加载常用类.

SecureClassLoader

继承自ClassLoader扩展了类权限方面的功能,加强了安全性.

URLClassLoader

继承自SecureClassLoader,用于通过URL路径加载类和资源.

BaseDexClassLoader

继承自ClassLoader,是抽象类ClassLoader的具体实现类.

InMemoryDexClassLoader(Android8.0新增)

继承自BaseDexClassLoader,用于加载内存中的dex文件.

PathClassLoader

继承自BaseDexClassLoader,用于加载已安装的apk的dex文件.

DexClassLoader

继承自BaseDexClassLoader,用于加载已安装的apk的dex文件,以及从SD卡中加载未安装的apk的dex文件.

实现Android加固时,壳程序动态加载被保护程序的dex文件主要使用以下3个类加载器:

DexClassLoader 可以加载未安装apk的dex文件

它是一代加固——整体加固(落地加载)的核心之一

InMemoryDexClassLoader 可以加载内存中的dex文件

它是二代加固——整体加固(不落地加载)的核心之一

BaseDexClassLoader是ClassLoader的具体实现类

实际上DexClassLoader,PathClassLoader以及InMemoryDexClassLoader加载类时,均通过委托BaseDexClassLoader实现

Dex文件的加载依赖于前文提到的PathClassLoader,DexClassLoader和InMemoryDexClassLoader

加载Dex文件的功能均通过委托父加载器BaseDexClassLoader实现.其中PathClassLoader和DexClassLoader调用相同的BaseDexClassLoader构造函数,InMemoryDexClassLoader调用另一个构造函数.

最终通过ArtDexFileLoader::OpenCommon方法在ART虚拟机中创建DexFile::DexFile对象,该对象是Dex文件在内存中的表示,用于安卓程序运行时加载类以及执行方法代码,也是后续第三代加固——代码抽取加固,进行指令回填时的关键对象.

三种ClassLoader加载Dex文件的流程如下(基于Android10.0):

Java层

PathClassLoader和DexClassLoader委托BaseDexClassLoader最终执行JNI方法DexFile.openDexFileNative进入Native层.

而InMemoryDexClassLoader委托BaseDexClassLoader后则执行DexFile.openInMemoryDexFiles进入Native层.

4-LoadDex-JavaLevel

Native层

PathClassLoader和DexClassLoader这条委托链会根据不同情况,调用ArtDexFileLoader::Open的不同重载,或者调用OpenOneDexFileFromZip.

InMemoryDexClassLoader调用ArtDexFileLoader::Open的第3种重载.

无论是调用哪个函数,最终都会调用ArtDexFileLoader::OpenCommon.

5-LoadDex-NativeLevel

经过以上调用流程后进入ArtDexFileLoader::OpenCommon,经过DexFile的初始化和验证操作后便成功创建了DexFile对象:

6-LoadDex-CreateDexFile

创建DexFile对象后,Class对应的文件便被加载到ART虚拟机中

前文通过ClassLoader.loadClass讲解了双亲委托机制,那么一个Class具体是如何被加载到JVM中的呢?

首先,继承自BaseDexClassLoader的3种ClassLoader调用自身loadClass方法时,委托父类查找,委托到ClassLoader.loadClass时返回.

BaseDexClassLoader.findClass调用DexPathList.findClass,其内部调用Element.findClass,最终调用DexFile.loadClassBinaryName进入DexFile中,该流程如图所示:

7-loadClass1

进入DexFile后,主要执行以下操作:

DexFile

通过JNI函数defineClassNative进入Native层.

DexFile_defineClassNative

通过FindClassDef枚举DexFile的所有DexClassDef结构并使用ClassLinker::DefineClass创建对应的Class对象.

之后调用ClassLinker::InsertDexFileInToClassLoader将对应的DexFile对象添加到ClassLoader的ClassTable中.

ClassLinker::DefineClass

调用LoadField加载类的相关字段,之后调用LoadMethod加载方法,再调用LinkCode执行方法代码的链接.

该流程如图所示:

8-loadClass2

综上所述,ClassLoader最终通过ClassLinker::DefineClass创建Class对象,并完成Field和Method的加载以及Code的链接.

调用链中有一个核心函数——ClassLinker::LoadMethod,通过该函数可以获取方法字节码在DexFile中的偏移值code_off,它是实现指令回填的核心之一

参考动态加载示例

示例文件: attachments\LoadDexDemo

经过前文的介绍,我们知道Android中可使用ClassLoader加载dex文件,并调用其保存的类的方法

创建空项目,编写一个测试类用于打印信息,编译后提取apk文件和该类所在的dex文件并推送至手机的tmp目录

创建另一个项目,通过DexClassLoader加载apk和提取的dex文件并反射执行print方法

创建私有目录用于创建DexClassLoader

分别是odex和lib目录

创建DexClassLoader

加载指定类

反射加载并执行类的方法

效果如下

DexClassLoader构造函数如下

Android应用程序启动流程相关知识是实现壳程序加载被保护程序的核心,只有了解该机制,才能理解壳程序的工作原理

Android应用程序启动涉及4个进程: Zygote进程,Launcher进程,AMS所在进程(SystemServer进程),应用程序进程

总体流程如下:

调用Activity.onCreate后便进入应用程序的生命周期,至此,成功启动应用程序

13-StartActivity2

以上流程的核心可总结为三个部分,后续将围绕这三部分展开:

要想启用一个Android应用程序,要保证该程序所需的应用程序进程存在.

当点击应用程序图标后,触发Launcher.onClick方法,经过一系列方法调用后,在ActivityStackSupervisor.startSpecificActivityLocked中会判断当前应用程序进程是否存在.

若不存在则调用AMS代理类ActivityManagerProxy的startProcessLocked请求AMS创建应用程序进程,AMS接收Launcher的请求后又向Zygote发送请求.

这一过程可分为两部分:

AMS向Zygote发送启动应用程序进程的请求的流程如下:

Launcher请求AMS,AMS请求Zygote,通过ZygoteInit.main进入Zygote

9-CreateAppProcess1

Zygote接收AMS的请求并创建应用程序进程的流程如下:

在进行一系列处理后进入ZygoteConnection.handleChildProc,该函数内部配置子进程的初始环境后并调用ZygoteInit.zygoteInit初始化应用程序进程.

ZygoteInit.zygoteInit中调用ZygoteInit.nativeZygoteInit启动了应用程序进程的Binder线程池,使得进程可以进行Binder通讯,之后调用RuntimeInit.applicationInit.

RuntimeInit调用invokeStaticMain并抛出Zygote.MethodAndArgsCaller异常,经过异常处理清空设置过程中的堆栈帧后,调用主线程管理类ActivityThread的main方法,至此,应用程序进程被成功创建.

10-CreateAppProcess2

ActivityThread.main进行部分初始化配置后,创建并启动主线程消息循环管理类Looper用于进行消息处理,并且调用attach方法通知AMS附加自身ApplicationThread类.

AMS中通过ApplicationThread的代理类IApplicationThread发送BIND_APPLICATION消息到应用程序的消息队列.

之后应用程序主线程管理类ActivityThread的handleMessage处理该消息,并调用handleBindApplication创建Application并绑定,主要执行以下4个步骤:

ActivityThread.getPackageInfoNoCheck

创建LoadedApk对象,设置ApplicationInfo和ClassLoader并加入mPackages中,该对象是应用程序apk包的管理类

ContextImpl.createAppContext

创建应用程序的上下文环境Context类

LoadedApk.makeApplication

创建Application,并调用Application.attachBaseContext方法

Instrumentation.callApplicationOnCreate

调用Application.onCreate方法

至此,Application创建完成,以上流程如图所示:

11-CreateApplication2

经过以上步骤后,创建了应用程序进程和对应的Application,回到Launcher请求AMS过程,在ActivityStackSupervisor.startSpecificActivityLocked中调用了ActivityStackSupervisor.realStartActivityLocked,其内部通过调用ApplicationThread.scheduleLaunchActivity通知应用程序启动Activity.

ApplicationThread通过sendMessage向H类(应用程序进程中主线程的消息管理类)发送H.LAUNCH_ACTIVITY消息,H.handleMessage接收到消息后调用ActivityThread.handleLaunchActivity将控制权转移给ActivityThread.

handleLaunchActivity调用performLaunchActivity执行启动Activity的步骤:

获取Activity信息类ActivityInfo

获取Apk文件描述类LoadedApk

为Activity创建上下文环境

获取Activity完整类名并通过ClassLoader创建Activity实例

调用LoadedApk.makeApplication

这一步需要解释,Application类是Android的应用程序描述类,每个应用程序只有一个全局单例的Application.

此处调用makeApplication时,由于ActivityThread.main中已经创建Application,所以会直接返回已创建的Application.

而performLaunchActivity用于启动任意Activity,根Activity必定使用ActivityThread.main创建的Application,但其他Activity可能在子进程中运行,所以此处的调用主要用于处理一般Activity.

调用Activity.attach初始化Activity

调用Instrumentation.callActivityOnCreate启动Activity

以上流程如图所示:

12-StartActivity

经过以上步骤后,安卓应用程序正式启动(根Activity启动),其中最值得关注的便是ActivityThread,Application和LoadedApk类.

ActivityThread是安卓应用程序进程的主线程管理类

保存了很多关键信息,通过该类可以反射获取应用程序进程的Application和LoadedApk.

Application用于描述当前的应用程序

应用程序启动过程中,Application实例是最先创建的,早于应用程序启动以及任何Activity或Service,它的生命周期是整个应用程序中最长的,等于应用程序的生命周期.

安卓应用程序开发时,可通过继承该类并重写attachBaseContext和onCreate方法进行部分资源的初始化配置.如果被保护程序没有指定自定义的Application,使用系统创建的默认Application即可;如果存在自定义的Application,则脱壳程序需要替换.

LoadedApk用于描述当前应用程序对应的APK文件

其mClassLoader字段保存了当前APK对应的类加载器,mApplication字段保存了当前Application.

关于Dex文件结构和解析器,由于篇幅限制此处不展开,可参考我的另一篇文章Dex文件结构-ReadDex解析器实现

在解析功能的基础之上,添加代码抽取功能,支持抽取指定dex文件的所有方法代码,并输出classes.dex置空指令后的.patched文件和字节码.codes文件.

原理:

16-DexClassDefToDexCode

代码抽取的步骤如下:

对应ReadDex项目中的关键函数如下:

extractAllClassDefCodes

功能: 抽取所有DexClassDef的代码.

逻辑: 遍历所有DexClassDef并过滤系统类,并解析对应DexClassData结构从而抽取代码.

extractClassDataCodes

功能: 抽取单个DexClassData的代码.

逻辑: 先通过readAndGetSizeUleb128读取获取DexClassDataHeader结构的staticFieldsSize,instanceFieldsSize,directMethodsSize和virtualMethodsSize.

再计算DexField数组占据的长度得到偏移值,最后访问DexMethod数组并进行代码抽取.

extractDexMethodArrayCodes

功能: 抽取DexMethod结构体数组的代码.

逻辑: 遍历DexMethod数组,对于每个DexMethod结构,通过DexFile基地址加codeOff得到DexCode的引用,之后根据insnsSize指定的大小抽取insns数组保存的指令字节码,每个DexCode抽取后保存为CodeItem并写入文件.自定义CodeItem结构用于后续代码回填时解析codes文件.

经过代码抽取后的Dex文件示意图如下:

26-Dex抽取代码图

详情可参考ReadDex项目,编译为ReadDex.exe后供加壳程序调用,通过-file参数指定dex文件路径,-extractAllCodes参数指定抽取所有代码.

一代加固是所有加固的基础,了解一代加固的基本思路有助于理解后续加固技术的演进

主要涉及三个对象:

加壳程序:

解包壳程序和源程序

得到壳程序的dex以及源程序的AndroidManifest.xml和资源文件等

复制源程序解包后的文件到新apk临时目录(忽略部分文件)

处理源程序AndroidManifest.xml 写入新apk临时目录

判断是否存在application标签的name属性指定了自定义Application

若存在则添加meta-data标签保存原始application

无论是否存在都要指定name值为壳的Application

合并壳dex和源程序apk 写入新apk临时目录

重打包新的Apk

对新Apk签名

删除临时目录

加壳后的壳Dex文件示意图如下

最终新APK中包括以下内容:

(脱)壳程序:

源程序即一般的用户程序,我们的主要目的是对源程序进行加固保护,所以需要注意源程序可能涉及到的技术点:

源程序自定义Application

Application.attachBaseContext是app进程真正的入口点

如果源程序没有使用自定义Application,则可以直接复用壳程序Application

如果源程序有自定义Application,必定在AndroidManifest.xml文件中由**<application android:name="">**标签声明指定,可以替换属性值为壳application,同时添加meta-data标签保存源程序application

Native

源程序使用NDK开发时会生成lib文件夹和对应so文件,需要进行处理

主要是创建lib库的释放文件夹,提供给新的ClassLoader

源程序主要包括以下文件:

注意关闭Multidex支持,保证只编译出一个Dex文件

主要功能:

注意点:

主要功能: log输出便于定位执行流程

注意:

程序执行的顺序为Application.attachBaseContext->Application.onCreate->MainActivity.onCreate

该类主要用于模拟存在自定义Application的情况

如果源程序不存在自定义Application则使用默认的Application即可,否则需要解析源Application并进行创建和替换

定义了静态注册的方法stringFromJNI和动态注册的方法add,sub

主要功能:

LinearLayout

加壳程序主要需要完成以下工作:

解包壳程序和源程序

得到壳程序的dex以及源程序的AndroidManifest.xml和资源文件等

复制源程序解包后的文件到新apk临时目录(忽略部分文件)

处理源程序AndroidManifest.xml 写入新apk临时目录

判断是否存在application标签的name属性指定了自定义Application

若存在则添加meta-data标签保存原始application

无论是否存在都要指定name值为壳的Application

合并壳dex和源程序apk 写入新apk临时目录

重打包新的Apk

对新Apk签名

删除临时目录

FirstShell.py代码如下

封装Paths类,保存全局使用到的路径 主要是apk路径和临时目录

封装Apktool类,通过apktool提供apk解包和打包功能,通过uber-apk-signer提供apk签名功能

封装ManifeseEditor类,提供ManifestEditor解析功能,支持获取和修改标签属性,添加新标签

combineShellDexAndSrcApk 合并壳dex和源apk

将原apk加密后填充到壳dex后方,并添加4字节标识apk大小

新dex=壳dex+加密后的源APK+源APK大小

handleManifest 处理AndroidManifest

分别提取源apk和壳的manifest文件,以源manifest为基准

根据源apk是否指定自定义application确定是否添加meta-data标签保存

最后修改application:name为壳application

start 完整的处理函数

注意关闭Multidex支持,保证只生成一个Dex文件

attachBaseContext中执行以下操作

创建私有目录,用于保存释放的dex,lib,源apk

调用readDexFromApk,从壳apk中提取壳dex文件,保存为字节数组

调用extractSrcApkFromShellDex 从壳dex文件提取源程序apk文件 解包lib文件到lib私有目录

调用replaceClassLoader替换壳程序的ClassLoader

新的ClassLoader指定了源apk dex文件,lib文件,odex路径 也就是前面释放的源apk和源lib

onCreate调用了replaceApplication

判断manifest文件是否通过meta-data标签保存了源apk的application

如果源apk未指定application则使用默认的application(即壳applicaiton)

如果源apk指定了自定义application则创建对应实例,替换掉壳的application,之后调用onCreate方法

封装的反射类,用于获取/设置指定类的成员变量,调用指定类的方法

通过name属性指定application

整体加固-落地加载的核心思路是将源程序apk和壳dex进行合并,运行时动态解密并执行相关环境处理操作重新执行源apk的代码

优点: 易于实现

缺点: 由于文件落地释放,所以非常容易在文件系统中获取到源程序apk;两次加载源程序apk到内存,效率低

针对落地加载的部分问题,引申出了不落地加载的思路: 即直接加载内存中的dex字节码,避免释放文件

如何在内存中加载dex?Android 8及以上系统中,可以使用系统提供的InMemoryDexClassLoader实现内存加载,Android7及以下则需要手动实现

另外需要针对第一代加固不支持Multidex进行优化:

源apk每个dex文件前添加4字节标识其大小,之后全部合并成一个文件再合并到壳dex,最后添加4字节标识文件总大小

结构如下

23-Dex合并图

同一代加固,可开启Multidex支持,其他部分不变

主要改动如下:

调用combineShellAndSourceDexs合并壳dex和源dex

内部调用readAndCombineDexs读取并合并源apk的多个dex为一个文件

复制源apk文件时忽略Manifest和dex文件

ManifeseEditor

添加getEtractNativeLibs,获取application标签的android:extractNativeLibs属性值

添加resetExtractNativeLibs,重置extractNativeLibs=true 强制释放lib文件

handleManifest

判断源程序Manifest是否设置了android:extractNativeLibs="true",若为false(默认)则改为true

SecondProxyApplication.java相比FirstProxyApplication.java改动如下:

attachBaseContext

读取壳dex文件后,提取源程序dex文件,之后替换ClassLoader

没有设置私有目录和释放文件等操作

extractDexFilesFromShellDex

替代splitSourceApkFromShellDex,从壳dex提取源程序dex文件并存储为ByteBuffer[]

replaceClassLoader

一代加固使用DexClassLoader从文件加载,二代加固使用InMemoryDexClassLoader

注意:

在Android 8.0以下不支持InMemoryDexClassLoader,需要手动实现

在Android 10.0以下不支持InMemoryDexClassLoader指定lib目录的重载

默认搜索路径为nativeLibraryDirectories=[/system/lib64, /product/lib64]]]

可参考以下文章修复lib目录的问题

3f1K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6T1L8r3!0Y4i4K6u0W2j5%4y4V1L8W2)9J5k6h3&6W2N6q4)9J5c8Y4p5$3x3e0l9H3z5e0R3K6x3o6S2Q4x3V1k6S2M7Y4c8A6j5$3I4W2i4K6u0r3k6r3g2@1j5h3W2D9M7#2)9J5c8U0p5H3y4e0t1@1y4U0x3#2y4b7`.`.

2afK9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8Y4N6%4N6#2)9J5k6i4W2^5K9s2g2S2L8X3N6Q4x3X3g2U0L8$3#2Q4x3V1j5J5x3o6t1H3i4K6u0r3x3o6y4Q4x3V1j5J5z5q4)9J5c8X3q4F1k6s2u0G2K9h3c8Q4x3X3c8K6L8#2)9J5k6r3I4G2j5h3c8Q4x3V1j5`.

若源程序设置了android:extractNativeLibs="false",则不会释放lib文件到文件系统,而是直接映射apk文件的lib数据

如果遇到这种情况则需要手动处理,模拟映射加载so的操作,背后的逻辑比较复杂,并且需要考虑兼容性

为了简化处理可以将加壳后apk的android:extractNativeLibs属性改为true,强行指定释放lib

二代加固使用不落地加载技术,可在内存中直接加载dex,实际上是对落地加载的更新

第一代和第二代加固统称为整体加固,在部分资料中将他们合称为一代加固,将代码抽取加固称为二代加固

优点: 相对一代加固更加高效,不容易从文件系统获取dex文件从而得到关键代码

缺点: 兼容性问题: 源程序的libso处理起来比较复杂;低版本需要手动实现InMemoryDexClassLoader

基于整体加固遇到的部分问题,引申出了三代加固: 代码抽取加固

思路: 将关键方法的指令抽空,替换为nop指令,运行时动态回填指令执行, 回填的核心是对Android系统核心函数进行hook

Hook点

常用的Hook点有ClassLinker::LoadMethod和ClassLinker::DefineClass,二者都可以获取DexFile对象

LoadMethod: 可获取Method对象,从而获取codeOff,方便回填处理,但兼容性差

DefineClass: 可获取ClassDef对象,需要解析得到codeOff,更复杂但兼容性好,变化不大

如何抽取代码(参考前文Dex文件结构和代码抽取部分)

Dex文件结构中DexClassDef结构定义了各个类的信息,其中的DexClassData结构记录了类的字段和方法数据

方法由DexMethod结构保存,其codeOff成员保存了方法的字节码数据在文件中的偏移,根据该偏移可以进行抽取

LoadMethod声明如下

DefineClass声明如下

主要包括3个技术点(同上):

加壳程序主要分为3个模块

14-加壳程序

加壳程序工作基本流程图如下

17-加壳程序流程

加壳程序工作流程总览图如下:

24-Apk合并图

相对于SecondShell.py的改动如下:

start

解包源apk后调用extractAllDexFiles抽取所有dex文件的代码

另外复制了壳apk的lib库到新apk的临时目录(hook和代码回填逻辑在native层)

extractAllDexFiles

遍历指定目录的所有dex文件,调用ReadDex抽取代码,得到对应的.patched和.codes文件

修复patch后的dex文件并覆写原dex文件,将codes移动到assets目录下

脱壳程序主要分为2个模块:

15-壳程序

经过前文加壳程序的处理后,源程序AndroidManifest.xml文件的application标签的name属性指定壳的Application,由于Application是安卓应用程序真正的入口类,所以启动加壳后的程序时控制权在壳的代理Application中.

在壳的代理Application中主要执行以下操作:

初始化操作

设置相关文件路径,解析相关文件用于后续处理.

替换ClassLoader

替换壳程序的ClassLoader为被保护程序的ClassLoader.

替换Application

若被保护程序存在自定义Application则创建实例并替换.

加载壳so

调用System.loadLibrary()主动加载即可,后续在Native层执行代码回填.

示意图如下

22-壳Application流程

主要执行以下操作:

设置相关私有目录,供后续释放文件以及设置DexClassLoader

从壳apk文件提取并解析被保护程序的dex文件,写入私有目录

调用 readDexFromApk从当前Apk文件中提取classes.dex,再调用extractDexFilesFromShellDex从中提取并分离源程序Dex文件,最后调用writeByteBuffersToDirectory将多个Dex文件依次写入私有目录.

从assets目录提取codes文件写入私有目录

调用copyClassesCodesFiles执行该操作.

拼接源程序所有dex文件的路径

用“:”分隔,拼接源程序所有dex文件路径,供后续DexClassLoader加载使用.

示意图如下

主要执行以下操作:

获取当前ClassLoader

调用this.getClassLoader()获取.

反射获取ActivityThread实例

通过反射直接获取ActivityThread.sCurrentActivityThread字段,即当前程序对应的ActivityThread实例.

反射获取LoadedApk实例

首先反射获取ActivityThread.mPackages字段,再根据当前程序的包名从中查找获取对应LoadedApk实例.

创建并替换ClassLoader

将环境初始化工作中创建的lib和dex文件的私有目录路径以及当前ClassLoader作为参数,新建DexClassLoader,该ClassLoader可用于加载之前释放的源程序Dex文件和libso文件.

最后通过反射修改LoadedApk.mClassLoader实例完成替换.

主要执行以下操作:

访问3.2.2节中加壳程序为AndroidManifest.xml添加的meta-data标签,其中保存了源程序自定义的Application类名.

获取LoadedApk实例同3.3.2,设置mApplication为空的原因是调用LoadedApk.makeApplication时,如果mApplication不为空,则直接返回当前的Application实例.所以想要替换Application必须先置空再创建.

流程图如下

调用System.loadLibrary主动加载了壳的SO文件,首先调用init函数,其中依次执行以下Hook操作(劫持对应函数):

Hook execve

在hook后添加判断逻辑,匹配到调用dex2oat系统调用时直接返回.

dex2oat是ART将所有Dex文件优化为一个OAT文件(本质为ELF文件)的操作,目的是加快指令执行速度,但这会影响加固工具执行指令回填.

Hook mmap

在mmap映射内存时添加写权限,保证可修改DexFile进行指令回填.

Hook LoadMethod

LoadMethod有两个关键参数: DexFile* dexFile和Method* method.

通过dexFile获取方法所在的Dex文件路径,从而判断是否为源程序被抽取了代码的Dex文件,如果是则判断是否进行过文件解析.

若没有解析过则调用parseExtractedCodeFiles函数解析Dex文件对应的codes文件后,便成功创建了一组CodeMap(保存codeOff和CodeItem映射).

之后调用目标方法时,根据Method.codeOff从CodeMap中提取对应CodeItem并执行指令回填,dexFile.begin+Method.codeOff即为insns[]指令字节数组的位置.

其中Hook主要使用Bhook和Dobby

Dobby

参考13bK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6D9N6h3!0&6k6i4y4A6M7h3W2#2i4K6u0r3k6s2m8@1i4K6u0V1M7$3S2W2L8r3I4Q4x3V1k6T1L8r3!0T1i4K6u0r3L8h3q4A6L8W2)9J5c8Y4y4Z5k6h3I4D9i4K6u0r3M7%4u0U0i4K6u0r3L8h3q4A6L8W2)9J5c8X3y4H3M7q4)9J5c8V1y4y4j5h3E0W2e0r3W2K6N6s2y4Q4x3X3g2@1P5s2c8Q4c8e0g2Q4z5e0u0Q4z5p5y4Z5N6s2c8H3M7#2)9K6b7g2)9J5c8W2)9J5c8Y4N6%4N6#2)9J5k6e0f1J5M7r3!0B7K9h3g2Q4x3X3g2U0L8W2)9J5c8Y4c8Z5M7X3g2S2k6q4)9J5k6o6p5%4y4K6V1&6z5o6c8Q4x3X3b7I4i4K6u0V1x3g2)9J5k6h3S2@1L8h3H3`.

源码编译似乎有点麻烦,静态导入dobby

Bhook

参考adaK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6T1P5i4c8W2k6r3q4F1j5$3g2Q4x3V1k6T1K9r3!0G2K9#2)9J5c8X3u0D9L8$3u0Q4x3V1k6E0j5h3W2F1i4K6u0r3f1V1g2m8c8p5#2q4i4K6u0W2P5X3S2Q4x3X3c8o6e0W2)9J5k6h3#2V1

build.gradle添加bhook依赖

CMakeLists.txt添加如下设置

Hook后的LoadMethod主要工作如下

25-HookLoadMethod

相对FirstProxyApplication.java改动如下:

System.loadLibrary("androidshell")

主动加载壳程序的so,设置hook

writeByteBuffersToDirectory

用于将壳dex中提取的源dex字节数组写为文件

copyClassesCodesFiles

用于将dex文件对应的codes文件复制到和dex相同的私有目录

主要提供以下功能:

hook execve

主要目的是禁止dex2oat,防止dex文件被优化

hook mmap

使dex文件可写,用于后续指令回填

hook LoadMethod

loadmethod用于加载dex文件的方法,可获取dex文件引用和codeoff

hook劫持后执行codes文件解析和指令回填

主要测试第三代加固,其中包括了动态加载和代码抽取与回填

使用frida-dexdump和blackdex尝试脱壳

加壳程序代码文件: ThirdAndroidShell.py

参数:

-src --src-apk 源程序Apk路径

-shell --shell-apk 壳程序Apk路径

-out --out-apk 输出的Apk路径

执行命令后,自动执行相关操作,成功时输出如下,加壳合并测试程序NativeDemo.apk和壳程序ThirdShell.apk为NewApk.apk:

安装并运行输出的NewApk.apk,查看logcat日志结果如下:

运行到壳代理类ThirdProxyApplication的attachBaseContext中时,由于主动调用了System.loadLibrary加载壳so,所以先执行了so的init函数并进行hook,此时由于没有执行源程序相关Java层代码所以没有触发Dex指令回填

新程序运行图1

首先从base.apk提取壳Dex进而提取源Dex,最后替换ClassLoader新程序运行图2

之后需要进行替换Application等操作,由于执行源程序的Java层代码所以触发了Dex指令回填,进行codes文件解析并创建CodeItem对象

新程序运行图3

代码回填和替换Application结束后,成功运行源程序Application的attachBaseContext和onCreate方法,最终运行MainActivity.onCreate进入源程序生命周期

新程序运行图4

Jadx分析被保护程序结果如下:

另外经测试frida-dexdump和blackdex得到的是被抽空的Dex文件,暂不清楚为什么没有进行内存搜索,也可能脱壳姿势不对

Dex文件的加载依赖于Android的ClassLoader,共有3个相关的ClassLoader:

InMemoryDexClassLoader

Android 8.0 新增的类加载器,用于加载内存中的dex文件

PathClassLoader

用于加载系统中已经安装过的apk的dex文件

DexClassLoader

用于加载已安装的jar、apk、dex以及从SD卡中加载未安装的apk

完整流程图如下

ClassLoader加载Dex文件流程

以下分析基于Android 10.0.0_r47

调用链如下

DexClassLoader和PathClassLoader都继承自BaseDexClassLoader,并且都调用这个4参数的构造方法重载

该构造方法内部主要是创建了DexPathList类

DexPathList构造方法代码如下,可以发现这条调用链中,optimizedDirectory=null,isTrusted=false

调用makeDexElements

遍历所有文件,寻找dex文件并调用loadDexFile加载dex文件

Android系统中,如果应用程序是首次启动,则对dex文件优化,并在cache/dalvik-cache目录下生成缓存文件,加快应用启动速度

optimizedDirectory即缓存文件所在路径,默认为cache/dalvik-cache目录

如果没有对应缓存文件,则调用DexFile解析dex文件(并进行优化)

如果有缓存文件,则调用DexFile.loadDex解析缓存文件

内部调用openDexFile方法

另外存在一个接收ByteBuffer[]的重载,内部调用openInMemoryDexFiles,应是用于InMemoryDexClassLoader加载dex

以及一个5参数的重载,内部也是调用openDexFile

内部调用了DexFile,之后的流程同上,最终调用openDexFile

最终调用openDexFileNative这个Native方法

另外有openInMemoryDexFiles和openInMemoryDexFilesNative代码如下

无论是PathClassLoader还是DexClassLoader,最终调用JNI函数DexFile::openDexFileNative

Native层对应函数为art/runtime/native/dalvik_system_DexFile.cc的DexFile_openDexFileNative

调用链如下

关键为调用OatFileManager.OpenDexFilesFromOat(),打开OAT文件

以上代码大致流程如下(Android 8.0的流程和此处有部分区别,使用DexFile::Open加载dex文件):

首先调用GetBestInfo()获取最佳的oat文件信息

然后调用ReleaseFileForUse()方法根据oat文件的状态释放文件以供使用(默认使用最新状态的oat文件)

OatFileAssistant::OatFileInfo::Status方法如下

总而言之,该函数的作用是从Oat文件中打开dex文件并添加到DexFile数组

以dex_location作为key,在oat_dex_files_ map中查找oat_dex文件位置,经过校验后返回

内部调用ArtDexFileLoader::Open

通过上述分析可以得知,在OatFileManager::OpenDexFilesFromOat中

OatFileAssistant::LoadDexFiles最终也调用ArtDexFileLoader::Open,参数个数有所区别

OatFileAssistant::LoadDexFiles内调用9参数的Open(比较奇怪,调用时只传递了8个参数,第9个container没传)

参数解释

直接调用了ArtDexFileLoader::OpenCommon

OatFileManager::OpenDexFilesFromOat内部调用的6参数ArtDexFileLoader::Open,参数解释如下

内部调用了ArtDexFileLoader::OpenWithMagic,判断文件头,如果是zip则通过OpenZip打开,如果是dex则通过OpenFile打开

内部调用OpenAllDexFilesFromZip

OpenAllDexFilesFromZip内部调用OpenOneDexFileFromZip打开dex文件

OpenOneDexFileFromZip内部调用OpenCommon打开dex文件

内部调用ArtDexFileLoader::OpenCommon打开dex文件

无论是ArtDexFileLoader::OpenZip还是ArtDexFileLoader::OpenFile,最终都调用OpenCommon打开dex文件(Android 7对应函数为OpenMemory)

内部调用了DexFileLoader::OpenCommon

检查dex文件头和版本号

检查dex文件头,maplist以及其他部分

StandardDexFile或CompactDexFile都继承自DexFile

构造函数DexFile():后的意思是赋值,将()中的变量依次赋值给()前的变量

该方法带有dex文件的base和size参数,所以可以作为一个脱壳点

内部调用了InitializeSectionsFromMapList

通过dex文件中的map_list结构解析dex文件

以下代码分析基于Android 10.0_r47 7bcK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6U0M7#2)9J5k6h3q4F1k6s2u0G2K9h3c8Q4x3X3g2U0L8$3#2Q4x3V1k6S2L8X3c8J5L8$3W2V1i4K6u0r3M7r3I4S2N6r3k6G2M7X3#2Q4x3V1k6K6N6i4m8W2M7Y4m8J5L8$3A6W2j5%4c8Q4x3V1k6Q4x3V1u0Q4x3V1k6S2L8X3c8J5L8$3W2V1i4K6u0V1x3e0m8Q4x3X3f1H3i4K6u0W2x3q4)9#2k6Y4t1@1y4H3`.`.

Android 8.0中新增了InMemoryDexClassLoader,包含以下2个重载

但执行壳代码进行替换classloader时,容易出现找不到lib库的情况,可参考以下文章解决so找不到的问题:

febK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6T1L8r3!0Y4i4K6u0W2j5%4y4V1L8W2)9J5k6h3&6W2N6q4)9J5c8Y4p5$3x3e0l9H3z5e0R3K6x3o6S2Q4x3V1k6S2M7Y4c8A6j5$3I4W2i4K6u0r3k6r3g2@1j5h3W2D9M7#2)9J5c8U0p5H3y4e0t1@1y4U0x3#2y4b7`.`.

3e7K9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8Y4N6%4N6#2)9J5k6i4W2^5K9s2g2S2L8X3N6Q4x3X3g2U0L8$3#2Q4x3V1j5J5x3o6t1H3i4K6u0r3x3o6y4Q4x3V1j5J5z5q4)9J5c8X3q4F1k6s2u0G2K9h3c8Q4x3X3c8K6L8#2)9J5k6r3I4G2j5h3c8Q4x3V1j5`.

Android 10.0中新增了一个重载,支持传递lib库路径

InMemoryDexClassLoader继承自BaseDexClassLoader,对应方法在父类中实现,内部创建了DexPathList对象

getAllNativeLibraryDirectories代码如下,将传入的lib库和系统lib库目录添加到一起并返回

调用DexFile创建了dex文件

调用openInMemoryDexFiles

调用openInMemoryDexFilesNative

openInMemoryDexFilesNative对应实现方法为DexFile_openInMemoryDexFilesNative

该函数是一个不同的重载,内部调用了OpenDexFilesFromOat_Impl创建dexfile

虽然和之前类似,调用了ArtDexFileLoader::Open,但是参数略有不同

不过内部依然通过调用OpenCommon打开dex文件,之后的流程类似

调用了DexFileLoader::OpenCommon,之后的流程同上

参考android类加载源码分析Android类加载流程

在类被加载之前,其对应的dex文件已经在内存中

ClassLoader类是所有类加载器的虚基类,在静态/动态加载一个类时会首先调用此虚基类的LoadClass函数,执行以下步骤

BaseDexClassLoader继承自ClassLoader虚类,Android常用的加载器基本都继承于BaseDexClassLoader类

所以会先调用BaseDexClassLoader.findClass方法,内部调用了DexPathList.findClass

DexPathList.findClass方法内部调用Element.findClass方法

Element.findClass方法调用DexFile.loadClassBinaryName

Element.findClass方法内部调用loadClassBinaryName函数

loadClassBinaryName函数接着会调用defineClass->defineClassNative,该函数是一个native函数在art虚拟机中实现

defineClassNative对应的art中的native函数为DexFile_defineClassNative,在art中进行类的字段,方法的加载

DexFile_defineClassNative的函数调用链如下

DefineClass主要就是调用ClassLinker::LoadClass加载对应类的字段和方法

ClassLinker::LoadClass调用LoadField加载所有的字段,调用LoadMethod加载所有的方法.

创建应用程序进程主要分为2部分:

时序图如下

假设此时启动应用程序所需的进程不存在,则AMS通过startProcessLocked方法向Zygote发送请求

startProcessLocked内部调用了Process.start启动进程,另外entryPoint是android.app.ActivityThread

注释1处 获取创建应用程序进程的用户ID

注释2处 对用户组ID (gids)进行创建和赋值

注释3处 如果entryPoint=null,则赋值为"android.app.ActivityThread" 即应用程序进程主线程的类名

注释4处 调用Process.start启动应用程序进程

传递了应用程序进程用户id,用户组gids,以及entryPoint

Process.start内部调用了zygoteProcess.start

ZygoteProcess.start调用了startViaZygote

ZygoteProcess.startViaZygote保存应用进程的启动参数后,调用zygoteSendArgsAndGetResult

首先调用了openZygoteSocketIfNeeded

ZygoteProcess.openZygoteSocketIfNeeded如下,主要是和Zygote进程通过Socket建立连接,并返回Zygote进程的状态

zygoteSendArgsAndGetResult代码如下

主要功能为将应用进程启动参数写入zygoteState(ZygoteProcess的静态内部类,用于表示与Zygote进程的通信状态)

然后与Zygote进程通信,等待发送新创建的进程pid和usingWrapper; 如果pid>=0则进程创建成功

时序图如下

socket连接成功并匹配abi后返回ZygoteState对象,之后zygoteSendArgsAndGetResult将应用进程启动参数写入ZygoteState

这样Zygote进程便可以接收创建新应用进程的请求,回到ZygoteInit.main方法中

Zygote创建Socket服务端后,会通过runSelectLoop方法等待AMS的请求来创建新的应用程序进程

runSelectLoop方法代码如下,通过注释2处的ZygoteConnection.runOnce处理请求数据

runOnce代码如下

获取应用程序进程的启动参数和文件描述符后,对参数进行解析并调用Zygote.forkAndSpecialize创建应用程序进程

再通过pid判断代码在哪个进程,pid=0为子进程时,调用handleChildProc方法

ZygoteConnection.handleChildProc代码如下

主要用于配置子进程的初始环境,包括关闭父进程(Zygote)的socket管道,重定向标准输入输出和错误输出,设置子进程名称等

之后调用ZygoteInit.zygoteInit方法初始化应用程序进程

ZygoteInit.zygoteInit代码如下

RuntimeInit.applicationInit代码如下,内部调用invokeStaticMain方法

invokeStaticMain

回到ZygoteInit.main,此处捕获到Zygote.MethodAndArgsCaller异常后,调用caller.run方法

MethodAndArgsCaller是zygote的静态内部类

捕获到异常后通过反射获取方法,再通过invoke调用ActivityThread.main方法

至此为止,应用程序进程就成功创建,并运行了主线程的管理类ActivityThread

参考Android中Application的创建流程

在学习Android进程启动时我们知道,AMS会向目标应用程序进程的ApplicationThread发起启动Activity的请求

之后ApplicationThread调用scheduleLaunchActivity方法向ActivityThread请求启动Activity

那么在此之前ApplicationThread对应的Application是何时被创建的呢?

实际上,Android系统中,系统进程和一般App进程的Application创建流程有所不同,我们主要关注一般的用户App进程

我们知道,应用程序进程创建后,通过Zygote.MethodAndArgsCaller回调ActivityThread.main方法

我们主要关注以下两行代码

创建ActivityThread线程并将当前线程附加到ActivityThread,参数false表示不使用新线程

调用的ActivityThread.attach方法代码如下,内部调用了AMS的attachApplication

attachApplication内部通过attachApplicationLocked为应用程序进程附加Application

attachApplicationLocked内部主要调用IApplicationThread.bindApplication

bindApplication内部创建AppBindData实例并赋值,之后调用sendMessage将BIND_APPLICATION消息发送至应用程序消息队列

ActivityThread.main中通过Looper.loop启动了主线程消息循环,并通过handleMessage处理消息,内部调用handleBindApplication

handleBindApplication代码如下,主要执行以下步骤

注释1处 获取LoadedApk对象,设置到AppBindData.info字段,用于创建appContext

注释2处 创建ContextImpl对象即appContext

注释3处 反射调用LoadedApk.makeApplication方法创建Application对象

注释4处 调用Application.onCreate方法

依次查看涉及到的方法

getPackageInfoNoCheck 创建LoadedApk对象,并加入到mPackages中,之后为LoadedApk设置ApplicationInfo和ClassLoader

每个app会创建唯一的LoadedApk对象

LoadedApk.installSystemApplicationInfo

创建ContextImpl实例并为其设置资源

构造函数中执行以下操作,完成之后便成功创建了AppContext

注释1处 判断是否创建过Application,保证一个LoadedApk对象只创建一个对应的Application对象

注释2处 获取类加载器

注释3处 创建Application对象,内部调用Application.attachBaseContext方法

注释4处 调用Application.onCreate

使用指定的类加载器,通过类名创建Application实例,并调用Application.atach方法,内部调用attachBaseContext

这也是为什么壳程序要先替换ClassLoader再创建Application

因为创建Application实例依赖到指定的ClassLoader,如果没有通过ClassLoader获取到被保护程序的dex文件,则无法成功创建

调用父类的attachBaseContext并设置LoadedApk

在壳Application的attachBaseContext中,替换ClassLoader

在壳Application的onCreate中,替换Application

因为在执行attachBaseContext时,已经创建好LoadedApk,ClassLoader,Application

其中LoadedApk用于描述apk文件本身,并不影响类的加载,类的加载主要通过ClassLoader进行

替换ClassLoader后,便可以正确加载源程序相关类,包括创建源程序的Application

在此之后,需要在Application.onCreate替换Application

因为LoadedApk.makeApplication中调用的是壳Application的实例方法,并且必定会执行onCreate

如果在attachBaseContext中替换了Application并执行可能会导致冲突: 壳Application.attachBaseContext创建并调用源Application相关方法后又调用壳Application.onCreate

调用Application.onCreate

当应用程序进程启动后就该启动应用程序了,即启动根Activity

Activity启动分为两种:

两者主流程基本一致,区别在于根Activity启动流程还包含启动APP进程和实例化Application的流程

经过以上流程之后,代码逻辑运行在应用程序进程中,ActivityThread启动Activity的时序图如下

ApplicationThread是ActivityThread的内部类,应用程序进程创建后会运行代表主线程的实例ActivityThread用于管理主线程

接着查看ApplicationThread.scheduleLaunchActivity方法:

sendMessage有多个重载,调用到的如下所示

mH指的是H类,他是ActivityThread的内部类并继承自Handler,是应用程序进程中主线程的消息管理类

由于ApplicationThread是一个Binder,它的调用逻辑在Binder线程池中,此处需要使用H将代码逻辑切换回主线程

sendMessage方法将消息添加到消息队列中,消息处理程序会自动从消息队列取出消息并调用对应的handleMessage方法处理

handleMessage方定义在H类中,代码如下

注释1处 将msg的obj成员转换为ActivityClientRecord

注释2处 通过getPackageInfoNoCheck获取LoadedApk对象,并赋值给ActivityClientRecord.packageInfo

应用程序进程要启动Activity时,需要将该Activity所属的APK加载进进程

而LoadedApk就是用于描述已加载的APK文件的

注释3处 调用handleLaunchActivity启动Activity

handleLaunchActivity代码如下

执行完部分初始化操作后,在注释1处调用performLaunchActivity启动Activity

若得到的Activity!=null则设置为Resume状态,否则通知AMS停止启动Activity

performLaunchActivity代码如下:

注释1处 获取ActivityInfo,用于存储代码以及AndroidManifest设置的Activity和Receiver结点信息,例如Activity的theme和launchMode

注释2处 获取APK文件的描述类LoadedApk

注释3处 获取要启动的Activity的ComponentName,该类保存该Activity的包名和类名

注释4处 为Activity创建上下文环境

注释5处 根据ComponentName存储的Activity类名,通过类加载器创建Activity的实例

注释6处 创建应用程序对象Application,makeApplication内部会调用Application.attachBaseContext和onCreate

注意: ActivityThread.main中会创建LoadedApk对象并调用makeApplication方法,创建主进程的application

此处调用主要是处理activity可能位于子进程,也需要application(一个进程对应一个application)

注释7处 调用Activity.attach方法初始化Activity,内部会创建Window对象PhoneWindow,并与Activity自身进行关联

注释8处 调用Instrumentation.callActivityOnCreate启动Activity

Instrumentation.callActivityOnCreate代码如下

调用的Activity.performCreate代码如下,主要是调用了Activity.onCreate方法

根Activity启动过程中涉及四个进程: Zygote进程, Launcher进程, AMS所在进程(SystemServer进程), 应用程序进程

如果是普通Activity启动,只涉及到AMS所在进程和应用程序进程

它们之间的关系如图所示, 启动根Activity的流程: Launcher->AMS->Zygote->AMS->Application->Activity

时序图如下

主要参考资料如下,部分资料可能有所遗漏:

Android系统源码 aospxref.com和cs.android.com

Android进阶解密 刘望舒著

Android应用安全防护和逆向分析 姜维编著

Android第一代加固壳的原理及实现 —— 落地加载

Android第二代加固壳的原理及实现 —— 不落地加载

Android一代整体壳简易实现和踩坑记录

Android二代抽取壳简易实现和踩坑记录

App加壳和脱壳

[原创]dex壳简单分析与实现过程

Android-Activity启动过程

dpt-shell

dpt-shell抽取壳项目源码及其逆向分析

[原创]dpt-shell源码详细解析(v1.11.3)

[原创]Android加壳与脱壳(11)——不落地加载壳的对抗研究

脱壳工具:

BlackDex

frida-dexdump

FART

Youpk

710K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6F1L8%4m8Q4x3X3g2Y4M7H3`.`.

Dex2C和Dex VMP相关项目:

dex2c

dcc

nmmp

Java.lang.Class;
Java.lang.reflect.Constructor;
Java.lang.reflect.Field;
Java.lang.reflect.Method;
Java.lang.reflect.Modifier;
Java.lang.Class;
Java.lang.reflect.Constructor;
Java.lang.reflect.Field;
Java.lang.reflect.Method;
Java.lang.reflect.Modifier;
// 示例类
class Person implements Runnable{
    public String name;
    private int age;
 
    //默认构造函数和有参构造函数
    public Person() {}
    private Person(String str){
        System.out.println("Private constructor:"+str);
    }
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
 
    // 不同访问权限的方法
    public void introduce() {
        System.out.println("我是" + name + ",年龄" + age);
    }
 
    private void privateMethod(String name,int age) {
 
        System.out.println("这是Person的私有方法,"+"name="+name+","+"age="+age);
    }
 
    @Override
    public void run() {
        System.out.println("Hello World");
    }
}
// 示例类
class Person implements Runnable{
    public String name;
    private int age;
 
    //默认构造函数和有参构造函数
    public Person() {}
    private Person(String str){
        System.out.println("Private constructor:"+str);
    }
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
 
    // 不同访问权限的方法
    public void introduce() {
        System.out.println("我是" + name + ",年龄" + age);
    }
 
    private void privateMethod(String name,int age) {
 
        System.out.println("这是Person的私有方法,"+"name="+name+","+"age="+age);
    }
 
    @Override
    public void run() {
        System.out.println("Hello World");
    }
}
//1. 获取类对象
// 动态加载
Class<?> clazz= Class.forName("MyUnidbgScripts.Person"); // 通过类的完整名加载
Class<?> clazz2=ClassLoader.getSystemClassLoader().loadClass("MyUnidbgScripts.Person");// 通过classloader加载
// 非动态加载
Class<?> clazz3=Person.class;
Class<?> clazz4=new Person().getClass();
System.out.println("Load Class:");
System.out.println(clazz);
System.out.println(clazz2);
System.out.println(clazz3);
System.out.println(clazz4);
System.out.println();
 
//2. 从类对象获取类的各种信息
System.out.println("Class info:");
System.out.println(clazz.getName());       // 完整类名
System.out.println(clazz.getSimpleName()); // 类名
System.out.println(clazz.getSuperclass()); // 父类类对象
System.out.println(Arrays.toString(clazz.getInterfaces()));    //接口类对象数组
System.out.println();
//1. 获取类对象
// 动态加载
Class<?> clazz= Class.forName("MyUnidbgScripts.Person"); // 通过类的完整名加载
Class<?> clazz2=ClassLoader.getSystemClassLoader().loadClass("MyUnidbgScripts.Person");// 通过classloader加载
// 非动态加载
Class<?> clazz3=Person.class;
Class<?> clazz4=new Person().getClass();
System.out.println("Load Class:");
System.out.println(clazz);
System.out.println(clazz2);
System.out.println(clazz3);
System.out.println(clazz4);
System.out.println();
 
//2. 从类对象获取类的各种信息
System.out.println("Class info:");
System.out.println(clazz.getName());       // 完整类名
System.out.println(clazz.getSimpleName()); // 类名
System.out.println(clazz.getSuperclass()); // 父类类对象
System.out.println(Arrays.toString(clazz.getInterfaces()));    //接口类对象数组
System.out.println();
Load Class:
class MyUnidbgScripts.Person
class MyUnidbgScripts.Person
class MyUnidbgScripts.Person
class MyUnidbgScripts.Person
 
Class info:
MyUnidbgScripts.Person
Person
class java.lang.Object
[interface java.lang.Runnable]
Load Class:
class MyUnidbgScripts.Person
class MyUnidbgScripts.Person
class MyUnidbgScripts.Person
class MyUnidbgScripts.Person
 
Class info:
MyUnidbgScripts.Person
Person
class java.lang.Object
[interface java.lang.Runnable]
//3. 获取构造方法
// 获取无参构造方法(默认构造方法)
System.out.println("Get constructor:");
Constructor<?> constructor=clazz.getConstructor();
System.out.println(constructor);
System.out.println();
 
// 获取public构造方法
System.out.println("Get public constructors:");
Constructor<?>[] constructors=clazz.getConstructors();
System.out.println(Arrays.toString(constructors));
System.out.println();
 
// 获取所有构造方法
System.out.println("Get all constructors:");
constructors=clazz.getDeclaredConstructors();
System.out.println(Arrays.toString(constructors));
System.out.println("Print All Constructors:");
for(Constructor<?> cons:constructors){
    System.out.println("constructor: "+cons);
    System.out.println("\tname: "+cons.getName()+
            "\n\tModifiers: "+Modifier.toString(cons.getModifiers())+
            "\n\tParameterTypes: "+Arrays.toString(cons.getParameterTypes()));
}
System.out.println();
//3. 获取构造方法
// 获取无参构造方法(默认构造方法)
System.out.println("Get constructor:");
Constructor<?> constructor=clazz.getConstructor();
System.out.println(constructor);
System.out.println();
 
// 获取public构造方法
System.out.println("Get public constructors:");
Constructor<?>[] constructors=clazz.getConstructors();
System.out.println(Arrays.toString(constructors));
System.out.println();
 
// 获取所有构造方法
System.out.println("Get all constructors:");
constructors=clazz.getDeclaredConstructors();
System.out.println(Arrays.toString(constructors));
System.out.println("Print All Constructors:");
for(Constructor<?> cons:constructors){
    System.out.println("constructor: "+cons);
    System.out.println("\tname: "+cons.getName()+
            "\n\tModifiers: "+Modifier.toString(cons.getModifiers())+
            "\n\tParameterTypes: "+Arrays.toString(cons.getParameterTypes()));
}
System.out.println();
Get constructor:
public MyUnidbgScripts.Person()
 
Get public constructors:
[public MyUnidbgScripts.Person(java.lang.String,int), public MyUnidbgScripts.Person()]
 
Get all constructors:
[public MyUnidbgScripts.Person(java.lang.String,int), private MyUnidbgScripts.Person(java.lang.String), public MyUnidbgScripts.Person()]
Print All Constructors:
constructor: public MyUnidbgScripts.Person(java.lang.String,int)
    name: MyUnidbgScripts.Person
    Modifiers: public
    ParameterTypes: [class java.lang.String, int]
constructor: private MyUnidbgScripts.Person(java.lang.String)
    name: MyUnidbgScripts.Person
    Modifiers: private
    ParameterTypes: [class java.lang.String]
constructor: public MyUnidbgScripts.Person()
    name: MyUnidbgScripts.Person
    Modifiers: public
    ParameterTypes: []
Get constructor:
public MyUnidbgScripts.Person()
 
Get public constructors:
[public MyUnidbgScripts.Person(java.lang.String,int), public MyUnidbgScripts.Person()]
 
Get all constructors:
[public MyUnidbgScripts.Person(java.lang.String,int), private MyUnidbgScripts.Person(java.lang.String), public MyUnidbgScripts.Person()]
Print All Constructors:
constructor: public MyUnidbgScripts.Person(java.lang.String,int)
    name: MyUnidbgScripts.Person
    Modifiers: public
    ParameterTypes: [class java.lang.String, int]
constructor: private MyUnidbgScripts.Person(java.lang.String)
    name: MyUnidbgScripts.Person
    Modifiers: private
    ParameterTypes: [class java.lang.String]
constructor: public MyUnidbgScripts.Person()
    name: MyUnidbgScripts.Person
    Modifiers: public
    ParameterTypes: []
//3. 获取属性
// 获取所有public属性
System.out.println("Get public fields:");
Field[] fields=clazz.getFields();
System.out.println(Arrays.toString(fields));
System.out.println();
 
// 获取所有属性
System.out.println("Get all fields:");
fields=clazz.getDeclaredFields();
System.out.println(Arrays.toString(fields));
System.out.println("Print all fields:");
for(Field field:fields){
    System.out.println("field: "+field);
    System.out.println("\ttype: "+field.getType()+
            "\n\tname: "+field.getName());
}
System.out.println();
 
System.out.println("Get specific field:");
// 获取public权限的指定属性
Field field=clazz.getField("name");
System.out.println(field);
// 获取任意权限的指定属性
field=clazz.getDeclaredField("age");
System.out.println(field);
//3. 获取属性
// 获取所有public属性
System.out.println("Get public fields:");
Field[] fields=clazz.getFields();
System.out.println(Arrays.toString(fields));
System.out.println();
 
// 获取所有属性
System.out.println("Get all fields:");
fields=clazz.getDeclaredFields();
System.out.println(Arrays.toString(fields));
System.out.println("Print all fields:");
for(Field field:fields){
    System.out.println("field: "+field);
    System.out.println("\ttype: "+field.getType()+
            "\n\tname: "+field.getName());
}
System.out.println();
 
System.out.println("Get specific field:");
// 获取public权限的指定属性
Field field=clazz.getField("name");
System.out.println(field);
// 获取任意权限的指定属性
field=clazz.getDeclaredField("age");
System.out.println(field);
Get public fields:
[public java.lang.String MyUnidbgScripts.Person.name]
 
Get all fields:
[public java.lang.String MyUnidbgScripts.Person.name, private int MyUnidbgScripts.Person.age]
Print all fields:
field: public java.lang.String MyUnidbgScripts.Person.name
    type: class java.lang.String
    name: name
field: private int MyUnidbgScripts.Person.age
    type: int
    name: age
 
Get specific field:
public java.lang.String MyUnidbgScripts.Person.name
private int MyUnidbgScripts.Person.age
Get public fields:
[public java.lang.String MyUnidbgScripts.Person.name]
 
Get all fields:
[public java.lang.String MyUnidbgScripts.Person.name, private int MyUnidbgScripts.Person.age]
Print all fields:
field: public java.lang.String MyUnidbgScripts.Person.name
    type: class java.lang.String
    name: name
field: private int MyUnidbgScripts.Person.age
    type: int
    name: age
 
Get specific field:
public java.lang.String MyUnidbgScripts.Person.name
private int MyUnidbgScripts.Person.age
//4. 获取方法
System.out.println("Get public methods:");
Method[] methods=clazz.getMethods();   // 注意会获取所实现接口的public方法
System.out.println(Arrays.toString(methods));
System.out.println();
 
System.out.println("Get all methods:");
methods=clazz.getDeclaredMethods();    // 获取所有声明的方法
System.out.println(Arrays.toString(methods));
System.out.println();
 
System.out.println("Print all methods:");
for(Method method:methods){
    System.out.println("method: "+method);
    System.out.println("\tname: "+method.getName());
    System.out.println("\treturnType: "+method.getReturnType());
    System.out.println("\tparameterTypes: "+Arrays.toString(method.getParameterTypes()));
}
System.out.println();
 
// 获取public的指定方法
Method method=clazz.getMethod("introduce");
System.out.println(method);
// 获取任意权限的指定方法
method=clazz.getDeclaredMethod("privateMethod",String.class,int.class);
System.out.println(method);
System.out.println();
//4. 获取方法
System.out.println("Get public methods:");
Method[] methods=clazz.getMethods();   // 注意会获取所实现接口的public方法
System.out.println(Arrays.toString(methods));
System.out.println();
 
System.out.println("Get all methods:");
methods=clazz.getDeclaredMethods();    // 获取所有声明的方法
System.out.println(Arrays.toString(methods));
System.out.println();
 
System.out.println("Print all methods:");
for(Method method:methods){
    System.out.println("method: "+method);
    System.out.println("\tname: "+method.getName());
    System.out.println("\treturnType: "+method.getReturnType());
    System.out.println("\tparameterTypes: "+Arrays.toString(method.getParameterTypes()));
}
System.out.println();
 
// 获取public的指定方法
Method method=clazz.getMethod("introduce");
System.out.println(method);
// 获取任意权限的指定方法
method=clazz.getDeclaredMethod("privateMethod",String.class,int.class);
System.out.println(method);
System.out.println();
Get public methods:
[public void MyUnidbgScripts.Person.run(), public void MyUnidbgScripts.Person.introduce(), public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException, public final void java.lang.Object.wait() throws java.lang.InterruptedException, public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException, public boolean java.lang.Object.equals(java.lang.Object), public java.lang.String java.lang.Object.toString(), public native int java.lang.Object.hashCode(), public final native java.lang.Class java.lang.Object.getClass(), public final native void java.lang.Object.notify(), public final native void java.lang.Object.notifyAll()]
 
Get all methods:
[public void MyUnidbgScripts.Person.run(), public void MyUnidbgScripts.Person.introduce(), private void MyUnidbgScripts.Person.privateMethod(java.lang.String,int)]
 
Print all methods:
method: public void MyUnidbgScripts.Person.run()
    name: run
    returnType: void
    parameterTypes: []
method: public void MyUnidbgScripts.Person.introduce()
    name: introduce
    returnType: void
    parameterTypes: []
method: private void MyUnidbgScripts.Person.privateMethod(java.lang.String,int)
    name: privateMethod
    returnType: void
    parameterTypes: [class java.lang.String, int]
 
public void MyUnidbgScripts.Person.introduce()
private void MyUnidbgScripts.Person.privateMethod(java.lang.String,int)
Get public methods:
[public void MyUnidbgScripts.Person.run(), public void MyUnidbgScripts.Person.introduce(), public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException, public final void java.lang.Object.wait() throws java.lang.InterruptedException, public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException, public boolean java.lang.Object.equals(java.lang.Object), public java.lang.String java.lang.Object.toString(), public native int java.lang.Object.hashCode(), public final native java.lang.Class java.lang.Object.getClass(), public final native void java.lang.Object.notify(), public final native void java.lang.Object.notifyAll()]
 
Get all methods:
[public void MyUnidbgScripts.Person.run(), public void MyUnidbgScripts.Person.introduce(), private void MyUnidbgScripts.Person.privateMethod(java.lang.String,int)]
 
Print all methods:
method: public void MyUnidbgScripts.Person.run()
    name: run
    returnType: void
    parameterTypes: []
method: public void MyUnidbgScripts.Person.introduce()
    name: introduce
    returnType: void
    parameterTypes: []
method: private void MyUnidbgScripts.Person.privateMethod(java.lang.String,int)
    name: privateMethod
    returnType: void
    parameterTypes: [class java.lang.String, int]
 
public void MyUnidbgScripts.Person.introduce()
private void MyUnidbgScripts.Person.privateMethod(java.lang.String,int)
//5. 反射创建对象
System.out.println("Create instance by reflection:");
//5.1 Class.newInstance() 要求Class对象对应类有无参构造方法,执行无参构造方法创建实例
System.out.println("Create instance by Class.newInstance():");
Object obj=clazz.newInstance();
System.out.println(obj.toString());
System.out.println();
 
//5.2 Constructor.newInstance() 通过Class获取Constructor,再创建对象,可使用指定构造方法
System.out.println("Create instance by Constructor.newInstance():");
Constructor<?> cons=clazz.getConstructor();// 获取无参构造方法
obj=cons.newInstance();
System.out.println(obj.toString());
cons=clazz.getDeclaredConstructors()[0];// 获取有参构造方法
obj=cons.newInstance("张三",18);
System.out.println(obj.toString());
System.out.println();
//5. 反射创建对象
System.out.println("Create instance by reflection:");
//5.1 Class.newInstance() 要求Class对象对应类有无参构造方法,执行无参构造方法创建实例
System.out.println("Create instance by Class.newInstance():");
Object obj=clazz.newInstance();
System.out.println(obj.toString());
System.out.println();
 
//5.2 Constructor.newInstance() 通过Class获取Constructor,再创建对象,可使用指定构造方法
System.out.println("Create instance by Constructor.newInstance():");
Constructor<?> cons=clazz.getConstructor();// 获取无参构造方法
obj=cons.newInstance();
System.out.println(obj.toString());
cons=clazz.getDeclaredConstructors()[0];// 获取有参构造方法
obj=cons.newInstance("张三",18);
System.out.println(obj.toString());
System.out.println();
Create instance by reflection:
Create instance by Class.newInstance():
MyUnidbgScripts.Person@30dae81
 
Create instance by Constructor.newInstance():
MyUnidbgScripts.Person@1b2c6ec2
MyUnidbgScripts.Person@4edde6e5
Create instance by reflection:
Create instance by Class.newInstance():
MyUnidbgScripts.Person@30dae81
 
Create instance by Constructor.newInstance():
MyUnidbgScripts.Person@1b2c6ec2
MyUnidbgScripts.Person@4edde6e5
//6. 反射操作属性
 System.out.println("Access field by reflection:");
 Field nameField=clazz.getField("name");
 nameField.set(obj,"王五");    // 修改指定对象的指定属性
 Field ageField=clazz.getDeclaredField("age");
 ageField.setAccessible(true);// 突破权限控制
 ageField.set(obj,20);
 System.out.println(nameField.get(obj));// get方法获取字段值
 System.out.println(ageField.get(obj));
//6. 反射操作属性
 System.out.println("Access field by reflection:");
 Field nameField=clazz.getField("name");
 nameField.set(obj,"王五");    // 修改指定对象的指定属性
 Field ageField=clazz.getDeclaredField("age");
 ageField.setAccessible(true);// 突破权限控制
 ageField.set(obj,20);
 System.out.println(nameField.get(obj));// get方法获取字段值
 System.out.println(ageField.get(obj));
Access field by reflection:
王五
20
Access field by reflection:
王五
20
//7. 反射调用方法
 System.out.println("Run method by reflection:");
 Method introduceMethod=clazz.getMethod("introduce");
 introduceMethod.invoke(obj); //person.introduce()
 Method privateMethod=clazz.getDeclaredMethod("privateMethod",String.class,int.class);// person.privateMethod("赵四",19)
 privateMethod.setAccessible(true);
 privateMethod.invoke(obj,"赵四",19);
//7. 反射调用方法
 System.out.println("Run method by reflection:");
 Method introduceMethod=clazz.getMethod("introduce");
 introduceMethod.invoke(obj); //person.introduce()
 Method privateMethod=clazz.getDeclaredMethod("privateMethod",String.class,int.class);// person.privateMethod("赵四",19)
 privateMethod.setAccessible(true);
 privateMethod.invoke(obj,"赵四",19);
Run method by reflection:
我是王五,年龄20
这是Person的私有方法,name=赵四,age=19
Run method by reflection:
我是王五,年龄20
这是Person的私有方法,name=赵四,age=19
package com.example.androidshell;
 
import android.util.Log;
 
import java.lang.reflect.Field;
import java.lang.reflect.Method;
 
public class Reflection {
    private static final String TAG="glass";
    public static Object invokeStaticMethod(String class_name,String method_name,Class<?>[] parameterTypes,Object[] parameterValues){
        try {
            Class<?> clazz = Class.forName(class_name); //反射获取Class类对象
            Method method = clazz.getMethod(method_name,parameterTypes);//反射获取方法
            method.setAccessible(true);//突破权限访问控制
            return method.invoke(null,parameterValues);//反射调用,静态方法无需指定所属实例,直接传参即可
        }catch (Exception e){
            Log.d(TAG, e.toString());
            return null;
        }
    }
 
    public static Object invokeMethod(String class_name,String method_name,Object obj,Class<?>[] parameterTypes,Object[] parameterValues)
    {
        try {
            Class<?> clazz = Class.forName(class_name);
            Method method = clazz.getMethod(method_name,parameterTypes);
            method.setAccessible(true);//突破权限访问控制
            return method.invoke(obj,parameterValues);// 反射调用,动态方法需要指定所属实例
        }catch (Exception e)
        {
            Log.d(TAG, e.toString());
            return null;
        }
    }
 
    public static Object getField(String class_name,Object obj,String field_name)
    {
        try {
            Class<?> clazz = Class.forName(class_name);
            Field field = clazz.getDeclaredField(field_name);
            field.setAccessible(true);
            return field.get(obj);  //获取实例字段,需要指定实例对象
        }catch(Exception e)
        {
            Log.d(TAG, e.toString());
            return null;
        }
    }
 
    public static Object getStaticField(String class_name,String field_name)
    {
        try {
            Class<?> clazz = Class.forName(class_name);
            Field field = clazz.getDeclaredField(field_name);
            field.setAccessible(true);
            return field.get(null);
        }catch (Exception e)
        {
            Log.d(TAG, e.toString());
            return null;
        }
    }
 
    public static void setField(String class_name,String field_name,Object obj,Object value)
    {
        try {
            Class<?> clazz = Class.forName(class_name);
            Field field = clazz.getDeclaredField(field_name);
            field.setAccessible(true);
            field.set(obj,value);
        }catch (Exception e)
        {
            Log.d(TAG, e.toString());
        }
    }
 
    public static void setStaticField(String class_name,String field_name,Object value)
    {
        try {
            Class<?> clazz = Class.forName(class_name);
            Field field = clazz.getDeclaredField(field_name);
            field.setAccessible(true);
            field.set(null,value);
        }catch (Exception e){
            Log.d(TAG, e.toString());
        }
    }
}
package com.example.androidshell;
 
import android.util.Log;
 
import java.lang.reflect.Field;
import java.lang.reflect.Method;
 
public class Reflection {
    private static final String TAG="glass";
    public static Object invokeStaticMethod(String class_name,String method_name,Class<?>[] parameterTypes,Object[] parameterValues){
        try {
            Class<?> clazz = Class.forName(class_name); //反射获取Class类对象
            Method method = clazz.getMethod(method_name,parameterTypes);//反射获取方法
            method.setAccessible(true);//突破权限访问控制
            return method.invoke(null,parameterValues);//反射调用,静态方法无需指定所属实例,直接传参即可
        }catch (Exception e){
            Log.d(TAG, e.toString());
            return null;
        }
    }
 
    public static Object invokeMethod(String class_name,String method_name,Object obj,Class<?>[] parameterTypes,Object[] parameterValues)
    {
        try {
            Class<?> clazz = Class.forName(class_name);
            Method method = clazz.getMethod(method_name,parameterTypes);
            method.setAccessible(true);//突破权限访问控制
            return method.invoke(obj,parameterValues);// 反射调用,动态方法需要指定所属实例
        }catch (Exception e)
        {
            Log.d(TAG, e.toString());
            return null;
        }
    }
 
    public static Object getField(String class_name,Object obj,String field_name)
    {
        try {
            Class<?> clazz = Class.forName(class_name);
            Field field = clazz.getDeclaredField(field_name);
            field.setAccessible(true);
            return field.get(obj);  //获取实例字段,需要指定实例对象
        }catch(Exception e)
        {
            Log.d(TAG, e.toString());
            return null;
        }
    }
 
    public static Object getStaticField(String class_name,String field_name)
    {
        try {
            Class<?> clazz = Class.forName(class_name);
            Field field = clazz.getDeclaredField(field_name);
            field.setAccessible(true);
            return field.get(null);
        }catch (Exception e)
        {
            Log.d(TAG, e.toString());
            return null;
        }
    }
 
    public static void setField(String class_name,String field_name,Object obj,Object value)
    {
        try {
            Class<?> clazz = Class.forName(class_name);
            Field field = clazz.getDeclaredField(field_name);
            field.setAccessible(true);
            field.set(obj,value);
        }catch (Exception e)
        {
            Log.d(TAG, e.toString());
        }
    }
 
    public static void setStaticField(String class_name,String field_name,Object value)
    {
        try {
            Class<?> clazz = Class.forName(class_name);
            Field field = clazz.getDeclaredField(field_name);
            field.setAccessible(true);
            field.set(null,value);
        }catch (Exception e){
            Log.d(TAG, e.toString());
        }
    }
}
public class Test0 {
        public static void main(String[] args) {
            System.out.println(System.getProperty("sun.boot.class.path"));
        }
}
public class Test0 {
        public static void main(String[] args) {
            System.out.println(System.getProperty("sun.boot.class.path"));
        }
}
public class JavaClassLoaderTest {
        public static void main(String[] args) {
            System.out.println(System.getProperty(java.ext.dirs));
        }
}
public class JavaClassLoaderTest {
        public static void main(String[] args) {
            System.out.println(System.getProperty(java.ext.dirs));
        }
}
public class JavaClassLoaderTest {
        public static void main(String[] args) {
            ClassLoader loader=JavaClassLoaderTest.class.getClassLoader();
            while(loader!=null){
              System.out.println(loader);
              loader=loader.getParent();
            }
        }
}
public class JavaClassLoaderTest {
        public static void main(String[] args) {
            ClassLoader loader=JavaClassLoaderTest.class.getClassLoader();
            while(loader!=null){
              System.out.println(loader);
              loader=loader.getParent();
            }
        }
}
protected Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException
{
    synchronized (getClassLoadingLock(name)) {
        Class<?> c = findLoadedClass(name);//1
        if (c == null) {
            long t0 = System.nanoTime();
            try {
                if (parent != null) {
                    c = parent.loadClass(name, false);//2.
                } else {
                    c = findBootstrapClassOrNull(name);//3
                }
            } catch (ClassNotFoundException e) {
                // ClassNotFoundException thrown if class not found
                // from the non-null parent class loader
            }
 
            if (c == null) {
                // If still not found, then invoke findClass in order
                // to find the class.
                long t1 = System.nanoTime();
                c = findClass(name);//4
 
                // this is the defining class loader; record the stats
                PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                PerfCounter.getFindClasses().increment();
            }
        }
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }
}
protected Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException
{
    synchronized (getClassLoadingLock(name)) {
        Class<?> c = findLoadedClass(name);//1
        if (c == null) {
            long t0 = System.nanoTime();
            try {
                if (parent != null) {
                    c = parent.loadClass(name, false);//2.
                } else {
                    c = findBootstrapClassOrNull(name);//3
                }
            } catch (ClassNotFoundException e) {
                // ClassNotFoundException thrown if class not found
                // from the non-null parent class loader
            }
 
            if (c == null) {
                // If still not found, then invoke findClass in order
                // to find the class.
                long t1 = System.nanoTime();
                c = findClass(name);//4
 
                // this is the defining class loader; record the stats
                PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                PerfCounter.getFindClasses().increment();
            }
        }
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }
}
package com.example.emptydemo;
import android.util.Log;
public class TestClass{
    public void print(){
        Log.d("glass","com.example.emptydemo.print is called!");
    }
}
package com.example.emptydemo;
import android.util.Log;
public class TestClass{
    public void print(){
        Log.d("glass","com.example.emptydemo.print is called!");
    }
}
package com.example.testdemo;
 
import android.content.Context;
import android.os.Bundle;
 
import androidx.appcompat.app.AppCompatActivity;
 
import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
 
import dalvik.system.DexClassLoader;
 
 
public class MainActivity extends AppCompatActivity {
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Context appContext = getApplicationContext();
        //com.example.emptydemo
        loadDexClassAndExecuteMethod(appContext, "/data/local/tmp/classes3.dex"); //直接加载dex文件
        loadDexClassAndExecuteMethod(appContext, "/data/local/tmp/EmptyDemo.apk"); //加载apk文件 本质还是加载dex
    }
    public void loadDexClassAndExecuteMethod(Context context, String strDexFilePath) {
        //1.创建优化私有目录app_opt_dex和app_lib_dex,用于ClassLoader
        File optFile = context.getDir("opt_dex", 0);// /data/user/0/com.example.testdemo/app_opt_dex
        File libFile = context.getDir("lib_dex", 0);// /data/user/0/com.example.testdemo/app_lib_dex
 
 
        //2.创建ClassLoader用于加载Dex文件 依次指定dex文件路径 Odex目录 lib库目录 父类加载器
        DexClassLoader dexClassLoader = new DexClassLoader(
                strDexFilePath,
                optFile.getAbsolutePath(),
                libFile.getAbsolutePath(),
                MainActivity.class.getClassLoader());
        Class<?> clazz = null;
        try {
            //3.通过创建的DexClassLoader加载dex文件的指定类
            clazz = dexClassLoader.loadClass("com.example.emptydemo.TestClass");
 
            if (clazz != null) {
                try {
                    //3.反射获取并调用类的方法
                    Object obj = clazz.newInstance();
                    Method method = clazz.getDeclaredMethod("print");
                    method.invoke(obj);
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                } catch (NoSuchMethodException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (InstantiationException e) {
                    e.printStackTrace();
                }
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}
package com.example.testdemo;
 
import android.content.Context;
import android.os.Bundle;
 
import androidx.appcompat.app.AppCompatActivity;
 
import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
 
import dalvik.system.DexClassLoader;
 
 
public class MainActivity extends AppCompatActivity {
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Context appContext = getApplicationContext();
        //com.example.emptydemo
        loadDexClassAndExecuteMethod(appContext, "/data/local/tmp/classes3.dex"); //直接加载dex文件
        loadDexClassAndExecuteMethod(appContext, "/data/local/tmp/EmptyDemo.apk"); //加载apk文件 本质还是加载dex
    }
    public void loadDexClassAndExecuteMethod(Context context, String strDexFilePath) {
        //1.创建优化私有目录app_opt_dex和app_lib_dex,用于ClassLoader
        File optFile = context.getDir("opt_dex", 0);// /data/user/0/com.example.testdemo/app_opt_dex
        File libFile = context.getDir("lib_dex", 0);// /data/user/0/com.example.testdemo/app_lib_dex
 
 
        //2.创建ClassLoader用于加载Dex文件 依次指定dex文件路径 Odex目录 lib库目录 父类加载器
        DexClassLoader dexClassLoader = new DexClassLoader(
                strDexFilePath,
                optFile.getAbsolutePath(),
                libFile.getAbsolutePath(),
                MainActivity.class.getClassLoader());
        Class<?> clazz = null;
        try {
            //3.通过创建的DexClassLoader加载dex文件的指定类
            clazz = dexClassLoader.loadClass("com.example.emptydemo.TestClass");
 
            if (clazz != null) {
                try {
                    //3.反射获取并调用类的方法
                    Object obj = clazz.newInstance();
                    Method method = clazz.getDeclaredMethod("print");
                    method.invoke(obj);
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                } catch (NoSuchMethodException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (InstantiationException e) {
                    e.printStackTrace();
                }
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}
/*
    参数一: String dexPath, Dex文件路径
    参数二: String optimizedDirectory, 优化后的dex即Odex目录
    Android中内存中不会出现上述参数一的Dex文件, 会先优化,然后运行,优化后为.odex文件
    参数三: String librarySearchPath, lib库搜索路径
    参数四: ClassLoader parent, 父类加载器
*/
public class DexClassLoader extends BaseDexClassLoader {
    public DexClassLoader(String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent) {
        super((String)null, (File)null, (String)null, (ClassLoader)null);
        throw new RuntimeException("Stub!");
    }
}
/*
    参数一: String dexPath, Dex文件路径
    参数二: String optimizedDirectory, 优化后的dex即Odex目录
    Android中内存中不会出现上述参数一的Dex文件, 会先优化,然后运行,优化后为.odex文件
    参数三: String librarySearchPath, lib库搜索路径
    参数四: ClassLoader parent, 父类加载器
*/
public class DexClassLoader extends BaseDexClassLoader {
    public DexClassLoader(String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent) {
        super((String)null, (File)null, (String)null, (ClassLoader)null);
        throw new RuntimeException("Stub!");
    }
}
package com.example.nativedemo;
 
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;
 
public class MainActivity extends Activity {
 
    static {
        System.loadLibrary("nativedemo");
    }
    public native String stringFromJNI();
    public native int add(int x,int y);
    public native int sub(int x,int y);
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // Example of a call to a native method
        TextView tv = findViewById(R.id.sample_text) ;
        String result=stringFromJNI()+" add(111,222)="+add(111,222)+" sub(999,333)="+sub(999,333);
        tv.setText(result);
        Log.d("glass","Run source MainActivity.onCreate "+this);
    }
}
package com.example.nativedemo;
 
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;
 
public class MainActivity extends Activity {
 
    static {
        System.loadLibrary("nativedemo");
    }
    public native String stringFromJNI();
    public native int add(int x,int y);
    public native int sub(int x,int y);
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // Example of a call to a native method
        TextView tv = findViewById(R.id.sample_text) ;
        String result=stringFromJNI()+" add(111,222)="+add(111,222)+" sub(999,333)="+sub(999,333);
        tv.setText(result);
        Log.d("glass","Run source MainActivity.onCreate "+this);
    }
}
package com.example.nativedemo;
 
import android.app.Application;
import android.content.Context;
import android.util.Log;
 
public class MyApplication extends Application {
 
    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);
        Log.d("glass", "Run source MyApplication.attachBaseContext "+this);
    }
    @Override
    public void onCreate() {
        super.onCreate();
        Log.d("glass", "Run source MyApplication.onCreate "+this);
    }
}
package com.example.nativedemo;
 
import android.app.Application;
import android.content.Context;
import android.util.Log;
 
public class MyApplication extends Application {
 
    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);
        Log.d("glass", "Run source MyApplication.attachBaseContext "+this);
    }
    @Override
    public void onCreate() {
        super.onCreate();
        Log.d("glass", "Run source MyApplication.onCreate "+this);
    }
}
#include <jni.h>
#include <string>
 
// 静态注册
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_nativedemo_MainActivity_stringFromJNI(
        JNIEnv* env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}
 
//native函数对应的java方法所属类
static const char *ClassName="com/example/nativedemo/MainActivity";
 
jint add(JNIEnv* env,jobject obj,jint x,jint y){
    return x+y;
}
jint sub(JNIEnv* env,jobject obj,jint x,jint y){
    return x-y;
}
 
//定义本地方法数组 建立映射关系
//JNINativeMethod结构体成员为函数名,函数签名,函数指针
static JNINativeMethod methods[]={
        {"add","(II)I",(void*)add},
        {"sub","(II)I",(void*)sub}
};
 
//编写加载注册方法
jint JNI_OnLoad(JavaVM* vm,void* reserved){
    JNIEnv* env=nullptr;//获取JNIEnv对象
    if(vm->GetEnv((void**)&env,JNI_VERSION_1_6)!=JNI_OK)
        return -1;
    //获取对应java方法所属的类对象
    jclass clazz=env->FindClass(ClassName);
    //调用RegisterNatives注册方法
    if(clazz){
        env->RegisterNatives(clazz,methods,sizeof(methods)/sizeof(methods[0]));
        return JNI_VERSION_1_6;//注意必须返回JNI版本
    }
    else
        return -1;
}
#include <jni.h>
#include <string>
 
// 静态注册
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_nativedemo_MainActivity_stringFromJNI(
        JNIEnv* env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}
 
//native函数对应的java方法所属类
static const char *ClassName="com/example/nativedemo/MainActivity";
 
jint add(JNIEnv* env,jobject obj,jint x,jint y){
    return x+y;
}
jint sub(JNIEnv* env,jobject obj,jint x,jint y){
    return x-y;
}
 
//定义本地方法数组 建立映射关系
//JNINativeMethod结构体成员为函数名,函数签名,函数指针
static JNINativeMethod methods[]={
        {"add","(II)I",(void*)add},
        {"sub","(II)I",(void*)sub}
};
 
//编写加载注册方法
jint JNI_OnLoad(JavaVM* vm,void* reserved){
    JNIEnv* env=nullptr;//获取JNIEnv对象
    if(vm->GetEnv((void**)&env,JNI_VERSION_1_6)!=JNI_OK)
        return -1;
    //获取对应java方法所属的类对象
    jclass clazz=env->FindClass(ClassName);
    //调用RegisterNatives注册方法
    if(clazz){
        env->RegisterNatives(clazz,methods,sizeof(methods)/sizeof(methods[0]));
        return JNI_VERSION_1_6;//注意必须返回JNI版本
    }
    else
        return -1;
}
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="eedK9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8Y4y4U0K9r3g2E0j5i4y4Q4x3X3g2S2L8X3c8J5L8$3W2V1i4K6u0W2j5$3!0E0i4K6u0r3j5i4m8C8i4K6u0r3M7X3g2K6i4K6u0r3j5h3&6V1M7X3!0A6k6l9`.`."
    xmlns:tools="98cK9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8Y4y4U0K9r3g2E0j5i4y4Q4x3X3g2S2L8X3c8J5L8$3W2V1i4K6u0W2j5$3!0E0i4K6u0r3N6r3!0G2L8s2x3`.">
 
    <application
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.NativeDemo"
        tools:targetApi="29"
        android:name=".MyApplication"
        >
        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="eedK9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8Y4y4U0K9r3g2E0j5i4y4Q4x3X3g2S2L8X3c8J5L8$3W2V1i4K6u0W2j5$3!0E0i4K6u0r3j5i4m8C8i4K6u0r3M7X3g2K6i4K6u0r3j5h3&6V1M7X3!0A6k6l9`.`."
    xmlns:tools="98cK9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8Y4y4U0K9r3g2E0j5i4y4Q4x3X3g2S2L8X3c8J5L8$3W2V1i4K6u0W2j5$3!0E0i4K6u0r3N6r3!0G2L8s2x3`.">
 
    <application
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.NativeDemo"
        tools:targetApi="29"
        android:name=".MyApplication"
        >
        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="974K9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8Y4y4U0K9r3g2E0j5i4y4Q4x3X3g2S2L8X3c8J5L8$3W2V1i4K6u0W2j5$3!0E0i4K6u0r3j5i4m8C8i4K6u0r3M7X3g2K6i4K6u0r3j5h3&6V1M7X3!0A6k6l9`.`."
    xmlns:app="cc7K9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8Y4y4U0K9r3g2E0j5i4y4Q4x3X3g2S2L8X3c8J5L8$3W2V1i4K6u0W2j5$3!0E0i4K6u0r3j5i4m8C8i4K6u0r3M7X3g2K6i4K6u0V1j5i4g2@1L8H3`.`."
    xmlns:tools="8acK9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8Y4y4U0K9r3g2E0j5i4y4Q4x3X3g2S2L8X3c8J5L8$3W2V1i4K6u0W2j5$3!0E0i4K6u0r3N6r3!0G2L8s2x3`."
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    tools:context="com.example.nativedemo.MainActivity"
    >
 
    <TextView
        android:id="@+id/sample_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        android:textAlignment="center"
        />
 
</LinearLayout>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="974K9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8Y4y4U0K9r3g2E0j5i4y4Q4x3X3g2S2L8X3c8J5L8$3W2V1i4K6u0W2j5$3!0E0i4K6u0r3j5i4m8C8i4K6u0r3M7X3g2K6i4K6u0r3j5h3&6V1M7X3!0A6k6l9`.`."
    xmlns:app="cc7K9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8Y4y4U0K9r3g2E0j5i4y4Q4x3X3g2S2L8X3c8J5L8$3W2V1i4K6u0W2j5$3!0E0i4K6u0r3j5i4m8C8i4K6u0r3M7X3g2K6i4K6u0V1j5i4g2@1L8H3`.`."
    xmlns:tools="8acK9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8Y4y4U0K9r3g2E0j5i4y4Q4x3X3g2S2L8X3c8J5L8$3W2V1i4K6u0W2j5$3!0E0i4K6u0r3N6r3!0G2L8s2x3`."
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    tools:context="com.example.nativedemo.MainActivity"
    >
 
    <TextView
        android:id="@+id/sample_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        android:textAlignment="center"
        />
 
</LinearLayout>
from zlib import adler32
from hashlib import sha1
from binascii import unhexlify
from lxml import etree
import subprocess
import shutil
from pathlib import Path
import argparse
# 封装path类,保存全局用到的路径
class Paths:
    def __init__(self, srcApk:Path, shellApk:Path, outputApk:Path):
        # Apk file paths
        self.srcApkPath= srcApk.resolve() # 解析为绝对路径
        self.shellApkPath= shellApk.resolve()
        self.newApkPath= outputApk.resolve()
 
        # Temp directories default python file path
        self.tmpdir= Path(__file__).parent/'temp'
        self.srcApkTempDir=  self.tmpdir/ 'srcApkTemp'
        self.shellApkTempDir= self.tmpdir/'shellApkTemp'
        self.newApkTempDir= self.tmpdir/ 'newApkTemp'
 
 
# Apktool类 提供解包,打包,签名功能
class Apktool:
    def __init__(self):
        self.apktool_path= Path(__file__).parent/'tools/apktool/apktool.bat'
        self.signer_path=Path(__file__).parent/'tools/uber-apk-signer-1.3.0.jar'
 
    def signApk(self,unsignedApkPath:Path):
        self.runCommand(['java','-jar', self.signer_path, '--apk',unsignedApkPath])
 
    # 使用apktool解包apk 只解包资源不解包dex
    def extractApk(self,apkPath:Path, outputDir:Path):
        self.runCommand([self.apktool_path, '-s', 'd' , apkPath, '-o', outputDir])
 
    # 重打包apk
    def repackApk(self,inputDir:Path, outApk:Path):
        self.runCommand([self.apktool_path, 'b' , inputDir, '-o', outApk])
 
    def runCommand(self,args):
        subprocess.run(args,stdout=subprocess.DEVNULL) #仅调用工具,不需要输出,重定向stdout到os.devnull
        # 参数列表 捕获输出 输出转为字符串
        #print(subprocess.run(args,  capture_output=True,text=True).stdout)
 
# AndroidManifest.xml的editor 用于获取和修改标签属性,以及添加标签
class ManifestEditor:
    def __init__(self, xml_content: bytes):
        self.ns = {'android': '6f0K9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8Y4y4U0K9r3g2E0j5i4y4Q4x3X3g2S2L8X3c8J5L8$3W2V1i4K6u0W2j5$3!0E0i4K6u0r3j5i4m8C8i4K6u0r3M7X3g2K6i4K6u0r3j5h3&6V1M7X3!0A6k6q4)9J5y4H3`.`.}
        self.tree = etree.fromstring(xml_content)
 
    # 获取指定标签的android属性值 examples: get_attr('application', 'name') get_attr('activity', 'name')
    def getTagAttribute(self, tag_name: str, attr_name: str):
        if tag_name == 'manifest'# 根标签特殊处理
            elem = self.tree
            if elem is not None:
                return elem.get(f'{attr_name}'# 寻找标签的属性
        else:
            elem = self.tree.find(f'.//{tag_name}', namespaces=self.ns)
            if elem is not None:
                return elem.get(f'{{{self.ns["android"]}}}{attr_name}'# 根标签之外的属性位于android命名空间下
        return None
 
    # 设置指定标签的属性值 example:set_attr('application','name',"com.example.ProxyApplication")
    def setTagAttribute(self, tag_name: str, attr_name: str, new_value: str):
        if tag_name == 'manifest'# 根标签特殊处理
            elem = self.tree
            if elem is not None:
                return elem.set(f'{attr_name}', new_value)  # 设置属性值
        else:
            elem = self.tree.find(f'.//{tag_name}', namespaces=self.ns)
            if elem is not None:
                elem.set(f'{{{self.ns["android"]}}}{attr_name}', new_value)
                return True
        return False
 
    # 在指定父标签下添加新子标签 example: add_tag('application',"meta-data",{'name': 'android.permission.CAMERA','value':'hello'})
    def addTagWithAttributes(self, parent_tag: str, new_tag: str, attrs: dict):
        if parent_tag == 'manifest':
            parent = self.tree
            if parent is not None:
                new_elem = etree.SubElement(parent, new_tag)
                for k, v in attrs.items():  # 支持一次给添加的标签设置多个属性
                    new_elem.set(f'{k}', v)
                return True
        else:
            parent = self.tree.find(f'.//{parent_tag}', namespaces=self.ns)
            if parent is not None:
                new_elem = etree.SubElement(parent, new_tag)
                for k, v in attrs.items():
                    new_elem.set(f'{{{self.ns["android"]}}}{k}', v)
                return True
        return False
 
    # 不以壳manifest为基准操作则用不到该函数,以源apk的manifest为基准自带,无需额外设置
    def getMainActivity(self):
        activities = self.tree.findall('.//activity', namespaces=self.ns)
        for activity in activities:
            intent_filters = activity.findall('.//intent-filter', namespaces=self.ns)
            for intent_filter in intent_filters:
                action = intent_filter.find('.//action[@android:name="android.intent.action.MAIN"]', namespaces=self.ns)
                category = intent_filter.find('.//category[@android:name="android.intent.category.LAUNCHER"]',
                                              namespaces=self.ns)
                if action is not None and category is not None:
                    return activity.get(f'{{{self.ns["android"]}}}name')
        return None
 
    def getApplication(self):
        return self.getTagAttribute('application', 'name')
 
    def setApplication(self, application: str):
        self.setTagAttribute('application', 'name', application)
 
    def addMetaData(self, name: str, value: str):
        self.addTagWithAttributes('application', 'meta-data', {'name': name, 'value': value})
 
    def getManifestData(self):
        """返回XML字符串"""
        return etree.tostring(self.tree, pretty_print=True, encoding='utf-8', xml_declaration=True).decode()
 
# 合并壳dex和源apk
def combineShellDexAndSrcApk(sourceApkPath:Path, shellApkTempDir:Path, newApkTempDir:Path):
    def fixCheckSum(dexBytesArray):
        # dexfile[8:12]
        # 小端存储
        value = adler32(bytes(dexBytesArray[12:]))
        valueArray = bytearray(value.to_bytes(4, 'little'))
        for i in range(len(valueArray)):
            dexBytesArray[8 + i] = valueArray[i]
 
    def fixSignature(dexBytesArray):
        # dexfile[12:32]
        sha_1 = sha1()
        sha_1.update(bytes(dexBytesArray[32:]))
        value = sha_1.hexdigest()
        valueArray = bytearray(unhexlify(value))
        for i in range(len(valueArray)):
            dexBytesArray[12 + i] = valueArray[i]
 
    def fixFileSize(dexBytesArray, fileSize):
        # dexfile[32:36]
        # 小端存储
        fileSizeArray = bytearray(fileSize.to_bytes(4, "little"))
        for i in range(len(fileSizeArray)):
            dexBytesArray[32 + i] = fileSizeArray[i]
 
    def encrypto(file):
        for i in range(len(file)):
            file[i] ^= 0xff
        return file
 
    # 获取源apk
    with open(sourceApkPath, 'rb') as f:
        SourceApkArray=bytearray(f.read())
    # 获取shelldex
    with open(shellApkTempDir/'classes.dex', 'rb') as f:
        shellDexArray=bytearray(f.read())
 
    SourceApkLen = len(SourceApkArray)
    shellDexLen = len(shellDexArray)
 
    # 新的dex文件长度
    newDexLen = shellDexLen + SourceApkLen + 4
    # 加密源文件
    enApkArray = encrypto(SourceApkArray)
    # 新的dex文件内容 = 壳dex + 加密的源apk + 四字节标识加密后源apk大小长度
    newDexArray = shellDexArray + enApkArray + bytearray(SourceApkLen.to_bytes(4, 'little'))
 
    # 修改filesize
    fixFileSize(newDexArray, newDexLen)
    # 修改signature
    fixSignature(newDexArray)
    # 修改checksum
    fixCheckSum(newDexArray)
    # 导出文件
    with open(newApkTempDir/'classes.dex', 'wb') as f:
        f.write(newDexArray)
 
# 提取源apk的Manifest文件,修改application为壳application(可能添加meta-data标签),输出新的Manifest文件
def handleManifest( srcApkTempDir:Path,shellApkTempDir:Path,newApkTempDir:Path):
    # 从源apk提取AndroidManifest.xml
    with open(srcApkTempDir/'AndroidManifest.xml', 'r') as f:
        srcManifestEditor=ManifestEditor(f.read().encode())
    srcApplication=srcManifestEditor.getApplication()
 
    # 从壳apk提取AndroidManifest.xml
    with open(shellApkTempDir/'AndroidManifest.xml', 'r') as f:
        shellManifestEditor=ManifestEditor(f.read().encode())
    print('ShellApplication:',shellManifestEditor.getApplication())
 
    # 修改源AndroidManifest.xml的application为壳的代理application
    srcManifestEditor.setApplication(shellManifestEditor.getApplication())
 
    # 写入meta-data标签 保存源apk的原始application
    if srcApplication != None:
        print('Source application:',srcApplication)
        srcManifestEditor.addMetaData('APPLICATION_CLASS_NAME',srcApplication)
 
    # 输出新的AndroidManifest.xml
    with open(newApkTempDir/'AndroidManifest.xml', 'w') as f:
        f.write(srcManifestEditor.getManifestData())
 
def start(paths:Paths):
    apktool=Apktool()
    # 1.分别解包源文件和壳文件到临时目录
    print('Extracting source and shell apk...')
    apktool.extractApk(paths.srcApkPath,paths.srcApkTempDir)
    print('Extract source apk success!')
 
    print('Extracting shell apk...')
    apktool.extractApk(paths.shellApkPath,paths.shellApkTempDir)
    print('Extract shell apk success!')
 
    # 2.复制源apk所有文件到新apk临时目录中
    print('Copying source apk files to new apk temp dir...')
    shutil.copytree(paths.srcApkTempDir,paths.newApkTempDir )
    print('Copy source apk files success!')
 
    # 3.处理AndroidManifest.xml
    print('Handling AndroidManifest.xml...')
    handleManifest(paths.srcApkTempDir,paths.shellApkTempDir,paths.newApkTempDir)
    print('Handle AndroidManifest.xml success!')
 
    # 4.合并壳dex和源apk并导出文件
    print('Combining shell dex and source apk...')
    combineShellDexAndSrcApk(paths.srcApkPath,paths.shellApkTempDir,paths.newApkTempDir)
    print('Combine shell dex and source apk success!')
 
    # 5.重打包apk
    print('Repacking apk...')
    apktool.repackApk(paths.newApkTempDir,paths.newApkPath)
    print('Repack apk success!')
 
    # 6.签名apk
    print('Signing apk...')
    apktool.signApk(paths.newApkPath)
    print('Resign apk success!')
 
    # 7.删除临时目录
    print('Deleting temp directories...')
    shutil.rmtree(paths.tmpdir) # 删除临时目录
    print('Delete temp directories success!')
     
def main():
    parser = argparse.ArgumentParser(description="Android APK Packer")
    parser.add_argument('-src','--src-apk', required=True, type=Path, help='Path to source APK file')
    parser.add_argument('-shell','--shell-apk', required=True, type=Path, help='Path to shell APK file')
    parser.add_argument('-o','-out','--output-apk',type=Path,help='Output path for packed APK (Default: ./out/<src-apk>_protected.apk)')
    args = parser.parse_args()
    if args.output_apk == None:
        args.output_apk = Path('./out')/(args.src_apk.stem+'_protected.apk') # 默认新apk名称及输出路径
    paths = Paths(args.src_apk, args.shell_apk, args.output_apk)
 
    print('Source APK:', paths.srcApkPath)
    print('Shell APK:', paths.shellApkPath)
    print('Output APK:', paths.newApkPath)
    start(paths)
 
 
if __name__=="__main__":
    main()
from zlib import adler32
from hashlib import sha1
from binascii import unhexlify
from lxml import etree
import subprocess
import shutil
from pathlib import Path
import argparse
# 封装path类,保存全局用到的路径
class Paths:
    def __init__(self, srcApk:Path, shellApk:Path, outputApk:Path):
        # Apk file paths
        self.srcApkPath= srcApk.resolve() # 解析为绝对路径
        self.shellApkPath= shellApk.resolve()
        self.newApkPath= outputApk.resolve()
 
        # Temp directories default python file path
        self.tmpdir= Path(__file__).parent/'temp'
        self.srcApkTempDir=  self.tmpdir/ 'srcApkTemp'
        self.shellApkTempDir= self.tmpdir/'shellApkTemp'
        self.newApkTempDir= self.tmpdir/ 'newApkTemp'
 
 
# Apktool类 提供解包,打包,签名功能
class Apktool:
    def __init__(self):
        self.apktool_path= Path(__file__).parent/'tools/apktool/apktool.bat'
        self.signer_path=Path(__file__).parent/'tools/uber-apk-signer-1.3.0.jar'
 
    def signApk(self,unsignedApkPath:Path):
        self.runCommand(['java','-jar', self.signer_path, '--apk',unsignedApkPath])
 
    # 使用apktool解包apk 只解包资源不解包dex
    def extractApk(self,apkPath:Path, outputDir:Path):
        self.runCommand([self.apktool_path, '-s', 'd' , apkPath, '-o', outputDir])
 
    # 重打包apk
    def repackApk(self,inputDir:Path, outApk:Path):
        self.runCommand([self.apktool_path, 'b' , inputDir, '-o', outApk])
 
    def runCommand(self,args):
        subprocess.run(args,stdout=subprocess.DEVNULL) #仅调用工具,不需要输出,重定向stdout到os.devnull
        # 参数列表 捕获输出 输出转为字符串
        #print(subprocess.run(args,  capture_output=True,text=True).stdout)
 
# AndroidManifest.xml的editor 用于获取和修改标签属性,以及添加标签
class ManifestEditor:
    def __init__(self, xml_content: bytes):
        self.ns = {'android': '6f0K9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8Y4y4U0K9r3g2E0j5i4y4Q4x3X3g2S2L8X3c8J5L8$3W2V1i4K6u0W2j5$3!0E0i4K6u0r3j5i4m8C8i4K6u0r3M7X3g2K6i4K6u0r3j5h3&6V1M7X3!0A6k6q4)9J5y4H3`.`.}
        self.tree = etree.fromstring(xml_content)
 
    # 获取指定标签的android属性值 examples: get_attr('application', 'name') get_attr('activity', 'name')
    def getTagAttribute(self, tag_name: str, attr_name: str):
        if tag_name == 'manifest'# 根标签特殊处理
            elem = self.tree
            if elem is not None:
                return elem.get(f'{attr_name}'# 寻找标签的属性
        else:
            elem = self.tree.find(f'.//{tag_name}', namespaces=self.ns)
            if elem is not None:
                return elem.get(f'{{{self.ns["android"]}}}{attr_name}'# 根标签之外的属性位于android命名空间下
        return None
 
    # 设置指定标签的属性值 example:set_attr('application','name',"com.example.ProxyApplication")
    def setTagAttribute(self, tag_name: str, attr_name: str, new_value: str):
        if tag_name == 'manifest'# 根标签特殊处理
            elem = self.tree
            if elem is not None:
                return elem.set(f'{attr_name}', new_value)  # 设置属性值
        else:
            elem = self.tree.find(f'.//{tag_name}', namespaces=self.ns)
            if elem is not None:
                elem.set(f'{{{self.ns["android"]}}}{attr_name}', new_value)
                return True
        return False
 
    # 在指定父标签下添加新子标签 example: add_tag('application',"meta-data",{'name': 'android.permission.CAMERA','value':'hello'})
    def addTagWithAttributes(self, parent_tag: str, new_tag: str, attrs: dict):
        if parent_tag == 'manifest':
            parent = self.tree
            if parent is not None:
                new_elem = etree.SubElement(parent, new_tag)
                for k, v in attrs.items():  # 支持一次给添加的标签设置多个属性
                    new_elem.set(f'{k}', v)
                return True
        else:
            parent = self.tree.find(f'.//{parent_tag}', namespaces=self.ns)
            if parent is not None:
                new_elem = etree.SubElement(parent, new_tag)
                for k, v in attrs.items():
                    new_elem.set(f'{{{self.ns["android"]}}}{k}', v)
                return True
        return False
 
    # 不以壳manifest为基准操作则用不到该函数,以源apk的manifest为基准自带,无需额外设置
    def getMainActivity(self):
        activities = self.tree.findall('.//activity', namespaces=self.ns)
        for activity in activities:
            intent_filters = activity.findall('.//intent-filter', namespaces=self.ns)
            for intent_filter in intent_filters:
                action = intent_filter.find('.//action[@android:name="android.intent.action.MAIN"]', namespaces=self.ns)
                category = intent_filter.find('.//category[@android:name="android.intent.category.LAUNCHER"]',
                                              namespaces=self.ns)
                if action is not None and category is not None:
                    return activity.get(f'{{{self.ns["android"]}}}name')
        return None
 
    def getApplication(self):
        return self.getTagAttribute('application', 'name')
 
    def setApplication(self, application: str):
        self.setTagAttribute('application', 'name', application)
 
    def addMetaData(self, name: str, value: str):
        self.addTagWithAttributes('application', 'meta-data', {'name': name, 'value': value})
 
    def getManifestData(self):
        """返回XML字符串"""
        return etree.tostring(self.tree, pretty_print=True, encoding='utf-8', xml_declaration=True).decode()
 
# 合并壳dex和源apk
def combineShellDexAndSrcApk(sourceApkPath:Path, shellApkTempDir:Path, newApkTempDir:Path):
    def fixCheckSum(dexBytesArray):
        # dexfile[8:12]
        # 小端存储
        value = adler32(bytes(dexBytesArray[12:]))
        valueArray = bytearray(value.to_bytes(4, 'little'))
        for i in range(len(valueArray)):
            dexBytesArray[8 + i] = valueArray[i]
 
    def fixSignature(dexBytesArray):
        # dexfile[12:32]
        sha_1 = sha1()
        sha_1.update(bytes(dexBytesArray[32:]))
        value = sha_1.hexdigest()
        valueArray = bytearray(unhexlify(value))
        for i in range(len(valueArray)):
            dexBytesArray[12 + i] = valueArray[i]
 
    def fixFileSize(dexBytesArray, fileSize):
        # dexfile[32:36]
        # 小端存储
        fileSizeArray = bytearray(fileSize.to_bytes(4, "little"))
        for i in range(len(fileSizeArray)):
            dexBytesArray[32 + i] = fileSizeArray[i]
 
    def encrypto(file):
        for i in range(len(file)):
            file[i] ^= 0xff
        return file
 
    # 获取源apk
    with open(sourceApkPath, 'rb') as f:
        SourceApkArray=bytearray(f.read())
    # 获取shelldex
    with open(shellApkTempDir/'classes.dex', 'rb') as f:
        shellDexArray=bytearray(f.read())
 
    SourceApkLen = len(SourceApkArray)
    shellDexLen = len(shellDexArray)
 
    # 新的dex文件长度
    newDexLen = shellDexLen + SourceApkLen + 4
    # 加密源文件
    enApkArray = encrypto(SourceApkArray)
    # 新的dex文件内容 = 壳dex + 加密的源apk + 四字节标识加密后源apk大小长度
    newDexArray = shellDexArray + enApkArray + bytearray(SourceApkLen.to_bytes(4, 'little'))
 
    # 修改filesize
    fixFileSize(newDexArray, newDexLen)
    # 修改signature
    fixSignature(newDexArray)
    # 修改checksum
    fixCheckSum(newDexArray)
    # 导出文件
    with open(newApkTempDir/'classes.dex', 'wb') as f:
        f.write(newDexArray)
 
# 提取源apk的Manifest文件,修改application为壳application(可能添加meta-data标签),输出新的Manifest文件
def handleManifest( srcApkTempDir:Path,shellApkTempDir:Path,newApkTempDir:Path):
    # 从源apk提取AndroidManifest.xml
    with open(srcApkTempDir/'AndroidManifest.xml', 'r') as f:
        srcManifestEditor=ManifestEditor(f.read().encode())
    srcApplication=srcManifestEditor.getApplication()
 
    # 从壳apk提取AndroidManifest.xml
    with open(shellApkTempDir/'AndroidManifest.xml', 'r') as f:
        shellManifestEditor=ManifestEditor(f.read().encode())
    print('ShellApplication:',shellManifestEditor.getApplication())
 
    # 修改源AndroidManifest.xml的application为壳的代理application
    srcManifestEditor.setApplication(shellManifestEditor.getApplication())
 
    # 写入meta-data标签 保存源apk的原始application
    if srcApplication != None:
        print('Source application:',srcApplication)
        srcManifestEditor.addMetaData('APPLICATION_CLASS_NAME',srcApplication)
 
    # 输出新的AndroidManifest.xml
    with open(newApkTempDir/'AndroidManifest.xml', 'w') as f:
        f.write(srcManifestEditor.getManifestData())
 
def start(paths:Paths):
    apktool=Apktool()
    # 1.分别解包源文件和壳文件到临时目录
    print('Extracting source and shell apk...')
    apktool.extractApk(paths.srcApkPath,paths.srcApkTempDir)
    print('Extract source apk success!')
 
    print('Extracting shell apk...')
    apktool.extractApk(paths.shellApkPath,paths.shellApkTempDir)
    print('Extract shell apk success!')
 
    # 2.复制源apk所有文件到新apk临时目录中
    print('Copying source apk files to new apk temp dir...')
    shutil.copytree(paths.srcApkTempDir,paths.newApkTempDir )
    print('Copy source apk files success!')
 
    # 3.处理AndroidManifest.xml
    print('Handling AndroidManifest.xml...')
    handleManifest(paths.srcApkTempDir,paths.shellApkTempDir,paths.newApkTempDir)
    print('Handle AndroidManifest.xml success!')
 
    # 4.合并壳dex和源apk并导出文件
    print('Combining shell dex and source apk...')
    combineShellDexAndSrcApk(paths.srcApkPath,paths.shellApkTempDir,paths.newApkTempDir)
    print('Combine shell dex and source apk success!')
 
    # 5.重打包apk
    print('Repacking apk...')
    apktool.repackApk(paths.newApkTempDir,paths.newApkPath)
    print('Repack apk success!')
 
    # 6.签名apk
    print('Signing apk...')
    apktool.signApk(paths.newApkPath)
    print('Resign apk success!')
 
    # 7.删除临时目录
    print('Deleting temp directories...')
    shutil.rmtree(paths.tmpdir) # 删除临时目录
    print('Delete temp directories success!')
     
def main():
    parser = argparse.ArgumentParser(description="Android APK Packer")
    parser.add_argument('-src','--src-apk', required=True, type=Path, help='Path to source APK file')
    parser.add_argument('-shell','--shell-apk', required=True, type=Path, help='Path to shell APK file')
    parser.add_argument('-o','-out','--output-apk',type=Path,help='Output path for packed APK (Default: ./out/<src-apk>_protected.apk)')
    args = parser.parse_args()
    if args.output_apk == None:
        args.output_apk = Path('./out')/(args.src_apk.stem+'_protected.apk') # 默认新apk名称及输出路径
    paths = Paths(args.src_apk, args.shell_apk, args.output_apk)
 
    print('Source APK:', paths.srcApkPath)
    print('Shell APK:', paths.shellApkPath)
    print('Output APK:', paths.newApkPath)
    start(paths)
 
 
if __name__=="__main__":
    main()
package com.example.androidshell;
 
import android.app.Application;
import android.app.Instrumentation;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.util.ArrayMap;
import android.util.Log;
 
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
 
import dalvik.system.DexClassLoader;
 
public class FirstProxyApplication extends  Application {
     private static final String TAG="glass";
     private String apkPath;
     private String dexPath;
     private String libPath;
     public void log(String message){Log.d(TAG,message);}
    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);
        log("FirstProxyApplication.attachBaseContext() running!");
        try {
            //1.创建私有目录,保存dex,lib和源apk 具体路径为data/user/0/<package_name>/app_tmp_dex
            File dex = getDir("tmp_dex", MODE_PRIVATE);
            File lib = getDir("tmp_lib", MODE_PRIVATE);
            dexPath = dex.getAbsolutePath();
            libPath = lib.getAbsolutePath();
            apkPath = dex.getAbsolutePath() + File.separator + "Source.apk";
            log("dexPath: " + dexPath);
            log("libPath: " + libPath);
            log("apkPath: " + apkPath);
 
            // 根据文件路径创建File对象
            File apkFile = new File(apkPath);
            // 只有首次运行时需要创建相关文件
            if (!apkFile.exists()) {
                // 根据路径创建文件
                apkFile.createNewFile();
                //读取Classes.dex文件
                byte[] shellDexData = readDexFromApk();
                //从中分离出源apk文件
                extractSrcApkFromShellDex(shellDexData);
            }
            //配置加载源程序的动态环境,即替换mClassLoader
            replaceClassLoader();
        } catch (Exception e) {
            Log.getStackTraceString(e);
        }
    }
    // 从当前程序的apk读取dex文件并存储为字节数组
    private byte[] readDexFromApk() throws IOException {
        //1.获取当前应用程序的源码路径(apk),一般是data/app目录下,该目录用于存放用户安装的软件
        String sourceDir = this.getApplicationInfo().sourceDir;
        log("this.getApplicationInfo().sourceDir: " +sourceDir);
 
        //2.创建相关输入流
        FileInputStream fileInputStream = new FileInputStream(sourceDir);
        BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream);
        ZipInputStream zipInputStream = new ZipInputStream(bufferedInputStream); //用于解析apk文件
 
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); //用于存放dex文件
 
        //3.遍历apk的所有文件并提取dex文件
        ZipEntry zipEntry;
        while((zipEntry = zipInputStream.getNextEntry()) != null){ //存在下一个文件
            // 将classes.dex文件存储到bytearray中 壳dex和源apk合并后只保留一个dex便于处理
            if (zipEntry.getName().equals("classes.dex")){
                byte[] bytes = new byte[1024];
                int num;
                while((num = zipInputStream.read(bytes))!=-1){      //每次读取1024byte,返回读取到的byte数
                    byteArrayOutputStream.write(bytes,0, num); //存放到开辟的byteArrayOutputStream中
                }
            }
            zipInputStream.closeEntry(); //关闭当前文件
        }
        zipInputStream.close();
 
        log("Read dex from apk succeed!");
        return byteArrayOutputStream.toByteArray(); //将读取到的dex文件以字节数组形式返回
    }
 
    // 从壳dex文件中提取源apk并解析
    private void extractSrcApkFromShellDex(byte[] shellDexData) throws IOException {
        int shellDexLen = shellDexData.length;
        //开始解析dex文件
        //1.读取源apk的大小
        byte[] srcApkSizeBytes = new byte[4];
        System.arraycopy(shellDexData, shellDexLen - 4, srcApkSizeBytes,0,4);
        int srcApkSize =ByteBuffer.wrap(srcApkSizeBytes).order(ByteOrder.LITTLE_ENDIAN).getInt();//转成bytebuffer,方便4 bytes转int 将bytes转成int,加壳时,长度按小端存储
 
        //2.读取源apk
        byte[] sourceApkData = new byte[srcApkSize];
        System.arraycopy(shellDexData, shellDexLen - srcApkSize - 4, sourceApkData, 0, srcApkSize);//注意减4
 
        //3.解密源apk
        sourceApkData = decrypt(sourceApkData);
        //写入新建的apk文件中
        File apkfile = new File(apkPath);
        try {
            FileOutputStream apkfileOutputStream = new FileOutputStream(apkfile);
            apkfileOutputStream.write(sourceApkData);
            apkfileOutputStream.close();
        }catch (IOException e){
            throw new IOException(e);
        }
 
        //分析源apk,取出so文件放入libPath目录中
        FileInputStream fileInputStream = new FileInputStream(apkfile);
        BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream);
        ZipInputStream zipInputStream = new ZipInputStream(bufferedInputStream);
        ZipEntry nextEntry;
        while ((nextEntry=zipInputStream.getNextEntry())!=null){
            String name = nextEntry.getName();
            if (name.startsWith("lib/") && name.endsWith(".so")){
                //获取文件名并创建相应文件
                String[] nameSplit = name.split("/");
                String soFileStorePath = libPath + File.separator + nameSplit[nameSplit.length - 1];
                File storeFile = new File(soFileStorePath);
                storeFile.createNewFile();
 
                //读数据到相应so文件中
                FileOutputStream fileOutputStream = new FileOutputStream(storeFile);
                byte[] bytes = new byte[1024];
                int num;
                while((num = zipInputStream.read(bytes))!=-1){
                    fileOutputStream.write(bytes,0,num);
                }
                fileOutputStream.flush();
                fileOutputStream.close();
            }
            zipInputStream.closeEntry();
        }
        zipInputStream.close();
    }
 
    // 解密函数
    private byte[] decrypt(byte[] data) {
        for (int i = 0; i < data.length; i++){
            data[i] ^= (byte) 0xff;
        }
        return data;
    }
 
    // 替换壳App的ClassLoader为源App的ClassLoader
    private void replaceClassLoader() {
        //1.获取当前的classloader
        ClassLoader classLoader = this.getClassLoader();
        log("Current ClassLoader: " + classLoader.toString());
        log("Parent ClassLoader: " + classLoader.getParent().toString());
 
        //2.反射获取ActivityThread
        Object sCurrentActivityThreadObj = Reflection.getStaticField("android.app.ActivityThread","sCurrentActivityThread");
        log("ActivityThread.sCurrentActivity: " + sCurrentActivityThreadObj.toString());
 
        //3.反射获取LoadedApk
        //获取当前ActivityThread实例的mPackages字段 类型为ArrayMap<String, WeakReference<LoadedApk>>, 里面存放了当前应用的LoadedApk对象
        ArrayMap mPackagesObj = (ArrayMap) Reflection.getField("android.app.ActivityThread",sCurrentActivityThreadObj,"mPackages");
        log( "mPackagesObj: " + mPackagesObj.toString());
 
        //获取mPackages中的当前应用包名
        String currentPackageName = this.getPackageName();
        log("currentPackageName: " + currentPackageName);
 
        // 获取loadedApk实例也有好几种,mInitialApplication mAllApplications mPackages
        // 通过包名获取当前应用的loadedApk实例
        WeakReference weakReference = (WeakReference) mPackagesObj.get(currentPackageName);
        Object loadedApkObj = weakReference.get();
        log( "LoadedApk: " + loadedApkObj.toString());
 
        //4.替换ClassLoader
        DexClassLoader dexClassLoader = new DexClassLoader(apkPath,dexPath,libPath, classLoader.getParent()); //动态加载源程序的dex文件,以当前classloader的父加载器作为parent
        Reflection.setField("android.app.LoadedApk","mClassLoader",loadedApkObj,dexClassLoader); //替换当前loadedApk实例中的mClassLoader字段
        log("New DexClassLoader: " + dexClassLoader);
    }
 
    //替换壳程序LoadedApk的Application为源程序Application,并调用其onCreate方法
    public boolean replaceApplication(){
        // Application实例存在于: LoadedApk.mApplication
        // 以及ActivityThread的mInitialApplication和mAllApplications和mBoundApplication
 
        //判断源程序是否使用自定义Application 若使用则需要进行替换,若未使用则直接返回,使用壳的默认Application即可
 
        String appClassName = null; //源程序的Application类名
        try {
            //获取AndroidManifest.xml 文件中的 <meta-data> 元素
            ApplicationInfo applicationInfo = getPackageManager().getApplicationInfo(this.getPackageName(), PackageManager.GET_META_DATA);
            Bundle metaData = applicationInfo.metaData;
            //获取xml文件声明的Application类
            if (metaData != null && metaData.containsKey("APPLICATION_CLASS_NAME")){
                appClassName = metaData.getString("APPLICATION_CLASS_NAME");
            } else {
                log("源程序中没有自定义Application");
                return false; //如果不存在直接返回,使用壳的application即可
            }
        } catch (PackageManager.NameNotFoundException e) {
            log(Log.getStackTraceString(e));
        }
 
        //源程序存在自定义application类,开始替换
        log("Try to replace Application");
 
        //1.反射获取ActivityThread实例
        Object sCurrentActivityThreadObj = Reflection.getStaticField("android.app.ActivityThread","sCurrentActivityThread");
        log("ActivityThread: " + sCurrentActivityThreadObj.toString());
 
        //2.获取并设置LoadedApk
        //获取mBoundApplication (AppBindData对象)
        Object mBoundApplicationObj = Reflection.getField("android.app.ActivityThread",sCurrentActivityThreadObj,"mBoundApplication") ;
        log("mBoundApplication: "+mBoundApplicationObj.toString());
        //获取mBoundApplication.info (即LoadedApk)
        Object infoObj = Reflection.getField("android.app.ActivityThread$AppBindData",mBoundApplicationObj,"info");
        log( "LoadedApk: " + infoObj.toString());
        //把LoadedApk的mApplication设置为null,这样后续才能调用makeApplication() 否则由于已存在Application,无法进行替换
        Reflection.setField("android.app.LoadedApk","mApplication",infoObj,null);
 
        //3.获取ActivityThread.mInitialApplication 即拿到旧的Application(对于要加载的Application来讲)
        Object mInitialApplicationObj = Reflection.getField("android.app.ActivityThread",sCurrentActivityThreadObj,"mInitialApplication");
        log("mInitialApplicationObj: " + mInitialApplicationObj.toString());
 
        //4.获取ActivityThread.mAllApplications并删除旧的application
        ArrayList<Application> mAllApplicationsObj = (ArrayList<Application>) Reflection.getField("android.app.ActivityThread",sCurrentActivityThreadObj,"mAllApplications");
        mAllApplicationsObj.remove(mInitialApplicationObj);
        log("mInitialApplication 从 mAllApplications 中移除成功");
 
        //5.重置相关类的Application类名 便于后续创建Application
        //获取LoadedApk.mApplicationInfo
        ApplicationInfo applicationInfo = (ApplicationInfo) Reflection.getField("android.app.LoadedApk",infoObj,"mApplicationInfo");
        log( "LoadedApk.mApplicationInfo: " + applicationInfo.toString());
        //获取mBoundApplication.appInfo
        ApplicationInfo appinfoInAppBindData = (ApplicationInfo) Reflection.getField("android.app.ActivityThread$AppBindData",mBoundApplicationObj,"appInfo");
        log("ActivityThread.mBoundApplication.appInfo: " + appinfoInAppBindData.toString());
        //此处通过引用修改值,虽然后续没有使用,但是实际上是修改其指向的LoadedApk相关字段的值
        //设置两个appinfo的classname为源程序的application类名,以便后续调用makeApplication()创建源程序的application
        applicationInfo.className = appClassName;
        appinfoInAppBindData.className = appClassName;
        log("Source Application name: " + appClassName);
 
 
        //6.反射调用makeApplication方法创建源程序的application
        Application application = (Application) Reflection.invokeMethod("android.app.LoadedApk","makeApplication",infoObj,new Class[]{boolean.class, Instrumentation.class},new Object[]{false,null}); //使用源程序中的application
        //Application app = (Application)ReflectionMethods.invokeMethod("android.app.LoadedApk","makeApplication",infoObj,new Class[]{boolean.class, Instrumentation.class},new Object[]{true,null}); //使用自定义的application 强制为系统默认
        log("Create source Application succeed: "+application);
 
        //7.重置ActivityThread.mInitialApplication为新的Application
        Reflection.setField("android.app.ActivityThread","mInitialApplication",sCurrentActivityThreadObj,application);
        log("Reset ActivityThread.mInitialApplication by new Application succeed!");
 
        //8.ContentProvider会持有代理的Application,需要特殊处理一下
        ArrayMap mProviderMap = (ArrayMap) Reflection.getField("android.app.ActivityThread",sCurrentActivityThreadObj,"mProviderMap");
        log("ActivityThread.mProviderMap: " + mProviderMap);
 
        //获取所有provider,装进迭代器中遍历
        Iterator iterator = mProviderMap.values().iterator();
        while(iterator.hasNext()){
            Object providerClientRecord = iterator.next();
            //获取ProviderClientRecord.mLocalProvider,可能为空
            Object mLocalProvider = Reflection.getField("android.app.ActivityThread$ProviderClientRecord",providerClientRecord,"mLocalProvider") ;
            if(mLocalProvider != null){
                log("ProviderClientRecord.mLocalProvider: " + mLocalProvider);
                //获取ContentProvider中的mContext字段,设置为新的Application
                Reflection.setField("android.content.ContentProvider","mContext",mLocalProvider,application);
            }
        }
 
        log( "Run Application.onCreate" );
        application.onCreate(); //源程序,启动!
        return true;
    }
    @Override
    public void onCreate() {
        super.onCreate();
        log("ProxyApplication.onCreate() is running!");
        if(replaceApplication())
            log("Replace application succeed!");
    }
}
package com.example.androidshell;
 
import android.app.Application;
import android.app.Instrumentation;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.util.ArrayMap;
import android.util.Log;
 
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
 
import dalvik.system.DexClassLoader;
 
public class FirstProxyApplication extends  Application {
     private static final String TAG="glass";
     private String apkPath;
     private String dexPath;
     private String libPath;
     public void log(String message){Log.d(TAG,message);}
    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);
        log("FirstProxyApplication.attachBaseContext() running!");
        try {
            //1.创建私有目录,保存dex,lib和源apk 具体路径为data/user/0/<package_name>/app_tmp_dex
            File dex = getDir("tmp_dex", MODE_PRIVATE);
            File lib = getDir("tmp_lib", MODE_PRIVATE);
            dexPath = dex.getAbsolutePath();
            libPath = lib.getAbsolutePath();
            apkPath = dex.getAbsolutePath() + File.separator + "Source.apk";
            log("dexPath: " + dexPath);
            log("libPath: " + libPath);
            log("apkPath: " + apkPath);
 
            // 根据文件路径创建File对象
            File apkFile = new File(apkPath);
            // 只有首次运行时需要创建相关文件
            if (!apkFile.exists()) {
                // 根据路径创建文件
                apkFile.createNewFile();
                //读取Classes.dex文件
                byte[] shellDexData = readDexFromApk();
                //从中分离出源apk文件
                extractSrcApkFromShellDex(shellDexData);
            }
            //配置加载源程序的动态环境,即替换mClassLoader
            replaceClassLoader();
        } catch (Exception e) {
            Log.getStackTraceString(e);
        }
    }
    // 从当前程序的apk读取dex文件并存储为字节数组
    private byte[] readDexFromApk() throws IOException {
        //1.获取当前应用程序的源码路径(apk),一般是data/app目录下,该目录用于存放用户安装的软件
        String sourceDir = this.getApplicationInfo().sourceDir;
        log("this.getApplicationInfo().sourceDir: " +sourceDir);
 
        //2.创建相关输入流
        FileInputStream fileInputStream = new FileInputStream(sourceDir);
        BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream);
        ZipInputStream zipInputStream = new ZipInputStream(bufferedInputStream); //用于解析apk文件
 
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); //用于存放dex文件
 
        //3.遍历apk的所有文件并提取dex文件
        ZipEntry zipEntry;
        while((zipEntry = zipInputStream.getNextEntry()) != null){ //存在下一个文件
            // 将classes.dex文件存储到bytearray中 壳dex和源apk合并后只保留一个dex便于处理
            if (zipEntry.getName().equals("classes.dex")){
                byte[] bytes = new byte[1024];
                int num;
                while((num = zipInputStream.read(bytes))!=-1){      //每次读取1024byte,返回读取到的byte数
                    byteArrayOutputStream.write(bytes,0, num); //存放到开辟的byteArrayOutputStream中
                }
            }
            zipInputStream.closeEntry(); //关闭当前文件
        }
        zipInputStream.close();
 
        log("Read dex from apk succeed!");
        return byteArrayOutputStream.toByteArray(); //将读取到的dex文件以字节数组形式返回
    }
 
    // 从壳dex文件中提取源apk并解析
    private void extractSrcApkFromShellDex(byte[] shellDexData) throws IOException {
        int shellDexLen = shellDexData.length;
        //开始解析dex文件
        //1.读取源apk的大小
        byte[] srcApkSizeBytes = new byte[4];
        System.arraycopy(shellDexData, shellDexLen - 4, srcApkSizeBytes,0,4);
        int srcApkSize =ByteBuffer.wrap(srcApkSizeBytes).order(ByteOrder.LITTLE_ENDIAN).getInt();//转成bytebuffer,方便4 bytes转int 将bytes转成int,加壳时,长度按小端存储
 
        //2.读取源apk
        byte[] sourceApkData = new byte[srcApkSize];
        System.arraycopy(shellDexData, shellDexLen - srcApkSize - 4, sourceApkData, 0, srcApkSize);//注意减4
 
        //3.解密源apk
        sourceApkData = decrypt(sourceApkData);
        //写入新建的apk文件中
        File apkfile = new File(apkPath);
        try {
            FileOutputStream apkfileOutputStream = new FileOutputStream(apkfile);
            apkfileOutputStream.write(sourceApkData);
            apkfileOutputStream.close();
        }catch (IOException e){
            throw new IOException(e);
        }
 
        //分析源apk,取出so文件放入libPath目录中
        FileInputStream fileInputStream = new FileInputStream(apkfile);
        BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream);
        ZipInputStream zipInputStream = new ZipInputStream(bufferedInputStream);
        ZipEntry nextEntry;
        while ((nextEntry=zipInputStream.getNextEntry())!=null){
            String name = nextEntry.getName();
            if (name.startsWith("lib/") && name.endsWith(".so")){
                //获取文件名并创建相应文件
                String[] nameSplit = name.split("/");
                String soFileStorePath = libPath + File.separator + nameSplit[nameSplit.length - 1];
                File storeFile = new File(soFileStorePath);
                storeFile.createNewFile();
 
                //读数据到相应so文件中
                FileOutputStream fileOutputStream = new FileOutputStream(storeFile);
                byte[] bytes = new byte[1024];
                int num;
                while((num = zipInputStream.read(bytes))!=-1){
                    fileOutputStream.write(bytes,0,num);
                }
                fileOutputStream.flush();
                fileOutputStream.close();
            }
            zipInputStream.closeEntry();
        }
        zipInputStream.close();
    }
 
    // 解密函数
    private byte[] decrypt(byte[] data) {
        for (int i = 0; i < data.length; i++){
            data[i] ^= (byte) 0xff;
        }
        return data;
    }
 
    // 替换壳App的ClassLoader为源App的ClassLoader
    private void replaceClassLoader() {
        //1.获取当前的classloader
        ClassLoader classLoader = this.getClassLoader();
        log("Current ClassLoader: " + classLoader.toString());
        log("Parent ClassLoader: " + classLoader.getParent().toString());
 
        //2.反射获取ActivityThread
        Object sCurrentActivityThreadObj = Reflection.getStaticField("android.app.ActivityThread","sCurrentActivityThread");
        log("ActivityThread.sCurrentActivity: " + sCurrentActivityThreadObj.toString());
 
        //3.反射获取LoadedApk
        //获取当前ActivityThread实例的mPackages字段 类型为ArrayMap<String, WeakReference<LoadedApk>>, 里面存放了当前应用的LoadedApk对象
        ArrayMap mPackagesObj = (ArrayMap) Reflection.getField("android.app.ActivityThread",sCurrentActivityThreadObj,"mPackages");
        log( "mPackagesObj: " + mPackagesObj.toString());
 
        //获取mPackages中的当前应用包名
        String currentPackageName = this.getPackageName();
        log("currentPackageName: " + currentPackageName);
 
        // 获取loadedApk实例也有好几种,mInitialApplication mAllApplications mPackages
        // 通过包名获取当前应用的loadedApk实例
        WeakReference weakReference = (WeakReference) mPackagesObj.get(currentPackageName);
        Object loadedApkObj = weakReference.get();
        log( "LoadedApk: " + loadedApkObj.toString());
 
        //4.替换ClassLoader
        DexClassLoader dexClassLoader = new DexClassLoader(apkPath,dexPath,libPath, classLoader.getParent()); //动态加载源程序的dex文件,以当前classloader的父加载器作为parent
        Reflection.setField("android.app.LoadedApk","mClassLoader",loadedApkObj,dexClassLoader); //替换当前loadedApk实例中的mClassLoader字段
        log("New DexClassLoader: " + dexClassLoader);
    }
 
    //替换壳程序LoadedApk的Application为源程序Application,并调用其onCreate方法
    public boolean replaceApplication(){
        // Application实例存在于: LoadedApk.mApplication
        // 以及ActivityThread的mInitialApplication和mAllApplications和mBoundApplication
 
        //判断源程序是否使用自定义Application 若使用则需要进行替换,若未使用则直接返回,使用壳的默认Application即可
 
        String appClassName = null; //源程序的Application类名
        try {
            //获取AndroidManifest.xml 文件中的 <meta-data> 元素
            ApplicationInfo applicationInfo = getPackageManager().getApplicationInfo(this.getPackageName(), PackageManager.GET_META_DATA);
            Bundle metaData = applicationInfo.metaData;
            //获取xml文件声明的Application类
            if (metaData != null && metaData.containsKey("APPLICATION_CLASS_NAME")){
                appClassName = metaData.getString("APPLICATION_CLASS_NAME");
            } else {
                log("源程序中没有自定义Application");
                return false; //如果不存在直接返回,使用壳的application即可
            }
        } catch (PackageManager.NameNotFoundException e) {
            log(Log.getStackTraceString(e));
        }
 
        //源程序存在自定义application类,开始替换
        log("Try to replace Application");
 
        //1.反射获取ActivityThread实例
        Object sCurrentActivityThreadObj = Reflection.getStaticField("android.app.ActivityThread","sCurrentActivityThread");
        log("ActivityThread: " + sCurrentActivityThreadObj.toString());
 
        //2.获取并设置LoadedApk
        //获取mBoundApplication (AppBindData对象)
        Object mBoundApplicationObj = Reflection.getField("android.app.ActivityThread",sCurrentActivityThreadObj,"mBoundApplication") ;
        log("mBoundApplication: "+mBoundApplicationObj.toString());
        //获取mBoundApplication.info (即LoadedApk)
        Object infoObj = Reflection.getField("android.app.ActivityThread$AppBindData",mBoundApplicationObj,"info");
        log( "LoadedApk: " + infoObj.toString());
        //把LoadedApk的mApplication设置为null,这样后续才能调用makeApplication() 否则由于已存在Application,无法进行替换
        Reflection.setField("android.app.LoadedApk","mApplication",infoObj,null);
 
        //3.获取ActivityThread.mInitialApplication 即拿到旧的Application(对于要加载的Application来讲)
        Object mInitialApplicationObj = Reflection.getField("android.app.ActivityThread",sCurrentActivityThreadObj,"mInitialApplication");
        log("mInitialApplicationObj: " + mInitialApplicationObj.toString());
 
        //4.获取ActivityThread.mAllApplications并删除旧的application
        ArrayList<Application> mAllApplicationsObj = (ArrayList<Application>) Reflection.getField("android.app.ActivityThread",sCurrentActivityThreadObj,"mAllApplications");
        mAllApplicationsObj.remove(mInitialApplicationObj);
        log("mInitialApplication 从 mAllApplications 中移除成功");
 
        //5.重置相关类的Application类名 便于后续创建Application
        //获取LoadedApk.mApplicationInfo
        ApplicationInfo applicationInfo = (ApplicationInfo) Reflection.getField("android.app.LoadedApk",infoObj,"mApplicationInfo");
        log( "LoadedApk.mApplicationInfo: " + applicationInfo.toString());
        //获取mBoundApplication.appInfo
        ApplicationInfo appinfoInAppBindData = (ApplicationInfo) Reflection.getField("android.app.ActivityThread$AppBindData",mBoundApplicationObj,"appInfo");
        log("ActivityThread.mBoundApplication.appInfo: " + appinfoInAppBindData.toString());
        //此处通过引用修改值,虽然后续没有使用,但是实际上是修改其指向的LoadedApk相关字段的值
        //设置两个appinfo的classname为源程序的application类名,以便后续调用makeApplication()创建源程序的application
        applicationInfo.className = appClassName;
        appinfoInAppBindData.className = appClassName;
        log("Source Application name: " + appClassName);
 
 
        //6.反射调用makeApplication方法创建源程序的application
        Application application = (Application) Reflection.invokeMethod("android.app.LoadedApk","makeApplication",infoObj,new Class[]{boolean.class, Instrumentation.class},new Object[]{false,null}); //使用源程序中的application
        //Application app = (Application)ReflectionMethods.invokeMethod("android.app.LoadedApk","makeApplication",infoObj,new Class[]{boolean.class, Instrumentation.class},new Object[]{true,null}); //使用自定义的application 强制为系统默认
        log("Create source Application succeed: "+application);
 
        //7.重置ActivityThread.mInitialApplication为新的Application
        Reflection.setField("android.app.ActivityThread","mInitialApplication",sCurrentActivityThreadObj,application);
        log("Reset ActivityThread.mInitialApplication by new Application succeed!");
 
        //8.ContentProvider会持有代理的Application,需要特殊处理一下
        ArrayMap mProviderMap = (ArrayMap) Reflection.getField("android.app.ActivityThread",sCurrentActivityThreadObj,"mProviderMap");
        log("ActivityThread.mProviderMap: " + mProviderMap);
 
        //获取所有provider,装进迭代器中遍历
        Iterator iterator = mProviderMap.values().iterator();
        while(iterator.hasNext()){
            Object providerClientRecord = iterator.next();
            //获取ProviderClientRecord.mLocalProvider,可能为空
            Object mLocalProvider = Reflection.getField("android.app.ActivityThread$ProviderClientRecord",providerClientRecord,"mLocalProvider") ;
            if(mLocalProvider != null){
                log("ProviderClientRecord.mLocalProvider: " + mLocalProvider);
                //获取ContentProvider中的mContext字段,设置为新的Application
                Reflection.setField("android.content.ContentProvider","mContext",mLocalProvider,application);
            }
        }
 
        log( "Run Application.onCreate" );
        application.onCreate(); //源程序,启动!
        return true;
    }
    @Override
    public void onCreate() {
        super.onCreate();
        log("ProxyApplication.onCreate() is running!");
        if(replaceApplication())
            log("Replace application succeed!");
    }
}
package com.example.androidshell;
 
import android.util.Log;
 
import java.lang.reflect.Field;
import java.lang.reflect.Method;
 
public class Reflection {
    private static final String TAG="glass";
    public static Object invokeStaticMethod(String class_name,String method_name,Class<?>[] parameterTypes,Object[] parameterValues){
        try {
            Class<?> clazz = Class.forName(class_name); //反射获取Class类对象
            Method method = clazz.getMethod(method_name,parameterTypes);//反射获取方法
            method.setAccessible(true);//突破权限访问控制
            return method.invoke(null,parameterValues);//反射调用,静态方法无需指定所属实例,直接传参即可
        }catch (Exception e){
            Log.d(TAG, e.toString());
            return null;
        }
    }
 
    public static Object invokeMethod(String class_name,String method_name,Object obj,Class<?>[] parameterTypes,Object[] parameterValues)
    {
        try {
            Class<?> clazz = Class.forName(class_name);
            Method method = clazz.getMethod(method_name,parameterTypes);
            method.setAccessible(true);//突破权限访问控制
            return method.invoke(obj,parameterValues);// 反射调用,动态方法需要指定所属实例
        }catch (Exception e)
        {
            Log.d(TAG, e.toString());
            return null;
        }
    }
 
    public static Object getField(String class_name,Object obj,String field_name)
    {
        try {
            Class<?> clazz = Class.forName(class_name);
            Field field = clazz.getDeclaredField(field_name);
            field.setAccessible(true);
            return field.get(obj);  //获取实例字段,需要指定实例对象
        }catch(Exception e)
        {
            Log.d(TAG, e.toString());
            return null;
        }
    }
 
    public static Object getStaticField(String class_name,String field_name)
    {
        try {
            Class<?> clazz = Class.forName(class_name);
            Field field = clazz.getDeclaredField(field_name);
            field.setAccessible(true);
            return field.get(null);
        }catch (Exception e)
        {
            Log.d(TAG, e.toString());
            return null;
        }
    }
 
    public static void setField(String class_name,String field_name,Object obj,Object value)
    {
        try {
            Class<?> clazz = Class.forName(class_name);
            Field field = clazz.getDeclaredField(field_name);
            field.setAccessible(true);
            field.set(obj,value);
        }catch (Exception e)
        {
            Log.d(TAG, e.toString());
        }
    }
 
    public static void setStaticField(String class_name,String field_name,Object value)
    {
        try {
            Class<?> clazz = Class.forName(class_name);
            Field field = clazz.getDeclaredField(field_name);
            field.setAccessible(true);
            field.set(null,value);
        }catch (Exception e){
            Log.d(TAG, e.toString());
        }
    }
}
package com.example.androidshell;
 
import android.util.Log;
 
import java.lang.reflect.Field;
import java.lang.reflect.Method;
 
public class Reflection {
    private static final String TAG="glass";
    public static Object invokeStaticMethod(String class_name,String method_name,Class<?>[] parameterTypes,Object[] parameterValues){
        try {
            Class<?> clazz = Class.forName(class_name); //反射获取Class类对象
            Method method = clazz.getMethod(method_name,parameterTypes);//反射获取方法
            method.setAccessible(true);//突破权限访问控制
            return method.invoke(null,parameterValues);//反射调用,静态方法无需指定所属实例,直接传参即可
        }catch (Exception e){
            Log.d(TAG, e.toString());
            return null;
        }
    }
 
    public static Object invokeMethod(String class_name,String method_name,Object obj,Class<?>[] parameterTypes,Object[] parameterValues)
    {
        try {
            Class<?> clazz = Class.forName(class_name);
            Method method = clazz.getMethod(method_name,parameterTypes);
            method.setAccessible(true);//突破权限访问控制
            return method.invoke(obj,parameterValues);// 反射调用,动态方法需要指定所属实例
        }catch (Exception e)
        {
            Log.d(TAG, e.toString());
            return null;
        }
    }
 
    public static Object getField(String class_name,Object obj,String field_name)
    {
        try {
            Class<?> clazz = Class.forName(class_name);
            Field field = clazz.getDeclaredField(field_name);
            field.setAccessible(true);
            return field.get(obj);  //获取实例字段,需要指定实例对象
        }catch(Exception e)
        {
            Log.d(TAG, e.toString());
            return null;
        }
    }
 
    public static Object getStaticField(String class_name,String field_name)
    {
        try {
            Class<?> clazz = Class.forName(class_name);
            Field field = clazz.getDeclaredField(field_name);
            field.setAccessible(true);
            return field.get(null);
        }catch (Exception e)
        {
            Log.d(TAG, e.toString());
            return null;
        }
    }
 
    public static void setField(String class_name,String field_name,Object obj,Object value)
    {
        try {
            Class<?> clazz = Class.forName(class_name);
            Field field = clazz.getDeclaredField(field_name);
            field.setAccessible(true);
            field.set(obj,value);
        }catch (Exception e)
        {
            Log.d(TAG, e.toString());
        }
    }
 
    public static void setStaticField(String class_name,String field_name,Object value)
    {
        try {
            Class<?> clazz = Class.forName(class_name);
            Field field = clazz.getDeclaredField(field_name);
            field.setAccessible(true);
            field.set(null,value);
        }catch (Exception e){
            Log.d(TAG, e.toString());
        }
    }
}
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="6daK9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8Y4y4U0K9r3g2E0j5i4y4Q4x3X3g2S2L8X3c8J5L8$3W2V1i4K6u0W2j5$3!0E0i4K6u0r3j5i4m8C8i4K6u0r3M7X3g2K6i4K6u0r3j5h3&6V1M7X3!0A6k6l9`.`."
    xmlns:tools="d34K9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8Y4y4U0K9r3g2E0j5i4y4Q4x3X3g2S2L8X3c8J5L8$3W2V1i4K6u0W2j5$3!0E0i4K6u0r3N6r3!0G2L8s2x3`.">
 
    <application
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.AndroidShell"
        tools:targetApi="29"
        android:name="com.example.androidshell.FirstProxyApplication"
        >
    </application>
</manifest>
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="6daK9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8Y4y4U0K9r3g2E0j5i4y4Q4x3X3g2S2L8X3c8J5L8$3W2V1i4K6u0W2j5$3!0E0i4K6u0r3j5i4m8C8i4K6u0r3M7X3g2K6i4K6u0r3j5h3&6V1M7X3!0A6k6l9`.`."
    xmlns:tools="d34K9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8Y4y4U0K9r3g2E0j5i4y4Q4x3X3g2S2L8X3c8J5L8$3W2V1i4K6u0W2j5$3!0E0i4K6u0r3N6r3!0G2L8s2x3`.">
 
    <application
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.AndroidShell"
        tools:targetApi="29"
        android:name="com.example.androidshell.FirstProxyApplication"
        >
    </application>
</manifest>
shutil.copytree(paths.srcApkTempDir,paths.newApkTempDir,ignore=shutil.ignore_patterns('AndroidManifest.xml','classes*.dex'))
shutil.copytree(paths.srcApkTempDir,paths.newApkTempDir,ignore=shutil.ignore_patterns('AndroidManifest.xml','classes*.dex'))
from zlib import adler32
from hashlib import sha1
from binascii import unhexlify
from lxml import etree
import subprocess
import shutil
from pathlib import Path
import argparse
# 封装path类,保存全局用到的路径
class Paths:
    def __init__(self, srcApk:Path, shellApk:Path, outputApk:Path):
        # Apk file paths
        self.srcApkPath= srcApk.resolve() # 解析为绝对路径
        self.shellApkPath= shellApk.resolve()
        self.newApkPath= outputApk.resolve()
 
        # Temp directories default python file path
        self.tmpdir= Path(__file__).parent/'temp'
        self.srcApkTempDir=  self.tmpdir/ 'srcApkTemp'
        self.shellApkTempDir= self.tmpdir/'shellApkTemp'
        self.newApkTempDir= self.tmpdir/ 'newApkTemp'
 
 
# Apktool类 提供解包,打包,签名功能
class Apktool:
    def __init__(self):
        self.apktool_path= Path(__file__).parent/'tools/apktool/apktool.bat'
        self.signer_path=Path(__file__).parent/'tools/uber-apk-signer-1.3.0.jar'
 
    def signApk(self,unsignedApkPath:Path):
        self.runCommand(['java','-jar', self.signer_path, '--apk',unsignedApkPath])
 
    # 使用apktool解包apk 只解包资源不解包dex
    def extractApk(self,apkPath:Path, outputDir:Path):
        self.runCommand([self.apktool_path, '-s', 'd' , apkPath, '-o', outputDir])
 
    # 重打包apk
    def repackApk(self,inputDir:Path, outApk:Path):
        self.runCommand([self.apktool_path, 'b' , inputDir, '-o', outApk])
 
    def runCommand(self,args):
        subprocess.run(args,stdout=subprocess.DEVNULL) #仅调用工具,不需要输出,重定向stdout到os.devnull
        # 参数列表 捕获输出 输出转为字符串
        #print(subprocess.run(args,  capture_output=True,text=True).stdout)
 
# AndroidManifest.xml的editor 用于获取和修改标签属性,以及添加标签
class ManifestEditor:
    def __init__(self, xml_content: bytes):
        self.ns = {'android': '299K9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8Y4y4U0K9r3g2E0j5i4y4Q4x3X3g2S2L8X3c8J5L8$3W2V1i4K6u0W2j5$3!0E0i4K6u0r3j5i4m8C8i4K6u0r3M7X3g2K6i4K6u0r3j5h3&6V1M7X3!0A6k6q4)9J5y4H3`.`.}
        self.tree = etree.fromstring(xml_content)
 
    # 获取指定标签的android属性值 examples: get_attr('application', 'name') get_attr('activity', 'name')
    def getTagAttribute(self, tag_name: str, attr_name: str):
        if tag_name == 'manifest'# 根标签特殊处理
            elem = self.tree
            if elem is not None:
                return elem.get(f'{attr_name}'# 寻找标签的属性
        else:
            elem = self.tree.find(f'.//{tag_name}', namespaces=self.ns)
            if elem is not None:
                return elem.get(f'{{{self.ns["android"]}}}{attr_name}'# 根标签之外的属性位于android命名空间下
        return None
 
    # 设置指定标签的属性值 example:set_attr('application','name',"com.example.ProxyApplication")
    def setTagAttribute(self, tag_name: str, attr_name: str, new_value: str):
        if tag_name == 'manifest'# 根标签特殊处理
            elem = self.tree
            if elem is not None:
                return elem.set(f'{attr_name}', new_value)  # 设置属性值
        else:
            elem = self.tree.find(f'.//{tag_name}', namespaces=self.ns)
            if elem is not None:
                elem.set(f'{{{self.ns["android"]}}}{attr_name}', new_value)
                return True
        return False
 
    # 在指定父标签下添加新子标签 example: add_tag('application',"meta-data",{'name': 'android.permission.CAMERA','value':'hello'})
    def addTagWithAttributes(self, parent_tag: str, new_tag: str, attrs: dict):
        if parent_tag == 'manifest':
            parent = self.tree
            if parent is not None:
                new_elem = etree.SubElement(parent, new_tag)
                for k, v in attrs.items():  # 支持一次给添加的标签设置多个属性
                    new_elem.set(f'{k}', v)
                return True
        else:
            parent = self.tree.find(f'.//{parent_tag}', namespaces=self.ns)
            if parent is not None:
                new_elem = etree.SubElement(parent, new_tag)
                for k, v in attrs.items():
                    new_elem.set(f'{{{self.ns["android"]}}}{k}', v)
                return True
        return False
 
    # 不以壳manifest为基准操作则用不到该函数,以源apk的manifest为基准自带,无需额外设置
    def getMainActivity(self):
        activities = self.tree.findall('.//activity', namespaces=self.ns)
        for activity in activities:
            intent_filters = activity.findall('.//intent-filter', namespaces=self.ns)
            for intent_filter in intent_filters:
                action = intent_filter.find('.//action[@android:name="android.intent.action.MAIN"]', namespaces=self.ns)
                category = intent_filter.find('.//category[@android:name="android.intent.category.LAUNCHER"]',
                                              namespaces=self.ns)
                if action is not None and category is not None:
                    return activity.get(f'{{{self.ns["android"]}}}name')
        return None
 
    def getApplication(self):
        return self.getTagAttribute('application', 'name')
 
    def setApplication(self, application: str):
        self.setTagAttribute('application', 'name', application)
 
    def addMetaData(self, name: str, value: str):
        self.addTagWithAttributes('application', 'meta-data', {'name': name, 'value': value})
 
    def getManifestData(self):
        """返回XML字符串"""
        return etree.tostring(self.tree, pretty_print=True, encoding='utf-8', xml_declaration=True).decode()
 
    def getEtractNativeLibs(self):
        """返回是否释放so文件"""
        return self.getTagAttribute('application', 'extractNativeLibs')
     
    def resetExtractNativeLibs(self):
        """重置etractNativeLibs属性为true"""
        self.setTagAttribute('application', 'extractNativeLibs', 'true')
 
 
# 合并壳dex和源apk的dex
def combineShellAndSourceDexs(shellApkTempDir:Path,srcApkTempDir:Path,newApkTempDir:Path):
     
    def fixCheckSum(dexBytesArray):
        # dexfile[8:12]
        # 小端存储
        value = adler32(bytes(dexBytesArray[12:]))
        valueArray = bytearray(value.to_bytes(4, 'little'))
        for i in range(len(valueArray)):
            dexBytesArray[8 + i] = valueArray[i]
 
    def fixSignature(dexBytesArray):
        # dexfile[12:32]
        sha_1 = sha1()
        sha_1.update(bytes(dexBytesArray[32:]))
        value = sha_1.hexdigest()
        valueArray = bytearray(unhexlify(value))
        for i in range(len(valueArray)):
            dexBytesArray[12 + i] = valueArray[i]
 
    def fixFileSize(dexBytesArray, fileSize):
        # dexfile[32:36]
        # 小端存储
        fileSizeArray = bytearray(fileSize.to_bytes(4, "little"))
        for i in range(len(fileSizeArray)):
            dexBytesArray[32 + i] = fileSizeArray[i]
 
    def encrypto(file):
        for i in range(len(file)):
            file[i] ^= 0xff
        return file
     
    def readAndCombineDexs(unpackedApkDir:Path):
        # 读取解包后的apk的所有dex文件,并合并为一个dex文件
        combinedDex = bytearray()
         # glob方法返回包含所有匹配文件的生成器
        for dex in unpackedApkDir.glob('classes*.dex'):
            print('Source Apk Dex file:', dex)
            with open(dex, 'rb') as f:
                data = bytearray(f.read())
                combinedDex+=bytearray(len(data).to_bytes(4, 'little'))  # dex文件的长度,小端序
                combinedDex+=data # dex文件内容
        return combinedDex
     
    # 获取shelldex
    with open(shellApkTempDir/'classes.dex', 'rb') as f:
        shellDexArray=bytearray(f.read())
 
    # 获取源apk的dex文件
    srcDexArray = readAndCombineDexs(srcApkTempDir)
    # 新的dex文件长度
    newDexLen = len(srcDexArray) + len(shellDexArray) + 4
    # 加密源文件
    encSrcDexArray = encrypto(srcDexArray)
    # 新的dex文件内容 = 壳dex + 加密的源dex + 四字节标识加密后源dex大小长度
    newDexArray = shellDexArray + encSrcDexArray  + bytearray(len(encSrcDexArray).to_bytes(4, 'little'))
 
    # 修改filesize
    fixFileSize(newDexArray, newDexLen)
    # 修改signature
    fixSignature(newDexArray)
    # 修改checksum
    fixCheckSum(newDexArray)
    # 导出文件
    with open(newApkTempDir/'classes.dex', 'wb') as f:
        f.write(newDexArray)
 
# 提取源apk的Manifest文件,修改application为壳application(可能添加meta-data标签),输出新的Manifest文件
def handleManifest( srcApkTempDir:Path,shellApkTempDir:Path,newApkTempDir:Path):
    # 从源apk提取AndroidManifest.xml
    with open(srcApkTempDir/'AndroidManifest.xml', 'r') as f:
        srcManifestEditor=ManifestEditor(f.read().encode())
    srcApplication=srcManifestEditor.getApplication()
    srcExtractNativeLibs=srcManifestEditor.getEtractNativeLibs()
    print('SourceApplication:',srcApplication)
    print('SourceExtractNativeLibs:',srcExtractNativeLibs)
     
    # 从壳apk提取AndroidManifest.xml
    with open(shellApkTempDir/'AndroidManifest.xml', 'r') as f:
        shellManifestEditor=ManifestEditor(f.read().encode())
    print('ShellApplication:',shellManifestEditor.getApplication())
 
    # 修改源AndroidManifest.xml的application为壳的代理application
    srcManifestEditor.setApplication(shellManifestEditor.getApplication())
 
    # 写入meta-data标签 保存源apk的原始application
    if srcApplication != None:
        print('Source application:',srcApplication)
        srcManifestEditor.addMetaData('APPLICATION_CLASS_NAME',srcApplication)
 
    # 如果源apk的manifest中默认设置etractNativeLibs=false,则重置为true,确保释放lib文件
    if srcExtractNativeLibs=='false':
        srcManifestEditor.resetExtractNativeLibs()
 
    # 输出新的AndroidManifest.xml
    with open(newApkTempDir/'AndroidManifest.xml', 'w') as f:
        f.write(srcManifestEditor.getManifestData())
 
def start(paths:Paths):
    apktool=Apktool()
    # 1.分别解包源文件和壳文件到临时目录
    print('Extracting source and shell apk...')
    apktool.extractApk(paths.srcApkPath,paths.srcApkTempDir)
    print('Extract source apk success!')
 
    print('Extracting shell apk...')
    apktool.extractApk(paths.shellApkPath,paths.shellApkTempDir)
    print('Extract shell apk success!')
 
    # 2.复制源apk所有文件到新apk临时目录中,忽略源dex和manifest文件
    print('Copying source apk files to new apk temp dir...')
    shutil.copytree(paths.srcApkTempDir,paths.newApkTempDir,ignore=shutil.ignore_patterns('AndroidManifest.xml','classes*.dex'))
    print('Copy source apk files success!')
 
    # 3.处理AndroidManifest.xml
    print('Handling AndroidManifest.xml...')
    handleManifest(paths.srcApkTempDir,paths.shellApkTempDir,paths.newApkTempDir)
    print('Handle AndroidManifest.xml success!')
 
    # 4.合并壳dex和源apk并导出文件
    print('Combining shell dex and source dexs...')
    combineShellAndSourceDexs(paths.shellApkTempDir,paths.srcApkTempDir,paths.newApkTempDir)
    print('Combine shell dex and source dexs success!')
 
    # 5.重打包apk
    print('Repacking apk...')
    apktool.repackApk(paths.newApkTempDir,paths.newApkPath)
    print('Repack apk success!')
 
    # 6.签名apk
    print('Signing apk...')
    apktool.signApk(paths.newApkPath)
    print('Resign apk success!')
 
    # 7.删除临时目录
    print('Deleting temp directories...')
    shutil.rmtree(paths.tmpdir) # 删除临时目录
    print('Delete temp directories success!')
     
def main():
    parser = argparse.ArgumentParser(description="Android APK Packer")
    parser.add_argument('-src','--src-apk', required=True, type=Path, help='Path to source APK file')
    parser.add_argument('-shell','--shell-apk', required=True, type=Path, help='Path to shell APK file')
    parser.add_argument('-o','-out','--output-apk',type=Path,help='Output path for packed APK (Default: ./out/<src-apk>_protected.apk)')
    args = parser.parse_args()
    if args.output_apk == None:
        args.output_apk = Path('./out')/(args.src_apk.stem+'_protected.apk') # 默认新apk名称及输出路径
    paths = Paths(args.src_apk, args.shell_apk, args.output_apk)
 
    print('Source APK:', paths.srcApkPath)
    print('Shell APK:', paths.shellApkPath)
    print('Output APK:', paths.newApkPath)
    start(paths)
 
 
if __name__=="__main__":
    main()
from zlib import adler32
from hashlib import sha1
from binascii import unhexlify
from lxml import etree
import subprocess
import shutil
from pathlib import Path
import argparse
# 封装path类,保存全局用到的路径
class Paths:
    def __init__(self, srcApk:Path, shellApk:Path, outputApk:Path):
        # Apk file paths
        self.srcApkPath= srcApk.resolve() # 解析为绝对路径
        self.shellApkPath= shellApk.resolve()
        self.newApkPath= outputApk.resolve()
 
        # Temp directories default python file path
        self.tmpdir= Path(__file__).parent/'temp'
        self.srcApkTempDir=  self.tmpdir/ 'srcApkTemp'
        self.shellApkTempDir= self.tmpdir/'shellApkTemp'
        self.newApkTempDir= self.tmpdir/ 'newApkTemp'
 
 
# Apktool类 提供解包,打包,签名功能
class Apktool:
    def __init__(self):
        self.apktool_path= Path(__file__).parent/'tools/apktool/apktool.bat'
        self.signer_path=Path(__file__).parent/'tools/uber-apk-signer-1.3.0.jar'
 
    def signApk(self,unsignedApkPath:Path):
        self.runCommand(['java','-jar', self.signer_path, '--apk',unsignedApkPath])
 
    # 使用apktool解包apk 只解包资源不解包dex
    def extractApk(self,apkPath:Path, outputDir:Path):
        self.runCommand([self.apktool_path, '-s', 'd' , apkPath, '-o', outputDir])
 
    # 重打包apk
    def repackApk(self,inputDir:Path, outApk:Path):
        self.runCommand([self.apktool_path, 'b' , inputDir, '-o', outApk])
 
    def runCommand(self,args):
        subprocess.run(args,stdout=subprocess.DEVNULL) #仅调用工具,不需要输出,重定向stdout到os.devnull
        # 参数列表 捕获输出 输出转为字符串
        #print(subprocess.run(args,  capture_output=True,text=True).stdout)
 
# AndroidManifest.xml的editor 用于获取和修改标签属性,以及添加标签
class ManifestEditor:
    def __init__(self, xml_content: bytes):
        self.ns = {'android': '299K9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8Y4y4U0K9r3g2E0j5i4y4Q4x3X3g2S2L8X3c8J5L8$3W2V1i4K6u0W2j5$3!0E0i4K6u0r3j5i4m8C8i4K6u0r3M7X3g2K6i4K6u0r3j5h3&6V1M7X3!0A6k6q4)9J5y4H3`.`.}
        self.tree = etree.fromstring(xml_content)
 
    # 获取指定标签的android属性值 examples: get_attr('application', 'name') get_attr('activity', 'name')
    def getTagAttribute(self, tag_name: str, attr_name: str):
        if tag_name == 'manifest'# 根标签特殊处理
            elem = self.tree
            if elem is not None:
                return elem.get(f'{attr_name}'# 寻找标签的属性
        else:
            elem = self.tree.find(f'.//{tag_name}', namespaces=self.ns)
            if elem is not None:
                return elem.get(f'{{{self.ns["android"]}}}{attr_name}'# 根标签之外的属性位于android命名空间下
        return None
 
    # 设置指定标签的属性值 example:set_attr('application','name',"com.example.ProxyApplication")
    def setTagAttribute(self, tag_name: str, attr_name: str, new_value: str):
        if tag_name == 'manifest'# 根标签特殊处理
            elem = self.tree
            if elem is not None:
                return elem.set(f'{attr_name}', new_value)  # 设置属性值
        else:
            elem = self.tree.find(f'.//{tag_name}', namespaces=self.ns)
            if elem is not None:
                elem.set(f'{{{self.ns["android"]}}}{attr_name}', new_value)
                return True
        return False
 
    # 在指定父标签下添加新子标签 example: add_tag('application',"meta-data",{'name': 'android.permission.CAMERA','value':'hello'})
    def addTagWithAttributes(self, parent_tag: str, new_tag: str, attrs: dict):
        if parent_tag == 'manifest':
            parent = self.tree
            if parent is not None:
                new_elem = etree.SubElement(parent, new_tag)
                for k, v in attrs.items():  # 支持一次给添加的标签设置多个属性
                    new_elem.set(f'{k}', v)
                return True
        else:
            parent = self.tree.find(f'.//{parent_tag}', namespaces=self.ns)
            if parent is not None:
                new_elem = etree.SubElement(parent, new_tag)
                for k, v in attrs.items():
                    new_elem.set(f'{{{self.ns["android"]}}}{k}', v)
                return True
        return False
 
    # 不以壳manifest为基准操作则用不到该函数,以源apk的manifest为基准自带,无需额外设置
    def getMainActivity(self):
        activities = self.tree.findall('.//activity', namespaces=self.ns)
        for activity in activities:
            intent_filters = activity.findall('.//intent-filter', namespaces=self.ns)
            for intent_filter in intent_filters:
                action = intent_filter.find('.//action[@android:name="android.intent.action.MAIN"]', namespaces=self.ns)
                category = intent_filter.find('.//category[@android:name="android.intent.category.LAUNCHER"]',
                                              namespaces=self.ns)
                if action is not None and category is not None:
                    return activity.get(f'{{{self.ns["android"]}}}name')
        return None
 
    def getApplication(self):
        return self.getTagAttribute('application', 'name')
 
    def setApplication(self, application: str):
        self.setTagAttribute('application', 'name', application)
 
    def addMetaData(self, name: str, value: str):
        self.addTagWithAttributes('application', 'meta-data', {'name': name, 'value': value})
 
    def getManifestData(self):
        """返回XML字符串"""
        return etree.tostring(self.tree, pretty_print=True, encoding='utf-8', xml_declaration=True).decode()
 
    def getEtractNativeLibs(self):
        """返回是否释放so文件"""
        return self.getTagAttribute('application', 'extractNativeLibs')
     
    def resetExtractNativeLibs(self):
        """重置etractNativeLibs属性为true"""
        self.setTagAttribute('application', 'extractNativeLibs', 'true')
 
 
# 合并壳dex和源apk的dex
def combineShellAndSourceDexs(shellApkTempDir:Path,srcApkTempDir:Path,newApkTempDir:Path):
     
    def fixCheckSum(dexBytesArray):
        # dexfile[8:12]
        # 小端存储
        value = adler32(bytes(dexBytesArray[12:]))
        valueArray = bytearray(value.to_bytes(4, 'little'))
        for i in range(len(valueArray)):
            dexBytesArray[8 + i] = valueArray[i]
 
    def fixSignature(dexBytesArray):
        # dexfile[12:32]
        sha_1 = sha1()
        sha_1.update(bytes(dexBytesArray[32:]))
        value = sha_1.hexdigest()
        valueArray = bytearray(unhexlify(value))
        for i in range(len(valueArray)):
            dexBytesArray[12 + i] = valueArray[i]
 
    def fixFileSize(dexBytesArray, fileSize):
        # dexfile[32:36]
        # 小端存储
        fileSizeArray = bytearray(fileSize.to_bytes(4, "little"))
        for i in range(len(fileSizeArray)):
            dexBytesArray[32 + i] = fileSizeArray[i]
 
    def encrypto(file):
        for i in range(len(file)):
            file[i] ^= 0xff
        return file
     
    def readAndCombineDexs(unpackedApkDir:Path):
        # 读取解包后的apk的所有dex文件,并合并为一个dex文件
        combinedDex = bytearray()
         # glob方法返回包含所有匹配文件的生成器
        for dex in unpackedApkDir.glob('classes*.dex'):
            print('Source Apk Dex file:', dex)
            with open(dex, 'rb') as f:
                data = bytearray(f.read())
                combinedDex+=bytearray(len(data).to_bytes(4, 'little'))  # dex文件的长度,小端序
                combinedDex+=data # dex文件内容
        return combinedDex
     
    # 获取shelldex
    with open(shellApkTempDir/'classes.dex', 'rb') as f:
        shellDexArray=bytearray(f.read())
 
    # 获取源apk的dex文件
    srcDexArray = readAndCombineDexs(srcApkTempDir)
    # 新的dex文件长度
    newDexLen = len(srcDexArray) + len(shellDexArray) + 4
    # 加密源文件
    encSrcDexArray = encrypto(srcDexArray)
    # 新的dex文件内容 = 壳dex + 加密的源dex + 四字节标识加密后源dex大小长度
    newDexArray = shellDexArray + encSrcDexArray  + bytearray(len(encSrcDexArray).to_bytes(4, 'little'))
 
    # 修改filesize
    fixFileSize(newDexArray, newDexLen)
    # 修改signature
    fixSignature(newDexArray)
    # 修改checksum
    fixCheckSum(newDexArray)
    # 导出文件
    with open(newApkTempDir/'classes.dex', 'wb') as f:
        f.write(newDexArray)
 
# 提取源apk的Manifest文件,修改application为壳application(可能添加meta-data标签),输出新的Manifest文件
def handleManifest( srcApkTempDir:Path,shellApkTempDir:Path,newApkTempDir:Path):
    # 从源apk提取AndroidManifest.xml
    with open(srcApkTempDir/'AndroidManifest.xml', 'r') as f:
        srcManifestEditor=ManifestEditor(f.read().encode())
    srcApplication=srcManifestEditor.getApplication()
    srcExtractNativeLibs=srcManifestEditor.getEtractNativeLibs()
    print('SourceApplication:',srcApplication)
    print('SourceExtractNativeLibs:',srcExtractNativeLibs)
     
    # 从壳apk提取AndroidManifest.xml
    with open(shellApkTempDir/'AndroidManifest.xml', 'r') as f:
        shellManifestEditor=ManifestEditor(f.read().encode())
    print('ShellApplication:',shellManifestEditor.getApplication())
 
    # 修改源AndroidManifest.xml的application为壳的代理application
    srcManifestEditor.setApplication(shellManifestEditor.getApplication())
 
    # 写入meta-data标签 保存源apk的原始application
    if srcApplication != None:
        print('Source application:',srcApplication)
        srcManifestEditor.addMetaData('APPLICATION_CLASS_NAME',srcApplication)
 
    # 如果源apk的manifest中默认设置etractNativeLibs=false,则重置为true,确保释放lib文件
    if srcExtractNativeLibs=='false':
        srcManifestEditor.resetExtractNativeLibs()
 
    # 输出新的AndroidManifest.xml
    with open(newApkTempDir/'AndroidManifest.xml', 'w') as f:
        f.write(srcManifestEditor.getManifestData())
 
def start(paths:Paths):
    apktool=Apktool()
    # 1.分别解包源文件和壳文件到临时目录
    print('Extracting source and shell apk...')
    apktool.extractApk(paths.srcApkPath,paths.srcApkTempDir)
    print('Extract source apk success!')
 
    print('Extracting shell apk...')
    apktool.extractApk(paths.shellApkPath,paths.shellApkTempDir)
    print('Extract shell apk success!')
 
    # 2.复制源apk所有文件到新apk临时目录中,忽略源dex和manifest文件
    print('Copying source apk files to new apk temp dir...')
    shutil.copytree(paths.srcApkTempDir,paths.newApkTempDir,ignore=shutil.ignore_patterns('AndroidManifest.xml','classes*.dex'))
    print('Copy source apk files success!')
 
    # 3.处理AndroidManifest.xml
    print('Handling AndroidManifest.xml...')
    handleManifest(paths.srcApkTempDir,paths.shellApkTempDir,paths.newApkTempDir)
    print('Handle AndroidManifest.xml success!')
 
    # 4.合并壳dex和源apk并导出文件
    print('Combining shell dex and source dexs...')
    combineShellAndSourceDexs(paths.shellApkTempDir,paths.srcApkTempDir,paths.newApkTempDir)
    print('Combine shell dex and source dexs success!')
 
    # 5.重打包apk
    print('Repacking apk...')
    apktool.repackApk(paths.newApkTempDir,paths.newApkPath)
    print('Repack apk success!')
 
    # 6.签名apk
    print('Signing apk...')
    apktool.signApk(paths.newApkPath)
    print('Resign apk success!')
 
    # 7.删除临时目录
    print('Deleting temp directories...')
    shutil.rmtree(paths.tmpdir) # 删除临时目录
    print('Delete temp directories success!')
     
def main():
    parser = argparse.ArgumentParser(description="Android APK Packer")
    parser.add_argument('-src','--src-apk', required=True, type=Path, help='Path to source APK file')
    parser.add_argument('-shell','--shell-apk', required=True, type=Path, help='Path to shell APK file')
    parser.add_argument('-o','-out','--output-apk',type=Path,help='Output path for packed APK (Default: ./out/<src-apk>_protected.apk)')
    args = parser.parse_args()
    if args.output_apk == None:
        args.output_apk = Path('./out')/(args.src_apk.stem+'_protected.apk') # 默认新apk名称及输出路径
    paths = Paths(args.src_apk, args.shell_apk, args.output_apk)
 
    print('Source APK:', paths.srcApkPath)
    print('Shell APK:', paths.shellApkPath)
    print('Output APK:', paths.newApkPath)
    start(paths)
 
 
if __name__=="__main__":
    main()
package com.example.androidshell;
 
import android.app.Application;
import android.app.Instrumentation;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.util.ArrayMap;
import android.util.Log;
 
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
 
import dalvik.system.InMemoryDexClassLoader;
 
public class SecondProxyApplication extends Application {
    private final String TAG="glass";
    public void log(String message){Log.d(TAG,message);}
    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);
        log("SecondProxyApplication.attachBaseContext is running!");
        try {
            byte[] shellDexData = readDexFromApk();
            log("成功从源APK中读取classes.dex");
            //从中分理出源dex文件
            ByteBuffer[] byteBuffers = extractDexFilesFromShellDex(shellDexData);
            log("成功分离出源dex集合");
            //配置加载源程序的动态环境,即替换mClassLoader
            replaceClassLoader(byteBuffers);
        } catch (Exception e) {
            log( Log.getStackTraceString(e));
        }
    }
    @Override
    public void onCreate() {
        super.onCreate();
        log("SecondProxyApplication.onCreate is running!");
        if(replaceApplication())
            log("替换Application成功");
    }
    // 从壳dex文件中提取源apk的dex并封装为ByteBuffer
    private ByteBuffer[] extractDexFilesFromShellDex(byte[] shellDexData) throws IOException {
        int shellDexlength = shellDexData.length;
        //开始解析dex文件
        byte[] sourceDexsSizeByte = new byte[4];
        //读取源dexs的大小
        System.arraycopy(shellDexData,shellDexlength - 4, sourceDexsSizeByte,0,4);
        //转成bytebuffer,方便4byte转int
        ByteBuffer wrap = ByteBuffer.wrap(sourceDexsSizeByte);
        //将byte转成int, 加壳时,长度按小端存储
        int sourceDexsSizeInt = wrap.order(ByteOrder.LITTLE_ENDIAN).getInt();
        Log.d(TAG, "源dex集合的大小: " + sourceDexsSizeInt);
        //读取源dexs
        byte[] sourceDexsData = new byte[sourceDexsSizeInt];
        System.arraycopy(shellDexData,shellDexlength - sourceDexsSizeInt - 4, sourceDexsData, 0, sourceDexsSizeInt);
        //解密源dexs
        sourceDexsData = decrypt(sourceDexsData);
 
        //更新部分
        //从源dexs中分离dex
        ArrayList<byte[]> sourceDexList = new ArrayList<>();
        int pos = 0;
        while(pos < sourceDexsSizeInt){
            //先提取四个字节,描述当前dex的大小
            //开始解析dex文件
            byte[] singleDexSizeByte = new byte[4];
            //读取源dexs的大小
            System.arraycopy(sourceDexsData, pos, singleDexSizeByte,0,4);
            //转成bytebuffer,方便4byte转int
            ByteBuffer singleDexwrap = ByteBuffer.wrap(singleDexSizeByte);
            int singleDexSizeInt = singleDexwrap.order(ByteOrder.LITTLE_ENDIAN).getInt();
            Log.d(TAG, "当前singleDex的大小: " + singleDexSizeInt);
            //读取单独dex
            byte[] singleDexData = new byte[singleDexSizeInt];
            System.arraycopy(sourceDexsData,pos + 4, singleDexData, 0, singleDexSizeInt);
            //加入到dexlist中
            sourceDexList.add(singleDexData);
            //更新pos
            pos += 4 + singleDexSizeInt;
        }
 
        //将dexlist包装成ByteBuffer
        int dexNum = sourceDexList.size();
        Log.d(TAG, "源dex的数量: " + dexNum);
        ByteBuffer[] dexBuffers = new ByteBuffer[dexNum];
        for (int i = 0; i < dexNum; i++){
            dexBuffers[i] = ByteBuffer.wrap(sourceDexList.get(i));
        }
 
        return dexBuffers;
 
    }
    // 从apk读取dex文件并返回dex对应字节数组
    // 从当前程序的apk读取dex文件并存储为字节数组
    private byte[] readDexFromApk() throws IOException {
        //1.获取当前应用程序的源码路径(apk),一般是data/app目录下,该目录用于存放用户安装的软件
        String sourceDir = this.getApplicationInfo().sourceDir;
        log("this.getApplicationInfo().sourceDir: " +sourceDir);
 
        //2.创建相关输入流
        FileInputStream fileInputStream = new FileInputStream(sourceDir);
        BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream);
        ZipInputStream zipInputStream = new ZipInputStream(bufferedInputStream); //用于解析apk文件
 
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); //用于存放dex文件
 
        //3.遍历apk的所有文件并提取dex文件
        ZipEntry zipEntry;
        while((zipEntry = zipInputStream.getNextEntry()) != null){ //存在下一个文件
            // 将classes.dex文件存储到bytearray中 壳dex和源apk合并后只保留一个dex便于处理
            if (zipEntry.getName().equals("classes.dex")){
                byte[] bytes = new byte[1024];
                int num;
                while((num = zipInputStream.read(bytes))!=-1){      //每次读取1024byte,返回读取到的byte数
                    byteArrayOutputStream.write(bytes,0, num); //存放到开辟的byteArrayOutputStream中
                }
            }
            zipInputStream.closeEntry(); //关闭当前文件
        }
        zipInputStream.close();
 
        log("Read dex from apk succeed!");
        return byteArrayOutputStream.toByteArray(); //将读取到的dex文件以字节数组形式返回
    }
    private byte[] decrypt(byte[] sourceApkdata) {
        for (int i = 0; i < sourceApkdata.length; i++){
            sourceApkdata[i] ^= (byte) 0xff;
        }
        return sourceApkdata;
 
    }
    //替换壳程序LoadedApk的Application为源程序Application,并调用其onCreate方法
    public boolean replaceApplication(){
        // Application实例存在于: LoadedApk.mApplication
        // 以及ActivityThread的mInitialApplication和mAllApplications和mBoundApplication
 
        //判断源程序是否使用自定义Application 若使用则需要进行替换,若未使用则直接返回,使用壳的默认Application即可
 
        String appClassName = null; //源程序的Application类名
        try {
            //获取AndroidManifest.xml 文件中的 <meta-data> 元素
            ApplicationInfo applicationInfo = getPackageManager().getApplicationInfo(this.getPackageName(), PackageManager.GET_META_DATA);
            Bundle metaData = applicationInfo.metaData;
            //获取xml文件声明的Application类
            if (metaData != null && metaData.containsKey("APPLICATION_CLASS_NAME")){
                appClassName = metaData.getString("APPLICATION_CLASS_NAME");
            } else {
                log("源程序中没有自定义Application");
                return false; //如果不存在直接返回,使用壳的application即可
            }
        } catch (PackageManager.NameNotFoundException e) {
            log(Log.getStackTraceString(e));
        }
 
        //源程序存在自定义application类,开始替换
        log("Try to replace Application");
 
        //1.反射获取ActivityThread实例
        Object sCurrentActivityThreadObj = Reflection.getStaticField("android.app.ActivityThread","sCurrentActivityThread");
        log("ActivityThread: " + sCurrentActivityThreadObj.toString());
 
        //2.获取并设置LoadedApk
        //获取mBoundApplication (AppBindData对象)
        Object mBoundApplicationObj = Reflection.getField("android.app.ActivityThread",sCurrentActivityThreadObj,"mBoundApplication") ;
        log("mBoundApplication: "+mBoundApplicationObj.toString());
        //获取mBoundApplication.info (即LoadedApk)
        Object infoObj = Reflection.getField("android.app.ActivityThread$AppBindData",mBoundApplicationObj,"info");
        log( "LoadedApk: " + infoObj.toString());
        //把LoadedApk的mApplication设置为null,这样后续才能调用makeApplication() 否则由于已存在Application,无法进行替换
        Reflection.setField("android.app.LoadedApk","mApplication",infoObj,null);
 
        //3.获取ActivityThread.mInitialApplication 即拿到旧的Application(对于要加载的Application来讲)
        Object mInitialApplicationObj = Reflection.getField("android.app.ActivityThread",sCurrentActivityThreadObj,"mInitialApplication");
        log("mInitialApplicationObj: " + mInitialApplicationObj.toString());
 
        //4.获取ActivityThread.mAllApplications并删除旧的application
        ArrayList<Application> mAllApplicationsObj = (ArrayList<Application>) Reflection.getField("android.app.ActivityThread",sCurrentActivityThreadObj,"mAllApplications");
        mAllApplicationsObj.remove(mInitialApplicationObj);
        log("mInitialApplication 从 mAllApplications 中移除成功");
 
        //5.重置相关类的Application类名 便于后续创建Application
        //获取LoadedApk.mApplicationInfo
        ApplicationInfo applicationInfo = (ApplicationInfo) Reflection.getField("android.app.LoadedApk",infoObj,"mApplicationInfo");
        log( "LoadedApk.mApplicationInfo: " + applicationInfo.toString());
        //获取mBoundApplication.appInfo
        ApplicationInfo appinfoInAppBindData = (ApplicationInfo) Reflection.getField("android.app.ActivityThread$AppBindData",mBoundApplicationObj,"appInfo");
        log("ActivityThread.mBoundApplication.appInfo: " + appinfoInAppBindData.toString());
        //此处通过引用修改值,虽然后续没有使用,但是实际上是修改其指向的LoadedApk相关字段的值
        //设置两个appinfo的classname为源程序的application类名,以便后续调用makeApplication()创建源程序的application
        applicationInfo.className = appClassName;
        appinfoInAppBindData.className = appClassName;
        log("Source Application name: " + appClassName);
 
 
        //6.反射调用makeApplication方法创建源程序的application
        Application application = (Application) Reflection.invokeMethod("android.app.LoadedApk","makeApplication",infoObj,new Class[]{boolean.class, Instrumentation.class},new Object[]{false,null}); //使用源程序中的application
        //Application app = (Application)ReflectionMethods.invokeMethod("android.app.LoadedApk","makeApplication",infoObj,new Class[]{boolean.class, Instrumentation.class},new Object[]{true,null}); //使用自定义的application 强制为系统默认
        log("Create source Application succeed: "+application);
 
        //7.重置ActivityThread.mInitialApplication为新的Application
        Reflection.setField("android.app.ActivityThread","mInitialApplication",sCurrentActivityThreadObj,application);
        log("Reset ActivityThread.mInitialApplication by new Application succeed!");
 
        //8.ContentProvider会持有代理的Application,需要特殊处理一下
        ArrayMap mProviderMap = (ArrayMap) Reflection.getField("android.app.ActivityThread",sCurrentActivityThreadObj,"mProviderMap");
        log("ActivityThread.mProviderMap: " + mProviderMap);
 
        //获取所有provider,装进迭代器中遍历
        Iterator iterator = mProviderMap.values().iterator();
        while(iterator.hasNext()){
            Object providerClientRecord = iterator.next();
            //获取ProviderClientRecord.mLocalProvider,可能为空
            Object mLocalProvider = Reflection.getField("android.app.ActivityThread$ProviderClientRecord",providerClientRecord,"mLocalProvider") ;
            if(mLocalProvider != null){
                log("ProviderClientRecord.mLocalProvider: " + mLocalProvider);
                //获取ContentProvider中的mContext字段,设置为新的Application
                Reflection.setField("android.content.ContentProvider","mContext",mLocalProvider,application);
            }
        }
 
        log( "Run Application.onCreate" );
        application.onCreate(); //源程序,启动!
        return true;
    }
    // 替换壳App的ClassLoader为源App的ClassLoader
    private void replaceClassLoader(ByteBuffer[] byteBuffers) throws Exception{
        //1.获取当前的classloader
        ClassLoader classLoader = this.getClassLoader();
        log("Current ClassLoader: " + classLoader.toString());
        log("Parent ClassLoader: " + classLoader.getParent().toString());
 
        //2.反射获取ActivityThread
        Object sCurrentActivityThreadObj = Reflection.getStaticField("android.app.ActivityThread","sCurrentActivityThread");
        log("ActivityThread.sCurrentActivity: " + sCurrentActivityThreadObj.toString());
 
        //3.反射获取LoadedApk
        //获取当前ActivityThread实例的mPackages字段 类型为ArrayMap<String, WeakReference<LoadedApk>>, 里面存放了当前应用的LoadedApk对象
        ArrayMap mPackagesObj = (ArrayMap) Reflection.getField("android.app.ActivityThread",sCurrentActivityThreadObj,"mPackages");
        log( "mPackagesObj: " + mPackagesObj.toString());
 
        //获取mPackages中的当前应用包名
        String currentPackageName = this.getPackageName();
        log("currentPackageName: " + currentPackageName);
 
        // 获取loadedApk实例也有好几种,mInitialApplication mAllApplications mPackages
        // 通过包名获取当前应用的loadedApk实例
        WeakReference weakReference = (WeakReference) mPackagesObj.get(currentPackageName);
        Object loadedApkObj = weakReference.get();
        log( "LoadedApk: " + loadedApkObj.toString());
 
        //动态加载源程序的dex文件
        if(Build.VERSION.SDK_INT>=29){
            Log.d(TAG,"Library path:"+this.getApplicationInfo().nativeLibraryDir);
            InMemoryDexClassLoader dexClassLoader=new InMemoryDexClassLoader(byteBuffers,this.getApplicationInfo().nativeLibraryDir,classLoader.getParent());
            Log.d(TAG, "New InMemoryDexClassLoader: " + dexClassLoader);
            //替换当前loadedApk实例中的mClassLoader字段
            Reflection.setField("android.app.LoadedApk","mClassLoader",loadedApkObj,dexClassLoader);
        }
        else{
 
            Log.d(TAG,"不支持Android 8.0以下版本");
        }
    }
}
package com.example.androidshell;
 
import android.app.Application;
import android.app.Instrumentation;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.util.ArrayMap;
import android.util.Log;
 
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
 
import dalvik.system.InMemoryDexClassLoader;
 
public class SecondProxyApplication extends Application {
    private final String TAG="glass";
    public void log(String message){Log.d(TAG,message);}
    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);
        log("SecondProxyApplication.attachBaseContext is running!");
        try {
            byte[] shellDexData = readDexFromApk();
            log("成功从源APK中读取classes.dex");
            //从中分理出源dex文件
            ByteBuffer[] byteBuffers = extractDexFilesFromShellDex(shellDexData);
            log("成功分离出源dex集合");
            //配置加载源程序的动态环境,即替换mClassLoader
            replaceClassLoader(byteBuffers);
        } catch (Exception e) {
            log( Log.getStackTraceString(e));
        }
    }
    @Override
    public void onCreate() {
        super.onCreate();
        log("SecondProxyApplication.onCreate is running!");
        if(replaceApplication())
            log("替换Application成功");
    }
    // 从壳dex文件中提取源apk的dex并封装为ByteBuffer
    private ByteBuffer[] extractDexFilesFromShellDex(byte[] shellDexData) throws IOException {
        int shellDexlength = shellDexData.length;
        //开始解析dex文件
        byte[] sourceDexsSizeByte = new byte[4];
        //读取源dexs的大小
        System.arraycopy(shellDexData,shellDexlength - 4, sourceDexsSizeByte,0,4);
        //转成bytebuffer,方便4byte转int
        ByteBuffer wrap = ByteBuffer.wrap(sourceDexsSizeByte);
        //将byte转成int, 加壳时,长度按小端存储
        int sourceDexsSizeInt = wrap.order(ByteOrder.LITTLE_ENDIAN).getInt();
        Log.d(TAG, "源dex集合的大小: " + sourceDexsSizeInt);
        //读取源dexs
        byte[] sourceDexsData = new byte[sourceDexsSizeInt];
        System.arraycopy(shellDexData,shellDexlength - sourceDexsSizeInt - 4, sourceDexsData, 0, sourceDexsSizeInt);
        //解密源dexs
        sourceDexsData = decrypt(sourceDexsData);
 
        //更新部分
        //从源dexs中分离dex
        ArrayList<byte[]> sourceDexList = new ArrayList<>();
        int pos = 0;
        while(pos < sourceDexsSizeInt){
            //先提取四个字节,描述当前dex的大小
            //开始解析dex文件
            byte[] singleDexSizeByte = new byte[4];
            //读取源dexs的大小
            System.arraycopy(sourceDexsData, pos, singleDexSizeByte,0,4);
            //转成bytebuffer,方便4byte转int
            ByteBuffer singleDexwrap = ByteBuffer.wrap(singleDexSizeByte);
            int singleDexSizeInt = singleDexwrap.order(ByteOrder.LITTLE_ENDIAN).getInt();
            Log.d(TAG, "当前singleDex的大小: " + singleDexSizeInt);
            //读取单独dex
            byte[] singleDexData = new byte[singleDexSizeInt];
            System.arraycopy(sourceDexsData,pos + 4, singleDexData, 0, singleDexSizeInt);
            //加入到dexlist中
            sourceDexList.add(singleDexData);
            //更新pos
            pos += 4 + singleDexSizeInt;
        }
 
        //将dexlist包装成ByteBuffer
        int dexNum = sourceDexList.size();
        Log.d(TAG, "源dex的数量: " + dexNum);
        ByteBuffer[] dexBuffers = new ByteBuffer[dexNum];
        for (int i = 0; i < dexNum; i++){
            dexBuffers[i] = ByteBuffer.wrap(sourceDexList.get(i));
        }
 
        return dexBuffers;
 
    }
    // 从apk读取dex文件并返回dex对应字节数组
    // 从当前程序的apk读取dex文件并存储为字节数组
    private byte[] readDexFromApk() throws IOException {
        //1.获取当前应用程序的源码路径(apk),一般是data/app目录下,该目录用于存放用户安装的软件
        String sourceDir = this.getApplicationInfo().sourceDir;
        log("this.getApplicationInfo().sourceDir: " +sourceDir);
 
        //2.创建相关输入流
        FileInputStream fileInputStream = new FileInputStream(sourceDir);
        BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream);
        ZipInputStream zipInputStream = new ZipInputStream(bufferedInputStream); //用于解析apk文件
 
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); //用于存放dex文件
 
        //3.遍历apk的所有文件并提取dex文件
        ZipEntry zipEntry;
        while((zipEntry = zipInputStream.getNextEntry()) != null){ //存在下一个文件
            // 将classes.dex文件存储到bytearray中 壳dex和源apk合并后只保留一个dex便于处理
            if (zipEntry.getName().equals("classes.dex")){
                byte[] bytes = new byte[1024];
                int num;
                while((num = zipInputStream.read(bytes))!=-1){      //每次读取1024byte,返回读取到的byte数
                    byteArrayOutputStream.write(bytes,0, num); //存放到开辟的byteArrayOutputStream中
                }
            }
            zipInputStream.closeEntry(); //关闭当前文件
        }
        zipInputStream.close();
 
        log("Read dex from apk succeed!");
        return byteArrayOutputStream.toByteArray(); //将读取到的dex文件以字节数组形式返回
    }
    private byte[] decrypt(byte[] sourceApkdata) {
        for (int i = 0; i < sourceApkdata.length; i++){
            sourceApkdata[i] ^= (byte) 0xff;
        }
        return sourceApkdata;
 
    }
    //替换壳程序LoadedApk的Application为源程序Application,并调用其onCreate方法
    public boolean replaceApplication(){
        // Application实例存在于: LoadedApk.mApplication
        // 以及ActivityThread的mInitialApplication和mAllApplications和mBoundApplication
 
        //判断源程序是否使用自定义Application 若使用则需要进行替换,若未使用则直接返回,使用壳的默认Application即可
 
        String appClassName = null; //源程序的Application类名
        try {
            //获取AndroidManifest.xml 文件中的 <meta-data> 元素
            ApplicationInfo applicationInfo = getPackageManager().getApplicationInfo(this.getPackageName(), PackageManager.GET_META_DATA);
            Bundle metaData = applicationInfo.metaData;
            //获取xml文件声明的Application类
            if (metaData != null && metaData.containsKey("APPLICATION_CLASS_NAME")){
                appClassName = metaData.getString("APPLICATION_CLASS_NAME");
            } else {
                log("源程序中没有自定义Application");
                return false; //如果不存在直接返回,使用壳的application即可
            }
        } catch (PackageManager.NameNotFoundException e) {
            log(Log.getStackTraceString(e));
        }
 
        //源程序存在自定义application类,开始替换
        log("Try to replace Application");
 
        //1.反射获取ActivityThread实例
        Object sCurrentActivityThreadObj = Reflection.getStaticField("android.app.ActivityThread","sCurrentActivityThread");
        log("ActivityThread: " + sCurrentActivityThreadObj.toString());
 
        //2.获取并设置LoadedApk
        //获取mBoundApplication (AppBindData对象)
        Object mBoundApplicationObj = Reflection.getField("android.app.ActivityThread",sCurrentActivityThreadObj,"mBoundApplication") ;
        log("mBoundApplication: "+mBoundApplicationObj.toString());
        //获取mBoundApplication.info (即LoadedApk)
        Object infoObj = Reflection.getField("android.app.ActivityThread$AppBindData",mBoundApplicationObj,"info");
        log( "LoadedApk: " + infoObj.toString());
        //把LoadedApk的mApplication设置为null,这样后续才能调用makeApplication() 否则由于已存在Application,无法进行替换
        Reflection.setField("android.app.LoadedApk","mApplication",infoObj,null);
 
        //3.获取ActivityThread.mInitialApplication 即拿到旧的Application(对于要加载的Application来讲)
        Object mInitialApplicationObj = Reflection.getField("android.app.ActivityThread",sCurrentActivityThreadObj,"mInitialApplication");
        log("mInitialApplicationObj: " + mInitialApplicationObj.toString());
 
        //4.获取ActivityThread.mAllApplications并删除旧的application
        ArrayList<Application> mAllApplicationsObj = (ArrayList<Application>) Reflection.getField("android.app.ActivityThread",sCurrentActivityThreadObj,"mAllApplications");
        mAllApplicationsObj.remove(mInitialApplicationObj);
        log("mInitialApplication 从 mAllApplications 中移除成功");
 
        //5.重置相关类的Application类名 便于后续创建Application
        //获取LoadedApk.mApplicationInfo
        ApplicationInfo applicationInfo = (ApplicationInfo) Reflection.getField("android.app.LoadedApk",infoObj,"mApplicationInfo");
        log( "LoadedApk.mApplicationInfo: " + applicationInfo.toString());
        //获取mBoundApplication.appInfo
        ApplicationInfo appinfoInAppBindData = (ApplicationInfo) Reflection.getField("android.app.ActivityThread$AppBindData",mBoundApplicationObj,"appInfo");
        log("ActivityThread.mBoundApplication.appInfo: " + appinfoInAppBindData.toString());
        //此处通过引用修改值,虽然后续没有使用,但是实际上是修改其指向的LoadedApk相关字段的值
        //设置两个appinfo的classname为源程序的application类名,以便后续调用makeApplication()创建源程序的application
        applicationInfo.className = appClassName;
        appinfoInAppBindData.className = appClassName;
        log("Source Application name: " + appClassName);
 
 
        //6.反射调用makeApplication方法创建源程序的application
        Application application = (Application) Reflection.invokeMethod("android.app.LoadedApk","makeApplication",infoObj,new Class[]{boolean.class, Instrumentation.class},new Object[]{false,null}); //使用源程序中的application
        //Application app = (Application)ReflectionMethods.invokeMethod("android.app.LoadedApk","makeApplication",infoObj,new Class[]{boolean.class, Instrumentation.class},new Object[]{true,null}); //使用自定义的application 强制为系统默认
        log("Create source Application succeed: "+application);
 
        //7.重置ActivityThread.mInitialApplication为新的Application
        Reflection.setField("android.app.ActivityThread","mInitialApplication",sCurrentActivityThreadObj,application);
        log("Reset ActivityThread.mInitialApplication by new Application succeed!");
 
        //8.ContentProvider会持有代理的Application,需要特殊处理一下
        ArrayMap mProviderMap = (ArrayMap) Reflection.getField("android.app.ActivityThread",sCurrentActivityThreadObj,"mProviderMap");
        log("ActivityThread.mProviderMap: " + mProviderMap);
 
        //获取所有provider,装进迭代器中遍历
        Iterator iterator = mProviderMap.values().iterator();
        while(iterator.hasNext()){
            Object providerClientRecord = iterator.next();
            //获取ProviderClientRecord.mLocalProvider,可能为空
            Object mLocalProvider = Reflection.getField("android.app.ActivityThread$ProviderClientRecord",providerClientRecord,"mLocalProvider") ;
            if(mLocalProvider != null){
                log("ProviderClientRecord.mLocalProvider: " + mLocalProvider);
                //获取ContentProvider中的mContext字段,设置为新的Application
                Reflection.setField("android.content.ContentProvider","mContext",mLocalProvider,application);
            }
        }
 
        log( "Run Application.onCreate" );
        application.onCreate(); //源程序,启动!
        return true;
    }
    // 替换壳App的ClassLoader为源App的ClassLoader
    private void replaceClassLoader(ByteBuffer[] byteBuffers) throws Exception{
        //1.获取当前的classloader
        ClassLoader classLoader = this.getClassLoader();
        log("Current ClassLoader: " + classLoader.toString());
        log("Parent ClassLoader: " + classLoader.getParent().toString());
 
        //2.反射获取ActivityThread
        Object sCurrentActivityThreadObj = Reflection.getStaticField("android.app.ActivityThread","sCurrentActivityThread");
        log("ActivityThread.sCurrentActivity: " + sCurrentActivityThreadObj.toString());
 
        //3.反射获取LoadedApk
        //获取当前ActivityThread实例的mPackages字段 类型为ArrayMap<String, WeakReference<LoadedApk>>, 里面存放了当前应用的LoadedApk对象
        ArrayMap mPackagesObj = (ArrayMap) Reflection.getField("android.app.ActivityThread",sCurrentActivityThreadObj,"mPackages");
        log( "mPackagesObj: " + mPackagesObj.toString());
 
        //获取mPackages中的当前应用包名
        String currentPackageName = this.getPackageName();
        log("currentPackageName: " + currentPackageName);
 
        // 获取loadedApk实例也有好几种,mInitialApplication mAllApplications mPackages
        // 通过包名获取当前应用的loadedApk实例
        WeakReference weakReference = (WeakReference) mPackagesObj.get(currentPackageName);
        Object loadedApkObj = weakReference.get();
        log( "LoadedApk: " + loadedApkObj.toString());
 
        //动态加载源程序的dex文件
        if(Build.VERSION.SDK_INT>=29){
            Log.d(TAG,"Library path:"+this.getApplicationInfo().nativeLibraryDir);
            InMemoryDexClassLoader dexClassLoader=new InMemoryDexClassLoader(byteBuffers,this.getApplicationInfo().nativeLibraryDir,classLoader.getParent());
            Log.d(TAG, "New InMemoryDexClassLoader: " + dexClassLoader);
            //替换当前loadedApk实例中的mClassLoader字段
            Reflection.setField("android.app.LoadedApk","mClassLoader",loadedApkObj,dexClassLoader);
        }
        else{
 
            Log.d(TAG,"不支持Android 8.0以下版本");
        }
    }
}
//Android 7及以前
  void LoadMethod(Thread* self,
                  const DexFile& dex_file,
                  const ClassDataItemIterator& it,
                  Handle<mirror::Class> klass, ArtMethod* dst)
//Android 8-9
  void LoadMethod(const DexFile& dex_file,
                  const ClassDataItemIterator& it,
                  Handle<mirror::Class> klass, ArtMethod* dst)
//Android 10-14
    void LoadMethod(const DexFile& dex_file,
                  const ClassAccessor::Method& method,
                  Handle<mirror::Class> klass,
                  ArtMethod* dst)
     
//Android 15
  void LoadMethod(const DexFile& dex_file,
                  const ClassAccessor::Method& method,
                  ObjPtr<mirror::Class> klass,
                  /*inout*/ MethodAnnotationsIterator* mai,
                  /*out*/ ArtMethod* dst)
//Android 7及以前
  void LoadMethod(Thread* self,
                  const DexFile& dex_file,
                  const ClassDataItemIterator& it,
                  Handle<mirror::Class> klass, ArtMethod* dst)
//Android 8-9
  void LoadMethod(const DexFile& dex_file,
                  const ClassDataItemIterator& it,
                  Handle<mirror::Class> klass, ArtMethod* dst)
//Android 10-14
    void LoadMethod(const DexFile& dex_file,
                  const ClassAccessor::Method& method,
                  Handle<mirror::Class> klass,
                  ArtMethod* dst)
     
//Android 15
  void LoadMethod(const DexFile& dex_file,
                  const ClassAccessor::Method& method,
                  ObjPtr<mirror::Class> klass,
                  /*inout*/ MethodAnnotationsIterator* mai,
                  /*out*/ ArtMethod* dst)
// Android 5.0 Level 21 及之前使用该声明
static void* (*g_originDefineClassV21)(void* thiz,
                                       const char* descriptor,
                                       void* class_loader,
                                       const void* dex_file,
                                       const void* dex_class_def);
/*
//原始声明
mirror::Class* DefineClass(const char* descriptor,
                            Handle<mirror::ClassLoader> class_loader,
                            const DexFile& dex_file,
                            const DexFile::ClassDef& dex_class_def)
*/
 
// Android 5.1-14 Level22-34使用该声明
static void* (*g_originDefineClassV22)(void* thiz,
                                       void* self,
                                       const char* descriptor,
                                       size_t hash,
                                       void* class_loader,
                                       const void* dex_file,
                                       const void* dex_class_def);
/*
//原始声明
ObjPtr<mirror::Class> DefineClass(Thread* self,
                                  const char* descriptor,
                                  size_t hash,
                                  Handle<mirror::ClassLoader> class_loader,
                                  const DexFile& dex_file,
                                  const dex::ClassDef& dex_class_def)
*/
 
//Android 15 Level 35 以后使用该声明
static void* (*g_originDefineClassV35)(void* thiz,
                                       void* self,
                                       const char* descriptor,
                                       size_t descriptor_length,
                                       size_t hash,
                                       void* class_loader,
                                       const void* dex_file,
                                       const void* dex_class_def);
//原始声明
/*
ObjPtr<mirror::Class> DefineClass(Thread* self,
                                  const char* descriptor,
                                  size_t descriptor_length,
                                  size_t hash,
                                  Handle<mirror::ClassLoader> class_loader,
                                  const DexFile& dex_file,
                                  const dex::ClassDef& dex_class_def)
*/
// Android 5.0 Level 21 及之前使用该声明
static void* (*g_originDefineClassV21)(void* thiz,
                                       const char* descriptor,
                                       void* class_loader,
                                       const void* dex_file,
                                       const void* dex_class_def);
/*
//原始声明
mirror::Class* DefineClass(const char* descriptor,
                            Handle<mirror::ClassLoader> class_loader,
                            const DexFile& dex_file,
                            const DexFile::ClassDef& dex_class_def)
*/
 
// Android 5.1-14 Level22-34使用该声明
static void* (*g_originDefineClassV22)(void* thiz,
                                       void* self,
                                       const char* descriptor,
                                       size_t hash,
                                       void* class_loader,
                                       const void* dex_file,
                                       const void* dex_class_def);
/*
//原始声明
ObjPtr<mirror::Class> DefineClass(Thread* self,
                                  const char* descriptor,
                                  size_t hash,
                                  Handle<mirror::ClassLoader> class_loader,
                                  const DexFile& dex_file,
                                  const dex::ClassDef& dex_class_def)
*/
 
//Android 15 Level 35 以后使用该声明
static void* (*g_originDefineClassV35)(void* thiz,
                                       void* self,
                                       const char* descriptor,
                                       size_t descriptor_length,
                                       size_t hash,
                                       void* class_loader,
                                       const void* dex_file,
                                       const void* dex_class_def);
//原始声明
/*
ObjPtr<mirror::Class> DefineClass(Thread* self,
                                  const char* descriptor,
                                  size_t descriptor_length,
                                  size_t hash,
                                  Handle<mirror::ClassLoader> class_loader,
                                  const DexFile& dex_file,
                                  const dex::ClassDef& dex_class_def)
*/
from zlib import adler32
from hashlib import sha1
from binascii import unhexlify
from lxml import etree
import subprocess
import shutil
from pathlib import Path
import argparse
 
# Paths类,管理全局用到的路径
class Paths:
    def __init__(self, srcApk:Path, shellApk:Path, outputApk:Path):
        # 相关APK文件路径
        self.srcApkPath= srcApk.resolve() # 解析为绝对路径
        self.shellApkPath= shellApk.resolve()
        self.newApkPath= outputApk.resolve()
 
        # 临时目录 以该脚本文件父目录为根目录
        self.tmpdir= Path(__file__).parent/'temp'
        self.srcApkTempDir=  self.tmpdir/ 'srcApkTemp'
        self.shellApkTempDir= self.tmpdir/'shellApkTemp'
        self.newApkTempDir= self.tmpdir/ 'newApkTemp'
 
# Apktool类,通过subprocess调用其他工具 提供解包,打包,签名,抽取代码功能
class Apktool:
    def __init__(self):
        self.apktool_path= Path(__file__).parent/'tools/apktool/apktool.bat'
        self.signer_path=Path(__file__).parent/'tools/uber-apk-signer-1.3.0.jar'
        self.readDex_path=Path(__file__).parent/'tools/ReadDex.exe'
 
    # 为apk签名
    def signApk(self,unsignedApkPath:Path):
        self.runCommand(['java','-jar', self.signer_path, '--apk',unsignedApkPath])
 
    # 使用apktool解包apk 只解包资源得到AndroidManifest.xml 不需要解包dex文件得到smali
    def unpackApk(self,apkPath:Path, outputDir:Path):
        self.runCommand([self.apktool_path, '-s', 'd' , apkPath, '-o', outputDir])
 
    # 重打包apk
    def repackApk(self,inputDir:Path, outApk:Path):
        self.runCommand([self.apktool_path, 'b' , inputDir, '-o', outApk])
 
    # 抽取指定dex文件的代码
    def extractDexCodes(self,dexPath:Path):
        # 调用ReadDex.exe抽取dex文件代码,输出到同级目录 例如classes.dex抽取后生成classes.dex.patched和classes.dex.codes
        self.runCommand([self.readDex_path,'-file',  dexPath, '-extractCodes'])
 
    # 执行命令
    def runCommand(self,args):
        #subprocess.run(args)
        subprocess.run(args,stdout=subprocess.DEVNULL) #仅调用工具,不需要额外输出,重定向stdout到os.devnull
        # 参数列表 捕获输出 输出转为字符串
        #print(subprocess.run(args,  capture_output=True,text=True).stdout)
 
# AndroidManifest.xml的editor 用于获取和修改标签属性,以及添加标签
class ManifestEditor:
    def __init__(self, xml_content: bytes):
        self.ns = {'android': 'd0fK9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8Y4y4U0K9r3g2E0j5i4y4Q4x3X3g2S2L8X3c8J5L8$3W2V1i4K6u0W2j5$3!0E0i4K6u0r3j5i4m8C8i4K6u0r3M7X3g2K6i4K6u0r3j5h3&6V1M7X3!0A6k6q4)9J5y4H3`.`.}
        self.tree = etree.fromstring(xml_content)
 
    # 获取指定标签的android属性值 examples: get_attr('application', 'name') get_attr('activity', 'name')
    def getTagAttribute(self, tag_name: str, attr_name: str):
        if tag_name == 'manifest'# 根标签特殊处理
            elem = self.tree
            if elem is not None:
                return elem.get(f'{attr_name}'# 寻找标签的属性
        else:
            elem = self.tree.find(f'.//{tag_name}', namespaces=self.ns)
            if elem is not None:
                return elem.get(f'{{{self.ns["android"]}}}{attr_name}'# 根标签之外的属性位于android命名空间下
        return None
 
    # 设置指定标签的属性值 example:s et_attr('application','name',"com.example.ProxyApplication")
    def setTagAttribute(self, tag_name: str, attr_name: str, new_value: str):
        if tag_name == 'manifest'# 根标签特殊处理
            elem = self.tree
            if elem is not None:
                return elem.set(f'{attr_name}', new_value)  # 设置属性值
        else:
            elem = self.tree.find(f'.//{tag_name}', namespaces=self.ns)
            if elem is not None:
                elem.set(f'{{{self.ns["android"]}}}{attr_name}', new_value)
                return True
        return False
 
    # 在指定父标签下添加新子标签 example: add_tag('application',"meta-data",{'name': 'android.permission.CAMERA','value':'hello'})
    def addTagWithAttributes(self, parent_tag: str, new_tag: str, attrs: dict):
        if parent_tag == 'manifest':
            parent = self.tree
            if parent is not None:
                new_elem = etree.SubElement(parent, new_tag)
                for k, v in attrs.items():  # 支持一次给添加的标签设置多个属性
                    new_elem.set(f'{k}', v)
                return True
        else:
            parent = self.tree.find(f'.//{parent_tag}', namespaces=self.ns)
            if parent is not None:
                new_elem = etree.SubElement(parent, new_tag)
                for k, v in attrs.items():
                    new_elem.set(f'{{{self.ns["android"]}}}{k}', v)
                return True
        return False
 
    # 不以壳manifest为基准操作则用不到该函数,以源apk的manifest为基准自带,无需额外设置
    def getMainActivity(self):
        activities = self.tree.findall('.//activity', namespaces=self.ns)
        for activity in activities:
            intent_filters = activity.findall('.//intent-filter', namespaces=self.ns)
            for intent_filter in intent_filters:
                action = intent_filter.find('.//action[@android:name="android.intent.action.MAIN"]', namespaces=self.ns)
                category = intent_filter.find('.//category[@android:name="android.intent.category.LAUNCHER"]',
                                              namespaces=self.ns)
                if action is not None and category is not None:
                    return activity.get(f'{{{self.ns["android"]}}}name')
        return None
 
 
    # 获取application标签的name属性值
    def getApplicationName(self):
        return self.getTagAttribute('application', 'name')
 
    # 设置application标签的name属性值
    def setApplicationName(self, application: str):
        self.setTagAttribute('application', 'name', application)
 
    # 添加meta-data标签,并设置name和value属性值
    def addMetaData(self, name: str, value: str):
        self.addTagWithAttributes('application', 'meta-data', {'name': name, 'value': value})
 
    # 获取AndroidManifest.xml的字符串
    def getManifestData(self):
        """返回XML字符串"""
        return etree.tostring(self.tree, pretty_print=True, encoding='utf-8', xml_declaration=True).decode()
 
    # 获取application标签的extractNativeLibs属性值
    def getEtractNativeLibs(self):
        return self.getTagAttribute('application', 'extractNativeLibs')
     
    # 设置application标签的extractNativeLibs属性值为true
    def resetExtractNativeLibs(self):
        self.setTagAttribute('application', 'extractNativeLibs', 'true')
 
# 工具函数,注意修复时顺序为: fileSize->signature->checksum
# 修复dex文件的checksum
def fixCheckSum(dexBytes:bytearray):
    # dexfile[8:12] 小端序4字节
    value = adler32(bytes(dexBytes[12:]))
    valueArray = bytearray(value.to_bytes(4, 'little'))
    for i in range(len(valueArray)):
        dexBytes[8 + i] = valueArray[i]
 
# 修复dex文件的signature
def fixSignature(dexBytes:bytearray):
    # dexfile[12:32] 小端序20字节
    sha_1 = sha1()
    sha_1.update(bytes(dexBytes[32:]))
    value = sha_1.hexdigest()
    valueArray = bytearray(unhexlify(value))
    for i in range(len(valueArray)):
        dexBytes[12 + i] = valueArray[i]
 
# 修复dex文件的filesize
def fixFileSize(dexBytes:bytearray, fileSize):
    # dexfile[32:36] 小端存储
    fileSizeArray = bytearray(fileSize.to_bytes(4, "little"))
    for i in range(len(fileSizeArray)):
        dexBytes[32 + i] = fileSizeArray[i]
 
# 加密函数,使用异或
def encrypt(data:bytearray):
    # todo:使用aes/sm4等加密算法替代
    for i in range(len(data)):
        data[i] ^= 0xff
    return data
 
# 抽取指定目录下的所有dex文件的代码 patch所有dex文件并修复,将codes文件移动到assets目录下
def extractAllDexFiles(directory:Path):
    apktool=Apktool()
    # 1.遍历目录下的所有dex文件,并抽取对应代码
    for dex in directory.glob('classes*.dex'):
        apktool.extractDexCodes(dex) # 抽取dex文件代码 得到classes*.dex.patched和classes*.dex.codes
     
    # 2.修复抽取后的文件并覆写原dex文件
    for patchedDex in directory.glob('classes*.dex.patched'):
        newDexName = str(patchedDex).replace('.patched', '') # 重命名
        # 读取文件内容
        with open(patchedDex, 'rb') as f:
            data = bytearray(f.read())
            # 修复signature和checksum,注意先后顺序
            fixSignature(data)
            fixCheckSum(data)
            # 修复后的文件覆写原dex文件
            with open(newDexName, 'wb') as newf:
                newf.write(data)
 
    # 3.删除patched文件
    for patchedDex in directory.glob('classes*.dex.patched'):
        patchedDex.unlink()
 
    # 4.移动.codes文件到assets目录下
    # 如果没有assets目录则创建
    if not (directory/'assets').exists():
        (directory/'assets').mkdir(parents=True)
 
    for codes in directory.glob('classes*.dex.codes'):
        shutil.move(codes,directory/'assets'/codes.name) # 移动到assets目录下
 
# 合并壳dex和源apk的dex,支持多dex文件,合并为一个dex
def combineShellAndSourceDexs(shellApkTempDir:Path,srcApkTempDir:Path,newApkTempDir:Path):
    # 读取解包后的apk的所有dex文件,并合并为一个dex文件
    def readAndCombineDexs(unpackedApkDir:Path):
        combinedDex = bytearray()
         # glob方法返回包含所有匹配文件的生成器
        for dex in unpackedApkDir.glob('classes*.dex'):
            print('Source Apk Dex file:', dex)
            with open(dex, 'rb') as f:
                data = bytearray(f.read())
                combinedDex+=bytearray(len(data).to_bytes(4, 'little'))  # dex文件的长度,小端序
                combinedDex+=data # dex文件内容
        return combinedDex
     
    # 获取shelldex
    with open(shellApkTempDir/'classes.dex', 'rb') as f:
        shellDexArray=bytearray(f.read())
 
    # 获取源apk的dex文件
    srcDexArray = readAndCombineDexs(srcApkTempDir)
    # 新的dex文件长度
    newDexLen = len(srcDexArray) + len(shellDexArray) + 4
    # 加密源文件
    encSrcDexArray = encrypt(srcDexArray)
    # 新的dex文件内容 = 壳dex + 加密的源dex + 四字节标识加密后源dex大小长度
    newDexArray = shellDexArray + encSrcDexArray  + bytearray(len(encSrcDexArray).to_bytes(4, 'little'))
 
    # 修改filesize
    fixFileSize(newDexArray, newDexLen)
    # 修改signature
    fixSignature(newDexArray)
    # 修改checksum
    fixCheckSum(newDexArray) # 注意先后顺序,先修改signature,再修改checksum
 
    # 导出文件
    with open(newApkTempDir/'classes.dex', 'wb') as f:
        f.write(newDexArray)
 
# 提取源apk的Manifest文件,修改application为壳application(可能添加meta-data标签),输出新的Manifest文件
def handleManifest(srcApkTempDir:Path,shellApkTempDir:Path,newApkTempDir:Path):
    # 从源apk提取AndroidManifest.xml
    with open(srcApkTempDir/'AndroidManifest.xml', 'r') as f:
        srcManifestEditor=ManifestEditor(f.read().encode())
    srcApplication=srcManifestEditor.getApplicationName()           # 获取application:name,确定是否存在自定义Application类
    srcExtractNativeLibs=srcManifestEditor.getEtractNativeLibs()    # 获取application:extractNativeLibs,判断是否释放lib文件
    print('SourceApplication:',srcApplication)
    print('SourceExtractNativeLibs:',srcExtractNativeLibs)
     
    # 从壳apk提取AndroidManifest.xml
    with open(shellApkTempDir/'AndroidManifest.xml', 'r') as f:
        shellManifestEditor=ManifestEditor(f.read().encode())
    print('ShellApplication:',shellManifestEditor.getApplicationName())
 
    # 修改源AndroidManifest.xml的application为壳的代理application
    srcManifestEditor.setApplicationName(shellManifestEditor.getApplicationName())
 
    # 写入meta-data标签 保存源apk的原始application
    if srcApplication != None:
        print('Source application:',srcApplication)
        srcManifestEditor.addMetaData('APPLICATION_CLASS_NAME',srcApplication)
 
    # 如果源apk的manifest中默认设置etractNativeLibs=false,则重置为true,确保释放lib文件
    if srcExtractNativeLibs=='false':
        srcManifestEditor.resetExtractNativeLibs()
 
    # 输出新的AndroidManifest.xml
    with open(newApkTempDir/'AndroidManifest.xml', 'w') as f:
        f.write(srcManifestEditor.getManifestData())
 
# 执行加固流程
def start(paths:Paths):
    apktool=Apktool()
    # 1.分别解包源文件和壳文件到临时目录
    print('Extracting source and shell apk...')
    apktool.unpackApk(paths.srcApkPath,paths.srcApkTempDir)
    print('Extract source apk success!')
 
    print('Extracting shell apk...')
    apktool.unpackApk(paths.shellApkPath,paths.shellApkTempDir)
    print('Extract shell apk success!')
     
    # 2.抽取源dex文件代码
    print('Exrtracting dex files codes...')
    extractAllDexFiles(paths.srcApkTempDir)
    print('Extract dex files codes success!')
 
    # 3.复制源apk所有文件到新apk临时目录中 忽略源dex和manifest文件
    print('Copying source apk files to new apk temp dir...')
    shutil.copytree(paths.srcApkTempDir,paths.newApkTempDir,ignore=shutil.ignore_patterns('AndroidManifest.xml','classes*.dex'))
    print('Copy source apk files success!')
 
    # 4.复制壳apk的lib库文件到新apk临时目录中 (壳的代码回填逻辑在lib中实现)
    print('Copying shell apk lib files to new apk temp dir...')
    shutil.copytree(paths.shellApkTempDir/'lib',paths.newApkTempDir/'lib',dirs_exist_ok=True) # dirs_exist_ok=True 如果目标目录已存在,则覆盖
    print('Copy shell apk lib files success!')
 
    # 5.处理AndroidManifest.xml
    print('Handling AndroidManifest.xml...')
    handleManifest(paths.srcApkTempDir,paths.shellApkTempDir,paths.newApkTempDir)
    print('Handle AndroidManifest.xml success!')
 
    # 6.合并壳dex和源apk的dex并导出文件
    print('Combining shell dex and source dexs...')
    combineShellAndSourceDexs(paths.shellApkTempDir,paths.srcApkTempDir,paths.newApkTempDir)
    print('Combine shell dex and source dexs success!')
 
    # 7.重打包apk
    print('Repacking apk...')
    apktool.repackApk(paths.newApkTempDir,paths.newApkPath)
    print('Repack apk success!')
 
    # 8.签名apk
    print('Signing apk...')
    apktool.signApk(paths.newApkPath)
    print('Resign apk success!')
 
    # 9.删除临时目录
    print('Deleting temp directories...')
    shutil.rmtree(paths.tmpdir)
    print('Delete temp directories success!')
     
def main():
    parser = argparse.ArgumentParser(description="Android APK Packer")
    parser.add_argument('-src','--src-apk', required=True, type=Path, help='Path to source APK file')
    parser.add_argument('-shell','--shell-apk', required=True, type=Path, help='Path to shell APK file')
    parser.add_argument('-o','-out','--output-apk',type=Path,help='Output path for packed APK (Default: ./out/<src-apk>_protected.apk)')
    args = parser.parse_args()
    if args.output_apk == None:
        args.output_apk = Path('./out')/(args.src_apk.stem+'_protected.apk') # 默认新apk名称及输出路径
    paths = Paths(args.src_apk, args.shell_apk, args.output_apk)
 
    print('Source APK:', paths.srcApkPath)
    print('Shell APK:', paths.shellApkPath)
    print('Output APK:', paths.newApkPath)
    start(paths)
 
if __name__=="__main__":
    main()
from zlib import adler32
from hashlib import sha1
from binascii import unhexlify
from lxml import etree
import subprocess
import shutil
from pathlib import Path
import argparse
 
# Paths类,管理全局用到的路径
class Paths:
    def __init__(self, srcApk:Path, shellApk:Path, outputApk:Path):
        # 相关APK文件路径
        self.srcApkPath= srcApk.resolve() # 解析为绝对路径
        self.shellApkPath= shellApk.resolve()
        self.newApkPath= outputApk.resolve()
 
        # 临时目录 以该脚本文件父目录为根目录
        self.tmpdir= Path(__file__).parent/'temp'
        self.srcApkTempDir=  self.tmpdir/ 'srcApkTemp'
        self.shellApkTempDir= self.tmpdir/'shellApkTemp'
        self.newApkTempDir= self.tmpdir/ 'newApkTemp'
 
# Apktool类,通过subprocess调用其他工具 提供解包,打包,签名,抽取代码功能
class Apktool:
    def __init__(self):
        self.apktool_path= Path(__file__).parent/'tools/apktool/apktool.bat'
        self.signer_path=Path(__file__).parent/'tools/uber-apk-signer-1.3.0.jar'
        self.readDex_path=Path(__file__).parent/'tools/ReadDex.exe'
 
    # 为apk签名
    def signApk(self,unsignedApkPath:Path):
        self.runCommand(['java','-jar', self.signer_path, '--apk',unsignedApkPath])
 
    # 使用apktool解包apk 只解包资源得到AndroidManifest.xml 不需要解包dex文件得到smali
    def unpackApk(self,apkPath:Path, outputDir:Path):
        self.runCommand([self.apktool_path, '-s', 'd' , apkPath, '-o', outputDir])
 
    # 重打包apk
    def repackApk(self,inputDir:Path, outApk:Path):
        self.runCommand([self.apktool_path, 'b' , inputDir, '-o', outApk])
 
    # 抽取指定dex文件的代码
    def extractDexCodes(self,dexPath:Path):
        # 调用ReadDex.exe抽取dex文件代码,输出到同级目录 例如classes.dex抽取后生成classes.dex.patched和classes.dex.codes
        self.runCommand([self.readDex_path,'-file',  dexPath, '-extractCodes'])
 
    # 执行命令
    def runCommand(self,args):
        #subprocess.run(args)
        subprocess.run(args,stdout=subprocess.DEVNULL) #仅调用工具,不需要额外输出,重定向stdout到os.devnull
        # 参数列表 捕获输出 输出转为字符串
        #print(subprocess.run(args,  capture_output=True,text=True).stdout)
 
# AndroidManifest.xml的editor 用于获取和修改标签属性,以及添加标签
class ManifestEditor:
    def __init__(self, xml_content: bytes):
        self.ns = {'android': 'd0fK9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8Y4y4U0K9r3g2E0j5i4y4Q4x3X3g2S2L8X3c8J5L8$3W2V1i4K6u0W2j5$3!0E0i4K6u0r3j5i4m8C8i4K6u0r3M7X3g2K6i4K6u0r3j5h3&6V1M7X3!0A6k6q4)9J5y4H3`.`.}
        self.tree = etree.fromstring(xml_content)
 
    # 获取指定标签的android属性值 examples: get_attr('application', 'name') get_attr('activity', 'name')
    def getTagAttribute(self, tag_name: str, attr_name: str):
        if tag_name == 'manifest'# 根标签特殊处理
            elem = self.tree
            if elem is not None:
                return elem.get(f'{attr_name}'# 寻找标签的属性
        else:
            elem = self.tree.find(f'.//{tag_name}', namespaces=self.ns)
            if elem is not None:
                return elem.get(f'{{{self.ns["android"]}}}{attr_name}'# 根标签之外的属性位于android命名空间下
        return None
 
    # 设置指定标签的属性值 example:s et_attr('application','name',"com.example.ProxyApplication")
    def setTagAttribute(self, tag_name: str, attr_name: str, new_value: str):
        if tag_name == 'manifest'# 根标签特殊处理
            elem = self.tree
            if elem is not None:
                return elem.set(f'{attr_name}', new_value)  # 设置属性值
        else:
            elem = self.tree.find(f'.//{tag_name}', namespaces=self.ns)
            if elem is not None:
                elem.set(f'{{{self.ns["android"]}}}{attr_name}', new_value)
                return True
        return False
 
    # 在指定父标签下添加新子标签 example: add_tag('application',"meta-data",{'name': 'android.permission.CAMERA','value':'hello'})
    def addTagWithAttributes(self, parent_tag: str, new_tag: str, attrs: dict):
        if parent_tag == 'manifest':
            parent = self.tree
            if parent is not None:
                new_elem = etree.SubElement(parent, new_tag)
                for k, v in attrs.items():  # 支持一次给添加的标签设置多个属性
                    new_elem.set(f'{k}', v)
                return True
        else:
            parent = self.tree.find(f'.//{parent_tag}', namespaces=self.ns)
            if parent is not None:
                new_elem = etree.SubElement(parent, new_tag)
                for k, v in attrs.items():
                    new_elem.set(f'{{{self.ns["android"]}}}{k}', v)
                return True
        return False
 
    # 不以壳manifest为基准操作则用不到该函数,以源apk的manifest为基准自带,无需额外设置
    def getMainActivity(self):
        activities = self.tree.findall('.//activity', namespaces=self.ns)
        for activity in activities:
            intent_filters = activity.findall('.//intent-filter', namespaces=self.ns)
            for intent_filter in intent_filters:
                action = intent_filter.find('.//action[@android:name="android.intent.action.MAIN"]', namespaces=self.ns)
                category = intent_filter.find('.//category[@android:name="android.intent.category.LAUNCHER"]',
                                              namespaces=self.ns)
                if action is not None and category is not None:
                    return activity.get(f'{{{self.ns["android"]}}}name')
        return None
 
 
    # 获取application标签的name属性值
    def getApplicationName(self):
        return self.getTagAttribute('application', 'name')
 
    # 设置application标签的name属性值
    def setApplicationName(self, application: str):
        self.setTagAttribute('application', 'name', application)
 
    # 添加meta-data标签,并设置name和value属性值
    def addMetaData(self, name: str, value: str):
        self.addTagWithAttributes('application', 'meta-data', {'name': name, 'value': value})
 
    # 获取AndroidManifest.xml的字符串
    def getManifestData(self):
        """返回XML字符串"""
        return etree.tostring(self.tree, pretty_print=True, encoding='utf-8', xml_declaration=True).decode()
 
    # 获取application标签的extractNativeLibs属性值
    def getEtractNativeLibs(self):
        return self.getTagAttribute('application', 'extractNativeLibs')
     
    # 设置application标签的extractNativeLibs属性值为true
    def resetExtractNativeLibs(self):
        self.setTagAttribute('application', 'extractNativeLibs', 'true')
 
# 工具函数,注意修复时顺序为: fileSize->signature->checksum
# 修复dex文件的checksum
def fixCheckSum(dexBytes:bytearray):
    # dexfile[8:12] 小端序4字节
    value = adler32(bytes(dexBytes[12:]))
    valueArray = bytearray(value.to_bytes(4, 'little'))
    for i in range(len(valueArray)):
        dexBytes[8 + i] = valueArray[i]
 
# 修复dex文件的signature
def fixSignature(dexBytes:bytearray):
    # dexfile[12:32] 小端序20字节
    sha_1 = sha1()
    sha_1.update(bytes(dexBytes[32:]))
    value = sha_1.hexdigest()
    valueArray = bytearray(unhexlify(value))
    for i in range(len(valueArray)):
        dexBytes[12 + i] = valueArray[i]
 
# 修复dex文件的filesize
def fixFileSize(dexBytes:bytearray, fileSize):
    # dexfile[32:36] 小端存储
    fileSizeArray = bytearray(fileSize.to_bytes(4, "little"))
    for i in range(len(fileSizeArray)):
        dexBytes[32 + i] = fileSizeArray[i]
 
# 加密函数,使用异或
def encrypt(data:bytearray):
    # todo:使用aes/sm4等加密算法替代
    for i in range(len(data)):
        data[i] ^= 0xff
    return data
 
# 抽取指定目录下的所有dex文件的代码 patch所有dex文件并修复,将codes文件移动到assets目录下
def extractAllDexFiles(directory:Path):
    apktool=Apktool()
    # 1.遍历目录下的所有dex文件,并抽取对应代码
    for dex in directory.glob('classes*.dex'):
        apktool.extractDexCodes(dex) # 抽取dex文件代码 得到classes*.dex.patched和classes*.dex.codes
     
    # 2.修复抽取后的文件并覆写原dex文件
    for patchedDex in directory.glob('classes*.dex.patched'):
        newDexName = str(patchedDex).replace('.patched', '') # 重命名
        # 读取文件内容
        with open(patchedDex, 'rb') as f:
            data = bytearray(f.read())
            # 修复signature和checksum,注意先后顺序
            fixSignature(data)
            fixCheckSum(data)
            # 修复后的文件覆写原dex文件
            with open(newDexName, 'wb') as newf:
                newf.write(data)
 
    # 3.删除patched文件
    for patchedDex in directory.glob('classes*.dex.patched'):
        patchedDex.unlink()
 
    # 4.移动.codes文件到assets目录下
    # 如果没有assets目录则创建
    if not (directory/'assets').exists():
        (directory/'assets').mkdir(parents=True)
 
    for codes in directory.glob('classes*.dex.codes'):
        shutil.move(codes,directory/'assets'/codes.name) # 移动到assets目录下
 
# 合并壳dex和源apk的dex,支持多dex文件,合并为一个dex
def combineShellAndSourceDexs(shellApkTempDir:Path,srcApkTempDir:Path,newApkTempDir:Path):
    # 读取解包后的apk的所有dex文件,并合并为一个dex文件
    def readAndCombineDexs(unpackedApkDir:Path):
        combinedDex = bytearray()
         # glob方法返回包含所有匹配文件的生成器
        for dex in unpackedApkDir.glob('classes*.dex'):
            print('Source Apk Dex file:', dex)
            with open(dex, 'rb') as f:
                data = bytearray(f.read())
                combinedDex+=bytearray(len(data).to_bytes(4, 'little'))  # dex文件的长度,小端序
                combinedDex+=data # dex文件内容
        return combinedDex
     
    # 获取shelldex
    with open(shellApkTempDir/'classes.dex', 'rb') as f:
        shellDexArray=bytearray(f.read())
 
    # 获取源apk的dex文件
    srcDexArray = readAndCombineDexs(srcApkTempDir)
    # 新的dex文件长度
    newDexLen = len(srcDexArray) + len(shellDexArray) + 4
    # 加密源文件
    encSrcDexArray = encrypt(srcDexArray)
    # 新的dex文件内容 = 壳dex + 加密的源dex + 四字节标识加密后源dex大小长度
    newDexArray = shellDexArray + encSrcDexArray  + bytearray(len(encSrcDexArray).to_bytes(4, 'little'))
 
    # 修改filesize
    fixFileSize(newDexArray, newDexLen)
    # 修改signature
    fixSignature(newDexArray)
    # 修改checksum
    fixCheckSum(newDexArray) # 注意先后顺序,先修改signature,再修改checksum
 
    # 导出文件
    with open(newApkTempDir/'classes.dex', 'wb') as f:
        f.write(newDexArray)
 
# 提取源apk的Manifest文件,修改application为壳application(可能添加meta-data标签),输出新的Manifest文件
def handleManifest(srcApkTempDir:Path,shellApkTempDir:Path,newApkTempDir:Path):
    # 从源apk提取AndroidManifest.xml
    with open(srcApkTempDir/'AndroidManifest.xml', 'r') as f:
        srcManifestEditor=ManifestEditor(f.read().encode())
    srcApplication=srcManifestEditor.getApplicationName()           # 获取application:name,确定是否存在自定义Application类
    srcExtractNativeLibs=srcManifestEditor.getEtractNativeLibs()    # 获取application:extractNativeLibs,判断是否释放lib文件
    print('SourceApplication:',srcApplication)
    print('SourceExtractNativeLibs:',srcExtractNativeLibs)
     
    # 从壳apk提取AndroidManifest.xml
    with open(shellApkTempDir/'AndroidManifest.xml', 'r') as f:
        shellManifestEditor=ManifestEditor(f.read().encode())
    print('ShellApplication:',shellManifestEditor.getApplicationName())
 
    # 修改源AndroidManifest.xml的application为壳的代理application
    srcManifestEditor.setApplicationName(shellManifestEditor.getApplicationName())
 
    # 写入meta-data标签 保存源apk的原始application
    if srcApplication != None:
        print('Source application:',srcApplication)
        srcManifestEditor.addMetaData('APPLICATION_CLASS_NAME',srcApplication)
 
    # 如果源apk的manifest中默认设置etractNativeLibs=false,则重置为true,确保释放lib文件
    if srcExtractNativeLibs=='false':
        srcManifestEditor.resetExtractNativeLibs()
 
    # 输出新的AndroidManifest.xml
    with open(newApkTempDir/'AndroidManifest.xml', 'w') as f:
        f.write(srcManifestEditor.getManifestData())
 
# 执行加固流程
def start(paths:Paths):
    apktool=Apktool()
    # 1.分别解包源文件和壳文件到临时目录
    print('Extracting source and shell apk...')
    apktool.unpackApk(paths.srcApkPath,paths.srcApkTempDir)
    print('Extract source apk success!')
 
    print('Extracting shell apk...')
    apktool.unpackApk(paths.shellApkPath,paths.shellApkTempDir)
    print('Extract shell apk success!')
     
    # 2.抽取源dex文件代码
    print('Exrtracting dex files codes...')
    extractAllDexFiles(paths.srcApkTempDir)
    print('Extract dex files codes success!')
 
    # 3.复制源apk所有文件到新apk临时目录中 忽略源dex和manifest文件
    print('Copying source apk files to new apk temp dir...')
    shutil.copytree(paths.srcApkTempDir,paths.newApkTempDir,ignore=shutil.ignore_patterns('AndroidManifest.xml','classes*.dex'))
    print('Copy source apk files success!')
 
    # 4.复制壳apk的lib库文件到新apk临时目录中 (壳的代码回填逻辑在lib中实现)
    print('Copying shell apk lib files to new apk temp dir...')
    shutil.copytree(paths.shellApkTempDir/'lib',paths.newApkTempDir/'lib',dirs_exist_ok=True) # dirs_exist_ok=True 如果目标目录已存在,则覆盖
    print('Copy shell apk lib files success!')
 
    # 5.处理AndroidManifest.xml
    print('Handling AndroidManifest.xml...')
    handleManifest(paths.srcApkTempDir,paths.shellApkTempDir,paths.newApkTempDir)
    print('Handle AndroidManifest.xml success!')
 
    # 6.合并壳dex和源apk的dex并导出文件
    print('Combining shell dex and source dexs...')
    combineShellAndSourceDexs(paths.shellApkTempDir,paths.srcApkTempDir,paths.newApkTempDir)
    print('Combine shell dex and source dexs success!')
 
    # 7.重打包apk
    print('Repacking apk...')
    apktool.repackApk(paths.newApkTempDir,paths.newApkPath)
    print('Repack apk success!')
 
    # 8.签名apk
    print('Signing apk...')
    apktool.signApk(paths.newApkPath)
    print('Resign apk success!')
 
    # 9.删除临时目录
    print('Deleting temp directories...')
    shutil.rmtree(paths.tmpdir)
    print('Delete temp directories success!')
     
def main():
    parser = argparse.ArgumentParser(description="Android APK Packer")
    parser.add_argument('-src','--src-apk', required=True, type=Path, help='Path to source APK file')
    parser.add_argument('-shell','--shell-apk', required=True, type=Path, help='Path to shell APK file')
    parser.add_argument('-o','-out','--output-apk',type=Path,help='Output path for packed APK (Default: ./out/<src-apk>_protected.apk)')
    args = parser.parse_args()
    if args.output_apk == None:
        args.output_apk = Path('./out')/(args.src_apk.stem+'_protected.apk') # 默认新apk名称及输出路径
    paths = Paths(args.src_apk, args.shell_apk, args.output_apk)
 
    print('Source APK:', paths.srcApkPath)
    print('Shell APK:', paths.shellApkPath)
    print('Output APK:', paths.newApkPath)
    start(paths)
 
if __name__=="__main__":
    main()
include_directories(
        dobby
)
add_library(local_dobby STATIC IMPORTED)
set_target_properties(local_dobby PROPERTIES IMPORTED_LOCATION ${CMAKE_CURRENT_SOURCE_DIR}/../../../libs/${ANDROID_ABI}/libdobby.a)
target_link_libraries(dpt
            ${log-lib}
        MINIZIP::minizip
        local_dobby
        bytehook
        ${android-lib}
        )
include_directories(
        dobby
)
add_library(local_dobby STATIC IMPORTED)
set_target_properties(local_dobby PROPERTIES IMPORTED_LOCATION ${CMAKE_CURRENT_SOURCE_DIR}/../../../libs/${ANDROID_ABI}/libdobby.a)
target_link_libraries(dpt
            ${log-lib}
        MINIZIP::minizip
        local_dobby
        bytehook
        ${android-lib}
        )
android {
    buildFeatures {
        prefab true
    }
}
 
dependencies {
    implementation 'com.bytedance:bytehook:1.1.1'
}
android {
    buildFeatures {
        prefab true
    }
}
 
dependencies {
    implementation 'com.bytedance:bytehook:1.1.1'
}
//其中mylib 表示需要使用bhook的模块名,也就是将这些模块和bytehook链接
find_package(bytehook REQUIRED CONFIG)  // 获取bytehook包
add_library(mylib SHARED mylib.c)       //用户模块
target_link_libraries(mylib bytehook::bytehook) //链接用户模块和bytehook
//其中mylib 表示需要使用bhook的模块名,也就是将这些模块和bytehook链接
find_package(bytehook REQUIRED CONFIG)  // 获取bytehook包
add_library(mylib SHARED mylib.c)       //用户模块
target_link_libraries(mylib bytehook::bytehook) //链接用户模块和bytehook
package com.example.androidshell;
 
import android.app.Application;
import android.app.Instrumentation;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.AssetManager;
import android.os.Bundle;
import android.util.ArrayMap;
import android.util.Log;
 
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.WeakReference;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
 
import dalvik.system.DexClassLoader;
 
public class ThirdProxyApplication extends Application {
    private final String TAG="glass";
    private String dexPath;
    private String odexPath;
    private String libPath;
    public void log(String message){Log.d(TAG,message);}
 
    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);
        log("ThirdProxyApplication.attachBaseContext is running!");
        System.loadLibrary("androidshell");     //主动加载so,设置hook,进行指令回填
        log("Load libandroidshell.so succeed!");
        try {
            //初始化相关环境
            initEnvironments();
            //配置加载源程序的动态环境,即替换mClassLoader
            replaceClassLoader();
        } catch (Exception e) {
            log( Log.getStackTraceString(e));
        }
    }
    @Override
    public void onCreate() {
        super.onCreate();
        log("ThirdProxyApplication.onCreate is running!");
        if(replaceApplication())
            log("Replace Application succeed!");
    }
 
    private void initEnvironments() throws IOException {
        //1.设置相关目录和路径
        File dex = getDir("tmp_dex", MODE_PRIVATE);     // 私有目录,存放dex文件
        //File lib = getDir("tmp_lib", MODE_PRIVATE);       // lib可使用默认目录
        //libPath = lib.getAbsolutePath();
        odexPath = dex.getAbsolutePath();
        libPath=this.getApplicationInfo().nativeLibraryDir; //默认lib路径
        dexPath =this.getApplicationInfo().sourceDir;   //当前base.apk路径
        //2.从当前base.apk读取classes.dex并读取为字节数组
        byte[] shellDexData = readDexFromApk();
        log("Get classes.dex from base.apk succeed!");
        //3.从壳dex文件中分离出源dex文件
        ByteBuffer[] byteBuffers = extractDexFilesFromShellDex(shellDexData);
        //4.将源dex文件依次写入私有目录
        writeByteBuffersToDirectory(byteBuffers, odexPath);
        //5.将codes文件依次写入私有目录
        copyClassesCodesFiles(this, odexPath);
        //6.拼接dex目录字符串,设置dex文件路径 DexClassLoader支持传递多个dex文件路径以加载多个dex文件,通过':'分隔路径
        StringBuffer dexFiles=new StringBuffer();
        for(File file:dex.listFiles()){
            if(file.getName().contains(".codes"))
                continue;
            dexFiles.append(file.getAbsolutePath());
            dexFiles.append(":");
        }
        dexPath=dexFiles.toString();
    }
    private void writeByteBuffersToDirectory(ByteBuffer[] byteBuffers, String directoryPath) throws IOException {
        // 创建目录对象
        File directory = new File(directoryPath);
        // 检查目录是否存在,不存在则创建
        if (!directory.exists()) {
            if (!directory.mkdirs()) {
                throw new IOException("无法创建目录: " + directoryPath);
            }
        }
 
        // 遍历 ByteBuffer 数组
        for (int i = 0; i < byteBuffers.length; i++) {
            // 生成文件名
            String fileName;
            if (i == 0) {
                fileName = "classes.dex";
            } else {
                fileName = "classes" + (i + 1) + ".dex";
            }
            // 构建文件对象
            File file = new File(directory, fileName);
            // 创建文件输出流
            try (FileOutputStream fos = new FileOutputStream(file)) {
                // 获取 ByteBuffer 中的字节数组
                ByteBuffer buffer = byteBuffers[i];
                byte[] bytes = new byte[buffer.remaining()];
                buffer.get(bytes);
                // 将字节数组写入文件
                fos.write(bytes);
            }
        }
    }
    private void copyClassesCodesFiles(Context context, String targetDirectoryPath) {
        AssetManager assetManager = context.getAssets();
        try {
            // 获取 assets 目录下的所有文件和文件夹
            String[] files = assetManager.list("");
            if (files != null) {
                // 创建目标目录
                File targetDirectory = new File(targetDirectoryPath);
                if (!targetDirectory.exists()) {
                    if (!targetDirectory.mkdirs()) {
                        throw new IOException("无法创建目标目录: " + targetDirectoryPath);
                    }
                }
                for (String fileName : files) {
                    // 筛选以 classes 开头且以 .codes 结尾的文件
                    if (fileName.startsWith("classes") && fileName.endsWith(".codes")) {
                        try (InputStream inputStream = assetManager.open(fileName);
                             BufferedInputStream bis = new BufferedInputStream(inputStream);
                             FileOutputStream fos = new FileOutputStream(new File(targetDirectory, fileName));
                             BufferedOutputStream bos = new BufferedOutputStream(fos)) {
 
                            byte[] buffer = new byte[1024];
                            int bytesRead;
                            while ((bytesRead = bis.read(buffer)) != -1) {
                                bos.write(buffer, 0, bytesRead);
                            }
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
 
    // 从壳dex文件中提取源apk的dex并封装为ByteBuffer
    private ByteBuffer[] extractDexFilesFromShellDex(byte[] shellDexData) {
        int shellDexlength = shellDexData.length;
        //开始解析dex文件
        byte[] sourceDexsSizeByte = new byte[4];
        //读取源dexs的大小
        System.arraycopy(shellDexData,shellDexlength - 4, sourceDexsSizeByte,0,4);
        //转成bytebuffer,方便4byte转int
        ByteBuffer wrap = ByteBuffer.wrap(sourceDexsSizeByte);
        //将byte转成int, 加壳时,长度按小端存储
        int sourceDexsSizeInt = wrap.order(ByteOrder.LITTLE_ENDIAN).getInt();
        Log.d(TAG, "源dex集合的大小: " + sourceDexsSizeInt);
        //读取源dexs
        byte[] sourceDexsData = new byte[sourceDexsSizeInt];
        System.arraycopy(shellDexData,shellDexlength - sourceDexsSizeInt - 4, sourceDexsData, 0, sourceDexsSizeInt);
        //解密源dexs
        sourceDexsData = decrypt(sourceDexsData);
 
        //更新部分
        //从源dexs中分离dex
        ArrayList<byte[]> sourceDexList = new ArrayList<>();
        int pos = 0;
        while(pos < sourceDexsSizeInt){
            //先提取四个字节,描述当前dex的大小
            //开始解析dex文件
            byte[] singleDexSizeByte = new byte[4];
            //读取源dexs的大小
            System.arraycopy(sourceDexsData, pos, singleDexSizeByte,0,4);
            //转成bytebuffer,方便4byte转int
            ByteBuffer singleDexwrap = ByteBuffer.wrap(singleDexSizeByte);
            int singleDexSizeInt = singleDexwrap.order(ByteOrder.LITTLE_ENDIAN).getInt();
            Log.d(TAG, "当前Dex的大小: " + singleDexSizeInt);
            //读取单独dex
            byte[] singleDexData = new byte[singleDexSizeInt];
            System.arraycopy(sourceDexsData,pos + 4, singleDexData, 0, singleDexSizeInt);
            //加入到dexlist中
            sourceDexList.add(singleDexData);
            //更新pos
            pos += 4 + singleDexSizeInt;
        }
 
        //将dexlist包装成ByteBuffer
        int dexNum = sourceDexList.size();
        Log.d(TAG, "源dex的数量: " + dexNum);
        ByteBuffer[] dexBuffers = new ByteBuffer[dexNum];
        for (int i = 0; i < dexNum; i++){
            dexBuffers[i] = ByteBuffer.wrap(sourceDexList.get(i));
        }
 
        return dexBuffers;
 
    }
    // 从当前程序的apk读取dex文件并存储为字节数组
    private byte[] readDexFromApk() throws IOException {
        //1.获取当前应用程序的源码路径(apk),一般是data/app目录下,该目录用于存放用户安装的软件
        String sourceDir = this.getApplicationInfo().sourceDir;
        log("this.getApplicationInfo().sourceDir: " +sourceDir);
 
        //2.创建相关输入流
        FileInputStream fileInputStream = new FileInputStream(sourceDir);
        BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream);
        ZipInputStream zipInputStream = new ZipInputStream(bufferedInputStream); //用于解析apk文件
 
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); //用于存放dex文件
 
        //3.遍历apk的所有文件并提取dex文件
        ZipEntry zipEntry;
        while((zipEntry = zipInputStream.getNextEntry()) != null){ //存在下一个文件
            // 将classes.dex文件存储到bytearray中 壳dex和源apk合并后只保留一个dex便于处理
            if (zipEntry.getName().equals("classes.dex")){
                byte[] bytes = new byte[1024];
                int num;
                while((num = zipInputStream.read(bytes))!=-1){      //每次读取1024byte,返回读取到的byte数
                    byteArrayOutputStream.write(bytes,0, num); //存放到开辟的byteArrayOutputStream中
                }
            }
            zipInputStream.closeEntry(); //关闭当前文件
        }
        zipInputStream.close();
 
        log("Read dex from apk succeed!");
        return byteArrayOutputStream.toByteArray(); //将读取到的dex文件以字节数组形式返回
    }
    // 解密
    private byte[] decrypt(byte[] data) {
        for (int i = 0; i < data.length; i++){
            data[i] ^= (byte) 0xff;
        }
        return data;
 
    }
    //替换壳程序LoadedApk的Application为源程序Application,并调用其onCreate方法
    private boolean replaceApplication(){
        // Application实例存在于: LoadedApk.mApplication
        // 以及ActivityThread的mInitialApplication和mAllApplications和mBoundApplication
 
        //判断源程序是否使用自定义Application 若使用则需要进行替换,若未使用则直接返回,使用壳的默认Application即可
 
        String appClassName = null; //源程序的Application类名
        try {
            //获取AndroidManifest.xml 文件中的 <meta-data> 元素
            ApplicationInfo applicationInfo = getPackageManager().getApplicationInfo(this.getPackageName(), PackageManager.GET_META_DATA);
            Bundle metaData = applicationInfo.metaData;
            //获取xml文件声明的Application类
            if (metaData != null && metaData.containsKey("APPLICATION_CLASS_NAME")){
                appClassName = metaData.getString("APPLICATION_CLASS_NAME");
            } else {
                log("源程序中没有自定义Application");
                return false; //如果不存在直接返回,使用壳的application即可
            }
        } catch (PackageManager.NameNotFoundException e) {
            log(Log.getStackTraceString(e));
        }
 
        //源程序存在自定义application类,开始替换
        log("Try to replace Application");
 
        //1.反射获取ActivityThread实例
        Object sCurrentActivityThreadObj = Reflection.getStaticField("android.app.ActivityThread","sCurrentActivityThread");
        log("ActivityThread: " + sCurrentActivityThreadObj.toString());
 
        //2.获取并设置LoadedApk
        //获取mBoundApplication (AppBindData对象)
        Object mBoundApplicationObj = Reflection.getField("android.app.ActivityThread",sCurrentActivityThreadObj,"mBoundApplication") ;
        log("mBoundApplication: "+mBoundApplicationObj.toString());
        //获取mBoundApplication.info (即LoadedApk)
        Object infoObj = Reflection.getField("android.app.ActivityThread$AppBindData",mBoundApplicationObj,"info");
        log( "LoadedApk: " + infoObj.toString());
        //把LoadedApk的mApplication设置为null,这样后续才能调用makeApplication() 否则由于已存在Application,无法进行替换
        Reflection.setField("android.app.LoadedApk","mApplication",infoObj,null);
 
        //3.获取ActivityThread.mInitialApplication 即拿到旧的Application(对于要加载的Application来讲)
        Object mInitialApplicationObj = Reflection.getField("android.app.ActivityThread",sCurrentActivityThreadObj,"mInitialApplication");
        log("mInitialApplicationObj: " + mInitialApplicationObj.toString());
 
        //4.获取ActivityThread.mAllApplications并删除旧的application
        ArrayList<Application> mAllApplicationsObj = (ArrayList<Application>) Reflection.getField("android.app.ActivityThread",sCurrentActivityThreadObj,"mAllApplications");
        mAllApplicationsObj.remove(mInitialApplicationObj);
        log("mInitialApplication 从 mAllApplications 中移除成功");
 
        //5.重置相关类的Application类名 便于后续创建Application
        //获取LoadedApk.mApplicationInfo
        ApplicationInfo applicationInfo = (ApplicationInfo) Reflection.getField("android.app.LoadedApk",infoObj,"mApplicationInfo");
        log( "LoadedApk.mApplicationInfo: " + applicationInfo.toString());
        //获取mBoundApplication.appInfo
        ApplicationInfo appinfoInAppBindData = (ApplicationInfo) Reflection.getField("android.app.ActivityThread$AppBindData",mBoundApplicationObj,"appInfo");
        log("ActivityThread.mBoundApplication.appInfo: " + appinfoInAppBindData.toString());
        //此处通过引用修改值,虽然后续没有使用,但是实际上是修改其指向的LoadedApk相关字段的值
        //设置两个appinfo的classname为源程序的application类名,以便后续调用makeApplication()创建源程序的application
        applicationInfo.className = appClassName;
        appinfoInAppBindData.className = appClassName;
        log("Source Application name: " + appClassName);
 
        //6.反射调用makeApplication方法创建源程序的application
        Application application = (Application) Reflection.invokeMethod("android.app.LoadedApk","makeApplication",infoObj,new Class[]{boolean.class, Instrumentation.class},new Object[]{false,null}); //使用源程序中的application
        //Application app = (Application)ReflectionMethods.invokeMethod("android.app.LoadedApk","makeApplication",infoObj,new Class[]{boolean.class, Instrumentation.class},new Object[]{true,null}); //使用自定义的application 强制为系统默认
        log("Create source Application succeed: "+application);
 
        //7.重置ActivityThread.mInitialApplication为新的Application
        Reflection.setField("android.app.ActivityThread","mInitialApplication",sCurrentActivityThreadObj,application);
        log("Reset ActivityThread.mInitialApplication by new Application succeed!");
 
        //8.ContentProvider会持有代理的Application,需要特殊处理一下
        ArrayMap mProviderMap = (ArrayMap) Reflection.getField("android.app.ActivityThread",sCurrentActivityThreadObj,"mProviderMap");
        log("ActivityThread.mProviderMap: " + mProviderMap);
 
        //获取所有provider,装进迭代器中遍历
        Iterator iterator = mProviderMap.values().iterator();
        while(iterator.hasNext()){
            Object providerClientRecord = iterator.next();
            //获取ProviderClientRecord.mLocalProvider,可能为空
            Object mLocalProvider = Reflection.getField("android.app.ActivityThread$ProviderClientRecord",providerClientRecord,"mLocalProvider") ;
            if(mLocalProvider != null){
                log("ProviderClientRecord.mLocalProvider: " + mLocalProvider);
                //获取ContentProvider中的mContext字段,设置为新的Application
                Reflection.setField("android.content.ContentProvider","mContext",mLocalProvider,application);
            }
        }
 
        log( "Run Application.onCreate" );
        application.onCreate(); //源程序,启动!
        return true;
    }
    // 替换壳App的ClassLoader为源App的ClassLoader
    private void replaceClassLoader() {
        //1.获取当前的classloader
        ClassLoader classLoader = this.getClassLoader();
        log("Current ClassLoader: " + classLoader.toString());
        log("Parent ClassLoader: " + classLoader.getParent().toString());
 
        //2.反射获取ActivityThread
        Object sCurrentActivityThreadObj = Reflection.getStaticField("android.app.ActivityThread","sCurrentActivityThread");
        log("ActivityThread.sCurrentActivityThread: " + sCurrentActivityThreadObj.toString());
 
        //3.反射获取LoadedApk
        //获取当前ActivityThread实例的mPackages字段 类型为ArrayMap<String, WeakReference<LoadedApk>>, 里面存放了当前应用的LoadedApk对象
        ArrayMap mPackagesObj = (ArrayMap) Reflection.getField("android.app.ActivityThread",sCurrentActivityThreadObj,"mPackages");
        log( "mPackagesObj: " + mPackagesObj.toString());
 
        //获取mPackages中的当前应用包名
        String currentPackageName = this.getPackageName();
        log("currentPackageName: " + currentPackageName);
 
        // 获取loadedApk实例也有好几种,mInitialApplication mAllApplications mPackages
        // 通过包名获取当前应用的loadedApk实例
        WeakReference weakReference = (WeakReference) mPackagesObj.get(currentPackageName);
        Object loadedApkObj = weakReference.get();
        log( "LoadedApk: " + loadedApkObj.toString());
 
        //4.替换ClassLoader
        DexClassLoader dexClassLoader = new DexClassLoader(dexPath, odexPath,libPath, classLoader.getParent()); //动态加载源程序的dex文件,以当前classloader的父加载器作为parent
        Reflection.setField("android.app.LoadedApk","mClassLoader",loadedApkObj,dexClassLoader); //替换当前loadedApk实例中的mClassLoader字段
        log("New DexClassLoader: " + dexClassLoader);
    }
}
package com.example.androidshell;
 
import android.app.Application;
import android.app.Instrumentation;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.AssetManager;
import android.os.Bundle;
import android.util.ArrayMap;
import android.util.Log;
 
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.WeakReference;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
 
import dalvik.system.DexClassLoader;
 
public class ThirdProxyApplication extends Application {
    private final String TAG="glass";
    private String dexPath;
    private String odexPath;
    private String libPath;
    public void log(String message){Log.d(TAG,message);}
 
    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);
        log("ThirdProxyApplication.attachBaseContext is running!");
        System.loadLibrary("androidshell");     //主动加载so,设置hook,进行指令回填
        log("Load libandroidshell.so succeed!");
        try {
            //初始化相关环境
            initEnvironments();
            //配置加载源程序的动态环境,即替换mClassLoader
            replaceClassLoader();
        } catch (Exception e) {
            log( Log.getStackTraceString(e));
        }
    }
    @Override
    public void onCreate() {
        super.onCreate();
        log("ThirdProxyApplication.onCreate is running!");
        if(replaceApplication())
            log("Replace Application succeed!");
    }
 
    private void initEnvironments() throws IOException {
        //1.设置相关目录和路径
        File dex = getDir("tmp_dex", MODE_PRIVATE);     // 私有目录,存放dex文件
        //File lib = getDir("tmp_lib", MODE_PRIVATE);       // lib可使用默认目录
        //libPath = lib.getAbsolutePath();
        odexPath = dex.getAbsolutePath();
        libPath=this.getApplicationInfo().nativeLibraryDir; //默认lib路径
        dexPath =this.getApplicationInfo().sourceDir;   //当前base.apk路径
        //2.从当前base.apk读取classes.dex并读取为字节数组
        byte[] shellDexData = readDexFromApk();
        log("Get classes.dex from base.apk succeed!");
        //3.从壳dex文件中分离出源dex文件
        ByteBuffer[] byteBuffers = extractDexFilesFromShellDex(shellDexData);
        //4.将源dex文件依次写入私有目录
        writeByteBuffersToDirectory(byteBuffers, odexPath);
        //5.将codes文件依次写入私有目录
        copyClassesCodesFiles(this, odexPath);
        //6.拼接dex目录字符串,设置dex文件路径 DexClassLoader支持传递多个dex文件路径以加载多个dex文件,通过':'分隔路径
        StringBuffer dexFiles=new StringBuffer();
        for(File file:dex.listFiles()){
            if(file.getName().contains(".codes"))
                continue;
            dexFiles.append(file.getAbsolutePath());
            dexFiles.append(":");
        }
        dexPath=dexFiles.toString();
    }
    private void writeByteBuffersToDirectory(ByteBuffer[] byteBuffers, String directoryPath) throws IOException {
        // 创建目录对象
        File directory = new File(directoryPath);
        // 检查目录是否存在,不存在则创建
        if (!directory.exists()) {
            if (!directory.mkdirs()) {
                throw new IOException("无法创建目录: " + directoryPath);
            }
        }
 
        // 遍历 ByteBuffer 数组
        for (int i = 0; i < byteBuffers.length; i++) {
            // 生成文件名
            String fileName;
            if (i == 0) {
                fileName = "classes.dex";
            } else {
                fileName = "classes" + (i + 1) + ".dex";
            }
            // 构建文件对象
            File file = new File(directory, fileName);
            // 创建文件输出流
            try (FileOutputStream fos = new FileOutputStream(file)) {
                // 获取 ByteBuffer 中的字节数组
                ByteBuffer buffer = byteBuffers[i];
                byte[] bytes = new byte[buffer.remaining()];
                buffer.get(bytes);
                // 将字节数组写入文件
                fos.write(bytes);
            }
        }
    }
    private void copyClassesCodesFiles(Context context, String targetDirectoryPath) {
        AssetManager assetManager = context.getAssets();
        try {
            // 获取 assets 目录下的所有文件和文件夹
            String[] files = assetManager.list("");
            if (files != null) {
                // 创建目标目录
                File targetDirectory = new File(targetDirectoryPath);
                if (!targetDirectory.exists()) {
                    if (!targetDirectory.mkdirs()) {
                        throw new IOException("无法创建目标目录: " + targetDirectoryPath);
                    }
                }
                for (String fileName : files) {
                    // 筛选以 classes 开头且以 .codes 结尾的文件
                    if (fileName.startsWith("classes") && fileName.endsWith(".codes")) {
                        try (InputStream inputStream = assetManager.open(fileName);
                             BufferedInputStream bis = new BufferedInputStream(inputStream);
                             FileOutputStream fos = new FileOutputStream(new File(targetDirectory, fileName));
                             BufferedOutputStream bos = new BufferedOutputStream(fos)) {
 
                            byte[] buffer = new byte[1024];
                            int bytesRead;
                            while ((bytesRead = bis.read(buffer)) != -1) {
                                bos.write(buffer, 0, bytesRead);
                            }
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
 
    // 从壳dex文件中提取源apk的dex并封装为ByteBuffer
    private ByteBuffer[] extractDexFilesFromShellDex(byte[] shellDexData) {
        int shellDexlength = shellDexData.length;
        //开始解析dex文件
        byte[] sourceDexsSizeByte = new byte[4];
        //读取源dexs的大小
        System.arraycopy(shellDexData,shellDexlength - 4, sourceDexsSizeByte,0,4);
        //转成bytebuffer,方便4byte转int
        ByteBuffer wrap = ByteBuffer.wrap(sourceDexsSizeByte);
        //将byte转成int, 加壳时,长度按小端存储
        int sourceDexsSizeInt = wrap.order(ByteOrder.LITTLE_ENDIAN).getInt();
        Log.d(TAG, "源dex集合的大小: " + sourceDexsSizeInt);
        //读取源dexs
        byte[] sourceDexsData = new byte[sourceDexsSizeInt];
        System.arraycopy(shellDexData,shellDexlength - sourceDexsSizeInt - 4, sourceDexsData, 0, sourceDexsSizeInt);
        //解密源dexs
        sourceDexsData = decrypt(sourceDexsData);
 
        //更新部分
        //从源dexs中分离dex
        ArrayList<byte[]> sourceDexList = new ArrayList<>();
        int pos = 0;
        while(pos < sourceDexsSizeInt){
            //先提取四个字节,描述当前dex的大小
            //开始解析dex文件
            byte[] singleDexSizeByte = new byte[4];
            //读取源dexs的大小
            System.arraycopy(sourceDexsData, pos, singleDexSizeByte,0,4);
            //转成bytebuffer,方便4byte转int
            ByteBuffer singleDexwrap = ByteBuffer.wrap(singleDexSizeByte);
            int singleDexSizeInt = singleDexwrap.order(ByteOrder.LITTLE_ENDIAN).getInt();
            Log.d(TAG, "当前Dex的大小: " + singleDexSizeInt);
            //读取单独dex
            byte[] singleDexData = new byte[singleDexSizeInt];
            System.arraycopy(sourceDexsData,pos + 4, singleDexData, 0, singleDexSizeInt);
            //加入到dexlist中
            sourceDexList.add(singleDexData);
            //更新pos
            pos += 4 + singleDexSizeInt;
        }
 
        //将dexlist包装成ByteBuffer
        int dexNum = sourceDexList.size();
        Log.d(TAG, "源dex的数量: " + dexNum);
        ByteBuffer[] dexBuffers = new ByteBuffer[dexNum];
        for (int i = 0; i < dexNum; i++){
            dexBuffers[i] = ByteBuffer.wrap(sourceDexList.get(i));
        }
 
        return dexBuffers;
 
    }
    // 从当前程序的apk读取dex文件并存储为字节数组
    private byte[] readDexFromApk() throws IOException {
        //1.获取当前应用程序的源码路径(apk),一般是data/app目录下,该目录用于存放用户安装的软件
        String sourceDir = this.getApplicationInfo().sourceDir;
        log("this.getApplicationInfo().sourceDir: " +sourceDir);
 
        //2.创建相关输入流
        FileInputStream fileInputStream = new FileInputStream(sourceDir);
        BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream);
        ZipInputStream zipInputStream = new ZipInputStream(bufferedInputStream); //用于解析apk文件
 
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); //用于存放dex文件
 
        //3.遍历apk的所有文件并提取dex文件
        ZipEntry zipEntry;
        while((zipEntry = zipInputStream.getNextEntry()) != null){ //存在下一个文件
            // 将classes.dex文件存储到bytearray中 壳dex和源apk合并后只保留一个dex便于处理
            if (zipEntry.getName().equals("classes.dex")){
                byte[] bytes = new byte[1024];
                int num;
                while((num = zipInputStream.read(bytes))!=-1){      //每次读取1024byte,返回读取到的byte数
                    byteArrayOutputStream.write(bytes,0, num); //存放到开辟的byteArrayOutputStream中
                }
            }
            zipInputStream.closeEntry(); //关闭当前文件
        }
        zipInputStream.close();
 
        log("Read dex from apk succeed!");
        return byteArrayOutputStream.toByteArray(); //将读取到的dex文件以字节数组形式返回
    }
    // 解密
    private byte[] decrypt(byte[] data) {
        for (int i = 0; i < data.length; i++){
            data[i] ^= (byte) 0xff;
        }
        return data;
 
    }
    //替换壳程序LoadedApk的Application为源程序Application,并调用其onCreate方法
    private boolean replaceApplication(){
        // Application实例存在于: LoadedApk.mApplication
        // 以及ActivityThread的mInitialApplication和mAllApplications和mBoundApplication
 
        //判断源程序是否使用自定义Application 若使用则需要进行替换,若未使用则直接返回,使用壳的默认Application即可
 
        String appClassName = null; //源程序的Application类名
        try {
            //获取AndroidManifest.xml 文件中的 <meta-data> 元素
            ApplicationInfo applicationInfo = getPackageManager().getApplicationInfo(this.getPackageName(), PackageManager.GET_META_DATA);
            Bundle metaData = applicationInfo.metaData;
            //获取xml文件声明的Application类
            if (metaData != null && metaData.containsKey("APPLICATION_CLASS_NAME")){
                appClassName = metaData.getString("APPLICATION_CLASS_NAME");
            } else {
                log("源程序中没有自定义Application");
                return false; //如果不存在直接返回,使用壳的application即可
            }
        } catch (PackageManager.NameNotFoundException e) {
            log(Log.getStackTraceString(e));
        }
 
        //源程序存在自定义application类,开始替换
        log("Try to replace Application");
 
        //1.反射获取ActivityThread实例
        Object sCurrentActivityThreadObj = Reflection.getStaticField("android.app.ActivityThread","sCurrentActivityThread");
        log("ActivityThread: " + sCurrentActivityThreadObj.toString());
 
        //2.获取并设置LoadedApk
        //获取mBoundApplication (AppBindData对象)
        Object mBoundApplicationObj = Reflection.getField("android.app.ActivityThread",sCurrentActivityThreadObj,"mBoundApplication") ;
        log("mBoundApplication: "+mBoundApplicationObj.toString());
        //获取mBoundApplication.info (即LoadedApk)
        Object infoObj = Reflection.getField("android.app.ActivityThread$AppBindData",mBoundApplicationObj,"info");
        log( "LoadedApk: " + infoObj.toString());
        //把LoadedApk的mApplication设置为null,这样后续才能调用makeApplication() 否则由于已存在Application,无法进行替换
        Reflection.setField("android.app.LoadedApk","mApplication",infoObj,null);
 
        //3.获取ActivityThread.mInitialApplication 即拿到旧的Application(对于要加载的Application来讲)
        Object mInitialApplicationObj = Reflection.getField("android.app.ActivityThread",sCurrentActivityThreadObj,"mInitialApplication");
        log("mInitialApplicationObj: " + mInitialApplicationObj.toString());
 
        //4.获取ActivityThread.mAllApplications并删除旧的application
        ArrayList<Application> mAllApplicationsObj = (ArrayList<Application>) Reflection.getField("android.app.ActivityThread",sCurrentActivityThreadObj,"mAllApplications");
        mAllApplicationsObj.remove(mInitialApplicationObj);
        log("mInitialApplication 从 mAllApplications 中移除成功");
 
        //5.重置相关类的Application类名 便于后续创建Application
        //获取LoadedApk.mApplicationInfo
        ApplicationInfo applicationInfo = (ApplicationInfo) Reflection.getField("android.app.LoadedApk",infoObj,"mApplicationInfo");
        log( "LoadedApk.mApplicationInfo: " + applicationInfo.toString());
        //获取mBoundApplication.appInfo
        ApplicationInfo appinfoInAppBindData = (ApplicationInfo) Reflection.getField("android.app.ActivityThread$AppBindData",mBoundApplicationObj,"appInfo");
        log("ActivityThread.mBoundApplication.appInfo: " + appinfoInAppBindData.toString());
        //此处通过引用修改值,虽然后续没有使用,但是实际上是修改其指向的LoadedApk相关字段的值
        //设置两个appinfo的classname为源程序的application类名,以便后续调用makeApplication()创建源程序的application
        applicationInfo.className = appClassName;
        appinfoInAppBindData.className = appClassName;
        log("Source Application name: " + appClassName);
 
        //6.反射调用makeApplication方法创建源程序的application
        Application application = (Application) Reflection.invokeMethod("android.app.LoadedApk","makeApplication",infoObj,new Class[]{boolean.class, Instrumentation.class},new Object[]{false,null}); //使用源程序中的application
        //Application app = (Application)ReflectionMethods.invokeMethod("android.app.LoadedApk","makeApplication",infoObj,new Class[]{boolean.class, Instrumentation.class},new Object[]{true,null}); //使用自定义的application 强制为系统默认
        log("Create source Application succeed: "+application);
 
        //7.重置ActivityThread.mInitialApplication为新的Application
        Reflection.setField("android.app.ActivityThread","mInitialApplication",sCurrentActivityThreadObj,application);
        log("Reset ActivityThread.mInitialApplication by new Application succeed!");
 
        //8.ContentProvider会持有代理的Application,需要特殊处理一下
        ArrayMap mProviderMap = (ArrayMap) Reflection.getField("android.app.ActivityThread",sCurrentActivityThreadObj,"mProviderMap");
        log("ActivityThread.mProviderMap: " + mProviderMap);
 
        //获取所有provider,装进迭代器中遍历
        Iterator iterator = mProviderMap.values().iterator();
        while(iterator.hasNext()){
            Object providerClientRecord = iterator.next();
            //获取ProviderClientRecord.mLocalProvider,可能为空
            Object mLocalProvider = Reflection.getField("android.app.ActivityThread$ProviderClientRecord",providerClientRecord,"mLocalProvider") ;
            if(mLocalProvider != null){
                log("ProviderClientRecord.mLocalProvider: " + mLocalProvider);
                //获取ContentProvider中的mContext字段,设置为新的Application
                Reflection.setField("android.content.ContentProvider","mContext",mLocalProvider,application);
            }
        }
 
        log( "Run Application.onCreate" );
        application.onCreate(); //源程序,启动!
        return true;
    }
    // 替换壳App的ClassLoader为源App的ClassLoader
    private void replaceClassLoader() {
        //1.获取当前的classloader
        ClassLoader classLoader = this.getClassLoader();
        log("Current ClassLoader: " + classLoader.toString());
        log("Parent ClassLoader: " + classLoader.getParent().toString());
 
        //2.反射获取ActivityThread
        Object sCurrentActivityThreadObj = Reflection.getStaticField("android.app.ActivityThread","sCurrentActivityThread");
        log("ActivityThread.sCurrentActivityThread: " + sCurrentActivityThreadObj.toString());
 
        //3.反射获取LoadedApk
        //获取当前ActivityThread实例的mPackages字段 类型为ArrayMap<String, WeakReference<LoadedApk>>, 里面存放了当前应用的LoadedApk对象
        ArrayMap mPackagesObj = (ArrayMap) Reflection.getField("android.app.ActivityThread",sCurrentActivityThreadObj,"mPackages");
        log( "mPackagesObj: " + mPackagesObj.toString());
 
        //获取mPackages中的当前应用包名
        String currentPackageName = this.getPackageName();
        log("currentPackageName: " + currentPackageName);
 
        // 获取loadedApk实例也有好几种,mInitialApplication mAllApplications mPackages
        // 通过包名获取当前应用的loadedApk实例
        WeakReference weakReference = (WeakReference) mPackagesObj.get(currentPackageName);
        Object loadedApkObj = weakReference.get();
        log( "LoadedApk: " + loadedApkObj.toString());
 
        //4.替换ClassLoader
        DexClassLoader dexClassLoader = new DexClassLoader(dexPath, odexPath,libPath, classLoader.getParent()); //动态加载源程序的dex文件,以当前classloader的父加载器作为parent
        Reflection.setField("android.app.LoadedApk","mClassLoader",loadedApkObj,dexClassLoader); //替换当前loadedApk实例中的mClassLoader字段
        log("New DexClassLoader: " + dexClassLoader);
    }
}
#include <jni.h>
#include <string>
#include <unistd.h>
#include <map>
#include <fstream>
#include <stdlib.h>
#include <elf.h>
#include <dlfcn.h>
#include "android/log.h"
#include "sys/mman.h"
#include "bytehook.h"
#include "dobby/dobby.h"
#include "dex/DexFile.h"
#include "dex/CodeItem.h"
#include "dex/class_accessor.h"
#define TAG "glass"
#define logd(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__);
#define logi(...) __android_log_print(ANDROID_LOG_INFO , TAG, __VA_ARGS__);
#define loge(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__);
 
// sdk版本,用于兼容适配
int APILevel;
// 函数声明
void hook();
void hookExecve();
void hookMmap();
void hook_LoadMethod();
 
// 抽取代码文件,与源.dex在同一私有目录
std::string codeFilePostfix = ".codes";
 
//dex文件名->codeOff->CodeItem 每个dex文件对应一个map,每个map内的codeoff对应一个CodeItem
std::map<std::string,std::map<uint32_t, CodeItem>> codeMapList;
 
//art/runtime/class_linker.h
// 函数声明
static void (*g_originLoadMethod)(void* thiz,
                                  const DexFile* dex_file,
                                  ClassAccessor::Method* method,
                                  void* klass,
                                  void* dst);
/*//Android 10-14 原型如下
void LoadMethod(const DexFile& dex_file,
                const ClassAccessor::Method& method,
                Handle<mirror::Class> klass,
                ArtMethod* dst);
*/
 
 
// Tool functions
 
// 以二进制形式读取整个文件,返回字节数组并返回文件长度
uint8_t* readFileToBytes(const std::string fileName,size_t* readSize) {
    FILE *file = fopen(fileName.c_str(), "rb");
    if (file == NULL) {
        logd("Error opening file");
        fclose(file);
        return NULL;
    }
    fseek(file, 0,SEEK_END);
    size_t fileSize = ftell(file);
    fseek(file, 0,SEEK_SET);
    uint8_t *buffer = (uint8_t *) malloc(fileSize);
    if (buffer == NULL) {
        logd("Error allocating memory\n");
        fclose(file);
        return NULL;
    }
    size_t bytesRead = fread(buffer, 1, fileSize, file);
    if(bytesRead!=fileSize) {
        logd("Read bytes not equal file size!\n");
        free(buffer);
        fclose(file);
        return NULL;
    }
    fclose(file);
    if(readSize)
        *readSize=bytesRead;
    return buffer;
}
 
// 4字节数组转uint32_t
uint32_t bytes2uint32(unsigned char * bytes){
    uint32_t retnum = 0;
    for(int i = 3;i >=0;i--){
        retnum <<= 8;
        retnum |= bytes[i];
    }
    return retnum;
}
 
const char * getArtLibPath() {
    if(APILevel < 29) {
        return "/system/lib64/libart.so";
    } else if(APILevel == 29) {
        return "/apex/com.android.runtime/lib64/libart.so";
    } else {
        return "/apex/com.android.art/lib64/libart.so";
    }
}
 
const char * getArtBaseLibPath() {
    if(APILevel == 29) {
        return "/apex/com.android.runtime/lib64/libartbase.so";
    } else {
        return "/apex/com.android.art/lib64/libartbase.so";
    }
}
 
const char* find_symbol_in_elf_file(const char *elf_file,int keyword_count,...) {
    FILE *elf_fp = fopen(elf_file, "r");
    if (elf_fp) {
        // 获取elf文件大小
        fseek(elf_fp, 0L, SEEK_END);
        size_t lib_size = ftell(elf_fp);
        fseek(elf_fp, 0L, SEEK_SET);
        // 读取elf文件数据
        char *data = (char *) calloc(lib_size, 1);
        fread(data, 1, lib_size, elf_fp);
        char *elf_bytes_data = data;
        // elf头
        Elf64_Ehdr *ehdr = (Elf64_Ehdr *) elf_bytes_data;
        // 节头
        Elf64_Shdr *shdr = (Elf64_Shdr *) (((uint8_t *) elf_bytes_data) + ehdr->e_shoff);
        va_list kw_list;
        // 遍历节
        for (int i = 0; i < ehdr->e_shnum; i++) {
            // 字符串表
            if (shdr->sh_type == SHT_STRTAB) {
                const char *str_base = (char *) ((uint8_t *) elf_bytes_data + shdr->sh_offset);
                char *ptr = (char *) str_base;
                // 遍历字符串表
                for (int k = 0; ptr < (str_base + shdr->sh_size); k++) {
                    const char *item_value = ptr;
                    size_t item_len = strnlen(item_value, 128);
                    ptr += (item_len + 1);
                    if (item_len == 0) {
                        continue;
                    }
                    int match_count = 0;
                    va_start(kw_list, keyword_count);
                    for (int n = 0; n < keyword_count; n++) {
                        const char *keyword = va_arg(kw_list, const char*);
                        if (strstr(item_value, keyword)) {
                            match_count++;
                        }
                    }
                    va_end(kw_list);
                    if (match_count == keyword_count) {
                        return item_value;
                    }
                }
                break;
            }
            shdr++;
        }
        fclose(elf_fp);
        free(data);
    }
    return nullptr;
}
 
const char * getClassLinkerDefineClassLibPath(){
    return getArtLibPath();
}
 
 
const char * getClassLinkerDefineClassSymbol() {
    const char * sym = find_symbol_in_elf_file(getClassLinkerDefineClassLibPath(),2,"ClassLinker","DefineClass");
    return sym;
}
 
 
const char * getClassLinkerLoadMethodLibPath(){
    return getArtLibPath();
}
 
//获取ClassLinker::LoadMethod真实符号名
const char * getClassLinkerLoadMethodSymbol() {
    const char * sym = find_symbol_in_elf_file(getClassLinkerLoadMethodLibPath(),2,"ClassLinker","LoadMethod");
    return sym;
}
 
//获取libart真实名称
const char * getArtLibName() {
    //Android 10及以后变为libartbase.so
    return APILevel >= 29 ? "libartbase.so" : "libart.so";
}
 
// 禁用dex2oat
int fakeExecve(const char *pathname, char *const argv[], char *const envp[]) {
    BYTEHOOK_STACK_SCOPE();
    // 禁用dex2oat
    if (strstr(pathname, "dex2oat") != nullptr) {
        errno = EACCES;
        return -1;
    }
    return BYTEHOOK_CALL_PREV(fakeExecve, pathname, argv, envp);
}
 
void hookExecve(){
    bytehook_stub_t stub = bytehook_hook_single(
            getArtLibName(),
            "libc.so",
            "execve",
            (void *) fakeExecve,
            nullptr,
            nullptr);
    if (stub != nullptr) {
        logd("hook execve done");
    }
}
 
//为dex文件添加可写权限
void* fakeMmap(void * __addr, size_t __size, int __prot, int __flags, int __fd, off_t __offset){
    BYTEHOOK_STACK_SCOPE();
    int prot = __prot;
    int hasRead = (__prot & PROT_READ) == PROT_READ;
    int hasWrite = (__prot & PROT_WRITE) == PROT_WRITE;
    // 添加写权限
    if(hasRead && !hasWrite) {
        prot |= PROT_WRITE;
    }
    void * addr = BYTEHOOK_CALL_PREV(fakeMmap, __addr, __size, prot, __flags, __fd, __offset);
    return addr;
}
 
void hookMmap(){
    bytehook_stub_t stub = bytehook_hook_single(
            getArtLibName(),
            "libc.so",
            "mmap",
            (void *) fakeMmap,
            nullptr,
            nullptr);
    if(stub != nullptr){
        logd("hook mmap done");
    }
}
 
// 解析抽取代码文件,每个dex.codes只解析一次
void parseExtractedCodeFiles(const std::string& dexPath){
    //1.读取代码文件为字节数组
    std::string codeFilePath=dexPath+codeFilePostfix;
    logd("Code File Path: %s",codeFilePath.c_str());
    size_t codeBytesLen = 0;
    uint8_t* codeBytes = readFileToBytes(codeFilePath, &codeBytesLen);
    if(codeBytes == nullptr || codeBytesLen == 0) {
        logd("Code file not found!")
        return;
    }
    logd("CodeFile: %s Len:%#llx", codeFilePath.c_str(),codeBytesLen);
 
    // 2.解析代码字节数组
    size_t offset=0;
    while(offset<codeBytesLen){
        uint8_t* pointer = codeBytes + offset;  //每个结构的起点指针
        uint32_t codeOff = bytes2uint32(pointer); // 4字节CodeOff和4字节InsnSize
        uint32_t insnSize = bytes2uint32(pointer+4);
        if(codeOff == 0 || insnSize == 0){
            logd("CodeOff or InsnSize equals 0!")
            break;
        }
        logd("CodeOff: %#x InsnSize: %#x", codeOff, insnSize);
        // 创建抽取代码对象
        CodeItem codeItem = CodeItem(insnSize, pointer+8);
        // 添加一组CodeOff:CodeItem映射
        codeMapList[dexPath].insert(std::pair<uint32_t, CodeItem>(codeOff, codeItem));
        logd("CodeItem codeOff: %#x insnSize: %#x has created!", codeOff, insnSize);
        offset += 8 + insnSize*2; //跳过CodeOff,InsnSize和Insn[]
    }
}
 
// 回填dex的方法代码,每次只回填一个Method
void innerLoadMethod(void* thiz, const DexFile* dexFile, ClassAccessor::Method* method, void* klass, void* dest){
    // dex文件路径
    std::string location = dexFile->location_;
    //logd("Load Dex File Location: %s",location.c_str())
    // 判断是否为解密释放的dex文件,位于私有目录内
    if(location.find("app_tmp_dex") == std::string::npos){
        return;
    }
    // 如果未解析过dexCodes文件则进行解析,每个dex文件只解析一次,创建对应的map<CodeOff,CodeItem>映射
    if(codeMapList.find(location)==codeMapList.end()){
        logd("Parse dex file %s codes",location.c_str());
        codeMapList[location]=std::map<uint32_t,CodeItem>(); //创建新的codeMap
        parseExtractedCodeFiles(location);
    }
    // 不存在DexCode 直接跳过
    if(method->code_off_==0){
        return;
    }
    // 指令地址
    uint8_t* codeAddr = (uint8_t*)(dexFile->begin_ + method->code_off_ + 16); //insn结构前面有16字节
 
    //logd("MethodCodeOff: %d",method->code_off_);
    // 回填指令
    std::map<uint32_t,CodeItem> codeMap=codeMapList[location];
    // 似乎没有走到回填指令处 (注意c++浅拷贝问题,不能随意delete)
    if(codeMap.find(method->code_off_) != codeMap.end()){
        CodeItem codeItem = codeMap[method->code_off_];
        memcpy(codeAddr,codeItem.getInsns(),codeItem.getInsnsSize()*2); //注意指令为u2类型,长度需要*2
    }
}
 
void newLoadMethod(void* thiz, const DexFile* dex_file, ClassAccessor::Method* method, void* klass, void* dest){
    if(g_originLoadMethod!= nullptr){
        // 先回填指令,再调用
        innerLoadMethod(thiz,dex_file,method,klass,dest);
        g_originLoadMethod(thiz,dex_file,method, klass, dest);
    }
    return;
}
 
void hook_LoadMethod(){
    void * loadMethodAddress =  DobbySymbolResolver(getClassLinkerLoadMethodLibPath(),getClassLinkerLoadMethodSymbol());
    DobbyHook(loadMethodAddress, (void *) newLoadMethod, (void **) &g_originLoadMethod);
    logd("hook LoadMethod done");
}
 
// 初始函数,实现hook
extern "C"
void _init(){
    APILevel = android_get_device_api_level();
    logd("Android API Level: %d", APILevel)
    logd("Setting hook...")
    hook();
}
// hook
void hook(){
    bytehook_init(BYTEHOOK_MODE_AUTOMATIC, false);
    hookExecve();   // 禁用dex2oat
    hookMmap();     // 使dex文件可写
    //hook_DefineClass(); //需手动解析ClassDef
    hook_LoadMethod();  // 加载方法时回填指令
}
#include <jni.h>
#include <string>
#include <unistd.h>
#include <map>
#include <fstream>
#include <stdlib.h>
#include <elf.h>
#include <dlfcn.h>
#include "android/log.h"
#include "sys/mman.h"
#include "bytehook.h"
#include "dobby/dobby.h"
#include "dex/DexFile.h"
#include "dex/CodeItem.h"
#include "dex/class_accessor.h"
#define TAG "glass"
#define logd(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__);
#define logi(...) __android_log_print(ANDROID_LOG_INFO , TAG, __VA_ARGS__);
#define loge(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__);
 
// sdk版本,用于兼容适配
int APILevel;
// 函数声明
void hook();
void hookExecve();
void hookMmap();
void hook_LoadMethod();
 
// 抽取代码文件,与源.dex在同一私有目录
std::string codeFilePostfix = ".codes";
 
//dex文件名->codeOff->CodeItem 每个dex文件对应一个map,每个map内的codeoff对应一个CodeItem
std::map<std::string,std::map<uint32_t, CodeItem>> codeMapList;
 
//art/runtime/class_linker.h
// 函数声明
static void (*g_originLoadMethod)(void* thiz,
                                  const DexFile* dex_file,
                                  ClassAccessor::Method* method,
                                  void* klass,
                                  void* dst);
/*//Android 10-14 原型如下
void LoadMethod(const DexFile& dex_file,
                const ClassAccessor::Method& method,
                Handle<mirror::Class> klass,
                ArtMethod* dst);
*/
 
 
// Tool functions
 
// 以二进制形式读取整个文件,返回字节数组并返回文件长度
uint8_t* readFileToBytes(const std::string fileName,size_t* readSize) {
    FILE *file = fopen(fileName.c_str(), "rb");
    if (file == NULL) {
        logd("Error opening file");
        fclose(file);
        return NULL;
    }
    fseek(file, 0,SEEK_END);
    size_t fileSize = ftell(file);
    fseek(file, 0,SEEK_SET);
    uint8_t *buffer = (uint8_t *) malloc(fileSize);
    if (buffer == NULL) {
        logd("Error allocating memory\n");
        fclose(file);
        return NULL;
    }
    size_t bytesRead = fread(buffer, 1, fileSize, file);
    if(bytesRead!=fileSize) {
        logd("Read bytes not equal file size!\n");
        free(buffer);
        fclose(file);
        return NULL;
    }
    fclose(file);
    if(readSize)
        *readSize=bytesRead;
    return buffer;
}
 
// 4字节数组转uint32_t
uint32_t bytes2uint32(unsigned char * bytes){
    uint32_t retnum = 0;
    for(int i = 3;i >=0;i--){
        retnum <<= 8;
        retnum |= bytes[i];
    }
    return retnum;
}
 
const char * getArtLibPath() {
    if(APILevel < 29) {
        return "/system/lib64/libart.so";
    } else if(APILevel == 29) {
        return "/apex/com.android.runtime/lib64/libart.so";
    } else {
        return "/apex/com.android.art/lib64/libart.so";
    }
}
 
const char * getArtBaseLibPath() {
    if(APILevel == 29) {
        return "/apex/com.android.runtime/lib64/libartbase.so";
    } else {
        return "/apex/com.android.art/lib64/libartbase.so";
    }
}
 
const char* find_symbol_in_elf_file(const char *elf_file,int keyword_count,...) {
    FILE *elf_fp = fopen(elf_file, "r");
    if (elf_fp) {
        // 获取elf文件大小
        fseek(elf_fp, 0L, SEEK_END);
        size_t lib_size = ftell(elf_fp);
        fseek(elf_fp, 0L, SEEK_SET);
        // 读取elf文件数据
        char *data = (char *) calloc(lib_size, 1);
        fread(data, 1, lib_size, elf_fp);
        char *elf_bytes_data = data;
        // elf头
        Elf64_Ehdr *ehdr = (Elf64_Ehdr *) elf_bytes_data;
        // 节头
        Elf64_Shdr *shdr = (Elf64_Shdr *) (((uint8_t *) elf_bytes_data) + ehdr->e_shoff);
        va_list kw_list;
        // 遍历节
        for (int i = 0; i < ehdr->e_shnum; i++) {
            // 字符串表
            if (shdr->sh_type == SHT_STRTAB) {
                const char *str_base = (char *) ((uint8_t *) elf_bytes_data + shdr->sh_offset);
                char *ptr = (char *) str_base;
                // 遍历字符串表
                for (int k = 0; ptr < (str_base + shdr->sh_size); k++) {
                    const char *item_value = ptr;
                    size_t item_len = strnlen(item_value, 128);
                    ptr += (item_len + 1);
                    if (item_len == 0) {
                        continue;
                    }
                    int match_count = 0;
                    va_start(kw_list, keyword_count);
                    for (int n = 0; n < keyword_count; n++) {
                        const char *keyword = va_arg(kw_list, const char*);
                        if (strstr(item_value, keyword)) {
                            match_count++;
                        }
                    }
                    va_end(kw_list);
                    if (match_count == keyword_count) {
                        return item_value;
                    }
                }
                break;
            }
            shdr++;
        }
        fclose(elf_fp);
        free(data);
    }
    return nullptr;
}
 
const char * getClassLinkerDefineClassLibPath(){
    return getArtLibPath();
}
 
 
const char * getClassLinkerDefineClassSymbol() {
    const char * sym = find_symbol_in_elf_file(getClassLinkerDefineClassLibPath(),2,"ClassLinker","DefineClass");
    return sym;
}
 
 
const char * getClassLinkerLoadMethodLibPath(){
    return getArtLibPath();
}
 
//获取ClassLinker::LoadMethod真实符号名
const char * getClassLinkerLoadMethodSymbol() {
    const char * sym = find_symbol_in_elf_file(getClassLinkerLoadMethodLibPath(),2,"ClassLinker","LoadMethod");
    return sym;
}
 
//获取libart真实名称
const char * getArtLibName() {
    //Android 10及以后变为libartbase.so
    return APILevel >= 29 ? "libartbase.so" : "libart.so";
}
 
// 禁用dex2oat
int fakeExecve(const char *pathname, char *const argv[], char *const envp[]) {
    BYTEHOOK_STACK_SCOPE();
    // 禁用dex2oat
    if (strstr(pathname, "dex2oat") != nullptr) {
        errno = EACCES;
        return -1;
    }
    return BYTEHOOK_CALL_PREV(fakeExecve, pathname, argv, envp);
}
 
void hookExecve(){
    bytehook_stub_t stub = bytehook_hook_single(
            getArtLibName(),
            "libc.so",
            "execve",
            (void *) fakeExecve,
            nullptr,
            nullptr);
    if (stub != nullptr) {
        logd("hook execve done");
    }
}
 
//为dex文件添加可写权限
void* fakeMmap(void * __addr, size_t __size, int __prot, int __flags, int __fd, off_t __offset){
    BYTEHOOK_STACK_SCOPE();
    int prot = __prot;
    int hasRead = (__prot & PROT_READ) == PROT_READ;
    int hasWrite = (__prot & PROT_WRITE) == PROT_WRITE;
    // 添加写权限
    if(hasRead && !hasWrite) {
        prot |= PROT_WRITE;
    }
    void * addr = BYTEHOOK_CALL_PREV(fakeMmap, __addr, __size, prot, __flags, __fd, __offset);
    return addr;
}
 
void hookMmap(){
    bytehook_stub_t stub = bytehook_hook_single(
            getArtLibName(),
            "libc.so",
            "mmap",
            (void *) fakeMmap,
            nullptr,
            nullptr);
    if(stub != nullptr){
        logd("hook mmap done");
    }
}
 
// 解析抽取代码文件,每个dex.codes只解析一次
void parseExtractedCodeFiles(const std::string& dexPath){
    //1.读取代码文件为字节数组
    std::string codeFilePath=dexPath+codeFilePostfix;
    logd("Code File Path: %s",codeFilePath.c_str());
    size_t codeBytesLen = 0;
    uint8_t* codeBytes = readFileToBytes(codeFilePath, &codeBytesLen);
    if(codeBytes == nullptr || codeBytesLen == 0) {
        logd("Code file not found!")
        return;
    }
    logd("CodeFile: %s Len:%#llx", codeFilePath.c_str(),codeBytesLen);
 
    // 2.解析代码字节数组
    size_t offset=0;
    while(offset<codeBytesLen){
        uint8_t* pointer = codeBytes + offset;  //每个结构的起点指针
        uint32_t codeOff = bytes2uint32(pointer); // 4字节CodeOff和4字节InsnSize
        uint32_t insnSize = bytes2uint32(pointer+4);
        if(codeOff == 0 || insnSize == 0){
            logd("CodeOff or InsnSize equals 0!")
            break;
        }
        logd("CodeOff: %#x InsnSize: %#x", codeOff, insnSize);
        // 创建抽取代码对象
        CodeItem codeItem = CodeItem(insnSize, pointer+8);
        // 添加一组CodeOff:CodeItem映射
        codeMapList[dexPath].insert(std::pair<uint32_t, CodeItem>(codeOff, codeItem));
        logd("CodeItem codeOff: %#x insnSize: %#x has created!", codeOff, insnSize);
        offset += 8 + insnSize*2; //跳过CodeOff,InsnSize和Insn[]
    }
}
 
// 回填dex的方法代码,每次只回填一个Method
void innerLoadMethod(void* thiz, const DexFile* dexFile, ClassAccessor::Method* method, void* klass, void* dest){
    // dex文件路径
    std::string location = dexFile->location_;
    //logd("Load Dex File Location: %s",location.c_str())
    // 判断是否为解密释放的dex文件,位于私有目录内
    if(location.find("app_tmp_dex") == std::string::npos){
        return;
    }
    // 如果未解析过dexCodes文件则进行解析,每个dex文件只解析一次,创建对应的map<CodeOff,CodeItem>映射
    if(codeMapList.find(location)==codeMapList.end()){
        logd("Parse dex file %s codes",location.c_str());
        codeMapList[location]=std::map<uint32_t,CodeItem>(); //创建新的codeMap
        parseExtractedCodeFiles(location);
    }
    // 不存在DexCode 直接跳过
    if(method->code_off_==0){
        return;
    }
    // 指令地址
    uint8_t* codeAddr = (uint8_t*)(dexFile->begin_ + method->code_off_ + 16); //insn结构前面有16字节
 
    //logd("MethodCodeOff: %d",method->code_off_);
    // 回填指令
    std::map<uint32_t,CodeItem> codeMap=codeMapList[location];
    // 似乎没有走到回填指令处 (注意c++浅拷贝问题,不能随意delete)
    if(codeMap.find(method->code_off_) != codeMap.end()){
        CodeItem codeItem = codeMap[method->code_off_];
        memcpy(codeAddr,codeItem.getInsns(),codeItem.getInsnsSize()*2); //注意指令为u2类型,长度需要*2
    }
}
 
void newLoadMethod(void* thiz, const DexFile* dex_file, ClassAccessor::Method* method, void* klass, void* dest){
    if(g_originLoadMethod!= nullptr){
        // 先回填指令,再调用
        innerLoadMethod(thiz,dex_file,method,klass,dest);
        g_originLoadMethod(thiz,dex_file,method, klass, dest);
    }
    return;
}
 
void hook_LoadMethod(){
    void * loadMethodAddress =  DobbySymbolResolver(getClassLinkerLoadMethodLibPath(),getClassLinkerLoadMethodSymbol());
    DobbyHook(loadMethodAddress, (void *) newLoadMethod, (void **) &g_originLoadMethod);
    logd("hook LoadMethod done");
}
 
// 初始函数,实现hook
extern "C"
void _init(){
    APILevel = android_get_device_api_level();
    logd("Android API Level: %d", APILevel)
    logd("Setting hook...")
    hook();

[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

收藏
免费 54
支持
分享
最新回复 (24)
雪    币: 1473
活跃值: (2736)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
介绍的很全面~
2025-5-22 05:20
0
雪    币: 180
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
3
感谢分享
2025-5-22 15:34
0
雪    币: 122
活跃值: (1053)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
全面系统,非常感谢科普
2025-5-22 15:51
0
雪    币: 5248
活跃值: (3329)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
666,学习学习
2025-5-23 11:11
0
雪    币: 3203
活跃值: (3937)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
6
很全面!   下次发帖 就只能发  dex 2c 和 vmp了  哟
2025-5-23 14:19
0
雪    币: 4513
活跃值: (5353)
能力值: ( LV8,RANK:130 )
在线值:
发帖
回帖
粉丝
7

占坑

最后于 2025-5-23 14:32 被东方玻璃编辑 ,原因:
2025-5-23 14:30
0
雪    币: 4513
活跃值: (5353)
能力值: ( LV8,RANK:130 )
在线值:
发帖
回帖
粉丝
8
逆天而行 很全面! 下次发帖 就只能发 dex 2c 和 vmp了 哟[em_003]
容我研究下常规脱壳再搞这两大爹
2025-5-23 14:32
1
雪    币: 4053
活跃值: (2702)
能力值: ( LV12,RANK:210 )
在线值:
发帖
回帖
粉丝
9
嘤! 大佬带带我
2025-5-24 10:52
0
雪    币: 4513
活跃值: (5353)
能力值: ( LV8,RANK:130 )
在线值:
发帖
回帖
粉丝
10
棕熊 嘤! 大佬带带我
大佬抢蒟蒻的台词
2025-5-24 10:54
0
雪    币: 1505
活跃值: (2608)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
11
呜呜呜泪目了,我学的时候全是老文章
2025-5-25 19:07
1
雪    币: 4513
活跃值: (5353)
能力值: ( LV8,RANK:130 )
在线值:
发帖
回帖
粉丝
12
WMBa0 呜呜呜泪目了,我学的时候全是老文章
站在前人的肩膀上罢了,大佬带带
2025-5-25 20:21
0
雪    币: 231
活跃值: (276)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
13
这个太专业了,只能学习了
2025-5-26 11:19
0
雪    币: 480
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
14
真长啊 颇有收获 
2025-5-27 17:16
0
雪    币: 19
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
15
太长了. 先赞再看
2025-5-27 18:53
0
雪    币: 4339
活跃值: (3207)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
16
表哥tql,带带弟弟!
2025-5-27 20:56
0
雪    币: 534
活跃值: (1090)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
17
tql
2025-5-27 21:23
0
雪    币: 4513
活跃值: (5353)
能力值: ( LV8,RANK:130 )
在线值:
发帖
回帖
粉丝
18
正己 表哥tql,带带弟弟!
看着大佬文章长大的,带带
2025-5-27 21:29
0
雪    币: 283
活跃值: (1036)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
19
总结的很完整 谢谢分享!
2025-5-27 22:24
0
雪    币: 492
活跃值: (540)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
20
干货满满,感谢分享
2025-5-28 08:46
0
雪    币: 5
活跃值: (1098)
能力值: ( LV2,RANK:15 )
在线值:
发帖
回帖
粉丝
21
看长度就知道是干货
2025-5-28 15:49
0
雪    币: 352
活跃值: (136)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
22
太详细了,非常感谢╰(*´︶`*)╯
2025-5-31 22:49
0
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
23
你好。我运行了你给的demo,    // 似乎没有走到回填指令处 (注意c++浅拷贝问题,不能随意delete)
这里的代码没走到,直接闪退了,报错在applicaion.oncreate,空指针异常。是为啥?mac电脑上试的
2025-6-10 16:56
0
雪    币: 45
活跃值: (3699)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
24
东方教主, 你好狂鸭,  我等小辈顶礼膜拜......
5天前
0
雪    币: 200
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
25
膜拜,精品文章
5天前
0
游客
登录 | 注册 方可回帖
返回