虽然网上针对整体加固和抽取加固已经有不少文章讨论如何实现以及脱壳,也有很多成熟的加固方案
但是在学习这方面技术时发现涉及到的理论知识很多,不能局限于基本流程和代码实现而不明白底层原理
身为初学者最大的痛苦是知识体系庞大不知道如何下手,所以希望本文能抛砖引玉,对于入门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 1f7K9s2c8@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对象后,反向访问实例对象
反射相关文件
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查找类
以上流程示意图如下
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层.
Native层
PathClassLoader和DexClassLoader这条委托链会根据不同情况,调用ArtDexFileLoader::Open的不同重载,或者调用OpenOneDexFileFromZip.
InMemoryDexClassLoader调用ArtDexFileLoader::Open的第3种重载.
无论是调用哪个函数,最终都会调用ArtDexFileLoader::OpenCommon.
经过以上调用流程后进入ArtDexFileLoader::OpenCommon,经过DexFile的初始化和验证操作后便成功创建了DexFile对象:
创建DexFile对象后,Class对应的文件便被加载到ART虚拟机中
前文通过ClassLoader.loadClass讲解了双亲委托机制,那么一个Class具体是如何被加载到JVM中的呢?
首先,继承自BaseDexClassLoader的3种ClassLoader调用自身loadClass方法时,委托父类查找,委托到ClassLoader.loadClass时返回.
BaseDexClassLoader.findClass调用DexPathList.findClass,其内部调用Element.findClass,最终调用DexFile.loadClassBinaryName进入DexFile中,该流程如图所示:
进入DexFile后,主要执行以下操作:
DexFile
通过JNI函数defineClassNative进入Native层.
DexFile_defineClassNative
通过FindClassDef枚举DexFile的所有DexClassDef结构并使用ClassLinker::DefineClass创建对应的Class对象.
之后调用ClassLinker::InsertDexFileInToClassLoader将对应的DexFile对象添加到ClassLoader的ClassTable中.
ClassLinker::DefineClass
调用LoadField加载类的相关字段,之后调用LoadMethod加载方法,再调用LinkCode执行方法代码的链接.
该流程如图所示:
综上所述,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后便进入应用程序的生命周期,至此,成功启动应用程序
以上流程的核心可总结为三个部分,后续将围绕这三部分展开:
要想启用一个Android应用程序,要保证该程序所需的应用程序进程存在.
当点击应用程序图标后,触发Launcher.onClick方法,经过一系列方法调用后,在ActivityStackSupervisor.startSpecificActivityLocked中会判断当前应用程序进程是否存在.
若不存在则调用AMS代理类ActivityManagerProxy的startProcessLocked请求AMS创建应用程序进程,AMS接收Launcher的请求后又向Zygote发送请求.
这一过程可分为两部分:
AMS向Zygote发送启动应用程序进程的请求的流程如下:
Launcher请求AMS,AMS请求Zygote,通过ZygoteInit.main进入Zygote
Zygote接收AMS的请求并创建应用程序进程的流程如下:
在进行一系列处理后进入ZygoteConnection.handleChildProc,该函数内部配置子进程的初始环境后并调用ZygoteInit.zygoteInit初始化应用程序进程.
ZygoteInit.zygoteInit中调用ZygoteInit.nativeZygoteInit启动了应用程序进程的Binder线程池,使得进程可以进行Binder通讯,之后调用RuntimeInit.applicationInit.
RuntimeInit调用invokeStaticMain并抛出Zygote.MethodAndArgsCaller异常,经过异常处理清空设置过程中的堆栈帧后,调用主线程管理类ActivityThread的main方法,至此,应用程序进程被成功创建.
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创建完成,以上流程如图所示:
经过以上步骤后,创建了应用程序进程和对应的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
以上流程如图所示:
经过以上步骤后,安卓应用程序正式启动(根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文件.
原理:
代码抽取的步骤如下:
对应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文件示意图如下:
详情可参考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字节标识文件总大小
结构如下
同一代加固,可开启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目录的问题
5d7K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6T1L8r3!0Y4i4K6u0W2j5%4y4V1L8W2)9J5k6h3&6W2N6q4)9J5c8Y4p5$3x3e0l9H3z5e0R3K6x3o6S2Q4x3V1k6S2M7Y4c8A6j5$3I4W2i4K6u0r3k6r3g2@1j5h3W2D9M7#2)9J5c8U0p5H3y4e0t1@1y4U0x3#2y4b7`.`.
30dK9s2c8@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个模块
加壳程序工作基本流程图如下
加壳程序工作流程总览图如下:
相对于SecondShell.py的改动如下:
start
解包源apk后调用extractAllDexFiles抽取所有dex文件的代码
另外复制了壳apk的lib库到新apk的临时目录(hook和代码回填逻辑在native层)
extractAllDexFiles
遍历指定目录的所有dex文件,调用ReadDex抽取代码,得到对应的.patched和.codes文件
修复patch后的dex文件并覆写原dex文件,将codes移动到assets目录下
脱壳程序主要分为2个模块:
经过前文加壳程序的处理后,源程序AndroidManifest.xml文件的application标签的name属性指定壳的Application,由于Application是安卓应用程序真正的入口类,所以启动加壳后的程序时控制权在壳的代理Application中.
在壳的代理Application中主要执行以下操作:
初始化操作
设置相关文件路径,解析相关文件用于后续处理.
替换ClassLoader
替换壳程序的ClassLoader为被保护程序的ClassLoader.
替换Application
若被保护程序存在自定义Application则创建实例并替换.
加载壳so
调用System.loadLibrary()主动加载即可,后续在Native层执行代码回填.
示意图如下
主要执行以下操作:
设置相关私有目录,供后续释放文件以及设置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
参考f32K9s2c8@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
参考151K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6T1P5i4c8W2k6r3q4F1j5$3g2Q4x3V1k6T1K9r3!0G2K9#2)9J5c8X3u0D9L8$3u0Q4x3V1k6E0j5h3W2F1i4K6u0r3f1V1g2m8c8p5#2q4i4K6u0W2P5X3S2Q4x3X3c8o6e0W2)9J5k6h3#2V1
build.gradle添加bhook依赖
CMakeLists.txt添加如下设置
Hook后的LoadMethod主要工作如下
相对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指令回填
首先从base.apk提取壳Dex进而提取源Dex,最后替换ClassLoader
之后需要进行替换Application等操作,由于执行源程序的Java层代码所以触发了Dex指令回填,进行codes文件解析并创建CodeItem对象
代码回填和替换Application结束后,成功运行源程序Application的attachBaseContext和onCreate方法,最终运行MainActivity.onCreate进入源程序生命周期
Jadx分析被保护程序结果如下:
另外经测试frida-dexdump和blackdex得到的是被抽空的Dex文件,暂不清楚为什么没有进行内存搜索,也可能脱壳姿势不对
Dex文件的加载依赖于Android的ClassLoader,共有3个相关的ClassLoader:
InMemoryDexClassLoader
Android 8.0 新增的类加载器,用于加载内存中的dex文件
PathClassLoader
用于加载系统中已经安装过的apk的dex文件
DexClassLoader
用于加载已安装的jar、apk、dex以及从SD卡中加载未安装的apk
完整流程图如下
以下分析基于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 1efK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6U0M7#2)9J5k6h3q4F1k6s2u0G2K9h3c8Q4x3X3g2U0L8$3#2Q4x3V1k6S2L8X3c8J5L8$3W2V1i4K6u0r3M7r3I4S2N6r3k6G2M7X3#2Q4x3V1k6K6N6i4m8W2M7Y4m8J5L8$3A6W2j5%4c8Q4x3V1k6Q4x3V1u0Q4x3V1k6S2L8X3c8J5L8$3W2V1i4K6u0V1x3e0m8Q4x3X3f1H3i4K6u0W2x3q4)9#2k6Y4t1@1y4H3`.`.
Android 8.0中新增了InMemoryDexClassLoader,包含以下2个重载
但执行壳代码进行替换classloader时,容易出现找不到lib库的情况,可参考以下文章解决so找不到的问题:
421K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6T1L8r3!0Y4i4K6u0W2j5%4y4V1L8W2)9J5k6h3&6W2N6q4)9J5c8Y4p5$3x3e0l9H3z5e0R3K6x3o6S2Q4x3V1k6S2M7Y4c8A6j5$3I4W2i4K6u0r3k6r3g2@1j5h3W2D9M7#2)9J5c8U0p5H3y4e0t1@1y4U0x3#2y4b7`.`.
843K9s2c8@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
3baK9s2c8@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"
);
}
}
Class<?> clazz= Class.forName(
"MyUnidbgScripts.Person"
);
Class<?> clazz2=ClassLoader.getSystemClassLoader().loadClass(
"MyUnidbgScripts.Person"
);
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();
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();
Class<?> clazz= Class.forName(
"MyUnidbgScripts.Person"
);
Class<?> clazz2=ClassLoader.getSystemClassLoader().loadClass(
"MyUnidbgScripts.Person"
);
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();
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]
System.out.println(
"Get constructor:"
);
Constructor<?> constructor=clazz.getConstructor();
System.out.println(constructor);
System.out.println();
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();
System.out.println(
"Get constructor:"
);
Constructor<?> constructor=clazz.getConstructor();
System.out.println(constructor);
System.out.println();
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: []
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:"
);
Field field=clazz.getField(
"name"
);
System.out.println(field);
field=clazz.getDeclaredField(
"age"
);
System.out.println(field);
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:"
);
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
System.out.println(
"Get public methods:"
);
Method[] methods=clazz.getMethods();
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();
Method method=clazz.getMethod(
"introduce"
);
System.out.println(method);
method=clazz.getDeclaredMethod(
"privateMethod"
,String.
class
,
int
.
class
);
System.out.println(method);
System.out.println();
System.out.println(
"Get public methods:"
);
Method[] methods=clazz.getMethods();
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();
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
)
System.out.println(
"Create instance by reflection:"
);
System.out.println(
"Create instance by Class.newInstance():"
);
Object obj=clazz.newInstance();
System.out.println(obj.toString());
System.out.println();
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();
System.out.println(
"Create instance by reflection:"
);
System.out.println(
"Create instance by Class.newInstance():"
);
Object obj=clazz.newInstance();
System.out.println(obj.toString());
System.out.println();
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
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));
System.out.println(ageField.get(obj));
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));
System.out.println(ageField.get(obj));
Access field by reflection:
王五
20
Access field by reflection:
王五
20
System.out.println(
"Run method by reflection:"
);
Method introduceMethod=clazz.getMethod(
"introduce"
);
introduceMethod.invoke(obj);
Method privateMethod=clazz.getDeclaredMethod(
"privateMethod"
,String.
class
,
int
.
class
);
privateMethod.setAccessible(
true
);
privateMethod.invoke(obj,
"赵四"
,
19
);
System.out.println(
"Run method by reflection:"
);
Method introduceMethod=clazz.getMethod(
"introduce"
);
introduceMethod.invoke(obj);
Method privateMethod=clazz.getDeclaredMethod(
"privateMethod"
,String.
class
,
int
.
class
);
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);
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);
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);
if
(c ==
null
) {
long
t0 = System.nanoTime();
try
{
if
(parent !=
null
) {
c = parent.loadClass(name,
false
);
}
else
{
c = findBootstrapClassOrNull(name);
}
}
catch
(ClassNotFoundException e) {
}
if
(c ==
null
) {
long
t1 = System.nanoTime();
c = findClass(name);
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);
if
(c ==
null
) {
long
t0 = System.nanoTime();
try
{
if
(parent !=
null
) {
c = parent.loadClass(name,
false
);
}
else
{
c = findBootstrapClassOrNull(name);
}
}
catch
(ClassNotFoundException e) {
}
if
(c ==
null
) {
long
t1 = System.nanoTime();
c = findClass(name);
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();
loadDexClassAndExecuteMethod(appContext,
"/data/local/tmp/classes3.dex"
);
loadDexClassAndExecuteMethod(appContext,
"/data/local/tmp/EmptyDemo.apk"
);
}
public
void
loadDexClassAndExecuteMethod(Context context, String strDexFilePath) {
File optFile = context.getDir(
"opt_dex"
,
0
);
File libFile = context.getDir(
"lib_dex"
,
0
);
DexClassLoader dexClassLoader =
new
DexClassLoader(
strDexFilePath,
optFile.getAbsolutePath(),
libFile.getAbsolutePath(),
MainActivity.
class
.getClassLoader());
Class<?> clazz =
null
;
try
{
clazz = dexClassLoader.loadClass(
"com.example.emptydemo.TestClass"
);
if
(clazz !=
null
) {
try
{
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();
loadDexClassAndExecuteMethod(appContext,
"/data/local/tmp/classes3.dex"
);
loadDexClassAndExecuteMethod(appContext,
"/data/local/tmp/EmptyDemo.apk"
);
}
public
void
loadDexClassAndExecuteMethod(Context context, String strDexFilePath) {
File optFile = context.getDir(
"opt_dex"
,
0
);
File libFile = context.getDir(
"lib_dex"
,
0
);
DexClassLoader dexClassLoader =
new
DexClassLoader(
strDexFilePath,
optFile.getAbsolutePath(),
libFile.getAbsolutePath(),
MainActivity.
class
.getClassLoader());
Class<?> clazz =
null
;
try
{
clazz = dexClassLoader.loadClass(
"com.example.emptydemo.TestClass"
);
if
(clazz !=
null
) {
try
{
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();
}
}
}
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!"
);
}
}
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);
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);
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
) {
std::string hello =
"Hello from C++"
;
return
env->NewStringUTF(hello.c_str());
}
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;
}
static
JNINativeMethod methods[]={
{
"add"
,
"(II)I"
,(
void
*)add},
{
"sub"
,
"(II)I"
,(
void
*)sub}
};
jint JNI_OnLoad(JavaVM* vm,
void
* reserved){
JNIEnv* env=nullptr;
if
(vm->GetEnv((
void
**)&env,JNI_VERSION_1_6)!=JNI_OK)
return
-1;
jclass clazz=env->FindClass(ClassName);
if
(clazz){
env->RegisterNatives(clazz,methods,
sizeof
(methods)/
sizeof
(methods[0]));
return
JNI_VERSION_1_6;
}
else
return
-1;
}
#include <jni.h>
#include <string>
extern
"C"
JNIEXPORT jstring JNICALL
Java_com_example_nativedemo_MainActivity_stringFromJNI(
JNIEnv* env,
jobject
) {
std::string hello =
"Hello from C++"
;
return
env->NewStringUTF(hello.c_str());
}
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;
}
static
JNINativeMethod methods[]={
{
"add"
,
"(II)I"
,(
void
*)add},
{
"sub"
,
"(II)I"
,(
void
*)sub}
};
jint JNI_OnLoad(JavaVM* vm,
void
* reserved){
JNIEnv* env=nullptr;
if
(vm->GetEnv((
void
**)&env,JNI_VERSION_1_6)!=JNI_OK)
return
-1;
jclass clazz=env->FindClass(ClassName);
if
(clazz){
env->RegisterNatives(clazz,methods,
sizeof
(methods)/
sizeof
(methods[0]));
return
JNI_VERSION_1_6;
}
else
return
-1;
}
<?
xml
version
=
"1.0"
encoding
=
"utf-8"
?>
<
manifest
xmlns:android
=
"713K9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8Y4y4U0K9r3g2E0j5i4y4Q4x3X3g2S2L8X3c8J5L8$3W2V1i4K6u0W2j5$3!0E0i4K6u0r3j5i4m8C8i4K6u0r3M7X3g2K6i4K6u0r3j5h3&6V1M7X3!0A6k6l9`.`. "
xmlns:tools
=
"93aK9s2c8@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
=
"713K9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8Y4y4U0K9r3g2E0j5i4y4Q4x3X3g2S2L8X3c8J5L8$3W2V1i4K6u0W2j5$3!0E0i4K6u0r3j5i4m8C8i4K6u0r3M7X3g2K6i4K6u0r3j5h3&6V1M7X3!0A6k6l9`.`. "
xmlns:tools
=
"93aK9s2c8@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
=
"9d8K9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8Y4y4U0K9r3g2E0j5i4y4Q4x3X3g2S2L8X3c8J5L8$3W2V1i4K6u0W2j5$3!0E0i4K6u0r3j5i4m8C8i4K6u0r3M7X3g2K6i4K6u0r3j5h3&6V1M7X3!0A6k6l9`.`. "
xmlns:app
=
"492K9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8Y4y4U0K9r3g2E0j5i4y4Q4x3X3g2S2L8X3c8J5L8$3W2V1i4K6u0W2j5$3!0E0i4K6u0r3j5i4m8C8i4K6u0r3M7X3g2K6i4K6u0V1j5i4g2@1L8H3`.`. "
xmlns:tools
=
"983K9s2c8@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
=
"9d8K9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8Y4y4U0K9r3g2E0j5i4y4Q4x3X3g2S2L8X3c8J5L8$3W2V1i4K6u0W2j5$3!0E0i4K6u0r3j5i4m8C8i4K6u0r3M7X3g2K6i4K6u0r3j5h3&6V1M7X3!0A6k6l9`.`. "
xmlns:app
=
"492K9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8Y4y4U0K9r3g2E0j5i4y4Q4x3X3g2S2L8X3c8J5L8$3W2V1i4K6u0W2j5$3!0E0i4K6u0r3j5i4m8C8i4K6u0r3M7X3g2K6i4K6u0V1j5i4g2@1L8H3`.`. "
xmlns:tools
=
"983K9s2c8@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
class
Paths:
def
__init__(
self
, srcApk:Path, shellApk:Path, outputApk:Path):
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'
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])
def
extractApk(
self
,apkPath:Path, outputDir:Path):
self
.runCommand([
self
.apktool_path,
'-s'
,
'd'
, apkPath,
'-o'
, outputDir])
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)
class
ManifestEditor:
def
__init__(
self
, xml_content: bytes):
self
.ns
=
{
'android'
:
'ca7K9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8Y4y4U0K9r3g2E0j5i4y4Q4x3X3g2S2L8X3c8J5L8$3W2V1i4K6u0W2j5$3!0E0i4K6u0r3j5i4m8C8i4K6u0r3M7X3g2K6i4K6u0r3j5h3&6V1M7X3!0A6k6q4)9J5y4H3`.`.
}
self
.tree
=
etree.fromstring(xml_content)
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}'
)
return
None
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
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
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
):
return
etree.tostring(
self
.tree, pretty_print
=
True
, encoding
=
'utf-8'
, xml_declaration
=
True
).decode()
def
combineShellDexAndSrcApk(sourceApkPath:Path, shellApkTempDir:Path, newApkTempDir:Path):
def
fixCheckSum(dexBytesArray):
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):
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):
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
with
open
(sourceApkPath,
'rb'
) as f:
SourceApkArray
=
bytearray(f.read())
with
open
(shellApkTempDir
/
'classes.dex'
,
'rb'
) as f:
shellDexArray
=
bytearray(f.read())
SourceApkLen
=
len
(SourceApkArray)
shellDexLen
=
len
(shellDexArray)
newDexLen
=
shellDexLen
+
SourceApkLen
+
4
enApkArray
=
encrypto(SourceApkArray)
newDexArray
=
shellDexArray
+
enApkArray
+
bytearray(SourceApkLen.to_bytes(
4
,
'little'
))
fixFileSize(newDexArray, newDexLen)
fixSignature(newDexArray)
fixCheckSum(newDexArray)
with
open
(newApkTempDir
/
'classes.dex'
,
'wb'
) as f:
f.write(newDexArray)
def
handleManifest( srcApkTempDir:Path,shellApkTempDir:Path,newApkTempDir:Path):
with
open
(srcApkTempDir
/
'AndroidManifest.xml'
,
'r'
) as f:
srcManifestEditor
=
ManifestEditor(f.read().encode())
srcApplication
=
srcManifestEditor.getApplication()
with
open
(shellApkTempDir
/
'AndroidManifest.xml'
,
'r'
) as f:
shellManifestEditor
=
ManifestEditor(f.read().encode())
print
(
'ShellApplication:'
,shellManifestEditor.getApplication())
srcManifestEditor.setApplication(shellManifestEditor.getApplication())
if
srcApplication !
=
None
:
print
(
'Source application:'
,srcApplication)
srcManifestEditor.addMetaData(
'APPLICATION_CLASS_NAME'
,srcApplication)
with
open
(newApkTempDir
/
'AndroidManifest.xml'
,
'w'
) as f:
f.write(srcManifestEditor.getManifestData())
def
start(paths:Paths):
apktool
=
Apktool()
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!'
)
print
(
'Copying source apk files to new apk temp dir...'
)
shutil.copytree(paths.srcApkTempDir,paths.newApkTempDir )
print
(
'Copy source apk files success!'
)
print
(
'Handling AndroidManifest.xml...'
)
handleManifest(paths.srcApkTempDir,paths.shellApkTempDir,paths.newApkTempDir)
print
(
'Handle AndroidManifest.xml success!'
)
print
(
'Combining shell dex and source apk...'
)
combineShellDexAndSrcApk(paths.srcApkPath,paths.shellApkTempDir,paths.newApkTempDir)
print
(
'Combine shell dex and source apk success!'
)
print
(
'Repacking apk...'
)
apktool.repackApk(paths.newApkTempDir,paths.newApkPath)
print
(
'Repack apk success!'
)
print
(
'Signing apk...'
)
apktool.signApk(paths.newApkPath)
print
(
'Resign apk success!'
)
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'
)
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
class
Paths:
def
__init__(
self
, srcApk:Path, shellApk:Path, outputApk:Path):
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'
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])
def
extractApk(
self
,apkPath:Path, outputDir:Path):
self
.runCommand([
self
.apktool_path,
'-s'
,
'd'
, apkPath,
'-o'
, outputDir])
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)
class
ManifestEditor:
def
__init__(
self
, xml_content: bytes):
self
.ns
=
{
'android'
:
'ca7K9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8Y4y4U0K9r3g2E0j5i4y4Q4x3X3g2S2L8X3c8J5L8$3W2V1i4K6u0W2j5$3!0E0i4K6u0r3j5i4m8C8i4K6u0r3M7X3g2K6i4K6u0r3j5h3&6V1M7X3!0A6k6q4)9J5y4H3`.`.
}
self
.tree
=
etree.fromstring(xml_content)
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}'
)
return
None
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
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
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
):
return
etree.tostring(
self
.tree, pretty_print
=
True
, encoding
=
'utf-8'
, xml_declaration
=
True
).decode()
def
combineShellDexAndSrcApk(sourceApkPath:Path, shellApkTempDir:Path, newApkTempDir:Path):
def
fixCheckSum(dexBytesArray):
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):
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):
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
with
open
(sourceApkPath,
'rb'
) as f:
SourceApkArray
=
bytearray(f.read())
with
open
(shellApkTempDir
/
'classes.dex'
,
'rb'
) as f:
shellDexArray
=
bytearray(f.read())
SourceApkLen
=
len
(SourceApkArray)
shellDexLen
=
len
(shellDexArray)
newDexLen
=
shellDexLen
+
SourceApkLen
+
4
enApkArray
=
encrypto(SourceApkArray)
newDexArray
=
shellDexArray
+
enApkArray
+
bytearray(SourceApkLen.to_bytes(
4
,
'little'
))
fixFileSize(newDexArray, newDexLen)
fixSignature(newDexArray)
fixCheckSum(newDexArray)
with
open
(newApkTempDir
/
'classes.dex'
,
'wb'
) as f:
f.write(newDexArray)
def
handleManifest( srcApkTempDir:Path,shellApkTempDir:Path,newApkTempDir:Path):
with
open
(srcApkTempDir
/
'AndroidManifest.xml'
,
'r'
) as f:
srcManifestEditor
=
ManifestEditor(f.read().encode())
srcApplication
=
srcManifestEditor.getApplication()
with
open
(shellApkTempDir
/
'AndroidManifest.xml'
,
'r'
) as f:
shellManifestEditor
=
ManifestEditor(f.read().encode())
print
(
'ShellApplication:'
,shellManifestEditor.getApplication())
srcManifestEditor.setApplication(shellManifestEditor.getApplication())
if
srcApplication !
=
None
:
print
(
'Source application:'
,srcApplication)
srcManifestEditor.addMetaData(
'APPLICATION_CLASS_NAME'
,srcApplication)
with
open
(newApkTempDir
/
'AndroidManifest.xml'
,
'w'
) as f:
f.write(srcManifestEditor.getManifestData())
def
start(paths:Paths):
apktool
=
Apktool()
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!'
)
print
(
'Copying source apk files to new apk temp dir...'
)
shutil.copytree(paths.srcApkTempDir,paths.newApkTempDir )
print
(
'Copy source apk files success!'
)
print
(
'Handling AndroidManifest.xml...'
)
handleManifest(paths.srcApkTempDir,paths.shellApkTempDir,paths.newApkTempDir)
print
(
'Handle AndroidManifest.xml success!'
)
print
(
'Combining shell dex and source apk...'
)
combineShellDexAndSrcApk(paths.srcApkPath,paths.shellApkTempDir,paths.newApkTempDir)
print
(
'Combine shell dex and source apk success!'
)
print
(
'Repacking apk...'
)
apktool.repackApk(paths.newApkTempDir,paths.newApkPath)
print
(
'Repack apk success!'
)
print
(
'Signing apk...'
)
apktool.signApk(paths.newApkPath)
print
(
'Resign apk success!'
)
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'
)
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
{
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 apkFile =
new
File(apkPath);
if
(!apkFile.exists()) {
apkFile.createNewFile();
byte
[] shellDexData = readDexFromApk();
extractSrcApkFromShellDex(shellDexData);
}
replaceClassLoader();
}
catch
(Exception e) {
Log.getStackTraceString(e);
}
}
private
byte
[] readDexFromApk()
throws
IOException {
String sourceDir =
this
.getApplicationInfo().sourceDir;
log(
"this.getApplicationInfo().sourceDir: "
+sourceDir);
FileInputStream fileInputStream =
new
FileInputStream(sourceDir);
BufferedInputStream bufferedInputStream =
new
BufferedInputStream(fileInputStream);
ZipInputStream zipInputStream =
new
ZipInputStream(bufferedInputStream);
ByteArrayOutputStream byteArrayOutputStream =
new
ByteArrayOutputStream();
ZipEntry zipEntry;
while
((zipEntry = zipInputStream.getNextEntry()) !=
null
){
if
(zipEntry.getName().equals(
"classes.dex"
)){
byte
[] bytes =
new
byte
[
1024
];
int
num;
while
((num = zipInputStream.read(bytes))!=-
1
){
byteArrayOutputStream.write(bytes,
0
, num);
}
}
zipInputStream.closeEntry();
}
zipInputStream.close();
log(
"Read dex from apk succeed!"
);
return
byteArrayOutputStream.toByteArray();
}
private
void
extractSrcApkFromShellDex(
byte
[] shellDexData)
throws
IOException {
int
shellDexLen = shellDexData.length;
byte
[] srcApkSizeBytes =
new
byte
[
4
];
System.arraycopy(shellDexData, shellDexLen -
4
, srcApkSizeBytes,
0
,
4
);
int
srcApkSize =ByteBuffer.wrap(srcApkSizeBytes).order(ByteOrder.LITTLE_ENDIAN).getInt();
byte
[] sourceApkData =
new
byte
[srcApkSize];
System.arraycopy(shellDexData, shellDexLen - srcApkSize -
4
, sourceApkData,
0
, srcApkSize);
sourceApkData = decrypt(sourceApkData);
File apkfile =
new
File(apkPath);
try
{
FileOutputStream apkfileOutputStream =
new
FileOutputStream(apkfile);
apkfileOutputStream.write(sourceApkData);
apkfileOutputStream.close();
}
catch
(IOException e){
throw
new
IOException(e);
}
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();
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;
}
private
void
replaceClassLoader() {
ClassLoader classLoader =
this
.getClassLoader();
log(
"Current ClassLoader: "
+ classLoader.toString());
log(
"Parent ClassLoader: "
+ classLoader.getParent().toString());
Object sCurrentActivityThreadObj = Reflection.getStaticField(
"android.app.ActivityThread"
,
"sCurrentActivityThread"
);
log(
"ActivityThread.sCurrentActivity: "
+ sCurrentActivityThreadObj.toString());
ArrayMap mPackagesObj = (ArrayMap) Reflection.getField(
"android.app.ActivityThread"
,sCurrentActivityThreadObj,
"mPackages"
);
log(
"mPackagesObj: "
+ mPackagesObj.toString());
String currentPackageName =
this
.getPackageName();
log(
"currentPackageName: "
+ currentPackageName);
WeakReference weakReference = (WeakReference) mPackagesObj.get(currentPackageName);
Object loadedApkObj = weakReference.get();
log(
"LoadedApk: "
+ loadedApkObj.toString());
DexClassLoader dexClassLoader =
new
DexClassLoader(apkPath,dexPath,libPath, classLoader.getParent());
Reflection.setField(
"android.app.LoadedApk"
,
"mClassLoader"
,loadedApkObj,dexClassLoader);
log(
"New DexClassLoader: "
+ dexClassLoader);
}
public
boolean
replaceApplication(){
String appClassName =
null
;
try
{
ApplicationInfo applicationInfo = getPackageManager().getApplicationInfo(
this
.getPackageName(), PackageManager.GET_META_DATA);
Bundle metaData = applicationInfo.metaData;
if
(metaData !=
null
&& metaData.containsKey(
"APPLICATION_CLASS_NAME"
)){
appClassName = metaData.getString(
"APPLICATION_CLASS_NAME"
);
}
else
{
log(
"源程序中没有自定义Application"
);
return
false
;
}
}
catch
(PackageManager.NameNotFoundException e) {
log(Log.getStackTraceString(e));
}
log(
"Try to replace Application"
);
Object sCurrentActivityThreadObj = Reflection.getStaticField(
"android.app.ActivityThread"
,
"sCurrentActivityThread"
);
log(
"ActivityThread: "
+ sCurrentActivityThreadObj.toString());
Object mBoundApplicationObj = Reflection.getField(
"android.app.ActivityThread"
,sCurrentActivityThreadObj,
"mBoundApplication"
) ;
log(
"mBoundApplication: "
+mBoundApplicationObj.toString());
Object infoObj = Reflection.getField(
"android.app.ActivityThread$AppBindData"
,mBoundApplicationObj,
"info"
);
log(
"LoadedApk: "
+ infoObj.toString());
Reflection.setField(
"android.app.LoadedApk"
,
"mApplication"
,infoObj,
null
);
Object mInitialApplicationObj = Reflection.getField(
"android.app.ActivityThread"
,sCurrentActivityThreadObj,
"mInitialApplication"
);
log(
"mInitialApplicationObj: "
+ mInitialApplicationObj.toString());
ArrayList<Application> mAllApplicationsObj = (ArrayList<Application>) Reflection.getField(
"android.app.ActivityThread"
,sCurrentActivityThreadObj,
"mAllApplications"
);
mAllApplicationsObj.remove(mInitialApplicationObj);
log(
"mInitialApplication 从 mAllApplications 中移除成功"
);
ApplicationInfo applicationInfo = (ApplicationInfo) Reflection.getField(
"android.app.LoadedApk"
,infoObj,
"mApplicationInfo"
);
log(
"LoadedApk.mApplicationInfo: "
+ applicationInfo.toString());
ApplicationInfo appinfoInAppBindData = (ApplicationInfo) Reflection.getField(
"android.app.ActivityThread$AppBindData"
,mBoundApplicationObj,
"appInfo"
);
log(
"ActivityThread.mBoundApplication.appInfo: "
+ appinfoInAppBindData.toString());
applicationInfo.className = appClassName;
appinfoInAppBindData.className = appClassName;
log(
"Source Application name: "
+ appClassName);
Application application = (Application) Reflection.invokeMethod(
"android.app.LoadedApk"
,
"makeApplication"
,infoObj,
new
Class[]{
boolean
.
class
, Instrumentation.
class
},
new
Object[]{
false
,
null
});
log(
"Create source Application succeed: "
+application);
Reflection.setField(
"android.app.ActivityThread"
,
"mInitialApplication"
,sCurrentActivityThreadObj,application);
log(
"Reset ActivityThread.mInitialApplication by new Application succeed!"
);
ArrayMap mProviderMap = (ArrayMap) Reflection.getField(
"android.app.ActivityThread"
,sCurrentActivityThreadObj,
"mProviderMap"
);
log(
"ActivityThread.mProviderMap: "
+ mProviderMap);
Iterator iterator = mProviderMap.values().iterator();
while
(iterator.hasNext()){
Object providerClientRecord = iterator.next();
Object mLocalProvider = Reflection.getField(
"android.app.ActivityThread$ProviderClientRecord"
,providerClientRecord,
"mLocalProvider"
) ;
if
(mLocalProvider !=
null
){
log(
"ProviderClientRecord.mLocalProvider: "
+ mLocalProvider);
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
{
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 apkFile =
new
File(apkPath);
if
(!apkFile.exists()) {
apkFile.createNewFile();
byte
[] shellDexData = readDexFromApk();
extractSrcApkFromShellDex(shellDexData);
}
replaceClassLoader();
}
catch
(Exception e) {
Log.getStackTraceString(e);
}
}
private
byte
[] readDexFromApk()
throws
IOException {
String sourceDir =
this
.getApplicationInfo().sourceDir;
log(
"this.getApplicationInfo().sourceDir: "
+sourceDir);
FileInputStream fileInputStream =
new
FileInputStream(sourceDir);
BufferedInputStream bufferedInputStream =
new
BufferedInputStream(fileInputStream);
ZipInputStream zipInputStream =
new
ZipInputStream(bufferedInputStream);
ByteArrayOutputStream byteArrayOutputStream =
new
ByteArrayOutputStream();
ZipEntry zipEntry;
while
((zipEntry = zipInputStream.getNextEntry()) !=
null
){
if
(zipEntry.getName().equals(
"classes.dex"
)){
byte
[] bytes =
new
byte
[
1024
];
int
num;
while
((num = zipInputStream.read(bytes))!=-
1
){
byteArrayOutputStream.write(bytes,
0
, num);
}
}
zipInputStream.closeEntry();
}
zipInputStream.close();
log(
"Read dex from apk succeed!"
);
return
byteArrayOutputStream.toByteArray();
}
private
void
extractSrcApkFromShellDex(
byte
[] shellDexData)
throws
IOException {
int
shellDexLen = shellDexData.length;
byte
[] srcApkSizeBytes =
new
byte
[
4
];
System.arraycopy(shellDexData, shellDexLen -
4
, srcApkSizeBytes,
0
,
4
);
int
srcApkSize =ByteBuffer.wrap(srcApkSizeBytes).order(ByteOrder.LITTLE_ENDIAN).getInt();
byte
[] sourceApkData =
new
byte
[srcApkSize];
System.arraycopy(shellDexData, shellDexLen - srcApkSize -
4
, sourceApkData,
0
, srcApkSize);
sourceApkData = decrypt(sourceApkData);
File apkfile =
new
File(apkPath);
try
{
FileOutputStream apkfileOutputStream =
new
FileOutputStream(apkfile);
apkfileOutputStream.write(sourceApkData);
apkfileOutputStream.close();
}
catch
(IOException e){
throw
new
IOException(e);
}
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();
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;
}
private
void
replaceClassLoader() {
ClassLoader classLoader =
this
.getClassLoader();
log(
"Current ClassLoader: "
+ classLoader.toString());
log(
"Parent ClassLoader: "
+ classLoader.getParent().toString());
Object sCurrentActivityThreadObj = Reflection.getStaticField(
"android.app.ActivityThread"
,
"sCurrentActivityThread"
);
log(
"ActivityThread.sCurrentActivity: "
+ sCurrentActivityThreadObj.toString());
ArrayMap mPackagesObj = (ArrayMap) Reflection.getField(
"android.app.ActivityThread"
,sCurrentActivityThreadObj,
"mPackages"
);
log(
"mPackagesObj: "
+ mPackagesObj.toString());
String currentPackageName =
this
.getPackageName();
log(
"currentPackageName: "
+ currentPackageName);
WeakReference weakReference = (WeakReference) mPackagesObj.get(currentPackageName);
Object loadedApkObj = weakReference.get();
log(
"LoadedApk: "
+ loadedApkObj.toString());
DexClassLoader dexClassLoader =
new
DexClassLoader(apkPath,dexPath,libPath, classLoader.getParent());
Reflection.setField(
"android.app.LoadedApk"
,
"mClassLoader"
,loadedApkObj,dexClassLoader);
log(
"New DexClassLoader: "
+ dexClassLoader);
}
public
boolean
replaceApplication(){
String appClassName =
null
;
try
{
ApplicationInfo applicationInfo = getPackageManager().getApplicationInfo(
this
.getPackageName(), PackageManager.GET_META_DATA);
Bundle metaData = applicationInfo.metaData;
if
(metaData !=
null
&& metaData.containsKey(
"APPLICATION_CLASS_NAME"
)){
appClassName = metaData.getString(
"APPLICATION_CLASS_NAME"
);
}
else
{
log(
"源程序中没有自定义Application"
);
return
false
;
}
}
catch
(PackageManager.NameNotFoundException e) {
log(Log.getStackTraceString(e));
}
log(
"Try to replace Application"
);
Object sCurrentActivityThreadObj = Reflection.getStaticField(
"android.app.ActivityThread"
,
"sCurrentActivityThread"
);
log(
"ActivityThread: "
+ sCurrentActivityThreadObj.toString());
Object mBoundApplicationObj = Reflection.getField(
"android.app.ActivityThread"
,sCurrentActivityThreadObj,
"mBoundApplication"
) ;
log(
"mBoundApplication: "
+mBoundApplicationObj.toString());
Object infoObj = Reflection.getField(
"android.app.ActivityThread$AppBindData"
,mBoundApplicationObj,
"info"
);
log(
"LoadedApk: "
+ infoObj.toString());
Reflection.setField(
"android.app.LoadedApk"
,
"mApplication"
,infoObj,
null
);
Object mInitialApplicationObj = Reflection.getField(
"android.app.ActivityThread"
,sCurrentActivityThreadObj,
"mInitialApplication"
);
log(
"mInitialApplicationObj: "
+ mInitialApplicationObj.toString());
ArrayList<Application> mAllApplicationsObj = (ArrayList<Application>) Reflection.getField(
"android.app.ActivityThread"
,sCurrentActivityThreadObj,
"mAllApplications"
);
mAllApplicationsObj.remove(mInitialApplicationObj);
log(
"mInitialApplication 从 mAllApplications 中移除成功"
);
ApplicationInfo applicationInfo = (ApplicationInfo) Reflection.getField(
"android.app.LoadedApk"
,infoObj,
"mApplicationInfo"
);
log(
"LoadedApk.mApplicationInfo: "
+ applicationInfo.toString());
ApplicationInfo appinfoInAppBindData = (ApplicationInfo) Reflection.getField(
"android.app.ActivityThread$AppBindData"
,mBoundApplicationObj,
"appInfo"
);
log(
"ActivityThread.mBoundApplication.appInfo: "
+ appinfoInAppBindData.toString());
applicationInfo.className = appClassName;
appinfoInAppBindData.className = appClassName;
log(
"Source Application name: "
+ appClassName);
Application application = (Application) Reflection.invokeMethod(
"android.app.LoadedApk"
,
"makeApplication"
,infoObj,
new
Class[]{
boolean
.
class
, Instrumentation.
class
},
new
Object[]{
false
,
null
});
log(
"Create source Application succeed: "
+application);
Reflection.setField(
"android.app.ActivityThread"
,
"mInitialApplication"
,sCurrentActivityThreadObj,application);
log(
"Reset ActivityThread.mInitialApplication by new Application succeed!"
);
ArrayMap mProviderMap = (ArrayMap) Reflection.getField(
"android.app.ActivityThread"
,sCurrentActivityThreadObj,
"mProviderMap"
);
log(
"ActivityThread.mProviderMap: "
+ mProviderMap);
Iterator iterator = mProviderMap.values().iterator();
while
(iterator.hasNext()){
Object providerClientRecord = iterator.next();
Object mLocalProvider = Reflection.getField(
"android.app.ActivityThread$ProviderClientRecord"
,providerClientRecord,
"mLocalProvider"
) ;
if
(mLocalProvider !=
null
){
log(
"ProviderClientRecord.mLocalProvider: "
+ mLocalProvider);
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);
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);
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
=
"bd7K9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8Y4y4U0K9r3g2E0j5i4y4Q4x3X3g2S2L8X3c8J5L8$3W2V1i4K6u0W2j5$3!0E0i4K6u0r3j5i4m8C8i4K6u0r3M7X3g2K6i4K6u0r3j5h3&6V1M7X3!0A6k6l9`.`. "
xmlns:tools
=
"99aK9s2c8@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
=
"bd7K9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8Y4y4U0K9r3g2E0j5i4y4Q4x3X3g2S2L8X3c8J5L8$3W2V1i4K6u0W2j5$3!0E0i4K6u0r3j5i4m8C8i4K6u0r3M7X3g2K6i4K6u0r3j5h3&6V1M7X3!0A6k6l9`.`. "
xmlns:tools
=
"99aK9s2c8@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
class
Paths:
def
__init__(
self
, srcApk:Path, shellApk:Path, outputApk:Path):
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'
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])
def
extractApk(
self
,apkPath:Path, outputDir:Path):
self
.runCommand([
self
.apktool_path,
'-s'
,
'd'
, apkPath,
'-o'
, outputDir])
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)
class
ManifestEditor:
def
__init__(
self
, xml_content: bytes):
self
.ns
=
{
'android'
:
'3f0K9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8Y4y4U0K9r3g2E0j5i4y4Q4x3X3g2S2L8X3c8J5L8$3W2V1i4K6u0W2j5$3!0E0i4K6u0r3j5i4m8C8i4K6u0r3M7X3g2K6i4K6u0r3j5h3&6V1M7X3!0A6k6q4)9J5y4H3`.`.
}
self
.tree
=
etree.fromstring(xml_content)
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}'
)
return
None
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
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
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
):
return
etree.tostring(
self
.tree, pretty_print
=
True
, encoding
=
'utf-8'
, xml_declaration
=
True
).decode()
def
getEtractNativeLibs(
self
):
return
self
.getTagAttribute(
'application'
,
'extractNativeLibs'
)
def
resetExtractNativeLibs(
self
):
self
.setTagAttribute(
'application'
,
'extractNativeLibs'
,
'true'
)
def
combineShellAndSourceDexs(shellApkTempDir:Path,srcApkTempDir:Path,newApkTempDir:Path):
def
fixCheckSum(dexBytesArray):
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):
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):
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):
combinedDex
=
bytearray()
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'
))
combinedDex
+
=
data
return
combinedDex
with
open
(shellApkTempDir
/
'classes.dex'
,
'rb'
) as f:
shellDexArray
=
bytearray(f.read())
srcDexArray
=
readAndCombineDexs(srcApkTempDir)
newDexLen
=
len
(srcDexArray)
+
len
(shellDexArray)
+
4
encSrcDexArray
=
encrypto(srcDexArray)
newDexArray
=
shellDexArray
+
encSrcDexArray
+
bytearray(
len
(encSrcDexArray).to_bytes(
4
,
'little'
))
fixFileSize(newDexArray, newDexLen)
fixSignature(newDexArray)
fixCheckSum(newDexArray)
with
open
(newApkTempDir
/
'classes.dex'
,
'wb'
) as f:
f.write(newDexArray)
def
handleManifest( srcApkTempDir:Path,shellApkTempDir:Path,newApkTempDir:Path):
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)
with
open
(shellApkTempDir
/
'AndroidManifest.xml'
,
'r'
) as f:
shellManifestEditor
=
ManifestEditor(f.read().encode())
print
(
'ShellApplication:'
,shellManifestEditor.getApplication())
srcManifestEditor.setApplication(shellManifestEditor.getApplication())
if
srcApplication !
=
None
:
print
(
'Source application:'
,srcApplication)
srcManifestEditor.addMetaData(
'APPLICATION_CLASS_NAME'
,srcApplication)
if
srcExtractNativeLibs
=
=
'false'
:
srcManifestEditor.resetExtractNativeLibs()
with
open
(newApkTempDir
/
'AndroidManifest.xml'
,
'w'
) as f:
f.write(srcManifestEditor.getManifestData())
def
start(paths:Paths):
apktool
=
Apktool()
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!'
)
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!'
)
print
(
'Handling AndroidManifest.xml...'
)
handleManifest(paths.srcApkTempDir,paths.shellApkTempDir,paths.newApkTempDir)
print
(
'Handle AndroidManifest.xml success!'
)
print
(
'Combining shell dex and source dexs...'
)
combineShellAndSourceDexs(paths.shellApkTempDir,paths.srcApkTempDir,paths.newApkTempDir)
print
(
'Combine shell dex and source dexs success!'
)
print
(
'Repacking apk...'
)
apktool.repackApk(paths.newApkTempDir,paths.newApkPath)
print
(
'Repack apk success!'
)
print
(
'Signing apk...'
)
apktool.signApk(paths.newApkPath)
print
(
'Resign apk success!'
)
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'
)
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
class
Paths:
def
__init__(
self
, srcApk:Path, shellApk:Path, outputApk:Path):
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'
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])
def
extractApk(
self
,apkPath:Path, outputDir:Path):
self
.runCommand([
self
.apktool_path,
'-s'
,
'd'
, apkPath,
'-o'
, outputDir])
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)
class
ManifestEditor:
def
__init__(
self
, xml_content: bytes):
self
.ns
=
{
'android'
:
'3f0K9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8Y4y4U0K9r3g2E0j5i4y4Q4x3X3g2S2L8X3c8J5L8$3W2V1i4K6u0W2j5$3!0E0i4K6u0r3j5i4m8C8i4K6u0r3M7X3g2K6i4K6u0r3j5h3&6V1M7X3!0A6k6q4)9J5y4H3`.`.
}
self
.tree
=
etree.fromstring(xml_content)
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}'
)
return
None
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
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
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
):
return
etree.tostring(
self
.tree, pretty_print
=
True
, encoding
=
'utf-8'
, xml_declaration
=
True
).decode()
def
getEtractNativeLibs(
self
):
return
self
.getTagAttribute(
'application'
,
'extractNativeLibs'
)
def
resetExtractNativeLibs(
self
):
self
.setTagAttribute(
'application'
,
'extractNativeLibs'
,
'true'
)
def
combineShellAndSourceDexs(shellApkTempDir:Path,srcApkTempDir:Path,newApkTempDir:Path):
def
fixCheckSum(dexBytesArray):
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):
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):
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):
combinedDex
=
bytearray()
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'
))
combinedDex
+
=
data
return
combinedDex
with
open
(shellApkTempDir
/
'classes.dex'
,
'rb'
) as f:
shellDexArray
=
bytearray(f.read())
srcDexArray
=
readAndCombineDexs(srcApkTempDir)
newDexLen
=
len
(srcDexArray)
+
len
(shellDexArray)
+
4
encSrcDexArray
=
encrypto(srcDexArray)
newDexArray
=
shellDexArray
+
encSrcDexArray
+
bytearray(
len
(encSrcDexArray).to_bytes(
4
,
'little'
))
fixFileSize(newDexArray, newDexLen)
fixSignature(newDexArray)
fixCheckSum(newDexArray)
with
open
(newApkTempDir
/
'classes.dex'
,
'wb'
) as f:
f.write(newDexArray)
def
handleManifest( srcApkTempDir:Path,shellApkTempDir:Path,newApkTempDir:Path):
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)
with
open
(shellApkTempDir
/
'AndroidManifest.xml'
,
'r'
) as f:
shellManifestEditor
=
ManifestEditor(f.read().encode())
print
(
'ShellApplication:'
,shellManifestEditor.getApplication())
srcManifestEditor.setApplication(shellManifestEditor.getApplication())
if
srcApplication !
=
None
:
print
(
'Source application:'
,srcApplication)
srcManifestEditor.addMetaData(
'APPLICATION_CLASS_NAME'
,srcApplication)
if
srcExtractNativeLibs
=
=
'false'
:
srcManifestEditor.resetExtractNativeLibs()
with
open
(newApkTempDir
/
'AndroidManifest.xml'
,
'w'
) as f:
f.write(srcManifestEditor.getManifestData())
def
start(paths:Paths):
apktool
=
Apktool()
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!'
)
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!'
)
print
(
'Handling AndroidManifest.xml...'
)
handleManifest(paths.srcApkTempDir,paths.shellApkTempDir,paths.newApkTempDir)
print
(
'Handle AndroidManifest.xml success!'
)
print
(
'Combining shell dex and source dexs...'
)
combineShellAndSourceDexs(paths.shellApkTempDir,paths.srcApkTempDir,paths.newApkTempDir)
print
(
'Combine shell dex and source dexs success!'
)
print
(
'Repacking apk...'
)
apktool.repackApk(paths.newApkTempDir,paths.newApkPath)
print
(
'Repack apk success!'
)
print
(
'Signing apk...'
)
apktool.signApk(paths.newApkPath)
print
(
'Resign apk success!'
)
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'
)
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"
);
ByteBuffer[] byteBuffers = extractDexFilesFromShellDex(shellDexData);
log(
"成功分离出源dex集合"
);
replaceClassLoader(byteBuffers);
}
catch
(Exception e) {
log( Log.getStackTraceString(e));
}
}
@Override
public
void
onCreate() {
super
.onCreate();
log(
"SecondProxyApplication.onCreate is running!"
);
if
(replaceApplication())
log(
"替换Application成功"
);
}
private
ByteBuffer[] extractDexFilesFromShellDex(
byte
[] shellDexData)
throws
IOException {
int
shellDexlength = shellDexData.length;
byte
[] sourceDexsSizeByte =
new
byte
[
4
];
System.arraycopy(shellDexData,shellDexlength -
4
, sourceDexsSizeByte,
0
,
4
);
ByteBuffer wrap = ByteBuffer.wrap(sourceDexsSizeByte);
int
sourceDexsSizeInt = wrap.order(ByteOrder.LITTLE_ENDIAN).getInt();
Log.d(TAG,
"源dex集合的大小: "
+ sourceDexsSizeInt);
byte
[] sourceDexsData =
new
byte
[sourceDexsSizeInt];
System.arraycopy(shellDexData,shellDexlength - sourceDexsSizeInt -
4
, sourceDexsData,
0
, sourceDexsSizeInt);
sourceDexsData = decrypt(sourceDexsData);
ArrayList<
byte
[]> sourceDexList =
new
ArrayList<>();
int
pos =
0
;
while
(pos < sourceDexsSizeInt){
byte
[] singleDexSizeByte =
new
byte
[
4
];
System.arraycopy(sourceDexsData, pos, singleDexSizeByte,
0
,
4
);
ByteBuffer singleDexwrap = ByteBuffer.wrap(singleDexSizeByte);
int
singleDexSizeInt = singleDexwrap.order(ByteOrder.LITTLE_ENDIAN).getInt();
Log.d(TAG,
"当前singleDex的大小: "
+ singleDexSizeInt);
byte
[] singleDexData =
new
byte
[singleDexSizeInt];
System.arraycopy(sourceDexsData,pos +
4
, singleDexData,
0
, singleDexSizeInt);
sourceDexList.add(singleDexData);
pos +=
4
+ singleDexSizeInt;
}
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;
}
private
byte
[] readDexFromApk()
throws
IOException {
String sourceDir =
this
.getApplicationInfo().sourceDir;
log(
"this.getApplicationInfo().sourceDir: "
+sourceDir);
FileInputStream fileInputStream =
new
FileInputStream(sourceDir);
BufferedInputStream bufferedInputStream =
new
BufferedInputStream(fileInputStream);
ZipInputStream zipInputStream =
new
ZipInputStream(bufferedInputStream);
ByteArrayOutputStream byteArrayOutputStream =
new
ByteArrayOutputStream();
ZipEntry zipEntry;
while
((zipEntry = zipInputStream.getNextEntry()) !=
null
){
if
(zipEntry.getName().equals(
"classes.dex"
)){
byte
[] bytes =
new
byte
[
1024
];
int
num;
while
((num = zipInputStream.read(bytes))!=-
1
){
byteArrayOutputStream.write(bytes,
0
, num);
}
}
zipInputStream.closeEntry();
}
zipInputStream.close();
log(
"Read dex from apk succeed!"
);
return
byteArrayOutputStream.toByteArray();
}
private
byte
[] decrypt(
byte
[] sourceApkdata) {
for
(
int
i =
0
; i < sourceApkdata.length; i++){
sourceApkdata[i] ^= (
byte
)
0xff
;
}
return
sourceApkdata;
}
public
boolean
replaceApplication(){
String appClassName =
null
;
try
{
ApplicationInfo applicationInfo = getPackageManager().getApplicationInfo(
this
.getPackageName(), PackageManager.GET_META_DATA);
Bundle metaData = applicationInfo.metaData;
if
(metaData !=
null
&& metaData.containsKey(
"APPLICATION_CLASS_NAME"
)){
appClassName = metaData.getString(
"APPLICATION_CLASS_NAME"
);
}
else
{
log(
"源程序中没有自定义Application"
);
return
false
;
}
}
catch
(PackageManager.NameNotFoundException e) {
log(Log.getStackTraceString(e));
}
log(
"Try to replace Application"
);
Object sCurrentActivityThreadObj = Reflection.getStaticField(
"android.app.ActivityThread"
,
"sCurrentActivityThread"
);
log(
"ActivityThread: "
+ sCurrentActivityThreadObj.toString());
Object mBoundApplicationObj = Reflection.getField(
"android.app.ActivityThread"
,sCurrentActivityThreadObj,
"mBoundApplication"
) ;
log(
"mBoundApplication: "
+mBoundApplicationObj.toString());
Object infoObj = Reflection.getField(
"android.app.ActivityThread$AppBindData"
,mBoundApplicationObj,
"info"
);
log(
"LoadedApk: "
+ infoObj.toString());
Reflection.setField(
"android.app.LoadedApk"
,
"mApplication"
,infoObj,
null
);
Object mInitialApplicationObj = Reflection.getField(
"android.app.ActivityThread"
,sCurrentActivityThreadObj,
"mInitialApplication"
);
log(
"mInitialApplicationObj: "
+ mInitialApplicationObj.toString());
ArrayList<Application> mAllApplicationsObj = (ArrayList<Application>) Reflection.getField(
"android.app.ActivityThread"
,sCurrentActivityThreadObj,
"mAllApplications"
);
mAllApplicationsObj.remove(mInitialApplicationObj);
log(
"mInitialApplication 从 mAllApplications 中移除成功"
);
ApplicationInfo applicationInfo = (ApplicationInfo) Reflection.getField(
"android.app.LoadedApk"
,infoObj,
"mApplicationInfo"
);
log(
"LoadedApk.mApplicationInfo: "
+ applicationInfo.toString());
ApplicationInfo appinfoInAppBindData = (ApplicationInfo) Reflection.getField(
"android.app.ActivityThread$AppBindData"
,mBoundApplicationObj,
"appInfo"
);
log(
"ActivityThread.mBoundApplication.appInfo: "
+ appinfoInAppBindData.toString());
applicationInfo.className = appClassName;
appinfoInAppBindData.className = appClassName;
log(
"Source Application name: "
+ appClassName);
Application application = (Application) Reflection.invokeMethod(
"android.app.LoadedApk"
,
"makeApplication"
,infoObj,
new
Class[]{
boolean
.
class
, Instrumentation.
class
},
new
Object[]{
false
,
null
});
log(
"Create source Application succeed: "
+application);
Reflection.setField(
"android.app.ActivityThread"
,
"mInitialApplication"
,sCurrentActivityThreadObj,application);
log(
"Reset ActivityThread.mInitialApplication by new Application succeed!"
);
ArrayMap mProviderMap = (ArrayMap) Reflection.getField(
"android.app.ActivityThread"
,sCurrentActivityThreadObj,
"mProviderMap"
);
log(
"ActivityThread.mProviderMap: "
+ mProviderMap);
Iterator iterator = mProviderMap.values().iterator();
while
(iterator.hasNext()){
Object providerClientRecord = iterator.next();
Object mLocalProvider = Reflection.getField(
"android.app.ActivityThread$ProviderClientRecord"
,providerClientRecord,
"mLocalProvider"
) ;
if
(mLocalProvider !=
null
){
log(
"ProviderClientRecord.mLocalProvider: "
+ mLocalProvider);
Reflection.setField(
"android.content.ContentProvider"
,
"mContext"
,mLocalProvider,application);
}
}
log(
"Run Application.onCreate"
);
application.onCreate();
return
true
;
}
private
void
replaceClassLoader(ByteBuffer[] byteBuffers)
throws
Exception{
ClassLoader classLoader =
this
.getClassLoader();
log(
"Current ClassLoader: "
+ classLoader.toString());
log(
"Parent ClassLoader: "
+ classLoader.getParent().toString());
Object sCurrentActivityThreadObj = Reflection.getStaticField(
"android.app.ActivityThread"
,
"sCurrentActivityThread"
);
log(
"ActivityThread.sCurrentActivity: "
+ sCurrentActivityThreadObj.toString());
ArrayMap mPackagesObj = (ArrayMap) Reflection.getField(
"android.app.ActivityThread"
,sCurrentActivityThreadObj,
"mPackages"
);
log(
"mPackagesObj: "
+ mPackagesObj.toString());
String currentPackageName =
this
.getPackageName();
log(
"currentPackageName: "
+ currentPackageName);
WeakReference weakReference = (WeakReference) mPackagesObj.get(currentPackageName);
Object loadedApkObj = weakReference.get();
log(
"LoadedApk: "
+ loadedApkObj.toString());
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);
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"
);
ByteBuffer[] byteBuffers = extractDexFilesFromShellDex(shellDexData);
log(
"成功分离出源dex集合"
);
replaceClassLoader(byteBuffers);
}
catch
(Exception e) {
log( Log.getStackTraceString(e));
}
}
@Override
public
void
onCreate() {
super
.onCreate();
log(
"SecondProxyApplication.onCreate is running!"
);
if
(replaceApplication())
log(
"替换Application成功"
);
}
private
ByteBuffer[] extractDexFilesFromShellDex(
byte
[] shellDexData)
throws
IOException {
int
shellDexlength = shellDexData.length;
byte
[] sourceDexsSizeByte =
new
byte
[
4
];
System.arraycopy(shellDexData,shellDexlength -
4
, sourceDexsSizeByte,
0
,
4
);
ByteBuffer wrap = ByteBuffer.wrap(sourceDexsSizeByte);
int
sourceDexsSizeInt = wrap.order(ByteOrder.LITTLE_ENDIAN).getInt();
Log.d(TAG,
"源dex集合的大小: "
+ sourceDexsSizeInt);
byte
[] sourceDexsData =
new
byte
[sourceDexsSizeInt];
System.arraycopy(shellDexData,shellDexlength - sourceDexsSizeInt -
4
, sourceDexsData,
0
, sourceDexsSizeInt);
sourceDexsData = decrypt(sourceDexsData);
ArrayList<
byte
[]> sourceDexList =
new
ArrayList<>();
int
pos =
0
;
while
(pos < sourceDexsSizeInt){
byte
[] singleDexSizeByte =
new
byte
[
4
];
System.arraycopy(sourceDexsData, pos, singleDexSizeByte,
0
,
4
);
ByteBuffer singleDexwrap = ByteBuffer.wrap(singleDexSizeByte);
int
singleDexSizeInt = singleDexwrap.order(ByteOrder.LITTLE_ENDIAN).getInt();
Log.d(TAG,
"当前singleDex的大小: "
+ singleDexSizeInt);
byte
[] singleDexData =
new
byte
[singleDexSizeInt];
System.arraycopy(sourceDexsData,pos +
4
, singleDexData,
0
, singleDexSizeInt);
sourceDexList.add(singleDexData);
pos +=
4
+ singleDexSizeInt;
}
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;
}
private
byte
[] readDexFromApk()
throws
IOException {
String sourceDir =
this
.getApplicationInfo().sourceDir;
log(
"this.getApplicationInfo().sourceDir: "
+sourceDir);
FileInputStream fileInputStream =
new
FileInputStream(sourceDir);
BufferedInputStream bufferedInputStream =
new
BufferedInputStream(fileInputStream);
ZipInputStream zipInputStream =
new
ZipInputStream(bufferedInputStream);
ByteArrayOutputStream byteArrayOutputStream =
new
ByteArrayOutputStream();
ZipEntry zipEntry;
while
((zipEntry = zipInputStream.getNextEntry()) !=
null
){
if
(zipEntry.getName().equals(
"classes.dex"
)){
byte
[] bytes =
new
byte
[
1024
];
int
num;
while
((num = zipInputStream.read(bytes))!=-
1
){
byteArrayOutputStream.write(bytes,
0
, num);
}
}
zipInputStream.closeEntry();
}
zipInputStream.close();
log(
"Read dex from apk succeed!"
);
return
byteArrayOutputStream.toByteArray();
}
private
byte
[] decrypt(
byte
[] sourceApkdata) {
for
(
int
i =
0
; i < sourceApkdata.length; i++){
sourceApkdata[i] ^= (
byte
)
0xff
;
}
return
sourceApkdata;
}
public
boolean
replaceApplication(){
String appClassName =
null
;
try
{
ApplicationInfo applicationInfo = getPackageManager().getApplicationInfo(
this
.getPackageName(), PackageManager.GET_META_DATA);
Bundle metaData = applicationInfo.metaData;
if
(metaData !=
null
&& metaData.containsKey(
"APPLICATION_CLASS_NAME"
)){
appClassName = metaData.getString(
"APPLICATION_CLASS_NAME"
);
}
else
{
log(
"源程序中没有自定义Application"
);
return
false
;
}
}
catch
(PackageManager.NameNotFoundException e) {
log(Log.getStackTraceString(e));
}
log(
"Try to replace Application"
);
Object sCurrentActivityThreadObj = Reflection.getStaticField(
"android.app.ActivityThread"
,
"sCurrentActivityThread"
);
log(
"ActivityThread: "
+ sCurrentActivityThreadObj.toString());
Object mBoundApplicationObj = Reflection.getField(
"android.app.ActivityThread"
,sCurrentActivityThreadObj,
"mBoundApplication"
) ;
log(
"mBoundApplication: "
+mBoundApplicationObj.toString());
Object infoObj = Reflection.getField(
"android.app.ActivityThread$AppBindData"
,mBoundApplicationObj,
"info"
);
log(
"LoadedApk: "
+ infoObj.toString());
Reflection.setField(
"android.app.LoadedApk"
,
"mApplication"
,infoObj,
null
);
Object mInitialApplicationObj = Reflection.getField(
"android.app.ActivityThread"
,sCurrentActivityThreadObj,
"mInitialApplication"
);
log(
"mInitialApplicationObj: "
+ mInitialApplicationObj.toString());
ArrayList<Application> mAllApplicationsObj = (ArrayList<Application>) Reflection.getField(
"android.app.ActivityThread"
,sCurrentActivityThreadObj,
"mAllApplications"
);
mAllApplicationsObj.remove(mInitialApplicationObj);
log(
"mInitialApplication 从 mAllApplications 中移除成功"
);
ApplicationInfo applicationInfo = (ApplicationInfo) Reflection.getField(
"android.app.LoadedApk"
,infoObj,
"mApplicationInfo"
);
log(
"LoadedApk.mApplicationInfo: "
+ applicationInfo.toString());
ApplicationInfo appinfoInAppBindData = (ApplicationInfo) Reflection.getField(
"android.app.ActivityThread$AppBindData"
,mBoundApplicationObj,
"appInfo"
);
log(
"ActivityThread.mBoundApplication.appInfo: "
+ appinfoInAppBindData.toString());
applicationInfo.className = appClassName;
appinfoInAppBindData.className = appClassName;
log(
"Source Application name: "
+ appClassName);
Application application = (Application) Reflection.invokeMethod(
"android.app.LoadedApk"
,
"makeApplication"
,infoObj,
new
Class[]{
boolean
.
class
, Instrumentation.
class
},
new
Object[]{
false
,
null
});
log(
"Create source Application succeed: "
+application);
Reflection.setField(
"android.app.ActivityThread"
,
"mInitialApplication"
,sCurrentActivityThreadObj,application);
log(
"Reset ActivityThread.mInitialApplication by new Application succeed!"
);
ArrayMap mProviderMap = (ArrayMap) Reflection.getField(
"android.app.ActivityThread"
,sCurrentActivityThreadObj,
"mProviderMap"
);
log(
"ActivityThread.mProviderMap: "
+ mProviderMap);
Iterator iterator = mProviderMap.values().iterator();
while
(iterator.hasNext()){
Object providerClientRecord = iterator.next();
Object mLocalProvider = Reflection.getField(
"android.app.ActivityThread$ProviderClientRecord"
,providerClientRecord,
"mLocalProvider"
) ;
if
(mLocalProvider !=
null
){
log(
"ProviderClientRecord.mLocalProvider: "
+ mLocalProvider);
Reflection.setField(
"android.content.ContentProvider"
,
"mContext"
,mLocalProvider,application);
}
}
log(
"Run Application.onCreate"
);
application.onCreate();
return
true
;
}
private
void
replaceClassLoader(ByteBuffer[] byteBuffers)
throws
Exception{
ClassLoader classLoader =
this
.getClassLoader();
log(
"Current ClassLoader: "
+ classLoader.toString());
log(
"Parent ClassLoader: "
+ classLoader.getParent().toString());
Object sCurrentActivityThreadObj = Reflection.getStaticField(
"android.app.ActivityThread"
,
"sCurrentActivityThread"
);
log(
"ActivityThread.sCurrentActivity: "
+ sCurrentActivityThreadObj.toString());
ArrayMap mPackagesObj = (ArrayMap) Reflection.getField(
"android.app.ActivityThread"
,sCurrentActivityThreadObj,
"mPackages"
);
log(
"mPackagesObj: "
+ mPackagesObj.toString());
String currentPackageName =
this
.getPackageName();
log(
"currentPackageName: "
+ currentPackageName);
WeakReference weakReference = (WeakReference) mPackagesObj.get(currentPackageName);
Object loadedApkObj = weakReference.get();
log(
"LoadedApk: "
+ loadedApkObj.toString());
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);
Reflection.setField(
"android.app.LoadedApk"
,
"mClassLoader"
,loadedApkObj,dexClassLoader);
}
else
{
Log.d(TAG,
"不支持Android 8.0以下版本"
);
}
}
}
void
LoadMethod(Thread* self,
const
DexFile& dex_file,
const
ClassDataItemIterator& it,
Handle<mirror::Class> klass, ArtMethod* dst)
void
LoadMethod(
const
DexFile& dex_file,
const
ClassDataItemIterator& it,
Handle<mirror::Class> klass, ArtMethod* dst)
void
LoadMethod(
const
DexFile& dex_file,
const
ClassAccessor::Method& method,
Handle<mirror::Class> klass,
ArtMethod* dst)
void
LoadMethod(
const
DexFile& dex_file,
const
ClassAccessor::Method& method,
ObjPtr<mirror::Class> klass,
MethodAnnotationsIterator* mai,
ArtMethod* dst)
void
LoadMethod(Thread* self,
const
DexFile& dex_file,
const
ClassDataItemIterator& it,
Handle<mirror::Class> klass, ArtMethod* dst)
void
LoadMethod(
const
DexFile& dex_file,
const
ClassDataItemIterator& it,
Handle<mirror::Class> klass, ArtMethod* dst)
void
LoadMethod(
const
DexFile& dex_file,
const
ClassAccessor::Method& method,
Handle<mirror::Class> klass,
ArtMethod* dst)
void
LoadMethod(
const
DexFile& dex_file,
const
ClassAccessor::Method& method,
ObjPtr<mirror::Class> klass,
MethodAnnotationsIterator* mai,
ArtMethod* dst)
static
void
* (*g_originDefineClassV21)(
void
* thiz,
const
char
* descriptor,
void
* class_loader,
const
void
* dex_file,
const
void
* dex_class_def);
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);
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);
static
void
* (*g_originDefineClassV21)(
void
* thiz,
const
char
* descriptor,
void
* class_loader,
const
void
* dex_file,
const
void
* dex_class_def);
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);
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);
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
class
Paths:
def
__init__(
self
, srcApk:Path, shellApk:Path, outputApk:Path):
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'
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'
def
signApk(
self
,unsignedApkPath:Path):
self
.runCommand([
'java'
,
'-jar'
,
self
.signer_path,
'--apk'
,unsignedApkPath])
def
unpackApk(
self
,apkPath:Path, outputDir:Path):
self
.runCommand([
self
.apktool_path,
'-s'
,
'd'
, apkPath,
'-o'
, outputDir])
def
repackApk(
self
,inputDir:Path, outApk:Path):
self
.runCommand([
self
.apktool_path,
'b'
, inputDir,
'-o'
, outApk])
def
extractDexCodes(
self
,dexPath:Path):
self
.runCommand([
self
.readDex_path,
'-file'
, dexPath,
'-extractCodes'
])
def
runCommand(
self
,args):
subprocess.run(args,stdout
=
subprocess.DEVNULL)
class
ManifestEditor:
def
__init__(
self
, xml_content: bytes):
self
.ns
=
{
'android'
:
'a5cK9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8Y4y4U0K9r3g2E0j5i4y4Q4x3X3g2S2L8X3c8J5L8$3W2V1i4K6u0W2j5$3!0E0i4K6u0r3j5i4m8C8i4K6u0r3M7X3g2K6i4K6u0r3j5h3&6V1M7X3!0A6k6q4)9J5y4H3`.`.
}
self
.tree
=
etree.fromstring(xml_content)
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}'
)
return
None
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
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
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
getApplicationName(
self
):
return
self
.getTagAttribute(
'application'
,
'name'
)
def
setApplicationName(
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
):
return
etree.tostring(
self
.tree, pretty_print
=
True
, encoding
=
'utf-8'
, xml_declaration
=
True
).decode()
def
getEtractNativeLibs(
self
):
return
self
.getTagAttribute(
'application'
,
'extractNativeLibs'
)
def
resetExtractNativeLibs(
self
):
self
.setTagAttribute(
'application'
,
'extractNativeLibs'
,
'true'
)
def
fixCheckSum(dexBytes:bytearray):
value
=
adler32(bytes(dexBytes[
12
:]))
valueArray
=
bytearray(value.to_bytes(
4
,
'little'
))
for
i
in
range
(
len
(valueArray)):
dexBytes[
8
+
i]
=
valueArray[i]
def
fixSignature(dexBytes:bytearray):
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]
def
fixFileSize(dexBytes:bytearray, fileSize):
fileSizeArray
=
bytearray(fileSize.to_bytes(
4
,
"little"
))
for
i
in
range
(
len
(fileSizeArray)):
dexBytes[
32
+
i]
=
fileSizeArray[i]
def
encrypt(data:bytearray):
for
i
in
range
(
len
(data)):
data[i] ^
=
0xff
return
data
def
extractAllDexFiles(directory:Path):
apktool
=
Apktool()
for
dex
in
directory.glob(
'classes*.dex'
):
apktool.extractDexCodes(dex)
for
patchedDex
in
directory.glob(
'classes*.dex.patched'
):
newDexName
=
str
(patchedDex).replace(
'.patched'
, '')
with
open
(patchedDex,
'rb'
) as f:
data
=
bytearray(f.read())
fixSignature(data)
fixCheckSum(data)
with
open
(newDexName,
'wb'
) as newf:
newf.write(data)
for
patchedDex
in
directory.glob(
'classes*.dex.patched'
):
patchedDex.unlink()
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)
def
combineShellAndSourceDexs(shellApkTempDir:Path,srcApkTempDir:Path,newApkTempDir:Path):
def
readAndCombineDexs(unpackedApkDir:Path):
combinedDex
=
bytearray()
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'
))
combinedDex
+
=
data
return
combinedDex
with
open
(shellApkTempDir
/
'classes.dex'
,
'rb'
) as f:
shellDexArray
=
bytearray(f.read())
srcDexArray
=
readAndCombineDexs(srcApkTempDir)
newDexLen
=
len
(srcDexArray)
+
len
(shellDexArray)
+
4
encSrcDexArray
=
encrypt(srcDexArray)
newDexArray
=
shellDexArray
+
encSrcDexArray
+
bytearray(
len
(encSrcDexArray).to_bytes(
4
,
'little'
))
fixFileSize(newDexArray, newDexLen)
fixSignature(newDexArray)
fixCheckSum(newDexArray)
with
open
(newApkTempDir
/
'classes.dex'
,
'wb'
) as f:
f.write(newDexArray)
def
handleManifest(srcApkTempDir:Path,shellApkTempDir:Path,newApkTempDir:Path):
with
open
(srcApkTempDir
/
'AndroidManifest.xml'
,
'r'
) as f:
srcManifestEditor
=
ManifestEditor(f.read().encode())
srcApplication
=
srcManifestEditor.getApplicationName()
srcExtractNativeLibs
=
srcManifestEditor.getEtractNativeLibs()
print
(
'SourceApplication:'
,srcApplication)
print
(
'SourceExtractNativeLibs:'
,srcExtractNativeLibs)
with
open
(shellApkTempDir
/
'AndroidManifest.xml'
,
'r'
) as f:
shellManifestEditor
=
ManifestEditor(f.read().encode())
print
(
'ShellApplication:'
,shellManifestEditor.getApplicationName())
srcManifestEditor.setApplicationName(shellManifestEditor.getApplicationName())
if
srcApplication !
=
None
:
print
(
'Source application:'
,srcApplication)
srcManifestEditor.addMetaData(
'APPLICATION_CLASS_NAME'
,srcApplication)
if
srcExtractNativeLibs
=
=
'false'
:
srcManifestEditor.resetExtractNativeLibs()
with
open
(newApkTempDir
/
'AndroidManifest.xml'
,
'w'
) as f:
f.write(srcManifestEditor.getManifestData())
def
start(paths:Paths):
apktool
=
Apktool()
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!'
)
print
(
'Exrtracting dex files codes...'
)
extractAllDexFiles(paths.srcApkTempDir)
print
(
'Extract dex files codes success!'
)
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!'
)
print
(
'Copying shell apk lib files to new apk temp dir...'
)
shutil.copytree(paths.shellApkTempDir
/
'lib'
,paths.newApkTempDir
/
'lib'
,dirs_exist_ok
=
True
)
print
(
'Copy shell apk lib files success!'
)
print
(
'Handling AndroidManifest.xml...'
)
handleManifest(paths.srcApkTempDir,paths.shellApkTempDir,paths.newApkTempDir)
print
(
'Handle AndroidManifest.xml success!'
)
print
(
'Combining shell dex and source dexs...'
)
combineShellAndSourceDexs(paths.shellApkTempDir,paths.srcApkTempDir,paths.newApkTempDir)
print
(
'Combine shell dex and source dexs success!'
)
print
(
'Repacking apk...'
)
apktool.repackApk(paths.newApkTempDir,paths.newApkPath)
print
(
'Repack apk success!'
)
print
(
'Signing apk...'
)
apktool.signApk(paths.newApkPath)
print
(
'Resign apk success!'
)
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'
)
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
class
Paths:
def
__init__(
self
, srcApk:Path, shellApk:Path, outputApk:Path):
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'
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'
def
signApk(
self
,unsignedApkPath:Path):
self
.runCommand([
'java'
,
'-jar'
,
self
.signer_path,
'--apk'
,unsignedApkPath])
def
unpackApk(
self
,apkPath:Path, outputDir:Path):
self
.runCommand([
self
.apktool_path,
'-s'
,
'd'
, apkPath,
'-o'
, outputDir])
def
repackApk(
self
,inputDir:Path, outApk:Path):
self
.runCommand([
self
.apktool_path,
'b'
, inputDir,
'-o'
, outApk])
def
extractDexCodes(
self
,dexPath:Path):
self
.runCommand([
self
.readDex_path,
'-file'
, dexPath,
'-extractCodes'
])
def
runCommand(
self
,args):
subprocess.run(args,stdout
=
subprocess.DEVNULL)
class
ManifestEditor:
def
__init__(
self
, xml_content: bytes):
self
.ns
=
{
'android'
:
'a5cK9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8Y4y4U0K9r3g2E0j5i4y4Q4x3X3g2S2L8X3c8J5L8$3W2V1i4K6u0W2j5$3!0E0i4K6u0r3j5i4m8C8i4K6u0r3M7X3g2K6i4K6u0r3j5h3&6V1M7X3!0A6k6q4)9J5y4H3`.`.
}
self
.tree
=
etree.fromstring(xml_content)
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}'
)
return
None
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
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
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
getApplicationName(
self
):
return
self
.getTagAttribute(
'application'
,
'name'
)
def
setApplicationName(
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
):
return
etree.tostring(
self
.tree, pretty_print
=
True
, encoding
=
'utf-8'
, xml_declaration
=
True
).decode()
def
getEtractNativeLibs(
self
):
return
self
.getTagAttribute(
'application'
,
'extractNativeLibs'
)
def
resetExtractNativeLibs(
self
):
self
.setTagAttribute(
'application'
,
'extractNativeLibs'
,
'true'
)
def
fixCheckSum(dexBytes:bytearray):
value
=
adler32(bytes(dexBytes[
12
:]))
valueArray
=
bytearray(value.to_bytes(
4
,
'little'
))
for
i
in
range
(
len
(valueArray)):
dexBytes[
8
+
i]
=
valueArray[i]
def
fixSignature(dexBytes:bytearray):
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]
def
fixFileSize(dexBytes:bytearray, fileSize):
fileSizeArray
=
bytearray(fileSize.to_bytes(
4
,
"little"
))
for
i
in
range
(
len
(fileSizeArray)):
dexBytes[
32
+
i]
=
fileSizeArray[i]
def
encrypt(data:bytearray):
for
i
in
range
(
len
(data)):
data[i] ^
=
0xff
return
data
def
extractAllDexFiles(directory:Path):
apktool
=
Apktool()
for
dex
in
directory.glob(
'classes*.dex'
):
apktool.extractDexCodes(dex)
for
patchedDex
in
directory.glob(
'classes*.dex.patched'
):
newDexName
=
str
(patchedDex).replace(
'.patched'
, '')
with
open
(patchedDex,
'rb'
) as f:
data
=
bytearray(f.read())
fixSignature(data)
fixCheckSum(data)
with
open
(newDexName,
'wb'
) as newf:
newf.write(data)
for
patchedDex
in
directory.glob(
'classes*.dex.patched'
):
patchedDex.unlink()
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)
def
combineShellAndSourceDexs(shellApkTempDir:Path,srcApkTempDir:Path,newApkTempDir:Path):
def
readAndCombineDexs(unpackedApkDir:Path):
combinedDex
=
bytearray()
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'
))
combinedDex
+
=
data
return
combinedDex
with
open
(shellApkTempDir
/
'classes.dex'
,
'rb'
) as f:
shellDexArray
=
bytearray(f.read())
srcDexArray
=
readAndCombineDexs(srcApkTempDir)
newDexLen
=
len
(srcDexArray)
+
len
(shellDexArray)
+
4
encSrcDexArray
=
encrypt(srcDexArray)
newDexArray
=
shellDexArray
+
encSrcDexArray
+
bytearray(
len
(encSrcDexArray).to_bytes(
4
,
'little'
))
fixFileSize(newDexArray, newDexLen)
fixSignature(newDexArray)
fixCheckSum(newDexArray)
with
open
(newApkTempDir
/
'classes.dex'
,
'wb'
) as f:
f.write(newDexArray)
def
handleManifest(srcApkTempDir:Path,shellApkTempDir:Path,newApkTempDir:Path):
with
open
(srcApkTempDir
/
'AndroidManifest.xml'
,
'r'
) as f:
srcManifestEditor
=
ManifestEditor(f.read().encode())
srcApplication
=
srcManifestEditor.getApplicationName()
srcExtractNativeLibs
=
srcManifestEditor.getEtractNativeLibs()
print
(
'SourceApplication:'
,srcApplication)
print
(
'SourceExtractNativeLibs:'
,srcExtractNativeLibs)
with
open
(shellApkTempDir
/
'AndroidManifest.xml'
,
'r'
) as f:
shellManifestEditor
=
ManifestEditor(f.read().encode())
print
(
'ShellApplication:'
,shellManifestEditor.getApplicationName())
srcManifestEditor.setApplicationName(shellManifestEditor.getApplicationName())
if
srcApplication !
=
None
:
print
(
'Source application:'
,srcApplication)
srcManifestEditor.addMetaData(
'APPLICATION_CLASS_NAME'
,srcApplication)
if
srcExtractNativeLibs
=
=
'false'
:
srcManifestEditor.resetExtractNativeLibs()
with
open
(newApkTempDir
/
'AndroidManifest.xml'
,
'w'
) as f:
f.write(srcManifestEditor.getManifestData())
def
start(paths:Paths):
apktool
=
Apktool()
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!'
)
print
(
'Exrtracting dex files codes...'
)
extractAllDexFiles(paths.srcApkTempDir)
print
(
'Extract dex files codes success!'
)
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!'
)
print
(
'Copying shell apk lib files to new apk temp dir...'
)
shutil.copytree(paths.shellApkTempDir
/
'lib'
,paths.newApkTempDir
/
'lib'
,dirs_exist_ok
=
True
)
print
(
'Copy shell apk lib files success!'
)
print
(
'Handling AndroidManifest.xml...'
)
handleManifest(paths.srcApkTempDir,paths.shellApkTempDir,paths.newApkTempDir)
print
(
'Handle AndroidManifest.xml success!'
)
print
(
'Combining shell dex and source dexs...'
)
combineShellAndSourceDexs(paths.shellApkTempDir,paths.srcApkTempDir,paths.newApkTempDir)
print
(
'Combine shell dex and source dexs success!'
)
print
(
'Repacking apk...'
)
apktool.repackApk(paths.newApkTempDir,paths.newApkPath)
print
(
'Repack apk success!'
)
print
(
'Signing apk...'
)
apktool.signApk(paths.newApkPath)
print
(
'Resign apk success!'
)
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'
)
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"
);
log(
"Load libandroidshell.so succeed!"
);
try
{
initEnvironments();
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 {
File dex = getDir(
"tmp_dex"
, MODE_PRIVATE);
odexPath = dex.getAbsolutePath();
libPath=
this
.getApplicationInfo().nativeLibraryDir;
dexPath =
this
.getApplicationInfo().sourceDir;
byte
[] shellDexData = readDexFromApk();
log(
"Get classes.dex from base.apk succeed!"
);
ByteBuffer[] byteBuffers = extractDexFilesFromShellDex(shellDexData);
writeByteBuffersToDirectory(byteBuffers, odexPath);
copyClassesCodesFiles(
this
, odexPath);
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);
}
}
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 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
{
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) {
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();
}
}
private
ByteBuffer[] extractDexFilesFromShellDex(
byte
[] shellDexData) {
int
shellDexlength = shellDexData.length;
byte
[] sourceDexsSizeByte =
new
byte
[
4
];
System.arraycopy(shellDexData,shellDexlength -
4
, sourceDexsSizeByte,
0
,
4
);
ByteBuffer wrap = ByteBuffer.wrap(sourceDexsSizeByte);
int
sourceDexsSizeInt = wrap.order(ByteOrder.LITTLE_ENDIAN).getInt();
Log.d(TAG,
"源dex集合的大小: "
+ sourceDexsSizeInt);
byte
[] sourceDexsData =
new
byte
[sourceDexsSizeInt];
System.arraycopy(shellDexData,shellDexlength - sourceDexsSizeInt -
4
, sourceDexsData,
0
, sourceDexsSizeInt);
sourceDexsData = decrypt(sourceDexsData);
ArrayList<
byte
[]> sourceDexList =
new
ArrayList<>();
int
pos =
0
;
while
(pos < sourceDexsSizeInt){
byte
[] singleDexSizeByte =
new
byte
[
4
];
System.arraycopy(sourceDexsData, pos, singleDexSizeByte,
0
,
4
);
ByteBuffer singleDexwrap = ByteBuffer.wrap(singleDexSizeByte);
int
singleDexSizeInt = singleDexwrap.order(ByteOrder.LITTLE_ENDIAN).getInt();
Log.d(TAG,
"当前Dex的大小: "
+ singleDexSizeInt);
byte
[] singleDexData =
new
byte
[singleDexSizeInt];
System.arraycopy(sourceDexsData,pos +
4
, singleDexData,
0
, singleDexSizeInt);
sourceDexList.add(singleDexData);
pos +=
4
+ singleDexSizeInt;
}
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;
}
private
byte
[] readDexFromApk()
throws
IOException {
String sourceDir =
this
.getApplicationInfo().sourceDir;
log(
"this.getApplicationInfo().sourceDir: "
+sourceDir);
FileInputStream fileInputStream =
new
FileInputStream(sourceDir);
BufferedInputStream bufferedInputStream =
new
BufferedInputStream(fileInputStream);
ZipInputStream zipInputStream =
new
ZipInputStream(bufferedInputStream);
ByteArrayOutputStream byteArrayOutputStream =
new
ByteArrayOutputStream();
ZipEntry zipEntry;
while
((zipEntry = zipInputStream.getNextEntry()) !=
null
){
if
(zipEntry.getName().equals(
"classes.dex"
)){
byte
[] bytes =
new
byte
[
1024
];
int
num;
while
((num = zipInputStream.read(bytes))!=-
1
){
byteArrayOutputStream.write(bytes,
0
, num);
}
}
zipInputStream.closeEntry();
}
zipInputStream.close();
log(
"Read dex from apk succeed!"
);
return
byteArrayOutputStream.toByteArray();
}
private
byte
[] decrypt(
byte
[] data) {
for
(
int
i =
0
; i < data.length; i++){
data[i] ^= (
byte
)
0xff
;
}
return
data;
}
private
boolean
replaceApplication(){
String appClassName =
null
;
try
{
ApplicationInfo applicationInfo = getPackageManager().getApplicationInfo(
this
.getPackageName(), PackageManager.GET_META_DATA);
Bundle metaData = applicationInfo.metaData;
if
(metaData !=
null
&& metaData.containsKey(
"APPLICATION_CLASS_NAME"
)){
appClassName = metaData.getString(
"APPLICATION_CLASS_NAME"
);
}
else
{
log(
"源程序中没有自定义Application"
);
return
false
;
}
}
catch
(PackageManager.NameNotFoundException e) {
log(Log.getStackTraceString(e));
}
log(
"Try to replace Application"
);
Object sCurrentActivityThreadObj = Reflection.getStaticField(
"android.app.ActivityThread"
,
"sCurrentActivityThread"
);
log(
"ActivityThread: "
+ sCurrentActivityThreadObj.toString());
Object mBoundApplicationObj = Reflection.getField(
"android.app.ActivityThread"
,sCurrentActivityThreadObj,
"mBoundApplication"
) ;
log(
"mBoundApplication: "
+mBoundApplicationObj.toString());
Object infoObj = Reflection.getField(
"android.app.ActivityThread$AppBindData"
,mBoundApplicationObj,
"info"
);
log(
"LoadedApk: "
+ infoObj.toString());
Reflection.setField(
"android.app.LoadedApk"
,
"mApplication"
,infoObj,
null
);
Object mInitialApplicationObj = Reflection.getField(
"android.app.ActivityThread"
,sCurrentActivityThreadObj,
"mInitialApplication"
);
log(
"mInitialApplicationObj: "
+ mInitialApplicationObj.toString());
ArrayList<Application> mAllApplicationsObj = (ArrayList<Application>) Reflection.getField(
"android.app.ActivityThread"
,sCurrentActivityThreadObj,
"mAllApplications"
);
mAllApplicationsObj.remove(mInitialApplicationObj);
log(
"mInitialApplication 从 mAllApplications 中移除成功"
);
ApplicationInfo applicationInfo = (ApplicationInfo) Reflection.getField(
"android.app.LoadedApk"
,infoObj,
"mApplicationInfo"
);
log(
"LoadedApk.mApplicationInfo: "
+ applicationInfo.toString());
ApplicationInfo appinfoInAppBindData = (ApplicationInfo) Reflection.getField(
"android.app.ActivityThread$AppBindData"
,mBoundApplicationObj,
"appInfo"
);
log(
"ActivityThread.mBoundApplication.appInfo: "
+ appinfoInAppBindData.toString());
applicationInfo.className = appClassName;
appinfoInAppBindData.className = appClassName;
log(
"Source Application name: "
+ appClassName);
Application application = (Application) Reflection.invokeMethod(
"android.app.LoadedApk"
,
"makeApplication"
,infoObj,
new
Class[]{
boolean
.
class
, Instrumentation.
class
},
new
Object[]{
false
,
null
});
log(
"Create source Application succeed: "
+application);
Reflection.setField(
"android.app.ActivityThread"
,
"mInitialApplication"
,sCurrentActivityThreadObj,application);
log(
"Reset ActivityThread.mInitialApplication by new Application succeed!"
);
ArrayMap mProviderMap = (ArrayMap) Reflection.getField(
"android.app.ActivityThread"
,sCurrentActivityThreadObj,
"mProviderMap"
);
log(
"ActivityThread.mProviderMap: "
+ mProviderMap);
Iterator iterator = mProviderMap.values().iterator();
while
(iterator.hasNext()){
Object providerClientRecord = iterator.next();
Object mLocalProvider = Reflection.getField(
"android.app.ActivityThread$ProviderClientRecord"
,providerClientRecord,
"mLocalProvider"
) ;
if
(mLocalProvider !=
null
){
log(
"ProviderClientRecord.mLocalProvider: "
+ mLocalProvider);
Reflection.setField(
"android.content.ContentProvider"
,
"mContext"
,mLocalProvider,application);
}
}
log(
"Run Application.onCreate"
);
application.onCreate();
return
true
;
}
private
void
replaceClassLoader() {
ClassLoader classLoader =
this
.getClassLoader();
log(
"Current ClassLoader: "
+ classLoader.toString());
log(
"Parent ClassLoader: "
+ classLoader.getParent().toString());
Object sCurrentActivityThreadObj = Reflection.getStaticField(
"android.app.ActivityThread"
,
"sCurrentActivityThread"
);
log(
"ActivityThread.sCurrentActivityThread: "
+ sCurrentActivityThreadObj.toString());
ArrayMap mPackagesObj = (ArrayMap) Reflection.getField(
"android.app.ActivityThread"
,sCurrentActivityThreadObj,
"mPackages"
);
log(
"mPackagesObj: "
+ mPackagesObj.toString());
String currentPackageName =
this
.getPackageName();
log(
"currentPackageName: "
+ currentPackageName);
WeakReference weakReference = (WeakReference) mPackagesObj.get(currentPackageName);
Object loadedApkObj = weakReference.get();
log(
"LoadedApk: "
+ loadedApkObj.toString());
DexClassLoader dexClassLoader =
new
DexClassLoader(dexPath, odexPath,libPath, classLoader.getParent());
Reflection.setField(
"android.app.LoadedApk"
,
"mClassLoader"
,loadedApkObj,dexClassLoader);
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"
);
log(
"Load libandroidshell.so succeed!"
);
try
{
initEnvironments();
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 {
File dex = getDir(
"tmp_dex"
, MODE_PRIVATE);
odexPath = dex.getAbsolutePath();
libPath=
this
.getApplicationInfo().nativeLibraryDir;
dexPath =
this
.getApplicationInfo().sourceDir;
byte
[] shellDexData = readDexFromApk();
log(
"Get classes.dex from base.apk succeed!"
);
ByteBuffer[] byteBuffers = extractDexFilesFromShellDex(shellDexData);
writeByteBuffersToDirectory(byteBuffers, odexPath);
copyClassesCodesFiles(
this
, odexPath);
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);
}
}
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 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
{
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) {
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();
}
}
private
ByteBuffer[] extractDexFilesFromShellDex(
byte
[] shellDexData) {
int
shellDexlength = shellDexData.length;
byte
[] sourceDexsSizeByte =
new
byte
[
4
];
System.arraycopy(shellDexData,shellDexlength -
4
, sourceDexsSizeByte,
0
,
4
);
ByteBuffer wrap = ByteBuffer.wrap(sourceDexsSizeByte);
int
sourceDexsSizeInt = wrap.order(ByteOrder.LITTLE_ENDIAN).getInt();
Log.d(TAG,
"源dex集合的大小: "
+ sourceDexsSizeInt);
byte
[] sourceDexsData =
new
byte
[sourceDexsSizeInt];
System.arraycopy(shellDexData,shellDexlength - sourceDexsSizeInt -
4
, sourceDexsData,
0
, sourceDexsSizeInt);
sourceDexsData = decrypt(sourceDexsData);
ArrayList<
byte
[]> sourceDexList =
new
ArrayList<>();
int
pos =
0
;
while
(pos < sourceDexsSizeInt){
byte
[] singleDexSizeByte =
new
byte
[
4
];
System.arraycopy(sourceDexsData, pos, singleDexSizeByte,
0
,
4
);
ByteBuffer singleDexwrap = ByteBuffer.wrap(singleDexSizeByte);
int
singleDexSizeInt = singleDexwrap.order(ByteOrder.LITTLE_ENDIAN).getInt();
Log.d(TAG,
"当前Dex的大小: "
+ singleDexSizeInt);
byte
[] singleDexData =
new
byte
[singleDexSizeInt];
System.arraycopy(sourceDexsData,pos +
4
, singleDexData,
0
, singleDexSizeInt);
sourceDexList.add(singleDexData);
pos +=
4
+ singleDexSizeInt;
}
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;
}
private
byte
[] readDexFromApk()
throws
IOException {
String sourceDir =
this
.getApplicationInfo().sourceDir;
log(
"this.getApplicationInfo().sourceDir: "
+sourceDir);
FileInputStream fileInputStream =
new
FileInputStream(sourceDir);
BufferedInputStream bufferedInputStream =
new
BufferedInputStream(fileInputStream);
ZipInputStream zipInputStream =
new
ZipInputStream(bufferedInputStream);
ByteArrayOutputStream byteArrayOutputStream =
new
ByteArrayOutputStream();
ZipEntry zipEntry;
while
((zipEntry = zipInputStream.getNextEntry()) !=
null
){
if
(zipEntry.getName().equals(
"classes.dex"
)){
byte
[] bytes =
new
byte
[
1024
];
int
num;
while
((num = zipInputStream.read(bytes))!=-
1
){
byteArrayOutputStream.write(bytes,
0
, num);
}
}
zipInputStream.closeEntry();
}
zipInputStream.close();
log(
"Read dex from apk succeed!"
);
return
byteArrayOutputStream.toByteArray();
}
private
byte
[] decrypt(
byte
[] data) {
for
(
int
i =
0
; i < data.length; i++){
data[i] ^= (
byte
)
0xff
;
}
return
data;
}
private
boolean
replaceApplication(){
String appClassName =
null
;
try
{
ApplicationInfo applicationInfo = getPackageManager().getApplicationInfo(
this
.getPackageName(), PackageManager.GET_META_DATA);
Bundle metaData = applicationInfo.metaData;
if
(metaData !=
null
&& metaData.containsKey(
"APPLICATION_CLASS_NAME"
)){
appClassName = metaData.getString(
"APPLICATION_CLASS_NAME"
);
}
else
{
log(
"源程序中没有自定义Application"
);
return
false
;
}
}
catch
(PackageManager.NameNotFoundException e) {
log(Log.getStackTraceString(e));
}
log(
"Try to replace Application"
);
Object sCurrentActivityThreadObj = Reflection.getStaticField(
"android.app.ActivityThread"
,
"sCurrentActivityThread"
);
log(
"ActivityThread: "
+ sCurrentActivityThreadObj.toString());
Object mBoundApplicationObj = Reflection.getField(
"android.app.ActivityThread"
,sCurrentActivityThreadObj,
"mBoundApplication"
) ;
log(
"mBoundApplication: "
+mBoundApplicationObj.toString());
Object infoObj = Reflection.getField(
"android.app.ActivityThread$AppBindData"
,mBoundApplicationObj,
"info"
);
log(
"LoadedApk: "
+ infoObj.toString());
Reflection.setField(
"android.app.LoadedApk"
,
"mApplication"
,infoObj,
null
);
Object mInitialApplicationObj = Reflection.getField(
"android.app.ActivityThread"
,sCurrentActivityThreadObj,
"mInitialApplication"
);
log(
"mInitialApplicationObj: "
+ mInitialApplicationObj.toString());
ArrayList<Application> mAllApplicationsObj = (ArrayList<Application>) Reflection.getField(
"android.app.ActivityThread"
,sCurrentActivityThreadObj,
"mAllApplications"
);
mAllApplicationsObj.remove(mInitialApplicationObj);
log(
"mInitialApplication 从 mAllApplications 中移除成功"
);
ApplicationInfo applicationInfo = (ApplicationInfo) Reflection.getField(
"android.app.LoadedApk"
,infoObj,
"mApplicationInfo"
);
log(
"LoadedApk.mApplicationInfo: "
+ applicationInfo.toString());
ApplicationInfo appinfoInAppBindData = (ApplicationInfo) Reflection.getField(
"android.app.ActivityThread$AppBindData"
,mBoundApplicationObj,
"appInfo"
);
log(
"ActivityThread.mBoundApplication.appInfo: "
+ appinfoInAppBindData.toString());
applicationInfo.className = appClassName;
appinfoInAppBindData.className = appClassName;
log(
"Source Application name: "
+ appClassName);
Application application = (Application) Reflection.invokeMethod(
"android.app.LoadedApk"
,
"makeApplication"
,infoObj,
new
Class[]{
boolean
.
class
, Instrumentation.
class
},
new
Object[]{
false
,
null
});
log(
"Create source Application succeed: "
+application);
Reflection.setField(
"android.app.ActivityThread"
,
"mInitialApplication"
,sCurrentActivityThreadObj,application);
log(
"Reset ActivityThread.mInitialApplication by new Application succeed!"
);
ArrayMap mProviderMap = (ArrayMap) Reflection.getField(
"android.app.ActivityThread"
,sCurrentActivityThreadObj,
"mProviderMap"
);
log(
"ActivityThread.mProviderMap: "
+ mProviderMap);
Iterator iterator = mProviderMap.values().iterator();
while
(iterator.hasNext()){
Object providerClientRecord = iterator.next();
Object mLocalProvider = Reflection.getField(
"android.app.ActivityThread$ProviderClientRecord"
,providerClientRecord,
"mLocalProvider"
) ;
if
(mLocalProvider !=
null
){
log(
"ProviderClientRecord.mLocalProvider: "
+ mLocalProvider);
Reflection.setField(
"android.content.ContentProvider"
,
"mContext"
,mLocalProvider,application);
}
}
log(
"Run Application.onCreate"
);
application.onCreate();
return
true
;
}
private
void
replaceClassLoader() {
ClassLoader classLoader =
this
.getClassLoader();
log(
"Current ClassLoader: "
+ classLoader.toString());
log(
"Parent ClassLoader: "
+ classLoader.getParent().toString());
Object sCurrentActivityThreadObj = Reflection.getStaticField(
"android.app.ActivityThread"
,
"sCurrentActivityThread"
);
log(
"ActivityThread.sCurrentActivityThread: "
+ sCurrentActivityThreadObj.toString());
ArrayMap mPackagesObj = (ArrayMap) Reflection.getField(
"android.app.ActivityThread"
,sCurrentActivityThreadObj,
"mPackages"
);
log(
"mPackagesObj: "
+ mPackagesObj.toString());
String currentPackageName =
this
.getPackageName();
log(
"currentPackageName: "
+ currentPackageName);
WeakReference weakReference = (WeakReference) mPackagesObj.get(currentPackageName);
Object loadedApkObj = weakReference.get();
log(
"LoadedApk: "
+ loadedApkObj.toString());
DexClassLoader dexClassLoader =
new
DexClassLoader(dexPath, odexPath,libPath, classLoader.getParent());
Reflection.setField(
"android.app.LoadedApk"
,
"mClassLoader"
,loadedApkObj,dexClassLoader);
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__);
int
APILevel;
void
hook();
void
hookExecve();
void
hookMmap();
void
hook_LoadMethod();
std::string codeFilePostfix =
".codes"
;
std::map<std::string,std::map<uint32_t, CodeItem>> codeMapList;
static
void
(*g_originLoadMethod)(
void
* thiz,
const
DexFile* dex_file,
ClassAccessor::Method* method,
void
* klass,
void
* dst);
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;
}
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) {
fseek
(elf_fp, 0L, SEEK_END);
size_t
lib_size =
ftell
(elf_fp);
fseek
(elf_fp, 0L, SEEK_SET);
char
*data = (
char
*)
calloc
(lib_size, 1);
fread
(data, 1, lib_size, elf_fp);
char
*elf_bytes_data = data;
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();
}
const
char
* getClassLinkerLoadMethodSymbol() {
const
char
* sym = find_symbol_in_elf_file(getClassLinkerLoadMethodLibPath(),2,
"ClassLinker"
,
"LoadMethod"
);
return
sym;
}
const
char
* getArtLibName() {
return
APILevel >= 29 ?
"libartbase.so"
:
"libart.so"
;
}
int
fakeExecve(
const
char
*pathname,
char
*
const
argv[],
char
*
const
envp[]) {
BYTEHOOK_STACK_SCOPE();
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"
);
}
}
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"
);
}
}
void
parseExtractedCodeFiles(
const
std::string& dexPath){
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);
size_t
offset=0;
while
(offset<codeBytesLen){
uint8_t* pointer = codeBytes + offset;
uint32_t codeOff = bytes2uint32(pointer);
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);
codeMapList[dexPath].insert(std::pair<uint32_t, CodeItem>(codeOff, codeItem));
logd(
"CodeItem codeOff: %#x insnSize: %#x has created!"
, codeOff, insnSize);
offset += 8 + insnSize*2;
}
}
void
innerLoadMethod(
void
* thiz,
const
DexFile* dexFile, ClassAccessor::Method* method,
void
* klass,
void
* dest){
std::string location = dexFile->location_;
if
(location.find(
"app_tmp_dex"
) == std::string::npos){
return
;
}
if
(codeMapList.find(location)==codeMapList.end()){
logd(
"Parse dex file %s codes"
,location.c_str());
codeMapList[location]=std::map<uint32_t,CodeItem>();
parseExtractedCodeFiles(location);
}
if
(method->code_off_==0){
return
;
}
uint8_t* codeAddr = (uint8_t*)(dexFile->begin_ + method->code_off_ + 16);
std::map<uint32_t,CodeItem> codeMap=codeMapList[location];
if
(codeMap.find(method->code_off_) != codeMap.end()){
CodeItem codeItem = codeMap[method->code_off_];
memcpy
(codeAddr,codeItem.getInsns(),codeItem.getInsnsSize()*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"
);
}
extern
"C"
void
_init(){
APILevel = android_get_device_api_level();
logd(
"Android API Level: %d"
, APILevel)
logd(
"Setting hook..."
)
hook();
}
void
hook(){
bytehook_init(BYTEHOOK_MODE_AUTOMATIC,
false
);
hookExecve();
hookMmap();
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__);
int
APILevel;
void
hook();
void
hookExecve();
void
hookMmap();
void
hook_LoadMethod();
std::string codeFilePostfix =
".codes"
;
std::map<std::string,std::map<uint32_t, CodeItem>> codeMapList;
static
void
(*g_originLoadMethod)(
void
* thiz,
const
DexFile* dex_file,
ClassAccessor::Method* method,
void
* klass,
void
* dst);
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;
}
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) {
fseek
(elf_fp, 0L, SEEK_END);
size_t
lib_size =
ftell
(elf_fp);
fseek
(elf_fp, 0L, SEEK_SET);
char
*data = (
char
*)
calloc
(lib_size, 1);
fread
(data, 1, lib_size, elf_fp);
char
*elf_bytes_data = data;
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();
}
const
char
* getClassLinkerLoadMethodSymbol() {
const
char
* sym = find_symbol_in_elf_file(getClassLinkerLoadMethodLibPath(),2,
"ClassLinker"
,
"LoadMethod"
);
return
sym;
}
const
char
* getArtLibName() {
return
APILevel >= 29 ?
"libartbase.so"
:
"libart.so"
;
}
int
fakeExecve(
const
char
*pathname,
char
*
const
argv[],
char
*
const
envp[]) {
BYTEHOOK_STACK_SCOPE();
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"
);
}
}
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"
);
}
}
void
parseExtractedCodeFiles(
const
std::string& dexPath){
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);
size_t
offset=0;
while
(offset<codeBytesLen){
uint8_t* pointer = codeBytes + offset;
uint32_t codeOff = bytes2uint32(pointer);
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);
codeMapList[dexPath].insert(std::pair<uint32_t, CodeItem>(codeOff, codeItem));
logd(
"CodeItem codeOff: %#x insnSize: %#x has created!"
, codeOff, insnSize);
offset += 8 + insnSize*2;
}
}
void
innerLoadMethod(
void
* thiz,
const
DexFile* dexFile, ClassAccessor::Method* method,
void
* klass,
void
* dest){
std::string location = dexFile->location_;
if
(location.find(
"app_tmp_dex"
) == std::string::npos){
return
;
}
if
(codeMapList.find(location)==codeMapList.end()){
logd(
"Parse dex file %s codes"
,location.c_str());
codeMapList[location]=std::map<uint32_t,CodeItem>();
parseExtractedCodeFiles(location);
}
if
(method->code_off_==0){
return
;
}
uint8_t* codeAddr = (uint8_t*)(dexFile->begin_ + method->code_off_ + 16);
std::map<uint32_t,CodeItem> codeMap=codeMapList[location];
if
(codeMap.find(method->code_off_) != codeMap.end()){
CodeItem codeItem = codeMap[method->code_off_];
memcpy
(codeAddr,codeItem.getInsns(),codeItem.getInsnsSize()*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"
);
}
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直播授课