閒來無事,研究一下activity的啟動,順便嘗試了一下hook系統api啟動一個未註冊Activity的方案。 本方案採用的是插樁的方案。主要hook了ActivityThread中的Instrumentation的newActivity方法和 Activity中的Instrumentation的execStratActivity方法。 我們知道啟動一個未註冊的Activity,系統會報ClassNotFound的異常,這是因為在Ams中的StartActivty中有對intent的檢測,所以我們可以啟動一個代理類ProxyActivty,繞過系統的檢測,然後在newActivity中還原為我們真正要啟動的Activity.
第一步:hook newActivity,在Application中新增如下的方法
public void replaceActivityThreadInstrumentation(){
try {
Class<?> clazz = Class.forName("android.app.ActivityThread");
Field activityThread = clazz.getDeclaredField("sCurrentActivityThread");
activityThread.setAccessible(true);
Object object = activityThread.get(Object.class);
Field mInstrumentation = clazz.getDeclaredField("mInstrumentation");
mInstrumentation.setAccessible(true);
Instrumentation instrumentation = (Instrumentation)mInstrumentation.get(object);
Instrumentation instrumentationProxy = new InstrumentationProxy(instrumentation);
mInstrumentation.set(object,instrumentationProxy);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
複製程式碼
獲取ActivityThread中的sCurrentActivityThread,然後交到我們自己的InstrumentationProxy做代理。這裡的方案參考了Android外掛化之startActivity hook的幾種姿勢
public class InstrumentationProxy extends Instrumentation {
private static final String TAG = "InstrumentationProxy";
private Instrumentation mBase;
public InstrumentationProxy(Instrumentation instrumentation) {
mBase = instrumentation;
}
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options) {
// Hook之前, XXX到此一遊!
Log.e(TAG, "\n執行了startActivity, 引數如下: \n" + "who = [" + who + "], " +
"\ncontextThread = [" + contextThread + "], \ntoken = [" + token + "], " +
"\ntarget = [" + target + "], \nintent = [" + intent +
"], \nrequestCode = [" + requestCode + "], \noptions = [" + options + "]");
Intent intent1 = new Intent(target,ProxyActivity.class);
// 開始呼叫原始的方法, 調不呼叫隨你,但是不呼叫的話, 所有的startActivity都失效了.
// 由於這個方法是隱藏的,因此需要使用反射呼叫;首先找到這個方法
try {
//這裡通過反射找到原始Instrumentation類的execStartActivity方法
Method execStartActivity = Instrumentation.class.getDeclaredMethod(
"execStartActivity",
Context.class, IBinder.class, IBinder.class, Activity.class,
Intent.class, int.class, Bundle.class);
execStartActivity.setAccessible(true);
return (ActivityResult) execStartActivity.invoke(mBase, who,
contextThread, token, target, intent1, requestCode, options);
} catch (Exception e) {
throw new RuntimeException("do not support!!! pls adapt it");
}
}
public Activity newActivity(ClassLoader cl, String className,
Intent intent)
throws InstantiationException, IllegalAccessException,
ClassNotFoundException {
Log.e(TAG,"before hook"+ className);
if(className.equals("com.edu.myapplication.ProxyActivity")){
className = "com.edu.myapplication.SecondActivity";
}
Log.e(TAG,"after hook"+ className);
return (Activity)cl.loadClass(className).newInstance();
}
}
複製程式碼
在newActivity中根據代理類的包名判斷是否是我們需要hook的時機,這個代理類同時也hook了execStartActivity。
第二步:在我們呼叫startActivity的Activity,新增如下方法:
public static void replaceInstrumentation(Activity activity){
Class<?> k = Activity.class;
try {
//通過Activity.class 拿到 mInstrumentation欄位
Field field = k.getDeclaredField("mInstrumentation");
field.setAccessible(true);
//根據activity內mInstrumentation欄位 獲取Instrumentation物件
Instrumentation instrumentation = (Instrumentation)field.get(activity);
//建立代理物件
Instrumentation instrumentationProxy = new InstrumentationProxy(instrumentation);
//進行替換
field.set(activity,instrumentationProxy);
} catch (IllegalAccessException e){
e.printStackTrace();
}catch (NoSuchFieldException e){
e.printStackTrace();
}
}
複製程式碼
然後呼叫startActivity,SecondActivity是未註冊的Activity.
replaceInstrumentation(this);
startActivity(new Intent(this,SecondActivity.class));
複製程式碼
這樣就啟動了SecondActivity