1. 程式人生 > >外掛化開發---Hook之動態代理方式

外掛化開發---Hook之動態代理方式

今天自己來了解下Hook原理,以及在安卓開發中佔有的意義,我們先來理解下什麼是hook呢?
hook就是對安卓原始碼、其他apk原始碼,在相應位置找hook點,然後通過反射等操作,來執行自己程式碼,進而達到需要的功能
以下2個截圖就是之前我公司進行的微信的hook,對微信聊天進行加解密
這裡寫圖片描述這裡寫圖片描述

下面我們Hook掉startActivity這個方法,使得每次呼叫這個方法之前輸出一條日誌;
然後我們分析一下startActivity的呼叫鏈,找出合適的Hook點。我們知道對於Context.startActivity(Activity.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); }

這裡,實際上使用了ActivityThread類的mInstrumentation成員的execStartActivity方法;
首先我們得拿到主執行緒物件的引用,如何獲取呢?ActivityThread類裡面有一個靜態方法currentActivityThread可以幫助我們拿到這個物件類;但是ActivityThread是一個隱藏類,我們需要用反射去獲取,程式碼如下:

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

拿到這個currentActivityThread之後,我們需要修改它的mInstrumentation這個欄位為我們的代理物件,我們先實現這個代理物件,由於JDK動態代理只支援介面,而這個Instrumentation是一個類,沒辦法,我們只有手動寫靜態代理類,覆蓋掉原始的方法即可

package com.wang.myapplication;

import android.app.Activity;
import android.app.Instrumentation;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;

import java.lang.reflect.Method;

/**
 * @author weishu
 * @date 16/1/28
 */
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");
        }
    }
}

Ok,有了代理物件,我們要做的就是偷樑換柱!程式碼比較簡單,採用反射直接修改:

package com.wang.myapplication;

import android.app.Instrumentation;

import java.lang.reflect.Field;
import java.lang.reflect.Method;

/**
 * @author weishu
 * @date 16/1/28
 */
public class HookHelper {

    public static void attachContext() throws Exception{
        // 先獲取到當前的ActivityThread物件
        Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
        Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread");
        currentActivityThreadMethod.setAccessible(true);
        //currentActivityThread是一個static函式所以可以直接invoke,不需要帶例項引數
        Object currentActivityThread = currentActivityThreadMethod.invoke(null);

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

        // 建立代理物件
        Instrumentation evilInstrumentation = new EvilInstrumentation(mInstrumentation);

        // 偷樑換柱
        mInstrumentationField.set(currentActivityThread, evilInstrumentation);
    }
}

然後是測試頁面

package com.wang.myapplication;

import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;


/**
 * @author weishu
 * @date 16/1/28
 */
public class MainActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        Button tv = new Button(this);
        tv.setText("測試介面");

        setContentView(tv);

        tv.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(Intent.ACTION_VIEW);
                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                intent.setData(Uri.parse("http://www.baidu.com"));

                // 注意這裡使用的ApplicationContext 啟動的Activity
                // 因為Activity物件的startActivity使用的並不是ContextImpl的mInstrumentation
                // 而是自己的mInstrumentation, 如果你需要這樣, 可以自己Hook
                // 比較簡單, 直接替換這個Activity的此欄位即可.
                getApplicationContext().startActivity(intent);
            }
        });
    }

    @Override
    protected void attachBaseContext(Context newBase) {
        super.attachBaseContext(newBase);
        try {
            // 在這裡進行Hook
            HookHelper.attachContext();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}