天天看點

Android插件化開發之Hook StartActivity方法(2)

1)首先我們得拿到主線程對象的引用,如何擷取呢?ActivityThread類裡面有一個靜态方法currentActivityThread可以幫助我們拿到這個對象類;但是ActivityThread是一個隐藏類,我們需要用反射去擷取,代碼如下:

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

2)拿到這個currentActivityThread之後,我們需要修改它的mInstrumentation這個字段為我們的代理對象,我們先實作這個代理對象,由于JDK動态代理隻支援接口,而這個Instrumentation是一個類,我們可以手動寫靜态代理類,覆寫掉原始的方法,代碼如下

package com.example.hookstartactivity;
 
import java.lang.reflect.Method;
 
import android.app.Activity;
import android.app.Instrumentation;
import android.app.Instrumentation.ActivityResult;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;
 
 
public class InstrumentationProxy extends Instrumentation {
 
 
     public static final String TAG = "InstrumentationProxy";
     public static final String EXEC_START_ACTIVITY = "execStartActivity";
     
     // ActivityThread裡面原始的Instrumentation對象,這裡千萬不能寫成mInstrumentation,這樣寫
     //抛出異常,已親測試,是以這個地方就要注意了
     public Instrumentation oldInstrumentation;
     
     //通過構造函數來傳遞對象
     public InstrumentationProxy(Instrumentation mInstrumentation) {
         oldInstrumentation = mInstrumentation;
     }
 
 
     //這個方法是由于原始方法裡面的Instrumentation有execStartActivity方法來定的
     public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Activity target,
             Intent intent, int requestCode, Bundle options) {
         Log.d(TAG, "\n列印調用startActivity相關參數: \n" + "who = [" + who + "], " +
              "\ncontextThread = [" + contextThread + "], \ntoken = [" + token + "], " +
              "\ntarget = [" + target + "], \nintent = [" + intent +
              "], \nrequestCode = [" + requestCode + "], \noptions = [" + options + "]");
 
 
        Log.i(TAG, "------------hook  success------------->");
        Log.i(TAG, "這裡可以做你在打開StartActivity方法之前的事情");
        Log.i(TAG, "------------hook  success------------->");
        Log.i(TAG, "");
            
        //由于這個方法是隐藏的,是以需要反射來調用,先找到這方法
        try {
            Method execStartActivity = Instrumentation.class.getDeclaredMethod(
                    EXEC_START_ACTIVITY,
                    Context.class, IBinder.class, IBinder.class, Activity.class, 
                    Intent.class, int.class, Bundle.class);
            execStartActivity.setAccessible(true);
            return (ActivityResult) execStartActivity.invoke(oldInstrumentation, who, 
                        contextThread, token, target, intent, requestCode, options);
            } catch (Exception e) {
                //如果你在這個類的成員變量Instrumentation的執行個體寫錯mInstrument,代碼講會執行到這裡來
                throw new RuntimeException("if Instrumentation paramerter is mInstrumentation, hook will fail");
            }
     }
}      

3)然後用代理對象替換,代碼如下

package com.example.hookstartactivity;
 
 
import java.lang.reflect.Field;
import java.lang.reflect.Method;
 
 
import android.app.Application;
import android.app.Instrumentation;
import android.util.Log;
 
 
public class MyApplication extends Application {
 
 
    public static final String TAG = "MyApplication";
    public static final String ACTIVIT_THREAD = "android.app.ActivityThread";
    public static final String CURRENT_ACTIVITY_THREAD = "currentActivityThread";
    public static final String INSTRUMENTATION = "mInstrumentation";
    
    @Override
    public void onCreate() {
        try {
             //這個方法一般是寫在Application的oncreate函數裡面,如果你寫在activity裡面的oncrate函數裡面就已經晚了
             attachContext();
        } catch (Exception e) {
             e.printStackTrace();
        }
    }
    
    public static void attachContext() throws Exception{
        
        //擷取目前的ActivityThread對象
        Class<?> activityThreadClass = Class.forName(ACTIVIT_THREAD);
        Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod(CURRENT_ACTIVITY_THREAD);
        currentActivityThreadMethod.setAccessible(true);
        Object currentActivityThread = currentActivityThreadMethod.invoke(null);
 
 
        //拿到在ActivityThread類裡面的原始mInstrumentation對象
        Field mInstrumentationField = activityThreadClass.getDeclaredField(INSTRUMENTATION);
        mInstrumentationField.setAccessible(true);
        Instrumentation mInstrumentation = (Instrumentation) mInstrumentationField.get(currentActivityThread);
 
 
        //建構我們的代理對象
        Instrumentation evilInstrumentation = new InstrumentationProxy(mInstrumentation);
        
        //通過反射,換掉字段,注意,這裡是反射的代碼,不是Instrumentation裡面的方法
        mInstrumentationField.set(currentActivityThread, evilInstrumentation);
        
        //做個标記,友善後面檢視
        Log.i(TAG, "has go in MyApplication attachContext method");
    }
}      

要注意這個替換要在Application裡面的oncreate方法裡面去執行,如果到Activity方法裡面去執行的話就晚了,程式不會報錯,但是hook不到。

然後我是在首頁面寫了一個按鈕,點選來觸發startActivity的。

MainActivity.java 檔案如下

package com.example.hookstartactivity;
 
 
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.TextView;
 
 
public class MainActivity extends ActionBarActivity {
    
    public static final String TAG  =  "MainActivity";
    public TextView tv;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        tv = (TextView)findViewById(R.id.start);
        tv.setOnClickListener(new OnClickListener(){
            @Override
            public void onClick(View v) {
                try {
                    Intent intent = new Intent(MainActivity.this, SecondActivity.class); 
                    Bundle bundle = new Bundle();
                    Log.i(TAG, "-------------------------------->");
                    Log.i(TAG, "startActivity before");
                    Log.i(TAG, "-------------------------------->");
                    
                    startActivity(intent, bundle);
                    
                    Log.i(TAG, "-------------------------------->");
                    Log.i(TAG, "startActivity after");
                    Log.i(TAG, "-------------------------------->");
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        } );
    }
}      

跳轉到的Second.java檔案如下

package com.example.hookstartactivity;
 
 
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
 
 
public class SecondActivity extends ActionBarActivity{
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_second);
    }
}      

第七步、運作代碼

啟動項目在ubuntu終端列印的日志圖檔如下:

Android插件化開發之Hook StartActivity方法(2)

然後我點選圖示調用startActivity方法後ubuntu終端列印的日志圖檔如下:

Android插件化開發之Hook StartActivity方法(2)

日志上面看到,hook success了

看到ubuntu終端日志列印,前面顯示了類,友善通過日志找bug,我用的是pidcat,下載下傳pidcat 

然後在ubuntu上面運作 

pidcat.py 包名

就可以非常友善看的日志找bug了。

第八步、總結

 通過這篇日志,希望打擊可以更好的了解hook、java反射、靜态代理、動态代理、ActivityThread、Instrumentation

 最後附上源碼下載下傳位址,需要的小夥伴請猛搓這裡  HookStartActivityDemo

繼續閱讀