Hook概念
Hook翻譯過來是鉤子的意思铜异,我們都知道無論是手機還是電腦運行的時候都依賴系統(tǒng)各種各樣的API黔州,當(dāng)某些API不能滿足我們的要求時键思,我們就得去修改某些api衩藤,使之能滿足我們的要求谦屑。這樣api hook就自然而然的出現(xiàn)了驳糯。我們可以通過api hook,改變一個系統(tǒng)api的原有功能氢橙≡褪啵基本的方法就是通過hook“接觸”到需要修改的api函數(shù)入口點,改變它的地址指向新的自定義的函數(shù)悍手。當(dāng)然這種技術(shù)同樣適用于Android系統(tǒng)帘睦,在Android開發(fā)中,我們同樣能利用Hook的原理讓系統(tǒng)某些方法運行時調(diào)用的是我們定義的方法坦康,從而滿足我們的要求
Hook原則
Android中主要是依靠分析系統(tǒng)源碼類來做到的竣付,首先我們得找到被Hook的對象,我稱之為Hook點滞欠;什么樣的對象比較好Hook呢卑笨?自然是容易找到的對象。什么樣的對象容易找到仑撞?靜態(tài)變量和單例赤兴;在一個進(jìn)程之內(nèi)妖滔,靜態(tài)變量和單例變量是相對不容易發(fā)生變化的,因此非常容易定位桶良,而普通的對象則要么無法標(biāo)志座舍,要么容易改變。我們根據(jù)這個原則找到所謂的Hook點
在安卓中實現(xiàn)hook主要通過兩種方式
1.反射技術(shù)和代理實現(xiàn)陨帆,當(dāng)然代理不管是動態(tài)還是靜態(tài)的都是可以實現(xiàn)的曲秉,但是只能hook自己應(yīng)用內(nèi)存中的對象;
2.在root的情況下疲牵,Xposed通過替換/system/bin/app_process程序控制zygote進(jìn)程承二,使得app_process在啟動過程中會加載XposedBridge.jar這個jar包,從而完成對Zygote進(jìn)程及其創(chuàng)建的Dalvik 虛擬機的劫持纲爸,可以達(dá)到hook整個系統(tǒng)中所有進(jìn)程內(nèi)存里面的對象的目的亥鸠;
HookAms實踐
插件技術(shù)中很重要的一項就是宿主啟動插件APK中的Activity,因為插件都是后面業(yè)務(wù)迭代加進(jìn)來的识啦,所以Activity不可能提前注冊在宿主Activity的清單文件中的负蚊,所以正常的情況下是不可能啟動插件里的Activity的,因為啟動Activity的過程是需要在清單文件中尋找是否注冊颓哮,若沒有家妆,則直接crash。
所以想實現(xiàn)跳過系統(tǒng)檢查冕茅,做法就是先在宿主里注冊一個ProxyActivity伤极,在啟動插件的Activity的時候,把我們這個真實意圖Intent替換為可以通過檢查的啟動ProxyActivity的代理意圖Intent姨伤,然后讓真實意圖作為Extra添加進(jìn)代理意圖里塑荒,在通過檢查后再取出來替換回來,而這樣的功能姜挺,是通過hook實現(xiàn)的齿税。
我們知道Activity的啟動過程是通過AIDL Binder的方式跟AMS進(jìn)行一系列的交互,最終通過反射newInstance創(chuàng)建出來的炊豪,由于AMS處于系統(tǒng)進(jìn)程中凌箕,所以我們是沒法從它里面尋找hook點的。所以這里所說的hookAms词渤,其實是hook位于我們自己的應(yīng)用這邊的與AMS交互的AIDL的接口IActivityManeger牵舱,那怎么才能找到這個對象并且進(jìn)行替換呢,需要看FrameWork源碼
Instrumentation execStartActivity
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options) {
........
try {
// 通過ActivityManagerNative.getDefault()的startActivity來啟動activity
int result = ActivityManagerNative.getDefault()
.startActivity(whoThread, who.getBasePackageName(), intent,
intent.resolveTypeIfNeeded(who.getContentResolver()),
token, target != null ? target.mEmbeddedID : null,
requestCode, 0, null, options);
// 根據(jù)返回的result結(jié)果缺虐,給用戶對應(yīng)的提示
checkStartActivityResult(result, intent);
} catch (RemoteException e) {
throw new RuntimeException("Failure from system", e);
}
return null;
}
ActivityManagerNative
public abstract class ActivityManagerNative extends Binder implements IActivityManager
{
static public IActivityManager getDefault() {
return gDefault.get();
}
private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() {
protected IActivityManager create() {
IBinder b = ServiceManager.getService("activity");
if (false) {
Log.v("ActivityManager", "default service binder = " + b);
}
IActivityManager am = asInterface(b);
if (false) {
Log.v("ActivityManager", "default service = " + am);
}
return am;
}
};
}
這里可以看到ActivityManagerNative.getDefault()內(nèi)部直接返回gDefault.get()芜壁,其中g(shù)Default是Singleton泛型類
public abstract class Singleton<T> {
private T mInstance;
protected abstract T create();
// get方法返回對應(yīng)的泛型類,這里是IActivityManager
public final T get() {
synchronized (this) {
if (mInstance == null) {
mInstance = create();
}
return mInstance;
}
}
}
接下來要揭開gDefault.get()的面紗,我們知道其中在Singleton中維護(hù)了一個mInstance變量慧妄,并且gDefault.get()方法最終返回的就是mInstance對應(yīng)的泛型類實例顷牌,也就是說通過ContextImpl#startActivity方法啟動activity的時候,最終是通過mInstance也就是IActivityManager來啟動的塞淹,其實就是ActivityManagerService窟蓝,所以我們要做的就是通過反射對mInstance重新賦值,將我們自己的代理類賦值給mInstance饱普,然后在代理類中根據(jù)系統(tǒng)獲取的IActivityManager在執(zhí)行操作运挫,所以這里我們就可以在對應(yīng)的操作前后,進(jìn)行一些記錄或者關(guān)鍵log的打印
創(chuàng)建一個代理類套耕,需要實現(xiàn)InvocationHandler接口谁帕,用來代理IActivityManager的操作
public class AMSProxy implements InvocationHandler {
private static final String TAG = "HookAMS";
private Object iActivityManager;
public AMSProxy(Object iActivityManager) {
this.iActivityManager = iActivityManager;
}
@Override
public Object invoke(Object obj, Method method, Object[] args) throws Throwable {
Log.d(TAG, "method name is :" + method.getName() + " args length is :" + args.length + " args is :" + args);
if ("startActivity".equals(method.getName())) {
// 第三個參數(shù)是intent
Intent intent = (Intent)args[2];
Log.d(TAG, "method name is :"+ method.getName()+" intent is :"+intent+" extradata is :"+intent.getStringExtra("DATA"));
}
return method.invoke(iActivityManager, args);
}
}
接下來開始Hook AMS
Class activityManagerNativeClazz = Class.forName("android.app.ActivityManagerNative");
Field gDefaultField = activityManagerNativeClazz.getDeclaredField("gDefault");
gDefaultField.setAccessible(true);
Object gDefault = gDefaultField.get(null); // 獲取gDefault實例,由于是靜態(tài)類型的屬性冯袍,所以這里直接傳遞null參數(shù)
// 下面通過反射執(zhí)行g(shù)Default.get();操作匈挖,最終返回IActivityManager,也就是ActivityManagerService的實例
Class singleTonClazz = Class.forName("android.util.Singleton");
Field mInstanceField = singleTonClazz.getDeclaredField("mInstance");
mInstanceField.setAccessible(true);
Object iActivityManager = mInstanceField.get(gDefault);
Class iActivityManagerClazz = Class.forName("android.app.IActivityManager");
// 指定被代理對象的類加載器
// 指定被代理對象所實現(xiàn)的接口颠猴,這里就是代理IActivityManager
// 表示這個動態(tài)代理對象在調(diào)用方法的時候关划,會關(guān)聯(lián)到哪一個InvocationHandler對象上
Object myProxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class<?>
[]{iActivityManagerClazz}, new AMSProxy(iActivityManager));
mInstanceField.set(gDefault,myProxy);
此時運行我們的程序小染,啟動activity翘瓮,就可以看到,每次在啟動activity之前都會打印相關(guān)的參數(shù)裤翩,其實不只是啟動activity资盅,啟動service也可以,只要最終是通過ActivityManagerNative來操作的都可以攔截踊赠。
總結(jié)
Hook是個蠻有樂趣的調(diào)用方式呵扛。
Hook 的選擇點:靜態(tài)變量和單例,因為一旦創(chuàng)建對象筐带,它們不容易變化今穿,非常容易定位。
Hook 過程:
尋找 Hook 點伦籍,原則是靜態(tài)變量或者單例對象蓝晒,盡量 Hook public 的對象和方法。
選擇合適的代理方式帖鸦,如果是接口可以用動態(tài)代理芝薇。
偷梁換柱——用代理對象替換原始對象。
Hook 的這個本領(lǐng)作儿,使它能夠?qū)⒆陨淼拇a「融入」被勾茁宥(Hook)的程序的進(jìn)程中,成為目標(biāo)進(jìn)程的一個部分。API Hook 技術(shù)是一種用于改變 API 執(zhí)行結(jié)果的技術(shù)晾嘶,能夠?qū)⑾到y(tǒng)的 API 函數(shù)執(zhí)行重定向妓雾。在 Android 系統(tǒng)中使用了沙箱機制,普通用戶程序的進(jìn)程空間都是獨立的变擒,程序的運行互不干擾君珠。這就使我們希望通過一個程序改變其他程序的某些行為的想法不能直接實現(xiàn),但是 Hook 的出現(xiàn)給我們開拓了解決此類問題的道路娇斑。當(dāng)然策添,根據(jù) Hook 對象與 Hook 后處理的事件方式不同,Hook 還分為不同的種類毫缆,比如消息 Hook唯竹、API Hook 等。