整体的流程
- HOOK 绕过AMS对非注册的Acitivity的检查,使用一个已经注册过的代理activity充当傀儡,让其通过校验
- HOOK 还原目标的Activity
- HOOK 方式融合宿主和插件DexElement
1. AMS对Activity的检查如何绕过去
- android.app.Instrumentation#checkStartActivityResult 会对startActivity后的result进行检测并抛出异常(have you declared this activity in your AndroidManifest) 那么解决办法就是使用一个已经注册过的代理activity充当傀儡,让其通过校验
在调用startActivity的方法时会再调用binder方法之前调用
ActivityManagerNative.getDefault().startActivity
此方法会返回IActivityManager是一个接口 ,ActivityManagerNative.getDefault() 又是一个单例,那么只需要使用动态代理,hook此方法,发现是插件中的activity将其替换为宿主中的代理activity即可
Class mIActivityManagerClass = Class.forName("android.app.IActivityManager");
Class mActivityManagerNativeClass = Class.forName("android.app.ActivityManagerNative");
final Object mIActivityManager = mActivityManagerNativeClass2.getMethod("getDefault").invoke(null);
Object mIActivityManagerProxy = Proxy.newProxyInstance(
getClassLoader(),
new Class[]{mIActivityManagerClass},
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("startActivity".equals(method.getName())) {
// 用ProxyActivity 绕过了 AMS检查
Intent intent = new Intent(MyApplication.this, ProxyActivity.class);
intent.putExtra("actionIntent", ((Intent) args[2])); // 把之前跳转参数传递过去方便还原
args[2] = intent;
}
return method.invoke(mIActivityManager, args);
}
});
Class mActivityManagerNativeClass = Class.forName("android.app.ActivityManagerNative");
Field gDefaultField = mActivityManagerNativeClass.getDeclaredField("gDefault");
gDefaultField.setAccessible(true);
Object gDefault = gDefaultField.get(null);
// 替换掉系统ActivityManager单例中的方法
Class mSingletonClass = Class.forName("android.util.Singleton");
Field mInstanceField = mSingletonClass.getDeclaredField("mInstance");
mInstanceField.setAccessible(true);
mInstanceField.set(gDefault, mIActivityManagerProxy);
2. 还原目标Activity
在经过了AMS 校验之后,真正启动是通过应用进程的ActivityThread进行启动的,然后最终会通过H类(extends Handler)的 msg.what为 ActivityThread.H#LAUNCH_ACTIVITY 来启动Activity的
public void handleMessage(Message msg) {
switch (msg.what) {
case LAUNCH_ACTIVITY: {
final ActivityClientRecord r = (ActivityClientRecord) msg.obj;
r.packageInfo = getPackageInfoNoCheck(
r.activityInfo.applicationInfo, r.compatInfo);
handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");
} break;
...
}
}
然后发现ActivityClientRecord此类是记录了要启动activity的信息的,还有Intent 那我们就可以将H类(extends Handler) 进行HOOK(Handler类有一个地方如果自定义了mCallBack 就优先给mCallBack继续处理), 发现有Intent 中包含了actionIntent字段,我们就将其替换为真正要启动的Activity
Field mCallbackFiled = Handler.class.getDeclaredField("mCallback");
mCallbackFiled.setAccessible(true); // 授权
/**
* handler对象怎么来
* 1.寻找H,先寻找ActivityThread
*
* 执行此方法 public static ActivityThread currentActivityThread()
*
* 通过ActivityThread 找到 H
*
*/
Class mActivityThreadClass = Class.forName("android.app.ActivityThread");
// 获得ActivityThrea对象
Object mActivityThread = mActivityThreadClass.getMethod("currentActivityThread").invoke(null);
Field mHField = mActivityThreadClass.getDeclaredField("mH");
mHField.setAccessible(true);
// 获取真正对象
Handler mH = (Handler) mHField.get(mActivityThread);
mCallbackFiled.set(mH, new MyCallback(mH)); // 替换 增加我们自己的实现代码
public static final int LAUNCH_ACTIVITY = 100;
class MyCallback implements Handler.Callback {
private Handler mH;
public MyCallback(Handler mH) {
this.mH = mH;
}
@Override
public boolean handleMessage(Message msg) {
switch (msg.what) {
case LAUNCH_ACTIVITY:
// 做我们在自己的业务逻辑(把ProxyActivity 换成 TestActivity)
Object obj = msg.obj;
try {
// 我们要获取之前Hook携带过来的 TestActivity
Field intentField = obj.getClass().getDeclaredField("intent");
intentField.setAccessible(true);
// 获取 intent 对象,才能取出携带过来的 actionIntent
Intent intent = (Intent) intentField.get(obj);
// actionIntent == TestActivity的Intent
Intent actionIntent = intent.getParcelableExtra("actionIntent");
if (actionIntent != null) {
/*
if (activityList.contains(actionIntent.getComponent().getClassName())) {
intentField.set(obj, actionIntent); // 把ProxyActivity 换成 TestActivity
} else { // 没有权限
intentField.set(obj, new Intent(HookApplication.this, PermissionActivity.class));
}
*/
intentField.set(obj, actionIntent); // 把ProxyActivity 换成 TestActivity
}
} catch (Exception e) {
e.printStackTrace();
}
break;
}
mH.handleMessage(msg);
// 让系统继续正常往下执行
// return false; // 系统就会往下执行
return true; // 系统不会往下执行
}
}
3. HOOK 类加载流程,将插件中的类加载
在执行到ActivityThread H类的LANUCH_ACTIVITY 之后会调用 handleLaunchActivity->performLaunchActivity->Instrumentation#newActivity(java.lang.ClassLoader, java.lang.String, android.content.Intent) 方法,里面调用了classLoader.loadClass 进行创建Activity对象
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
// First, check if the class has already been loaded
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) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
}
}
return c;
}
这就是经典的双亲委派了,那么我们发现如果都找不到会执行findClass方法,这里肯定会调用PathClassLoader findClass方法,但是PathClassLoader并没有覆盖实现此方法所以执行的是BaseDexClassLoader的findClass方法
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
Class c = pathList.findClass(name, suppressedExceptions);
if (c == null) {
ClassNotFoundException cnfe = new ClassNotFoundException(
"Didn't find class \"" + name + "\" on path: " + pathList);
for (Throwable t : suppressedExceptions) {
cnfe.addSuppressed(t);
}
throw cnfe;
}
return c;
}
也就是通过pathList进行查找的
DexPathList.java
public Class<?> findClass(String name, List<Throwable> suppressed) {
for (Element element : dexElements) {
Class<?> clazz = element.findClass(name, definingContext, suppressed);
if (clazz != null) {
return clazz;
}
}
if (dexElementsSuppressedExceptions != null) {
suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
}
return null;
}
又通过遍历Element进行查找,Element 实际上封装的就是我们app安装时的classes.dex classes2.dex 等
Element.java
public Class<?> findClass(String name, ClassLoader definingContext,
List<Throwable> suppressed) {
return dexFile != null ? dexFile.loadClassBinaryName(name, definingContext, suppressed)
: null;
}
最终调用DexFile loadClassBinaryName (此方法以下就是native了)
那我们就可以将PathClassLoader进行HOOK ,使用DexClassLoader load插件APK,然后反射获取到PathList中的dexElements,添加到原来的classLoader中DexPathList中的dexElements,即可实现插件和宿主的类加载融合起来
// 第一步:找到宿主 dexElements 得到此对象 PathClassLoader代表是宿主
PathClassLoader pathClassLoader = (PathClassLoader) this.getClassLoader(); // 本质就是PathClassLoader
Class mBaseDexClassLoaderClass = Class.forName("dalvik.system.BaseDexClassLoader");
// private final DexPathList pathList;
Field pathListField = mBaseDexClassLoaderClass.getDeclaredField("pathList");
pathListField.setAccessible(true);
Object mDexPathList = pathListField.get(pathClassLoader);
Field dexElementsField = mDexPathList.getClass().getDeclaredField("dexElements");
dexElementsField.setAccessible(true);
// 本质就是 Element[] dexElements
Object dexElements = dexElementsField.get(mDexPathList);
/*** ---------------------- ***/
// 第二步:找到插件 dexElements 得到此对象,代表插件 DexClassLoader--代表插件
File file = new File(Environment.getExternalStorageDirectory() + File.separator + "p.apk");
if (!file.exists()) {
throw new FileNotFoundException("没有找到插件包!!");
}
String pluginPath = file.getAbsolutePath();
File fileDir = this.getDir("pluginDir", Context.MODE_PRIVATE); // data/data/包名/pluginDir/
DexClassLoader dexClassLoader = new
DexClassLoader(pluginPath, fileDir.getAbsolutePath(), null, getClassLoader());
Class mBaseDexClassLoaderClassPlugin = Class.forName("dalvik.system.BaseDexClassLoader");
// private final DexPathList pathList;
Field pathListFieldPlugin = mBaseDexClassLoaderClassPlugin.getDeclaredField("pathList");
pathListFieldPlugin.setAccessible(true);
Object mDexPathListPlugin = pathListFieldPlugin.get(dexClassLoader);
Field dexElementsFieldPlugin = mDexPathListPlugin.getClass().getDeclaredField("dexElements");
dexElementsFieldPlugin.setAccessible(true);
// 本质就是 Element[] dexElements
Object dexElementsPlugin = dexElementsFieldPlugin.get(mDexPathListPlugin);
// 第三步:创建出 新的 dexElements []
int mainDexLeng = Array.getLength(dexElements);
int pluginDexLeng = Array.getLength(dexElementsPlugin);
int sumDexLeng = mainDexLeng + pluginDexLeng;
// 参数一:int[] String[] ... 我们需要Element[]
// 参数二:数组对象的长度
// 本质就是 Element[] newDexElements
Object newDexElements = Array.newInstance(dexElements.getClass().getComponentType(),sumDexLeng); // 创建数组对象
// 第四步:宿主dexElements + 插件dexElements =----> 融合 新的 newDexElements
for (int i = 0; i < sumDexLeng; i++) {
// 先融合宿主
if (i < mainDexLeng) {
// 参数一:新要融合的容器 -- newDexElements
Array.set(newDexElements, i, Array.get(dexElements, i));
} else { // 再融合插件的
Array.set(newDexElements, i, Array.get(dexElementsPlugin, i - mainDexLeng));
}
}
// 第五步:把新的 newDexElements,设置到宿主中去
// 宿主
dexElementsField.set(mDexPathList, newDexElements);
4. 额外的知识
Android中有三个classLoad 比较常用
- BootClassLoader 加载系统类,处于最顶级的位置
- PathClassLoader 只能加载系统中已经安装过的apk
- DexClassLoader 能够加载未安装的jar/apk/dex
从启动了解下
- 内核启动
- init进程启动(系统的第一个进程,也称为守护进程)
- init进程会启动zygote进程(zygote进程会孵化其他的所有子进程)
- zygote会孵化SystemServer进程
- SystemServer会启动很多服务(AMS, PMS)
在启动zygote进程时会调用ZygoteInit#main 里面的preload-> preloadClasses() ,里面会最终调用
loader = BootClassLoader.getInstance();
还是会调用startSystemServer -> createSystemServerClassLoader-> PathClassLoaderFactory.createClassLoader 生成pathClassLoader