一.簡介
??????Hook技術是一種用于改變API執(zhí)行結果的技術,Android系統(tǒng)中有一套自己的事件分發(fā)機制,所有的代碼調用和回調都是按照一定順序執(zhí)行的,Hook技術存在的意義就在于,Hook可以幫助我們在Android中在SDK源代碼邏輯執(zhí)行過程中,通過代碼手動攔截執(zhí)行該邏輯,加入自己的代碼邏輯。
??????為了保證hook的穩(wěn)定性,一般攔截的點都會選擇比較容易找到并且不易發(fā)生變化的對象,比如靜態(tài)變量和單例。
??????提到Hook,就不得不說一下Java的反射機制。
二.反射
??????Java反射機制主要提供了以下功能:
????????????在運行時判斷任意一個對象所屬的類
????????????在運行時構造任意一個類的對象
????????????在運行時判斷任意一個類所具有的成員變量和方法
????????????在運行時調用任意一個對象的方法
????????????生成動態(tài)代理
??????在運行狀態(tài)中,對于任意一個類,都能夠獲取到這個類的所有屬性和方法,對于任意一個對象,都能夠調用它的任意一個方法和屬性(包括私有的方法和屬性),這種動態(tài)獲取的信息以及動態(tài)調用對象的方法的功能就稱為java語言的反射機制。

三.Hook使用案例分析
1.實現啟動未注冊的activity
??????比如我們想啟動一個activity,如果未在AndroidManifest.xml里面注冊的話,調用Context.startActivity()時會出現以下異常:
1-25 10:44:54.811 E/AndroidRuntime(25309): Caused by: android.content.ActivityNotFoundException: Unable to
find explicit activity class {com.hly.learn/com.hly.learn.HookActivity}; have you declared this activity in
your AndroidManifest.xml?
??????下面就一起來實現啟動一個未在AndroidManifest.xml注冊的activity。
??????a.尋找hook點
??????對于Context.startActivity,由于Context的實現類為ContextImpl,因此直接分析ContextImpl類的startActivity()的方法:
@Override
public void startActivity(Intent intent) {
warnIfCallingFromSystemProcess();
startActivity(intent, null);
}
@Override
public void startActivity(Intent intent, Bundle options) {
......
mMainThread.getInstrumentation().execStartActivity(
getOuterContext(), mMainThread.getApplicationThread(), null,
(Activity) null, intent, -1, options);
}
??????從上面可以看到,最終會調用mMainThread.getInstrumentation().execStartActivity(),mMainThread是ActivityThread實例,getInstrumentation()返回的是Instrumentation實例,實際上使用了ActivityThread類的mInstrumentation成員的execStartActivity方法;而ActivityThread 實際上是主線程,因為主線程一個進程只有一個,所以這里是一個良好的Hook點,即Hook主線程對象。
??????b.選擇合適代理方式
??????要將這個主線程對象里面的mInstrumentation替換成修改過的代理對象;要替換主線程對象里面的字段,得先拿到主線程對象的引用,如何獲取呢?
??????ActivityThread類里面有一個靜態(tài)方法currentActivityThread,通過它可以拿到這個對象類;但ActivityThread是一個隱藏類,需用反射去獲取拿到currentActivityThread后,要修改它的mInstrumentation字段為修改后的代理對象。實現如下:
public static void hookInstrumentation(Context context) {
try {
//step1:先獲取到當前的ActivityThread對象, 該對象是mInstrumentation的持有者
Class<?> activityThreadClz = Class.forName("android.app.ActivityThread");
Method currentActivityThreadMethod = activityThreadClz.getDeclaredMethod("currentActivityThread");
currentActivityThreadMethod.setAccessible(true);
Object currentActivityThread = currentActivityThreadMethod.invoke(null);
//step2:從ActivityThread里面拿到原始的mInstrumentation
Field instrumentation = activityThreadClz.getDeclaredField("mInstrumentation");
instrumentation.setAccessible(true);
Instrumentation mInstrumentation = (Instrumentation) instrumentation.get(currentActivityThread);
//step3:創(chuàng)建Instrumentation的代理對象[接下來會講到]
Instrumentation proxyInstrumentation = new ProxyInstrumentation(mInstrumentation, context.getPackageManager());
//step4:將持有的Instrumentation原始對象替換成代理對象[將currentActivityThread里面的instrumentation變量替換為proxyInstrumentation]
instrumentation.set(currentActivityThread, proxyInstrumentation);
} catch (ClassNotFoundException | IllegalAccessException | NoSuchFieldException | NoSuchMethodException | InvocationTargetException e) {
e.printStackTrace();
}
}
??????接下來實現這個代理對象,由于JDK動態(tài)代理只支持接口,而這個Instrumentation是一個類,因此只能手寫一個靜態(tài)代理類,用來覆蓋掉原始的方法,實現如下:
public class ProxyInstrumentation extends Instrumentation {
private Instrumentation mBase;
private PackageManager mPm;
public ProxyInstrumentation(Instrumentation base, PackageManager pm) {
mBase = base;
mPm = pm;
}
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options) {
Log.d("Seven", "------Hook打印execStartActivity------");
List<ResolveInfo> resolveInfos = mPm.queryIntentActivities(intent, PackageManager.MATCH_ALL);
//檢查要跳轉的activity是否在Manifest.xml里面注冊
if (resolveInfos == null || resolveInfos.size() == 0) {
//把要跳轉的activity記錄下來,在接下來newActivity還原的時候要拿來還原
intent.putExtra("intent_name", intent.getComponent().getClassName());
//把要跳轉的activity改成已經在Manifest.xml里面注冊過的MainActivity
intent.setClassName(who, "com.hly.learn.MainActivity");
}
// 開始調用Instrumentation原始的方法,由于這個方法是隱藏的,因此需要使用反射調用;
try {
//找到這個方法
Method execStartActivity = Instrumentation.class.getDeclaredMethod(
"execStartActivity", Context.class, IBinder.class, IBinder.class, Activity.class,
Intent.class, int.class, Bundle.class);
execStartActivity.setAccessible(true);
//通過反射調用Instrumentation的execStartActivity方法
return (ActivityResult) execStartActivity.invoke(mBase, who,
contextThread, token, target, intent, requestCode, options);
} catch (Exception e) {
throw new RuntimeException("do not support!");
}
}
@Override
public Activity newActivity(ClassLoader cl, String className, Intent intent)
throws InstantiationException, IllegalAccessException, ClassNotFoundException {
//取出上步記錄下來的要跳轉的activity并進行替換,來啟動未注冊的activity
String intentName = intent.getStringExtra("intent_name");
Log.d("Seven", "------newActivity內進行替換------");
if (!TextUtils.isEmpty(intentName)) {
return super.newActivity(cl, intentName, intent);
}
return super.newActivity(cl, className, intent);
}
}
??????c.啟動運行
HookUtils.hookInstrumentation(mContext);
private void hookActivity() {
Intent i = new Intent();
i.setClass(mContext, HookActivity.class);
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.getApplicationContext().startActivity(i);
}
1-25 11:28:37.627 D/Seven ( 1852): ------Hook打印execStartActivity------
1-25 11:28:37.665 D/Seven ( 1852): ------newActivity內進行替換------
??????至此,一個未在AndroidManifest.xml里面注冊的activity通過hook技術方式就可以啟動了。
2.攔截點擊事件統(tǒng)計點擊次數
??????a.尋找hook點
??????對一個view設置點擊事件的調用流程如下:
hkBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
}
});
public void setOnClickListener(@Nullable OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
getListenerInfo().mOnClickListener = l;
}
ListenerInfo getListenerInfo() {
if (mListenerInfo != null) {
return mListenerInfo;
}
mListenerInfo = new ListenerInfo();
return mListenerInfo;
}
static class ListenerInfo {
......
/**
* Listener used to dispatch click events.
* This field should be made private, so it is hidden from the SDK.
* {@hide}
*/
public OnClickListener mOnClickListener;
......
}
??????Hook操作onClickListener實例,需要首先獲取到onClickListener的擁有者,即:ListenerInfo,最后通過ListenerInfo獲取到原始的mOnClickListener。
??????b.選擇合適的代理方式
??????從上面的流程可以發(fā)現,要將ListenerInfo類里面的mOnClickListener替換成修改過的代理對象;要替換ListenerInfo里面的字段,得先拿到ListenerInfo,View類中有一個方法getListenerInfo() ,通過它可以拿到這個ListenerInfo,實現如下:
public static void hookOnClickListener(View view) {
//step1:反射執(zhí)行View類的getListenerInfo()方法,拿到view的mListenerInfo對象,這個對象是點擊事件mOnClickListener的持有者
try {
Class<?> viewClz = Class.forName("android.view.View");
Method method = viewClz.getDeclaredMethod("getListenerInfo");
//由于getListenerInfo()方法并不是public的,所以要加這個代碼來保證訪問權限
method.setAccessible(true);
//拿到mListenerInfo,也就是點擊事件的持有者
Object listenerInfo = method.invoke(view);
//step2:找到mListenerInfo持有的點擊事件對象mOnClickListener
//內部類的表示方法:android.view.View$ListenerInfo
Class<?> listenerInfoClz = Class.forName("android.view.View$ListenerInfo");
Field onClickListener = listenerInfoClz.getDeclaredField("mOnClickListener");
final View.OnClickListener onClickListenerInstance = (View.OnClickListener) onClickListener.get(
listenerInfo);
//step3:創(chuàng)建自己點擊事件的OnClickListener代理類
動態(tài)或靜態(tài)創(chuàng)建OnClickListener的代理類proxyOnClickListener
//step4:將持有者擁有的點擊事件替換成代理對象[將listenerInfo里面的onClickListener變量替換為proxyOnClickListener]
onClickListener.set(listenerInfo, proxyOnClickListener);
} catch (NoSuchMethodException | IllegalAccessException | ClassNotFoundException | NoSuchFieldException | InvocationTargetException e) {
e.printStackTrace();
}
}
}
??????接下來要創(chuàng)建OnClickListener的代理對象,由于OnClickListener是一個接口,因此可以使用JDK動態(tài)代理方式Java動態(tài)代理,也可以用靜態(tài)代理類實現,實現方式如下:
//方式1:自己實現代理類,將原始的View.OnClickListener對象onClickListenerInstance作為參數傳入
ProxyOnClickListener proxyOnClickListener = new ProxyOnClickListener(onClickListenerInstance);
static class ProxyOnClickListener implements View.OnClickListener{
private View.OnClickListener listener;
private int clickCount = 0;
ProxyOnClickListener(View.OnClickListener listener){
this.listener = listener;
}
@Override
public void onClick(View v) {
clickCount++;
Log.d("Seven", "Hook OnClickListener 2 click count " + clickCount);
if(this.listener != null){
this.listener.onClick(v);
}
}
//方式2:由于View.OnClickListener是一個接口,所以可以直接用動態(tài)代理模式
//參數:類的加載器,要代理實現的接口(用Class數組表示,支持多接口),代理類的實際邏輯封裝在new出來的InvocationHandler內
Object proxyOnClickListener = Proxy.newProxyInstance(View.OnClickListener.class.getClassLoader(),
new Class[]{View.OnClickListener.class}, new InvocationHandler() {
private int clickCount = 0;
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
clickCount++;
Log.d("Seven", "Hook OnClickListener 1 click count " + clickCount);
return method.invoke(onClickListenerInstance, args);
}
});
??????c.啟動運行
hkBtn.setOnClickListener(this);
HookUtils.hookonClickListener(hkBtn);
??????運行結果如下:
1-25 10:51:12.190 D/Seven (26125): Hook OnClickListener 1 click count 1
1-25 10:51:21.428 D/Seven (26125): Hook OnClickListener 1 click count 2
1-25 10:51:21.607 D/Seven (26125): Hook OnClickListener 1 click count 3
1-25 10:51:21.770 D/Seven (26125): Hook OnClickListener 1 click count 4
1-25 10:51:21.918 D/Seven (26125): Hook OnClickListener 1 click count 5
1-25 10:51:22.068 D/Seven (26125): Hook OnClickListener 1 click count 6
1-25 10:51:22.217 D/Seven (26125): Hook OnClickListener 1 click count 7
1-25 10:51:22.394 D/Seven (26125): Hook OnClickListener 1 click count 8
1-25 10:51:22.543 D/Seven (26125): Hook OnClickListener 1 click count 9
1-25 10:51:22.722 D/Seven (26125): Hook OnClickListener 1 click count 10
1-25 10:51:22.854 D/Seven (26125): Hook OnClickListener 1 click count 11
1-25 10:51:23.009 D/Seven (26125): Hook OnClickListener 1 click count 12
1-25 10:51:23.158 D/Seven (26125): Hook OnClickListener 1 click count 13
??????至此已經實現了對點擊事件的hook。
3.總結
??????針對以上兩個案例分析,我們可以看到在進行hook時主要分為以下幾步:
??????a.尋找合適的hook點;
??????b.找到Hook點對應的class;
??????c.直接通過class或class內部的method找到對應的filed;
??????d.替換對象是interface的話,直接用動態(tài)代理方式創(chuàng)建對象;否則,直接寫個類繼承要替換的類;
??????e.調用filed.set(obj, proxy)來對變量進行替換;
??????f.針對hook點進行相應的處理;
??????hook技術涉及到的知識點主要有反射、代理及android源碼的熟練程度。