動態代理實現方法以及對象HooK

上一篇文章裏面已經把動態代理的作用以及實現方法分析了一下,很明顯我們可以用HooK做很多事情,比如例子裏面的代理做了拿了回扣和偷換行貨這種骯髒齷齪的事情。

在真正應用的時候我們可以做更多的事情,比如用戶登錄的時候動態代理他的驗證方法,是不是就可以獲取用戶的賬號密碼呢?還有比如Activity的啓動,我們使用動態代理的手段將contextImp對象進行動態代理,對startActivity()函數進行修改,比如修改Intent的flag或者Intent內部數據等等,導致跳轉的頁面發生變化,或者改變傳遞的信息等等。

而實現HOOK的步驟一般分爲三步:
1. 尋找Hook點,原則是靜態變量或者單例對象,儘量Hook pulic的對象和方法,非public不保證每個版本都一樣,需要適配。
2. 選擇合適的代理方式,如果是接口可以用動態代理;如果是類可以手動寫代理也可以使用cglib。
3. 偷樑換柱——用代理對象替換原始對象

下面以改變startActivity() 邏輯爲例來展示HOOK的威力。

首先我們得找到被Hook的對象,我稱之爲Hook點;什麼樣的對象比較好Hook呢?自然是容易找到的對象。什麼樣的對象容易找到?靜態變量和單例;在一個進程之內,靜態變量和單例變量是相對不容易發生變化的,因此非常容易定位,而普通的對象則要麼無法標誌,要麼容易改變。我們根據這個原則找到所謂的Hook點。

然後我們分析一下startActivity的調用鏈,找出合適的Hook點。
我們知道對於Context.startActivityActivity.startActivity的調用鏈與之不同,由於Context的實現實際上是ContextImpl;我們看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);
}

可以看到contextImp的startActivity的邏輯是:

(1) 先判斷intent的flag是不是FLAG_ACTIVITY_NEW_TASK類型的,如果是走步驟2;如果不是,說明startActivity 不是在activity中調用的,在activity中調用的話,走的流程如下:

 Step 1. Activity.startActivity通過指定名稱“activity.subactivity”來告訴應用程序框架層,它要隱式地啓動SubActivity。所不同的是傳入的參數intent沒有Intent.FLAG_ACTIVITY_NEW_TASK標誌,表示這個SubActivity和啓動它的MainActivity運行在同一個Task中。

 Step 2. Activity.startActivityForResult

 Step 3. Instrumentation.execStartActivity

 Step 4. ActivityManagerProxy.startActivity

 詳見羅老師源碼分析:[Android應用程序內部啓動Activity過程(startActivity)的源代碼分析](http://blog.csdn.net/Luoshengyang/article/details/6703247)

(2) 調用了ActivityThread類的mInstrumentation成員的execStartActivity方法。

注意到,ActivityThread 實際上是主線程,而主線程一個進程只有一個mInstrumentation,只在應用剛剛打開第一個activity的時候創建(單例模式),之後不會發生變化,而且看到不管是Activity中startActivity還是在其他地方,調用的都是mInstrumentation.execStartActivity(),因此mInstrumentation對象是一個良好的Hook點。
分析完我們的HOOK的點後,接下來就要替換了我們的mInstrumentation對象了,代碼如下:

第一步首先通過反射把當前進程的ActivityThread對象拿到手:

// 先獲取到當前的ActivityThread對象
Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread");
currentActivityThreadMethod.setAccessible(true);
Object currentActivityThread = currentActivityThreadMethod.invoke(null);

第二步,雖然動態代理可以非常方便的進行代理對象,但是我們的mInstrumentation對象不是接口,因此沒有辦法採用動態代理方式創建代理類,那就沒辦法只能通過繼承來靜態代理我們的mInstrumentation,然後覆寫我們的mInstrumentation的execStartActivity方法,代碼如下:

public class EvilInstrumentation extends Instrumentation {

    private static final String TAG = "EvilInstrumentation";

    // ActivityThread中原始的對象, 保存起來
    Instrumentation mBase;

    public EvilInstrumentation(Instrumentation base) {
        mBase = base;
    }

    public ActivityResult execStartActivity(
            Context who, IBinder contextThread, IBinder token, Activity target,
            Intent intent, int requestCode, Bundle options) {

        // Hook之前, XXX到此一遊!
        Log.d(TAG, "\n執行了startActivity, 參數如下: \n" + "who = [" + who + "], " +
                "\ncontextThread = [" + contextThread + "], \ntoken = [" + token + "], " +
                "\ntarget = [" + target + "], \nintent = [" + intent +
                "], \nrequestCode = [" + requestCode + "], \noptions = [" + options + "]");

        // 開始調用原始的方法, 調不調用隨你,但是不調用的話, 所有的startActivity都失效了.
        // 由於這個方法是隱藏的,因此需要使用反射調用;首先找到這個方法
        try {
            Method execStartActivity = Instrumentation.class.getDeclaredMethod(
                    "execStartActivity",
                    Context.class, IBinder.class, IBinder.class, Activity.class, 
                    Intent.class, int.class, Bundle.class);
            execStartActivity.setAccessible(true);
            return (ActivityResult) execStartActivity.invoke(mBase, who, 
                    contextThread, token, target, intent, requestCode, options);
        } catch (Exception e) {
            // 某該死的rom修改了  需要手動適配
            throw new RuntimeException("do not support!!! pls adapt it");
        }
    }
}

上面代碼就是靜態代理的代碼,execStartActivity中先是打印一些信息,然後通過反射拿到Instrumentation的execStartActivity方法,進行調用,之所以要反射是因爲這個方法不可見,必須要反射才能調用,這是靜態代理所不能實現的功能。
創建了Instrumentation的代理對象,又找到了HOOK點,最後就只需要把需要替換的對象換掉就可以了。

第三步,使用反射進行Instrumentation對象的替換:代碼如下:

public static void attachContext() throws Exception{
    // 1先獲取到當前的ActivityThread對象
    Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
    Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread");
    currentActivityThreadMethod.setAccessible(true);
    Object currentActivityThread = currentActivityThreadMethod.invoke(null);

    // 2拿到原始的 mInstrumentation字段
    Field mInstrumentationField = activityThreadClass.getDeclaredField("mInstrumentation");
    mInstrumentationField.setAccessible(true);
    Instrumentation mInstrumentation = (Instrumentation) mInstrumentationField.get(currentActivityThread);

    //3 創建代理對象,使用反射偷樑換柱
    Instrumentation evilInstrumentation = new EvilInstrumentation(mInstrumentation);
    mInstrumentationField.set(currentActivityThread, evilInstrumentation);
}

最後都完成了,那麼就要測試一下了,看看能不能打印出我們代理函數裏面的數據了,打印結果如下:

07-11 22:19:20 9207-9207/com.dynamic_proxy_hook.app D/EvilInstrumentation:執行了startActivity,參數如下:
who = [android.app.Application@76726c01],
contextThread = [android.app.ActivityThread$ApplicationThread@4353489dd1],
token = [null],
target = [null],
intent = [Intent { act=android.intent.action.test dat=sadjksadk flg-0x10000000}],
requestCode = [-1],
options = [null]

可以看到打印出來了,結果就是HOOK成功了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章