背景
最近開始研究雙開迷殿,發(fā)現(xiàn)很多還是要用到代理一類的東西庆寺,先來研究下Proxy
什么是Proxy
動態(tài)代理(以下稱代理),利用Java的反射技術(shù)(Java Reflection)懦尝,在運行時創(chuàng)建一個實現(xiàn)某些給定接口的新類(也稱“動態(tài)代理類”)及其實例(對象)
(Using Java Reflection to create dynamic implementations of interfaces at runtime)。
作用
可以HOOK接口琅轧,直接修改實現(xiàn)的方法 踊挠, 在實現(xiàn)方法中加點料 。
想要學(xué)習(xí)一些基礎(chǔ)內(nèi)容可以看看這個文章 Hook系列之反射
實現(xiàn)一個簡單的HOOK試試
下面我們來實現(xiàn)一個Hook掉startActivity效床,使得每次調(diào)用這個方法,輸出一個日志剩檀。
首先看下startActivity的源碼。
@Override
public void startActivity(Intent intent, Bundle options) {
warnIfCallingFromSystemProcess();
if ((intent.getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) {
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);
}
這里辐啄,實際上使用了ActivityThread類的mInstrumentation成員的execStartActivity方法运嗜;注意到,ActivityThread 實際上是主線程士复,而主線程一個進(jìn)程只有一個,因此這里是一個良好的Hook點阱洪。
接下來想辦法把mInstrumentation替換成我們修改過的代理對象。
首先我們要拿到主線程對象的應(yīng)用冗荸。ActivityThread類里面有一個靜態(tài)方法currentActivityThread可以幫助我們拿到這個對象類;但是ActivityThread是一個隱藏類盔粹,我們需要用反射去獲取程癌,代碼如下:
// 先獲取到當(dāng)前的ActivityThread對象
Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread");
currentActivityThreadMethod.setAccessible(true);
Object currentActivityThread = currentActivityThreadMethod.invoke(null);
拿到這個對象后,我們需要修改它的mInstrumentation這個字段為我們的代理對象嵌莉,我們先實現(xiàn)這個代理對象,由于JDK動態(tài)代理只支持接口中鼠,而這個Instrumentation是一個類沿癞,沒辦法,我們只有手動寫靜態(tài)代理類椎扬,覆蓋掉原始的方法即可。
public class EvilInstrumentation extends Instrumentation {
private static final String TAG = "EvilInstrumentation";
// ActivityThread中原始的對象, 保存起來
Instrumentation mBase;
public EvilInstrumentation(Instrumentation base) {
mBase = base;
}
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options) {
// Hook之前, XXX到此一游!
Log.d(TAG, "\n執(zhí)行了startActivity, 參數(shù)如下: \n" + "who = [" + who + "], " +
"\ncontextThread = [" + contextThread + "], \ntoken = [" + token + "], " +
"\ntarget = [" + target + "], \nintent = [" + intent +
"], \nrequestCode = [" + requestCode + "], \noptions = [" + options + "]");
// 開始調(diào)用原始的方法, 調(diào)不調(diào)用隨你,但是不調(diào)用的話, 所有的startActivity都失效了.
// 由于這個方法是隱藏的,因此需要使用反射調(diào)用;首先找到這個方法
try {
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, intent, requestCode, options);
} catch (Exception e) {
// 某該死的rom修改了 需要手動適配
throw new RuntimeException("do not support!!! pls adapt it");
}
}
}
后面就比較簡單了晶府,只要把原來的getInstrumentation()的方法里的mInstrumentation替換掉就可以了钻趋。
public static void attachContext() throws Exception{
// 先獲取到當(dāng)前的ActivityThread對象
Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread");
currentActivityThreadMethod.setAccessible(true);
Object currentActivityThread = currentActivityThreadMethod.invoke(null);
// 拿到原始的 mInstrumentation字段
Field mInstrumentationField = activityThreadClass.getDeclaredField("mInstrumentation");
mInstrumentationField.setAccessible(true);
Instrumentation mInstrumentation = (Instrumentation) mInstrumentationField.get(currentActivityThread);
// 創(chuàng)建代理對象
Instrumentation evilInstrumentation = new EvilInstrumentation(mInstrumentation);
// 偷梁換柱
mInstrumentationField.set(currentActivityThread, evilInstrumentation);
}
這個方法只要在Activity的attachBaseContext去調(diào)用即可
@Override
protected void attachBaseContext(Context newBase) {
super.attachBaseContext(newBase);
try {
// 在這里進(jìn)行Hook
attachContext();
} catch (Exception e) {
e.printStackTrace();
}
}
點擊下按鈕調(diào)用下一個Activity就可以了
Hook成功了蛮位。 成功的輸出了日志鳞绕。