1)首先我们得拿到主线程对象的引用,如何获取呢?ActivityThread类里面有一个静态方法currentActivityThread可以帮助我们拿到这个对象类;但是ActivityThread是一个隐藏类,我们需要用反射去获取,代码如下:
// 先获取到当前的ActivityThread对象 Class<?> activityThreadClass = Class.forName("android.app.ActivityThread"); Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread"); currentActivityThreadMethod.setAccessible(true); Object currentActivityThread = currentActivityThreadMethod.invoke(null);
2)拿到这个currentActivityThread之后,我们需要修改它的mInstrumentation这个字段为我们的代理对象,我们先实现这个代理对象,由于JDK动态代理只支持接口,而这个Instrumentation是一个类,我们可以手动写静态代理类,覆盖掉原始的方法,代码如下
package com.example.hookstartactivity; import java.lang.reflect.Method; import android.app.Activity; import android.app.Instrumentation; import android.app.Instrumentation.ActivityResult; import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.os.IBinder; import android.util.Log; public class InstrumentationProxy extends Instrumentation { public static final String TAG = "InstrumentationProxy"; public static final String EXEC_START_ACTIVITY = "execStartActivity"; // ActivityThread里面原始的Instrumentation对象,这里千万不能写成mInstrumentation,这样写 //抛出异常,已亲测试,所以这个地方就要注意了 public Instrumentation oldInstrumentation; //通过构造函数来传递对象 public InstrumentationProxy(Instrumentation mInstrumentation) { oldInstrumentation = mInstrumentation; } //这个方法是由于原始方法里面的Instrumentation有execStartActivity方法来定的 public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Activity target, Intent intent, int requestCode, Bundle options) { Log.d(TAG, "\n打印调用startActivity相关参数: \n" + "who = [" + who + "], " + "\ncontextThread = [" + contextThread + "], \ntoken = [" + token + "], " + "\ntarget = [" + target + "], \nintent = [" + intent + "], \nrequestCode = [" + requestCode + "], \noptions = [" + options + "]"); Log.i(TAG, "------------hook success------------->"); Log.i(TAG, "这里可以做你在打开StartActivity方法之前的事情"); Log.i(TAG, "------------hook success------------->"); Log.i(TAG, ""); //由于这个方法是隐藏的,所以需要反射来调用,先找到这方法 try { Method execStartActivity = Instrumentation.class.getDeclaredMethod( EXEC_START_ACTIVITY, Context.class, IBinder.class, IBinder.class, Activity.class, Intent.class, int.class, Bundle.class); execStartActivity.setAccessible(true); return (ActivityResult) execStartActivity.invoke(oldInstrumentation, who, contextThread, token, target, intent, requestCode, options); } catch (Exception e) { //如果你在这个类的成员变量Instrumentation的实例写错mInstrument,代码讲会执行到这里来 throw new RuntimeException("if Instrumentation paramerter is mInstrumentation, hook will fail"); } } }
3)然后用代理对象替换,代码如下
package com.example.hookstartactivity; import java.lang.reflect.Field; import java.lang.reflect.Method; import android.app.Application; import android.app.Instrumentation; import android.util.Log; public class MyApplication extends Application { public static final String TAG = "MyApplication"; public static final String ACTIVIT_THREAD = "android.app.ActivityThread"; public static final String CURRENT_ACTIVITY_THREAD = "currentActivityThread"; public static final String INSTRUMENTATION = "mInstrumentation"; @Override public void onCreate() { try { //这个方法一般是写在Application的oncreate函数里面,如果你写在activity里面的oncrate函数里面就已经晚了 attachContext(); } catch (Exception e) { e.printStackTrace(); } } public static void attachContext() throws Exception{ //获取当前的ActivityThread对象 Class<?> activityThreadClass = Class.forName(ACTIVIT_THREAD); Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod(CURRENT_ACTIVITY_THREAD); currentActivityThreadMethod.setAccessible(true); Object currentActivityThread = currentActivityThreadMethod.invoke(null); //拿到在ActivityThread类里面的原始mInstrumentation对象 Field mInstrumentationField = activityThreadClass.getDeclaredField(INSTRUMENTATION); mInstrumentationField.setAccessible(true); Instrumentation mInstrumentation = (Instrumentation) mInstrumentationField.get(currentActivityThread); //构建我们的代理对象 Instrumentation evilInstrumentation = new InstrumentationProxy(mInstrumentation); //通过反射,换掉字段,注意,这里是反射的代码,不是Instrumentation里面的方法 mInstrumentationField.set(currentActivityThread, evilInstrumentation); //做个标记,方便后面查看 Log.i(TAG, "has go in MyApplication attachContext method"); } }
要注意这个替换要在Application里面的oncreate方法里面去执行,如果到Activity方法里面去执行的话就晚了,程序不会报错,但是hook不到。
然后我是在主页面写了一个按钮,点击来触发startActivity的。
MainActivity.java 文件如下
package com.example.hookstartactivity; import android.content.Intent; import android.os.Bundle; import android.support.v7.app.ActionBarActivity; import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.View.OnClickListener; import android.widget.TextView; public class MainActivity extends ActionBarActivity { public static final String TAG = "MainActivity"; public TextView tv; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); tv = (TextView)findViewById(R.id.start); tv.setOnClickListener(new OnClickListener(){ @Override public void onClick(View v) { try { Intent intent = new Intent(MainActivity.this, SecondActivity.class); Bundle bundle = new Bundle(); Log.i(TAG, "-------------------------------->"); Log.i(TAG, "startActivity before"); Log.i(TAG, "-------------------------------->"); startActivity(intent, bundle); Log.i(TAG, "-------------------------------->"); Log.i(TAG, "startActivity after"); Log.i(TAG, "-------------------------------->"); } catch (Exception e) { e.printStackTrace(); } } } ); } }
跳转到的Second.java文件如下
package com.example.hookstartactivity; import android.os.Bundle; import android.support.v7.app.ActionBarActivity; public class SecondActivity extends ActionBarActivity{ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_second); } }
第七步、运行代码
启动项目在ubuntu终端打印的日志图片如下:
然后我点击图标调用startActivity方法后ubuntu终端打印的日志图片如下:
日志上面看到,hook success了
看到ubuntu终端日志打印,前面显示了类,方便通过日志找bug,我用的是pidcat,下载pidcat
然后在ubuntu上面运行
pidcat.py 包名
就可以非常方便看的日志找bug了。
第八步、总结
通过这篇日志,希望打击可以更好的理解hook、java反射、静态代理、动态代理、ActivityThread、Instrumentation
最后附上源码下载地址,需要的小伙伴请猛搓这里 HookStartActivityDemo