说到Hook技术需要先提到逆向工程,主要目的是在不能轻易获得必要生产信息的情况下,直接从成品分析,推导出产品的设计原理 。
逆向分析又分为
静态分析:不执行程序的情况下对程序行为进行分析的技术
动态分析:在程序运行时对程序进行调试的技术。Hook属于动态分析。
代理模式 设计模式--静态代理模式和动态代理模式原理及实现
Hook技术概述
对象A直接调用B,对象B结果直接回调给A。
Hook可以是一个方法或者对象,它位于对象A和B之间,当对象A调用对象B时会在之前做一些处理。也可以用于应用进程调用系统进程时做一些处理,更改他们间的关系。
其中被Hook的对象B,称作Hook点 。
整个Hook的过程分为三步:
寻找Hook点 。原则上是静态变量或者单例对象(容易找到并且不易变化的对象 ),尽量Hook Public的对象和方法,非Public不保证每个版本保持一致,可能需要适配。
选择合适的代理方式 。如果是接口可以使用动态代理方式,类的话多考虑使用静态模式。
用代理对象替换原始对象。
Hook实例简析 Hook startActivity() Activity的启动方式有两种
1.Hook Activity.startActivity() 从源码分析上需要从startActivity()开始
../android/app/Activity.java 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 @Override public void startActivity (Intent intent) { this .startActivity(intent, null ); } @Override public void startActivity (Intent intent, @Nullable Bundle options) { if (options != null ) { startActivityForResult(intent, -1 , options); } else { startActivityForResult(intent, -1 ); } }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); ... } }
最终调用到的是Instrumentation.execStartActivity()执行启动下一个Activity的逻辑。
按照Hook过程分析,需要先找到Hook点。由于要Hook的就是Activity的启动,所以我们可以设置Instrumentation为Hook点,然后使用静态代理模式生成代理对象,最后替换掉原始的Instrumentation继续执行启动逻辑。
先创建Instrumentation代理对象InstrumentationProxy
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 public class InstrumentationProxy extends Instrumentation { private static final String TAG = "InstrumentationProxy" ; Instrumentation mInstrumentation; public InstrumentationProxy (Instrumentation _instrumentation) { mInstrumentation = _instrumentation; } public Activity newActivity (ClassLoader cl, String className, Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException { return mInstrumentation.newActivity(cl, className, intent); } public ActivityResult execStartActivity (Context who, IBinder contextThread, IBinder token, Activity target, Intent intent, int requestCode, Bundle options) { Log.e(TAG, "hook success" + who); try { @SuppressLint("PrivateApi") 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(mInstrumentation, who, contextThread, token, target, intent, requestCode, options); } catch (Exception e) { throw new RuntimeException ("do not support!!! pls adapt it" ); } } }
在需要使用的Activity中实现Hook方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 public class LoadActivity extends AppCompatActivity { @Override public void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.act_load); replaceActivityInstrumentation(LoadActivity.this ); Button btn_jump = findViewById(R.id.btn_jump); btn_jump.setOnClickListener(new View .OnClickListener() { @Override public void onClick (View v) { startActivity(new Intent (LoadActivity.this , MainActivity.class)); } }); } public void replaceActivityInstrumentation (Activity activity) { try { Field field = Activity.class.getDeclaredField("mInstrumentation" ); field.setAccessible(true ); Instrumentation instrumentation = (Instrumentation) field.get(activity); Instrumentation instrumentationProxy = new InstrumentationProxy (instrumentation); field.set(activity, instrumentationProxy); } catch (Exception e) { e.printStackTrace(); } } }
理论上来说Hook操作越早越好,handleLaunchActivity()内部开始执行启动流程,然后会调用到Activity.attach()内部继续执行。attachBaseContext()是最早执行的,但是其中无法去执行Hook操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 final void attach (Context context, ActivityThread aThread, Instrumentation instr, IBinder token, int ident, Application application, Intent intent, ActivityInfo info, CharSequence title, Activity parent, String id, NonConfigurationInstances lastNonConfigurationInstances, Configuration config, String referrer, IVoiceInteractor voiceInteractor, Window window, ActivityConfigCallback activityConfigCallback) { attachBaseContext(context); ... mInstrumentation = instr; ... }
2.Hook Context.startActivity() Context的具体实现类为ContextImpl,ContextImpl.startActivity()如下所示
./android/app/ContextImpl.java 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @Override public void startActivity (Intent intent) { warnIfCallingFromSystemProcess(); startActivity(intent, null ); }public void startActivity (Intent intent, Bundle options) { warnIfCallingFromSystemProcess(); if ((intent.getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0 && options != null && ActivityOptions.fromBundle(options).getLaunchTaskId() == -1 ) { throw new AndroidRuntimeException ( "Calling startActivity() from outside of an Activity " + " context requires the FLAG_ACTIVITY_NEW_TASK flag." + " Is this really what you want?" ); } mMainThread.getInstrumentation().execStartActivity( getOuterContext(), mMainThread.getApplicationThread(), null , (Activity) null , intent, -1 , options); }
getInstrumentation()去获取对应的Instrumentation不过这个是可以全局生效的,ActivityThread是主线程的管理类,Instrumentation是其成员变量,一个进程中只会存在一个ActivityThread,因此依然设置Instrumentation为Hook点。
可以在Application中或者Activity中去设置Hook方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 public class App extends Application { @Override protected void attachBaseContext (Context base) { super .attachBaseContext(base); replaceContextInstrumentation(); } @Override public void onCreate () { super .onCreate(); } public void replaceContextInstrumentation () { try { @SuppressLint("PrivateApi") Class<?> activityThreadClazz = Class.forName("android.app.ActivityThread" ); Field activityThreadField = activityThreadClazz.getDeclaredField("sCurrentActivityThread" ); activityThreadField.setAccessible(true ); Object currentActivityThread = activityThreadField.get(null ); Field mInstrumentationField = activityThreadClazz.getDeclaredField("mInstrumentation" ); mInstrumentationField.setAccessible(true ); Instrumentation mInstrumentation = (Instrumentation) mInstrumentationField.get(currentActivityThread); Instrumentation instrumentationProxy = new InstrumentationProxy (mInstrumentation); mInstrumentationField.set(currentActivityThread, instrumentationProxy); } catch (Exception e) { e.printStackTrace(); } } }
可能出现的问题
无法进行Hook操作
1 E/Instrumentation: Uninitialized ActivityThread, likely app-created Instrumentation, disabling AppComponentFactory
出现上述提示,发生的情况是在Android P上运行应用时。
主要是因为在Android P的源代码中对Instrumentation.newActivity()进行了调整
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 private ActivityThread mThread = null ; public Activity newActivity (ClassLoader cl, String className, Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException { String pkg = intent != null && intent.getComponent() != null ? intent.getComponent().getPackageName() : null ; return getFactory(pkg).instantiateActivity(cl, className, intent); } private AppComponentFactory getFactory (String pkg) { if (pkg == null ) { Log.e(TAG, "No pkg specified, disabling AppComponentFactory" ); return AppComponentFactory.DEFAULT; } if (mThread == null ) { Log.e(TAG, "Uninitialized ActivityThread, likely app-created Instrumentation," + " disabling AppComponentFactory" , new Throwable ()); return AppComponentFactory.DEFAULT; } LoadedApk apk = mThread.peekPackageInfo(pkg, true ); if (apk == null ) apk = mThread.getSystemContext().mPackageInfo; return apk.getAppFactory(); }
因为只是hook了execStartActivity()而newActivity()就会抛出如上异常,解决方案就是在我们自定义的InstrumentationProxy中去重写newActivity()
1 2 3 4 5 6 7 public Activity newActivity (ClassLoader cl, String className, Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException { return mBase.newActivity(cl, className, intent); }
内容引用 Android 9.0相关源码
Android插件化原理解析