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