Android插件化開發(fā)之Hook StartActivity方法
第一步、先爆項目demo照片,代碼不多,不要怕
第二步、應(yīng)該知道Java反射相關(guān)知識
如果不知道或者忘記的小伙伴請猛搓這里,Android插件化開發(fā)基礎(chǔ)之Java反射機制研究 http://blog.csdn.net/u011068702/article/details/49863931
第三步、應(yīng)該知道Java靜態(tài)代理知識
如果不知道或者忘記的小伙伴請猛搓這里,Android插件化開發(fā)基礎(chǔ)之靜態(tài)代理模式 http://blog.csdn.net/u011068702/article/details/51765578
第四部、應(yīng)該知道Java動態(tài)代理知識
如果不知道或者忘記的小伙伴請猛搓這里,Android插件化開發(fā)基礎(chǔ)之Java動態(tài)代理(proxy)機制的簡單例子 http://blog.csdn.net/u011068702/article/details/53185210
上面只有代碼的解釋,如果不是很清楚的小伙伴可以再去到網(wǎng)上搜一搜相關(guān)知識。
第五步、應(yīng)該知道Android里面的ActivityThread類和Instrumentation類
如果不知道或者忘記的小伙伴請猛搓這里,Android插件化開發(fā)之AMS與應(yīng)用程序(客戶端ActivityThread、Instrumentation、Activity)通信模型分析
http://blog.csdn.net/u011068702/article/details/53207039
希望認真看,知道ActivityThread和Instrumentation是干嘛的,方便下面分析。
第六步、理解hook,并且分析源碼
如果我們自己創(chuàng)建代理對象,然后把原始對象替換為我們的代理對象,那么就可以在這個代理對象為所欲為了;修改參數(shù),替換返回值,我們稱之為Hook。
接下來我們來實現(xiàn)Hook掉startActivity這個方法,當每次調(diào)用這個方法的時候來做我們需要做的事情,我這里之打印了一些信息,讀者可以更具自己的需求來修改,
我們的目的是攔截startActivity方法,有點類是spring 里面AOP的思想。
1、hook一般在哪里hook?
首先分析我們需要hook哪些對象,也就是說我們要hook的地方,什么樣的對象比較好Hook呢?一般是容易找到和不容易改變的對象,這思路就來了,不改變一般在我們類的里面單例對象是唯一對象,靜態(tài)變量我們一般加上final
static 修飾,有了final就不會改變,不變模式里面比如String類,里面就很多final,我們更加這些找到hook的地方。
2、我們hook startActivity,分析startActivity到底是怎么實現(xiàn)的
我們每次Context.startActivity,由于Context的實現(xiàn)實際上是ContextImpl來實現(xiàn)的,;我們看ConetxtImpl類的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);
}
我們可以看到startActivity方法最后還是通過Instrumentation對象來執(zhí)行execStartActivity來實現(xiàn)的,如果你認真看了《Android插件化開發(fā)之AMS與應(yīng)用程序(客戶端ActivityThread、Instrumentation、Activity)通信模型分析
》上面這篇博客,我們知道ActivityThread就是主線程,也就是我們常說的UI線程,可以去更新UI,一個進程只有一個主線程,我們可以在這里hook.
我們需要Hook掉我們的主線程對象,把主線程對象里面的mInstrumentation給替換成我們修改過的代理對象;要替換主線程對象里面的字段
1)首先我們得拿到主線程對象的引用,如何獲取呢?ActivityThread類里面有一個靜態(tài)方法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這個字段為我們的代理對象,我們先實現(xiàn)這個代理對象,由于JDK動態(tài)代理只支持接口,而這個Instrumentation是一個類,我們可以手動寫靜態(tài)代理類,覆蓋掉原始的方法,代碼如下
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;
//通過構(gòu)造函數(shù)來傳遞對象
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打印調(diào)用startActivity相關(guān)參數(shù): \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, "");
//由于這個方法是隱藏的,所以需要反射來調(diào)用,先找到這方法
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,代碼講會執(zhí)行到這里來
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函數(shù)里面,如果你寫在activity里面的oncrate函數(shù)里面就已經(jīng)晚了
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);
//構(gòu)建我們的代理對象
Instrumentation evilInstrumentation = new InstrumentationProxy(mInstrumentation);
//通過反射,換掉字段,注意,這里是反射的代碼,不是Instrumentation里面的方法
mInstrumentationField.set(currentActivityThread, evilInstrumentation);
//做個標記,方便后面查看
Log.i(TAG, "has go in MyApplication attachContext method");
}
}
要注意這個替換要在Application里面的oncreate方法里面去執(zhí)行,如果到Activity方法里面去執(zhí)行的話就晚了,程序不會報錯,但是hook不到。
然后我是在主頁面寫了一個按鈕,點擊來觸發(fā)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();
}
}
} );
}
}
跳轉(zhuǎn)到的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終端打印的日志圖片如下:
然后我點擊圖標調(diào)用startActivity方法后ubuntu終端打印的日志圖片如下:
日志上面看到,hook success了
看到ubuntu終端日志打印,前面顯示了類,方便通過日志找bug,我用的是pidcat,下載pidcat
然后在ubuntu上面運行
pidcat.py 包名
就可以非常方便看的日志找bug了。
第八步、總結(jié)
通過這篇日志,希望打擊可以更好的理解hook、java反射、靜態(tài)代理、動態(tài)代理、ActivityThread、Instrumentation
最后附上源碼下載地址,需要的小伙伴請猛搓這里 HookStartActivityDemo
作者:chen.yu
深信服三年半工作經(jīng)驗,目前就職游戲廠商,希望能和大家交流和學(xué)習(xí),
微信公眾號:編程入門到禿頭 或掃描下面二維碼
零基礎(chǔ)入門進階人工智能(鏈接)