
到activitythread才算进入到我们app的代码,来看会源码
这个activitythread里面这个

先获取到仅有的这个单例的实例,,然后反射到这个mpackages的这个arraymap,然后通过当前的这个包名获得loadedapk,然后通过loadedapk获得当前的mclassloader


这个handlebindapplication才是第一次进入app代码世界,重点是第四步的makeappapplication


来,我们跟进去在 这个newapplication是关键

newapplication调用其重载 执行attach函数
再跟 熟悉的attachbasecontext


经过上面的源码分析也可以看到attachbasecontext和oncreate是最先执行的,加载壳一般也在这里做文章。

壳程序要解密dex,还要跟正确的application交付各种资源,不然的话类似于组件等资源加载是不正常。
何时进行dex解密
attachbasecontext和oncreate里面
何时解决动态加载的dex中类的生命周期问题

通过反射进行替换
下面是一个例子
package com.example.kanxue122;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.util.ArrayMap;
import android.util.Log;
import java.lang.Object;
import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import java.io.File;
import java.lang.ref.WeakReference;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
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);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_main);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets;
});
Context appContext = this.getApplicationContext();
startTestActivityFirstMethod(this,"/sdcard/4.dex");
}
public void testDexClassLoader(Context context,String dexfilepath){
File optfile=context.getDir("opt_dex",0);
File libfile=context.getDir("lib_path",0);
ClassLoader parentClassloader=MainActivity.class.getClassLoader();
ClassLoader tmpClassLoader=context.getClassLoader();
DexClassLoader dexClassLoader=new DexClassLoader(dexfilepath,optfile.getAbsolutePath(),libfile.getAbsolutePath(),MainActivity.class.getClassLoader());
Class<?> clazz=null;
try {
clazz = dexClassLoader.loadClass("com.kanxue.test02.TestClass");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
if(clazz!=null){
try {
Method testFuncMethod=clazz.getDeclaredMethod("testFunc");
Object obj=clazz.newInstance();
testFuncMethod.invoke(obj);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
public void replaceClassloader(ClassLoader classloader){
try {
Class<?> ActivityThreadClazz=classloader.loadClass("android.app.ActivityThread");
Method currentActivityThreadMethod= ActivityThreadClazz.getDeclaredMethod("currentActivityThread");
currentActivityThreadMethod.setAccessible(true);
Object activityThreadObj=currentActivityThreadMethod.invoke(null);
//final ArrayMap<String, WeakReference<LoadedApk>> mPackages = new ArrayMap<>();
Field mPackagesField=ActivityThreadClazz.getDeclaredField("mPackages");
mPackagesField.setAccessible(true);
ArrayMap mPackagesObj= (ArrayMap) mPackagesField.get(activityThreadObj);
WeakReference wr= (WeakReference) mPackagesObj.get(this.getPackageName());
Object loadedApkObj=wr.get();
Class LoadedApkClazz=classloader.loadClass("android.app.LoadedApk");
//private ClassLoader mClassLoader;
Field mClassLoaderField=LoadedApkClazz.getDeclaredField("mClassLoader");
mClassLoaderField.setAccessible(true);
mClassLoaderField.set(loadedApkObj,classloader);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
}
public void startTestActivityFirstMethod(Context context,String dexfilepath){
File optfile=context.getDir("opt_dex",0);
File libfile=context.getDir("lib_path",0);
ClassLoader parentClassloader=MainActivity.class.getClassLoader();
ClassLoader tmpClassLoader=context.getClassLoader();
DexClassLoader dexClassLoader=new DexClassLoader(dexfilepath,optfile.getAbsolutePath(),libfile.getAbsolutePath(),MainActivity.class.getClassLoader());
replaceClassloader(dexClassLoader);
Class<?> clazz=null;
try {
clazz = dexClassLoader.loadClass("com.kanxue.test02.TestActivity");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
context.startActivity(new Intent(context,clazz));
}
public void startTestActivitySecondMethod(Context context,String dexfilepath){
File optfile=context.getDir("opt_dex",0);
File libfile=context.getDir("lib_path",0);
ClassLoader pathClassloader=MainActivity.class.getClassLoader();
ClassLoader bootClassloader=MainActivity.class.getClassLoader().getParent();
DexClassLoader dexClassLoader=new DexClassLoader(dexfilepath,optfile.getAbsolutePath(),libfile.getAbsolutePath(),bootClassloader);
try {
Field parentField=ClassLoader.class.getDeclaredField("parent");
parentField.setAccessible(true);
parentField.set(pathClassloader,dexClassLoader);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
/*
* this:dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.kanxue.loaddex-8_fCxispeBuExjw1ryrRZg==/base.apk"],nativeLibraryDirectories=[/data/app/com.kanxue.loaddex-8_fCxispeBuExjw1ryrRZg==/lib/arm64, /system/lib64, /vendor/lib64]]]--parent:dalvik.system.DexClassLoader[DexPathList[[dex file "/sdcard/6.dex"],nativeLibraryDirectories=[/data/user/0/com.kanxue.loaddex/app_lib_path, /system/lib64, /vendor/lib64]]]
this:dalvik.system.DexClassLoader[DexPathList[[dex file "/sdcard/6.dex"],nativeLibraryDirectories=[/data/user/0/com.kanxue.loaddex/app_lib_path, /system/lib64, /vendor/lib64]]]--parent:java.lang.BootClassLoader@fd4323d
root:java.lang.BootClassLoader@fd4323d*/
ClassLoader tmpClassloader=pathClassloader;
ClassLoader parentClassloader=pathClassloader.getParent();
while(parentClassloader!=null){
Log.i("kanxue","this:"+tmpClassloader+"--parent:"+parentClassloader);
tmpClassloader=parentClassloader;
parentClassloader=parentClassloader.getParent();
}
Log.i("kanxue","root:"+tmpClassloader);
Class<?> clazz=null;
try {
clazz = dexClassLoader.loadClass("com.kanxue.test02.TestActivity");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
context.startActivity(new Intent(context,clazz));
}
}
大概是怎么样一个替换法
oncreate
startTestActivityFirstMethodstar
replaceClassloader 通过反射 替换了目标dex当中mclassloader
startTestActivityFirstMethodstar 完成对目标dex目标activity的调用
注意manifest还要改
注意是替换组件的classloader
第二种解决方法

利用了这个双亲委派机制,当前pathclassloader无法加载的时候,通过调用我们加载的classloader来加载,这就需要对当前classloader的父节点做操作
public void startTestActivitySecondMethod(Context context,String dexfilepath){
File optfile=context.getDir("opt_dex",0);
File libfile=context.getDir("lib_path",0);
ClassLoader pathClassloader=MainActivity.class.getClassLoader();
ClassLoader bootClassloader=MainActivity.class.getClassLoader().getParent();
DexClassLoader dexClassLoader=new DexClassLoader(dexfilepath,optfile.getAbsolutePath(),libfile.getAbsolutePath(),bootClassloader);
try {
Field parentField=ClassLoader.class.getDeclaredField("parent");
parentField.setAccessible(true);
parentField.set(pathClassloader,dexClassLoader);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
/*
其实也是通过反射来实现插入的,
DexClassLoader dexClassLoader=new DexClassLoader(dexfilepath,optfile.getAbsolutePath(),libfile.getAbsolutePath(),bootClassloader);
把插入的dexclassloader的parent变成现在classloader的parent
把现在的parent变成dexclassloader
经过log打印
* this:dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.kanxue.loaddex-8_fCxispeBuExjw1ryrRZg==/base.apk"],nativeLibraryDirectories=[/data/app/com.kanxue.loaddex-8_fCxispeBuExjw1ryrRZg==/lib/arm64, /system/lib64, /vendor/lib64]]]--parent:dalvik.system.DexClassLoader[DexPathList[[dex file "/sdcard/6.dex"],nativeLibraryDirectories=[/data/user/0/com.kanxue.loaddex/app_lib_path, /system/lib64, /vendor/lib64]]]
this:dalvik.system.DexClassLoader[DexPathList[[dex file "/sdcard/6.dex"],nativeLibraryDirectories=[/data/user/0/com.kanxue.loaddex/app_lib_path, /system/lib64, /vendor/lib64]]]--parent:java.lang.BootClassLoader@fd4323d
root:java.lang.BootClassLoader@fd4323d*/
可以看到很明显修改了classloader的双亲委派关系
说回加壳,有两种方式,本身没有application的声明,只要自己进行声明,然后壳程序实现解密,加载就可以
另外的一种要额外去调用原apk的application
8以上的inmemoryclassloader从内存中读,避免直接被找到文件
4左右的 opendexfile是文件形式 安全性低
动态加载
是dex加壳,插件化,热更新的一个基础
什么是热加载 就是用到的时候再去加载
动态加载的dex是不具有声明周期特征的,app当中的activity,service等组件无法正常工作。
因为组件的class loader和我们使用的dexclassloader 不具备双亲委派的特征,导致pathclassloader找不到相关的组件类
所以需要去修复这个classloader
解决方案一 就是

第二种

上面有做过
壳的知识

一代壳也叫dex整体壳 内存当中的dex连续的 好脱一些
二代壳 首先对dex有个整体的加密 但是对特定的函数进行了抽取,只有被调用,或者被加载,才在内存有。这里有一个dex hunter 遍历dex所有类进行调用和初始化


发展 整体—函数为粒度的保护

在so的加固方面

vmp的核心在于自定义的解释器
dex2c 也可以理解成编译器 是把dex的代码编译成c的一个过程,c文件再经过ndk变成so文件
对一经过dex2c的apk进行反编译发现

apkotool 反编译
语法分析 找到需要保护的代码 然后编译


加载activity的地址 vmp相同 dex2c不同
针对每一种加壳技术进行分析

一代壳
文件加载和内存加载两种表现形式

抽取解密的时机点 是一个重点

vmp 解释器 找到映射关系
dex2c 语义转换很恶心
要关注JNI相关的调用