一直據說hook,以前也瞭解了一個hook在Android中的應用,可是也一直沒搞懂是什麼,此次決定使用一個場景,用例子理解什麼是hook,這個場景就是hook免註冊跳轉Activity,因爲該例子中直接反射Activity啓動流程中的源碼和Handler機制中的源碼,所以對Activity啓動流程和Handler機制不太熟悉的小夥伴能夠參考我上兩篇文章哦。html
juejin.im/post/5cd7b8…android
免註冊跳轉Activity:直接上代碼bash
package com.android.hookjumpactivity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class HookUtil {
private Context context;
private Class<?> proxyActivity;
public HookUtil(Context context, Class<?> proxyActivity) {
this.context = context;
this.proxyActivity = proxyActivity;
}
/**
* 在AMS檢測前設置鉤子
*
* 根據Activity跳轉流程源碼分析可知,Activity跳轉須要使用到IActivityManager,
* 而IActivityManager可經過反射獲取,而可經過動態代理替換掉原有的IActivityManager,
* 隨後經過替換過的iActivityManager調用startActivity時便可觸發代理類中真正執行方法先後的操做,
* 所以只須要在代理類中將真實想跳轉的Intent替換成在清單文件中註冊的ProxyActivity
* ,而且將真實的Intent看成參數傳遞過去,經過ProxyActivity的意圖繞過AMS檢測,並在後面將真實的Intent替換回來便可。
*/
public void hookAms() {
try {
Class<?> ActivityManagerNativecls = Class.forName("android.app.ActivityManagerNative");
Field gDefault = ActivityManagerNativecls.getDeclaredField("gDefault");
gDefault.setAccessible(true);
//由於是靜態變量 因此獲取的到的是系統值 hook 僞hook
Object defaltValue = gDefault.get(null);
//mInstance對象
Class<?> SingletonClass = Class.forName("android.util.Singleton");
Field mInstance = SingletonClass.getDeclaredField("mInstance");
//還原 IActivityManager對象 系統對象
mInstance.setAccessible(true);
Object iActivityManagerObject = mInstance.get(defaltValue);
Class<?> iActivityManagerIntercept = Class.forName("android.app.IActivityManager");
//第二參數 是即將返回的對象 須要實現那些接口,其中這些接口包含OnClickListener,和IActivityManagerIntercept所實現的接口。
//也就是說IActivityManager和OnClickListener所實現的接口都動態替換成startActivtyMethod了
Object oldIactivityManager = Proxy
.newProxyInstance(Thread.currentThread().getContextClassLoader()
, new Class[]{iActivityManagerIntercept}, new AmsInvocationHandler
(iActivityManagerObject));
//將系統的iActivityManager 替換成 本身經過動態代理實現的對象
//oldIactivityManager對象 實現了 IActivityManager這個接口的全部方法
mInstance.set(defaltValue, oldIactivityManager);
} catch (Exception e) {
e.printStackTrace();
}
}
class AmsInvocationHandler implements InvocationHandler {
private Object iActivityManagerObject;
public AmsInvocationHandler(Object iActivityManagerObject) {
this.iActivityManagerObject = iActivityManagerObject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Log.i("INFO", "invoke " + method.getName());
if ("startActivity".equals(method.getName())) {
Log.i("INFO", "-----------------startActivity--------------------------");
//瞞天過海
//尋找傳進來的原intent(要跳轉的意圖)
Intent intent = null;
int index = 0;
for (int i = 0; i < args.length; i++) {
//intent
Object arg = args[i];
if (arg instanceof Intent) {
// 此Intent爲原意圖,未註冊清單文件的意圖
intent = (Intent) args[i];
index = i;
}
}
Intent proxyIntent = new Intent();
//ProxyActivity是合法意圖(註冊清單文件的意圖),這裏用它經過AMS檢測
ComponentName componentName = new ComponentName(context, proxyActivity);
proxyIntent.setComponent(componentName);
//真實的意圖 被我隱藏到了 鍵值對中,等待待會繞過AMS後再經過ActivityMH取出來。
proxyIntent.putExtra("realIntent", intent);
args[index] = proxyIntent;
}
return method.invoke(iActivityManagerObject, args);
}
}
/**
* 在AMS檢測後設置鉤子
*
* 根據Activity跳轉流程源碼分析可知,Activity的建立必不可少的是須要走ActivityThread類中的
* scheduleLaunchActivity方法,此方法中調用sendMessage(H.LAUNCH_ACTIVITY, r)發送此消息執行
* handleLaunchActivity(r, null, "LAUNCH_ACTIVITY")方法
* ,此方法調用performLaunchActivity(r, customIntent)來真正建立Activity
* 主要關注點在sendMessage(H.LAUNCH_ACTIVITY, r)方法中,此方法經過Handler發送消息,
* 而經過Handler機制可知,消息的分發方法dispatchMessage中,首先判斷msg.callback此回調是否爲空,
* 若不爲空則直接執行handleCallback(msg)方法,而後執行handleCallback方法message.callback.run(),
* 所以只須要在 AmsInvocationHandler 繞過AMS檢測後,可經過反射msg.callback,在callback將真實要跳轉的
* Intent意圖替換回來,便可在後面執行handleLaunchActivity時建立出咱們真實須要跳轉的Activity
*/
public void hookSysHandler() {
try {
Class<?> forName = Class.forName("android.app.ActivityThread");
Field currentActivityThreadField = forName.getDeclaredField("sCurrentActivityThread");
currentActivityThreadField.setAccessible(true);
//還原系統的ActivityTread mH
Object activityThreadObj = currentActivityThreadField.get(null);
Field handlerField = forName.getDeclaredField("mH");
handlerField.setAccessible(true);
//hook點找到了
Handler mH = (Handler) handlerField.get(activityThreadObj);
Field callbackField = Handler.class.getDeclaredField("mCallback");
callbackField.setAccessible(true);
callbackField.set(mH, new ActivityThreadHandlerCallback(mH));
} catch (Exception e) {
e.printStackTrace();
}
}
class ActivityThreadHandlerCallback implements Handler.Callback {
private Handler mH;
public ActivityThreadHandlerCallback(Handler mH) {
this.mH = mH;
}
@Override
public boolean handleMessage(Message msg) {
//LAUNCH_ACTIVITY ==100 即將要加載一個activity了,這裏是系統的規範定義的
if (msg.what == 100) {
//替換回真實的Intent
handleLuachActivity(msg);
}
//作了真正的跳轉
mH.handleMessage(msg);
return true;
}
private void handleLuachActivity(Message msg) {
//還原
Object obj = msg.obj;
try {
Field intentField = obj.getClass().getDeclaredField("intent");
intentField.setAccessible(true);
// ProxyActivity 2
Intent proxyIntent = (Intent) intentField.get(obj);
// 到這裏後,其實已經經過AMS檢測了,
// 這裏將咱們看成參數的realIntent取出來,並將真實用來跳轉的Component設置回去。
Intent realIntent = proxyIntent.getParcelableExtra("realIntent");
if (realIntent != null) {
proxyIntent.setComponent(realIntent.getComponent());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
複製代碼
使用方法:app
package com.android.hookjumpactivity;
import android.app.Application;
public class HookApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
HookUtil hookUtil = new HookUtil(this, ProxyActivity.class);
hookUtil.hookAms();
hookUtil.hookSysHandler();
}
}
AndroidManifest.xml文件中:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.hookjumpactivity">
<application
android:name=".HookApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".ProxyActivity"/>
</application>
</manifest>
複製代碼
具體講解已在代碼中。ide
參考文章:源碼分析
www.jianshu.com/p/1a10703e2…post
...ui
hook防範可參考:this
注:如有什麼地方闡述有誤,敬請指正。