整體流程
本篇主要基於Hook代理對象IActivityManager,Activity啓動流程不清楚的可以看看我分析Activity流程的文章。
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options) {
...
int result = ActivityManagerNative.getDefault()
.startActivity(whoThread, who.getBasePackageName(), intent,
intent.resolveTypeIfNeeded(who.getContentResolver()),
token, target != null ? target.mEmbeddedID : null,
requestCode, 0, null, options);
checkStartActivityResult(result, intent);
...
}
啓動Activity交給了代理對象ActivityManagerNative,他接着會調用AMS啓動Activity。
下面checkStartActivityResult(result, intent);是對result和intent做一個檢測,也就是看要啓動的intent是不是合法。
所以我們需要hook這個ActivityManagerNative的startActivity方法。
我們需要提前創建一個佔坑的Activity,用於欺騙系統。
在這個方法裏面將intent的信息替換成佔坑的Activity信息。剩下流程等就交給AMS,因爲我們啓動的Activity是合法的,所以不會出現問題。
根據activity的啓動流程我們也可以知道,到最後啓動activity的準備工作做完後,會調用ApplicationThread的scheduleLaunchActivity方法會給主線程發送一個message。
private class H extends Handler {
...
public void handleMessage(Message msg) {
if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
switch (msg.what) {
case LAUNCH_ACTIVITY: {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
final ActivityClientRecord r = (ActivityClientRecord) msg.obj;
r.packageInfo = getPackageInfoNoCheck(
r.activityInfo.applicationInfo, r.compatInfo);
handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
} break;
...
}
}
通過handleLaunchActivity方法就正式創建啓動Activity了。
在這裏就又要藉助Handler的消息處理機制,將我們的Intent替換回來了。
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
handler最先調用message的CallBack,然後會調用自己的mCallBack,最後才調用handleMessage(msg)。
H類中實現了handleMessage方法,並沒有實現mCallBack,所以我們需要通過反射創建我們的mCallback 並賦值給H,這樣我們就可以在他處理消息之前做一些我們的處理,也就是將Intent替換回來。
還有一個問題,就是系統如何才能從我們提供的APK中加載class文件呢?
這裏我們就要使用ClassLoader了。
這裏直接給出結論,Android裏面PathClassLoader用於加載系統和主dex文件,DexClassLoader用於其他Dex文件。
所以我們只要創建我們的ClassLoader,加載我們的apk。然後通過反射將最後的結果合併至PathClassLoader裏面就可以。
基本流程就是如此
接着我們看一下實現
實現
首先創建我們的classloader
private void hook() throws IllegalAccessException, NoSuchFieldException,
ClassNotFoundException, NoSuchMethodException, InvocationTargetException {
String cachePath = getCacheDir().getAbsolutePath();
String apkPath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/chajian" +
".apk";
DexClassLoader dexClassLoader = new DexClassLoader(apkPath, cachePath, cachePath,
getClassLoader());
RejectPluginHelper.loadPlugin(dexClassLoader, getApplicationContext());
HookHelper.hookActivityManager(this);
HookHelper.hookActivityThread();
}
在loadPlugin(dexClassLoader, getApplicationContext());裏面,我們合併兩個dex的list
//加載插件apk
public static void loadPlugin(DexClassLoader dexClassLoader, Context applicationContext) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
//獲取PathClassLoader
PathClassLoader pathClassLoader = (PathClassLoader) applicationContext.getClassLoader();
//分別獲取宿主和插件的PathList
Object suzhuPathList = getPathList(pathClassLoader);
Object chajianPathList = getPathList(dexClassLoader);
//獲得PathList的element併合並
Object newElements = mergeElements(getElements(suzhuPathList),
getElements(chajianPathList));
Log.d("Lpp", "newElements: " + Array.getLength(newElements));
//重新設置給宿主的dexElement
Field field = suzhuPathList.getClass().getDeclaredField("dexElements");
field.setAccessible(true);
field.set(suzhuPathList, newElements);
}
private static Object mergeElements(Object elements2, Object elements1) {
Log.d("Lpp", "suzhuPathList: " + Array.getLength(elements1));
Log.d("Lpp", "chajianPathList: " + Array.getLength(elements2));
int len1 = Array.getLength(elements1);
int len2 = Array.getLength(elements2);
Object newArr = Array.newInstance(elements1.getClass().getComponentType(), len1 + len2);
for (int i = 0; i < len1; i++) {
Array.set(newArr, i, Array.get(elements1, i));
}
for (int i = len1; i < len1 + len2; i++) {
Array.set(newArr, i, Array.get(elements2, i - len1));
}
return newArr;
}
// 獲取DexPathList 中的dexElements
private static Object getElements(Object suzhuPathList) throws NoSuchFieldException,
IllegalAccessException {
Class cl = suzhuPathList.getClass();
Field field = cl.getDeclaredField("dexElements");
field.setAccessible(true);
return field.get(suzhuPathList);
}
// 獲取DexPathList
private static Object getPathList(Object loader) throws ClassNotFoundException,
NoSuchFieldException, IllegalAccessException {
Class cl = Class.forName("dalvik.system.BaseDexClassLoader");
Field field = cl.getDeclaredField("pathList");
field.setAccessible(true);
return field.get(loader);
}
然後通過
HookHelper.hookActivityManager(this);
HookHelper.hookActivityThread();
分別hook ActivityManager 和 Handler的CallBack
對Hook不熟悉的得先了解了解hook。
public static void hookActivityManager(Context context) throws ClassNotFoundException, NoSuchFieldException,
IllegalAccessException {
//得到ActivityManagerNative的class對象
Class<?> activityManagerNativeClass = Class.forName("android.app.ActivityManagerNative");
//得到他的gDefault字段,他是Singleton類型
Field field = activityManagerNativeClass.getDeclaredField("gDefault");
field.setAccessible(true);
//靜態直接傳null即可
Object gDefaultObj = field.get(null);
//得到Singleton class
Class singletonCLass = Class.forName("android.util.Singleton");
//得到他的成員mInstance 他是IActivityManager
Field mInstanceField = singletonCLass.getDeclaredField("mInstance");
mInstanceField.setAccessible(true);
//得到gDefault的 mInstance
Object activityManagerObj = mInstanceField.get(gDefaultObj);
//給IActivityManager設置代理
Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
new Class[]{Class.forName("android.app.IActivityManager")},
new IActivityManagerProxy(activityManagerObj, context));
//將代理對象設置給gDefault
mInstanceField.set(gDefaultObj, proxy);
Log.d("Lpp", "已經替換掉了Intent");
}
代理類
/**
* @author liupeidong
* Created on 2019/10/9 11:08
*/
public class IActivityManagerProxy implements InvocationHandler {
private Context context;
private Object obj;
public IActivityManagerProxy(Object obj, Context context) {
this.context = context;
this.obj = obj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().equals("startActivity")) {
//獲得傳入的Intent
Intent targetIntent = null;
int index;
for (index = 0; index < args.length; index++) {
if (args[index] instanceof Intent) {
targetIntent = (Intent) args[index];
break;
}
}
//創建假Intent
Intent fakeIntent = new Intent(context, FakeActivity.class);
//把真的先存裏面
fakeIntent.putExtra("targetIntent", targetIntent);
//設置到參數裏面
args[index] = fakeIntent;
return method.invoke(obj, args);
}
return method.invoke(obj, args);
}
}
主要做的任務就是Intent替換,然後反射賦值代理類。
接着通過反射,給H賦值我們封裝的CallBack
public static void hookActivityThread() throws ClassNotFoundException, NoSuchMethodException,
InvocationTargetException, IllegalAccessException, NoSuchFieldException {
//先得到ActivityThread對象,他有一個返回自己本身的方法
Class activityThreadClass = Class.forName("android.app.ActivityThread");
Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod(
"currentActivityThread");
currentActivityThreadMethod.setAccessible(true);
Object activityThreadObj = currentActivityThreadMethod.invoke(null);
//得到他的成員 mH
Field mHField = activityThreadClass.getDeclaredField("mH");
mHField.setAccessible(true);
Object handleObj = mHField.get(activityThreadObj);
//給mH設置mCallback
Field callBackField = Handler.class.getDeclaredField("mCallback");
callBackField.setAccessible(true);
ChangeCallBack changeCallBack = new ChangeCallBack((android.os.Handler) handleObj);
Log.d("Lpp", "hookActivityThread 1: " + changeCallBack);
callBackField.set(handleObj, changeCallBack);
Log.d("Lpp", "替換回Intent");
Class activityThreadClass2 = Class.forName("android.app.ActivityThread");
Method currentActivityThreadMethod2 = activityThreadClass2.getDeclaredMethod(
"currentActivityThread");
currentActivityThreadMethod2.setAccessible(true);
Object activityThreadObj2 = currentActivityThreadMethod2.invoke(null);
//得到他的成員 mH
Field mHField2 = activityThreadClass2.getDeclaredField("mH");
mHField2.setAccessible(true);
Object handleObj2 = mHField2.get(activityThreadObj2);
//給mH設置mCallback
Field callBackField2 = Handler.class.getDeclaredField("mCallback");
callBackField2.setAccessible(true);
Log.d("Lpp", "hookActivityThread 2: " + callBackField2.get(handleObj2));
}
我們的CallBack
/**
* @author liupeidong
* Created on 2019/10/9 13:05
*/
public class ChangeCallBack implements Handler.Callback {
private Handler handler;
public ChangeCallBack(Handler handler) {
this.handler = handler;
}
@Override
public boolean handleMessage(Message msg) {
switch (msg.what) {
case 100:
try {
handleLaunchActivity(msg);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
break;
}
handler.handleMessage(msg);
return true;
}
private void handleLaunchActivity(Message msg) throws NoSuchFieldException,
IllegalAccessException {
Object obj = msg.obj;
Field intent = obj.getClass().getDeclaredField("intent");
intent.setAccessible(true);
Intent fakeIntent = (Intent) intent.get(obj);
Intent targetIntent = fakeIntent.getParcelableExtra("targetIntent");
fakeIntent.setComponent(targetIntent.getComponent());
// intent.set(obj, targetIntent);
}