一、Hook技術概述
Hook技術的核心實際上是動態分析技術,動態分析是指在程序運行時對程序進行調試的技術。眾所周知,Android系統的代碼和回調是按照一定的順序執行的,這里舉一個簡單的例子,如圖所示。
二、Hook Instrumentation
上面講了Hook可以劫持對象,被劫持的對象叫hook點,用代理對象來替代這個Hook點,這樣我們就可以在代理上實現自己想做的操作。這里我們用Hook startActivity來舉例。Activity的插件化中需要解決的一個問題就是啟動一個沒有在AndroidManifest中注冊的Activity,如果按照正常的啟動流程是會報crash的。這里先簡要介紹一下Activity的啟動,具體的啟動方式講解還需移步專門的文獻。
2.1 Activity的Hook點
啟動Activity時應用進程會發消息給AMS,請求AMS創建Activity,AMS在SystemServer系統進程中,其與應用進程是隔離的,AMS管理所有APP的啟動,所以我們無法在系統進程下做hook操作,應該在應用進程中。為了繞過AMS的驗證,我們需要添加一個在Manifest中注冊過的Activity,這個Activity稱為占坑,這樣可以達到欺上瞞下的效果,當AMS驗證通過后再用插件Activity替換占坑去實現相應的功能。 核心功能兩點:
- 替換插件Activity為占坑Activity
- 繞過AMS驗證后需要還原插件Activity
啟動Activity的時候會調用Activity的startActivity()如下:
@Overridepublic void startActivity(Intent intent) {this.startActivity(intent, null);}
復制代碼
接著又調用了startActivity()
@Overridepublic void startActivity(Intent intent, @Nullable Bundle options) {if (options != null) {startActivityForResult(intent, -1, options);} else {// Note we want to go through this call for compatibility with// applications that may have overridden the method.startActivityForResult(intent, -1);}}
復制代碼
查看startActivityForResult方法
public void startActivityForResult(@RequiresPermission Intent intent, int requestCode,@Nullable Bundle options) {if (mParent == null) {options = transferSpringboardActivityOptions(options);Instrumentation.ActivityResult ar =mInstrumentation.execStartActivity(this, mMainThread.getApplicationThread(), mToken, this,intent, requestCode, options);if (ar != null) {mMainThread.sendActivityResult(mToken, mEmbeddedID, requestCode, ar.getResultCode(),ar.getResultData());}if (requestCode >= 0) {// If this start is requesting a result, we can avoid making// the activity visible until the result is received. Setting// this code during onCreate(Bundle savedInstanceState) or onResume() will keep the// activity hidden during this time, to avoid flickering.// This can only be done when a result is requested because// that guarantees we will get information back when the// activity is finished, no matter what happens to it.mStartedActivity = true;}cancelInputsAndStartExitTransition(options);// TODO Consider clearing/flushing other event sources and events for child windows.} else {if (options != null) {mParent.startActivityFromChild(this, intent, requestCode, options);} else {// Note we want to go through this method for compatibility with// existing applications that may have overridden it.mParent.startActivityFromChild(this, intent, requestCode);}}}
復制代碼
上述方法中調用mInstrumentation的execStartActivity方法來啟動Activity,這個mInstrumentation是Activity的成員變量,我們就選擇Instrumentation為Hook點,用代理的Instrumentation去替換原始的Instrumentation來完成Hook,如下是代理類:
public class InstrumentationProxy extends Instrumentation {private Instrumentation mInstrumentation;private PackageManager mPackageManager;public InstrumentationProxy(Instrumentation instrumentation, PackageManager packageManager) {this.mInstrumentation = instrumentation;this.mPackageManager = packageManager;}public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Activity target,Intent intent, int requestCode, Bundle options) {List<ResolveInfo> resolveInfo = mPackageManager.queryIntentActivities(intent, PackageManager.MATCH_ALL);//判斷啟動的插件Activity是否在AndroidManifest.xml中注冊過if (null == resolveInfo || resolveInfo.size() == 0) {//保存目標插件intent.putExtra(HookHelper.REQUEST_TARGET_INTENT_NAME, intent.getComponent().getClassName());//設置為占坑Activityintent.setClassName(who, "replugin.StubActivity");}try {Method execStartActivity = Instrumentation.class.getDeclaredMethod("execStartActivity",Context.class, IBinder.class, IBinder.class, Activity.class,Intent.class, int.class, Bundle.class);return (ActivityResult) execStartActivity.invoke(mInstrumentation, who, contextThread, token, target, intent, requestCode, options);} catch (NoSuchMethodException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();} catch (InvocationTargetException e) {e.printStackTrace();}return null;}public Activity newActivity(ClassLoader cl, String className, Intent intent) throws InstantiationException,IllegalAccessException, ClassNotFoundException {String intentName = intent.getStringExtra(HookHelper.REQUEST_TARGET_INTENT_NAME);if (!TextUtils.isEmpty(intentName)) {return super.newActivity(cl, intentName, intent);}return super.newActivity(cl, className, intent);}}
復制代碼
InstrumentationProxy類繼承類Instrumentation,實現了類execStartActivity方法,接著通過反射去用原始Instrumentation的execStartActivity方法,這就是替換為占坑Activity的過程。Activity的創建是在ActivityThread中,里面有個performLaunchActivity方法;
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {...try {java.lang.ClassLoader cl = appContext.getClassLoader();activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);StrictMode.incrementExpectedActivityCount(activity.getClass());r.intent.setExtrasClassLoader(cl);r.intent.prepareToEnterProcess();if (r.state != null) {r.state.setClassLoader(cl);}}...activity.attach(appContext, this, getInstrumentation(), r.token,r.ident, app, r.intent, r.activityInfo, title, r.parent,r.embeddedID, r.lastNonConfigurationInstances, config,r.referrer, r.voiceInteractor, window, r.configCallback);...
}
復制代碼
這里的newActivity就是創建Activity的過程,我們同樣的在代理類中去實現這個方法,這就是還原插件Activity 的過程。
接下來我們看個例子: 占位坑Activity:
public class StubActivity extends BaseActivity {@Overridepublic int bindLayout() {return R.layout.activity_stub;}@Overridepublic void initViews() {}@Overridepublic void onClick(View v) {}
}
復制代碼
這個Activity一定是需要在AndroidManifest中去注冊。 再寫一個插件Activity
public class TargetActivity extends BaseActivity {@Overridepublic int bindLayout() {return R.layout.activity_target;}@Overridepublic void initViews() {}@Overridepublic void onClick(View v) {}
}
復制代碼
都是很簡單的Activity,TargetActivity并沒有注冊,現在我們需要啟動這個Activity。代理類上面代碼已經貼出來了。接下來就是替換代理類,達到Hook的目的,我們在Application中做這個事情:
public class MyApplication extends Application {@Overrideprotected void attachBaseContext(Context base) {super.attachBaseContext(base);hookActivityThreadInstrumentation();}private void hookActivityThreadInstrumentation() {try {Class<?> activityThreadClass=Class.forName("android.app.ActivityThread");Field activityThreadField=activityThreadClass.getDeclaredField("sCurrentActivityThread");activityThreadField.setAccessible(true);//獲取ActivityThread對象sCurrentActivityThreadObject activityThread=activityThreadField.get(null);Field instrumentationField=activityThreadClass.getDeclaredField("mInstrumentation");instrumentationField.setAccessible(true);//從sCurrentActivityThread中獲取成員變量mInstrumentationInstrumentation instrumentation= (Instrumentation) instrumentationField.get(activityThread);//創建代理對象InstrumentationProxyInstrumentationProxy proxy=new InstrumentationProxy(instrumentation,getPackageManager());//將sCurrentActivityThread中成員變量mInstrumentation替換成代理類InstrumentationProxyinstrumentationField.set(activityThread,proxy);} catch (NoSuchFieldException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();} catch (ClassNotFoundException e) {e.printStackTrace();}}
}
復制代碼
這樣就把原始的Instrumentation替換為代理的了,具體的操作我們在InstrumentationProxy中去做實現。接下來我們就是從主界面跳轉插件Activity了:
public class PluginActivity extends BaseActivity {@Overridepublic int bindLayout() {return R.layout.activity_stub;}@Overridepublic void initViews() {Log.d("", "initViews: ");findViewById(R.id.btn_start_replugin).setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {startActivity(new Intent(PluginActivity.this, TargetActivity.class));}});}@Overridepublic void onClick(View v) {}public static void startActivity(Context context) {Intent i = new Intent(context, PluginActivity.class);context.startActivity(i);}}
復制代碼