外掛化開發---Hook之動態代理方式
阿新 • • 發佈:2019-02-06
今天自己來了解下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();
}
}
}