写点什么

安卓程序员必备 hook 技术之进阶篇

  • 2021 年 11 月 11 日
  • 本文字数:5538 字

    阅读完需:约 18 分钟

创建一个HookActivityHelper.java ,然后三步走:


  1. 找到hook点,以及hook对象的持有者,上文中已经说明:hook点是ActivitymInstrumentation成员,持有者就是Activity


  Field mInstrumentationField = Activity.class.getDeclaredField("mInstrumentation");
  mInstrumentationField.setAccessible(true);
  Instrumentation base = (Instrumentation) mInstrumentationField.get(activity);


base是系统原来的执行逻辑,存起来后面用得着.


  1. 创建Instrumentation代理类, 继承Instrumentation然后,重写execStartActivity方法,加入自己的逻辑,然后再执行系统的逻辑.


private static class ProxyInstrumentation extends Instrumentation {public ProxyInstrumentation(Instrumentation base) {this.base = base;}


Instrumentation base;


public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Activity target,Intent intent, int requestCode, Bundle options) {


Log.d("ProxyInstrumentation", "我们自己的逻辑");


//这里还要执行系统的原本逻辑,但是突然发现,这个 execStartActivity 居然是 hide 的,只能反射咯 try {Class<?> InstrumentationClz = Class.forName("android.app.Instrumentation");Method execStartActivity = InstrumentationClz.getDeclaredMethod("execStartActivity",Context.class, IBinder.class, IBinder.class, Activity.class,Intent.class, int.class, Bundle.class);return (ActivityResult) execStartActivity.invoke(base,who, contextThread, token, target, intent, requestCode, options);} catch (Exception e) {e.printStackTrace();}


return null;}


}


  1. 用代理类对象替换 hook对象.


ProxyInstrumentation proxyInstrumentation = new ProxyInstrumentation(base);mInstrumentationField.set(activity, proxyInstrumentation);


如何使用: 在MainActivityonCreate中加入一行ActivityHookHelper.hook(this)


public class MainActivity extends AppCompatActivity {


@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);


ActivityHookHelper.hook(this);findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {startActivityByActivity();}});


}


private void startActivityByActivity() {Intent i = new Intent(MainActivity.this, Main2Activity.class);startActivity(i);}


}


效果:跳转依然正常,并且logcat中可以发现下面的日志.


#####ok,插入自己的逻辑,成功


##三. 第二种启动方式的 hook 方案创建ApplicationContextHookHelper.java,然后 同样是三步走


1.确定 hook 的对象和该对象的持有者锁定 ActivityThreadmInstrumentation成员.


//1.主线程 ActivityThread 内部的 mInstrumentation 对象,先把他拿出来 Class<?> ActivityThreadClz = Class.forName("android.app.ActivityThread");//再拿到 sCurrentActivityThreadField sCurrentActivityThreadField = ActivityThreadClz.getDeclaredField("sCurrentActivityThread");sCurrentActivityThreadField.setAccessible(true);Object activityThreadObj = sCurrentActivityThreadField.get(null);//静态变量的属性 get 不需要参数,传 null 即可.//再去拿它的 mInstrumentationField mInstrumentationField = ActivityThreadClz.getDeclaredField("mInstrumentation");mInstrumentationField.setAccessible(true);Instrumentation base = (Instrumentation) mInstrumentationField.get(activityThreadObj);// OK,拿到


2.创建代理对象 和上面的代理类一模一样,就不重复贴代码了


//2.构建自己的代理对象,这里 Instrumentation 是一个 class,而不是接口,所以只能用创建内部类的方式来做 ProxyInstrumentation proxyInstrumentation = new ProxyInstrumentation(base);


3.替换掉原对象


//3.偷梁换柱 mInstrumentationField.set(activityThreadObj, proxyInstrumentation);


如何使用: 在Main4ActivityonCreate中加入一行ApplicationContextHookHelper.hook();


public class Main4Activity extends AppCompatActivity {


@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main4);


ApplicationContextHookHelper.hook();findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {startActivityByApplicationContext();}});}


private void startActivityByApplicationContext() {Intent i = new Intent(Main4Activity.this, Main5Activity.class);i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);getApplicationContext().startActivity(i);}}


效果


####OK,第二种启动方式,我们也可以加入自己的逻辑了.hook 成功!


##四. 目前方案弊端分析启动方式 1 的 hook: 只是在针对单个Activity类,来进行hook,多个Activity则需要写多次,或者写在BaseActivity里面.启动方式 2 的 hook:可以针对全局进行 hook,无论多少个 Activity,只需要调用一次ApplicationContextHookHelper.hook();函数即可,但是,它只能针对 getApplicationContext().startActivity(i); 普通的Activity.startActivity则不能起作用.


那么有没有一种完全体的解决方案:能够在全局起作用,并且可以在两种启动方式下都能hook.回顾之前的两张代码索引结论图,会发现,两种启动 Activity 的方式,最终都被执行到了 AMS内部,下一步,尝试 hook AMS.


##五. 最终解决方案


代码索引: 基于SDK 28 ~ android9.0


下方红框标记的部分,就是取得AMSActivityManagerService实例)的代码.

如果可以在系统接收到 AMS 实例之前,把他了,是不是就可以达到我们的目的?进去看看 getService 的代码:


真正的AMS实例来自一个Singleton单例辅助类的create()方法,并且这个Singleton单例类,提供get方法,获得真正的实例.



那么,我们从这个单例中,就可以获得系统当前的 AMS实例,将它取出来,然后保存.OK,确认:hook对象: ActivityManagerIActivityManagerSingleton成员 变量内的 单例 mInstance.hook对象的持有者:ActivityManagerIActivityManagerSingleton成员变量


那么,动手:


  1. 找到hook对象,并且存起来


//1.把 hook 的对象取出来保存//矮油,静态的耶,开心.Class<?> ActivityManagerClz = Class.forName("android.app.ActivityManager");Method getServiceMethod = ActivityManagerClz.getDeclaredMethod("getService");final Object IActivityManagerObj = getServiceMethod.invoke(null);//OK,已经取得这个系统自己的 AMS 实例


  1. 创建自己的代理类对象,IActivityManager 是一个 AIDL 生成的动态接口类,所以在编译时,androidStudio会找不到这个类,所以,先反射,然后用 Proxy 进行创建代理。


//2.现在创建我们的 AMS 实例//由于 IActivityManager 是一个接口,那么我们可以使用 Proxy 类来进行代理对象的创建// 结果被摆了一道,IActivityManager 这玩意居然还是个 AIDL,动态生成的类,编译器还不认识这个类,怎么办?反射咯 Class<?> IActivityManagerClz = Class.forName("android.app.IActivityManager");Object proxyIActivityManager = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),new Class[]{IActivityManagerClz}, new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {//proxy 是创建出来的代理类,method 是接口中的方法,args 是接口执行时的实参 if (method.getName().equals("startActivity")) {Log.d("GlobalActivityHook", "全局 hook 到了 startActivity");}return method.invoke(IActivityManagerObj, args);}});


  1. 偷梁换柱:这次有点复杂, 不再是简单的field.set,因为这次的 hook 对象被包裹在了一个Singleton里。


//3.偷梁换柱,这里有点纠结,这个实例居然被藏在了一个单例辅助类里面 Field IActivityManagerSingletonField = ActivityManagerClz.getDeclaredField("IActivityManagerSingleton");IActivityManagerSingletonField.setAccessible(true);Object IActivityManagerSingletonObj = IActivityManagerSingletonField.get(null);//反射创建一个 Singleton 的 classClass<?> SingletonClz = Class.forName("android.util.Singleton");Field mInstanceField = SingletonClz.getDeclaredField("mInstance");mInstanceField.setAccessible(true);mInstanceField.set(IActivityManagerSingletonObj, proxyIActivityManager);


使用方法:老样子,在你自己的Activity onCreate里面加入GlobalActivityHookHelper.hook();运行起来,预期结果应该是:能够在 logcat 中看到日志 :GlobalActivityHook - 全局hook 到了 startActivity;但是,你运行起来可能看不到这一行。如果你看不到这个日志,那么原因就是:程序报错了,


没有这样的方法,怎么回事?debug找原因:

为什么会没有getService这个方法!?查看了我当前设备的系统版本号

居然是23版本,6.0.所以,恍然大悟,我们写的 hook 代码并没有兼容性,遇到低版本的设备,就失灵了.


解决方案:1.找到SDK 23的源码(注意,前方有坑,androidStudio,你如果直接把combileSDK改成 23.会出现很多位置问题,所以不建议这么做. 但是我们一定要看SDK 23的源码,怎么办?



2.查看getService方法不存在的原因,两个版本 28 和 23,在这一块代码上有什么不同.3.改造 GlobalActivityHookHelper.java ,判定当前设备的系统版本号,让它可以兼容所有版本.


按照上面的步骤:我发现 SDK 23 里面:Instrumentation类的 execStartActivitiesAsUser(Context who, IBinder contextThread, IBinder token, Activity target, Intent[] intents, Bundle options, int userId) 方法里,获取AMS实例的方式完全不同.


它是使用 ActivityManagerNative.getDefault()来获得的,继续往下找,看看有没有什么不同。进去ActivityManagerNative 找找看:


OK,找到了区别,确定结论:SDK 2823在这块代码上的区别就是:获得 AMS 实例的类名和方法名都不同.另外,查了度娘之后发现,这个变化是在 SDK 26 版本修改的,所以 26 和 26 以后,ActivityManager.getService()来获取,26 以前,用ActivityManagerNative.getDefault()来获得调整当前的hook方法,修改为下面这样:


public class GlobalActivityHookHelper {


//设备系统版本是不是大于等于 26private static boolean ifSdkOverIncluding26() {int SDK_INT = Build.VERSION.SDK_INT;if (SDK_INT > 26 || SDK_INT == 26) {return true;} else {return false;}}


public static void hook() {


try {Class<?> ActivityManagerClz;final Object IActivityManagerObj;if (ifSdkOverIncluding26()) {ActivityManagerClz = Class.forName("android.app.ActivityManager");Method getServiceMethod = ActivityManagerClz.getDeclaredMethod("getService");IActivityManagerObj = getServiceMethod.invoke(null);//OK,已经取得这个系统自己的 AMS 实例} else {ActivityManagerClz = Class.forName("android.app.ActivityManagerNative");Method getServiceMethod = ActivityManagerClz.getDeclaredMethod("getDefault");IActivityManagerObj = getServiceMethod.invoke(null);//OK,已经取得这个系统自己的 AMS 实例}


//2.现在创建我们的 AMS 实例//由于 IActivityManager 是一个接口,那么其实我们可以使用 Proxy 类来进行代理对象的创建// 结果被摆了一道,IActivityManager 这玩意居然还是个 AIDL,动态生成的类,编译器还不认识这个类,怎么办?反射咯 Class<?> IActivityManagerClz = Class.forName("android.app.IActivityManager");Object proxyIActivityManager = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[]{IActivityManagerClz}, new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {//proxy 是创建出来的代理类,method 是接口中的方法,args 是接口执行时的实参 if (method.getName().equals("startActivity")) {Log.d("GlobalActivityHook", "全局 hook 到了 startActivity");}return method.invoke(IActivityManagerObj, args);}});


//3.偷梁换柱,这里有点纠结,这个实例居然被藏在了一个单例辅助类里面 Field IActivityManagerSingletonField;if (ifSdkOverIncluding26()) {IActivityManagerSingletonField = ActivityManagerClz.getDeclaredField("IActivityManagerSingleton");} else {IActivityManagerSingletonField = ActivityManagerClz.getDeclaredField("gDefault");}


IActivityManagerSingletonField.setAccessible(true);Object IActivityManagerSingletonObj = IActivityManagerSingletonField.get(null);Class<?> SingletonClz = Class.forName("android.util.Singleton");//反射创建一个 Singleton 的 classField mInstanceField = SingletonClz.getDeclaredField("mInstance");mInstanceField.setAccessi


【一线大厂Java面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义】
浏览器打开:qq.cn.hn/FTf 免费领取
复制代码


ble(true);mInstanceField.set(IActivityManagerSingletonObj, proxyIActivityManager);


} catch (Exception e) {e.printStackTrace();

评论

发布
暂无评论
安卓程序员必备hook技术之进阶篇