Android插件化——高手必備的Hook技術

一、Hook技術

在程序開發中方法的調用和執行都是按順序執行的,那若是咱們想修改或接入程序的執行,就必須在執行的過程當中插入本身的程序,但同時又不影響原來程序的執行,這就是Hook 技術,Hook中文意思是鉤子,它是一個兩頭的鉤子,切開原來的程序而後用Hook勾住兩端,是程序仍然總體執行但數據都同過鉤子傳遞,既然數據都要經過鉤子,那咱們就能夠在這個過程當中偷樑換柱了;java

  • Hook分類
  1. 根據Hook的API語言劃分,分爲Hook Java 和 Hook Native
  2. 根據Hook進程劃分,分爲應用進程Hook 和 全局 Hook
  • 代理模式
  1. 代理模式是Hook模式的基礎原型,代理模式指爲某個類的操做提供代理執行
  2. 代理模式的意義在於無需修改原來的程序結構,增長或擴展程序的功能
  3. 代理類的實現:聲明一個功能接口,代理類和真實類都實現這個功能接口,在代理類中保存真實類的對象,當調用代理類執行方法時,內部調用真實對象執行;
interface Function { // 聲明的方法接口
    fun function()
}

class RealFunction : Function { // 建立的真實實現類,須要執行具體的邏輯
    override fun function() {
        TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
    }
}

class ProxyFunction(val realFunction: Function) : Function { // 代理類,內部傳入真實實例
    override fun function() {
        // doSomething.....
        realFunction.function()
    }
}
複製代碼
  1. 代理模式的使用
val realFunction = RealFunction()
val proxyFunction = ProxyFunction(realFunction)
proxyFunction.realFunction // 執行代理方法
複製代碼
  • 動態代理,關於動態代理的使用和原理見另外一篇文章Java動態代理

從上面的代理模式和動態代理能夠看出,咱們徹底能夠經過代理的代碼去攔截和修改原來程序的邏輯,這裏針對的是本身建立的類,那針對系統或框架的源碼呢?是否是也能夠實現呢?答案是確定的,但從上面對Hook的定以中能夠看出,咱們入過要想穩定的Hook程序,那必須找到最合適且必定會執行的地方,也就是尋找Hook點,這也是重點和難點的地方,通常選擇不太變換的對象最做爲Hook點,如:單例、靜態對象等;android

二、Hook系統中的Instrumentation

經過上面的學習大概知道Hook的原理和使用,剩下部分已安卓中常見的Hook使用爲例深刻理解和使用Hook技術,咱們如今利用Hook技術修改掉Activity的啓動過程,由Android進階知識樹——Android四大組件啓動過程知道,Activity在啓動的開始和最後建立對象時都是經過Instrumentation類,並且Instrumentation在ActivityThread中的對象即進程中只有一個實例,正好符合Hook點,下面就一塊兒實現Hook Instrumentation,具體步驟以下:緩存

  • 建立Instrumentation的代理類,內部保存系統原來的Instrumentation
public class FixInstrumentation extends Instrumentation {
   private Instrumentation instrumentation;
   private static final String ACTIVITY_RAW = "raw_activity";
   public FixInstrumentation(Instrumentation instrumentation) {
      this.instrumentation = instrumentation;
   }
}
複製代碼
  • Hook系統的Instrumentation
Class<?> classes = Class.forName("android.app.ActivityThread");
Method activityThread = classes.getDeclaredMethod("currentActivityThread");
activityThread.setAccessible(true);
Object currentThread = activityThread.invoke(null);
Field instrumentationField = classes.getDeclaredField("mInstrumentation");
instrumentationField.setAccessible(true);
Instrumentation instrumentationInfo = (Instrumentation) instrumentationField.get(currentThread);
FixInstrumentation fixInstrumentation = new FixInstrumentation(instrumentationInfo);
instrumentationField.set(currentThread, fixInstrumentation);
複製代碼
  1. 首先反射獲取進程中ActivityThread的對象,而後已此對象反射獲取系統中的mInstrumentation對象
  2. 建立FixInstrumentation對象,內部保存上一步獲取的mInstrumentation的實例
  3. 反射將FixInstrumentation對象設置到ActivityThread中,此時進程中的mInstrumentation就是FixInstrumentation
  • 重寫Instrumentation方法,用於替換代理Activity

如今要在FixInstrumentation中實現啓動Main3Activity的功能,可是Main3Activity爲加載進來的插件活動,未在程序的註冊清單中註冊,正常啓動的話必定會拋出異常,這裏想利用Hook的Instrumentation對象將啓動對象替換車成已註冊的活動,從而逃避系統的檢查,這也是Android插件化技術的方案,下面咱們就在execStartActivity()方法中替換app

public ActivityResult execStartActivity( Context who, IBinder contextThread, IBinder token, Activity target, Intent intent, int requestCode, Bundle options) {
   
   ComponentName componentName = intent.getComponent();
   String packageName = componentName.getPackageName();
   String classname = componentName.getClassName();
   if (classname.equals("com.alex.kotlin.plugin.Main3Activity")) { //判斷是否爲Main3Activity
      intent.setClassName(who, ProxyActivity.class.getCanonicalName()); // 替換爲註冊的ProxyActivity啓動
   }
   intent.putExtra(ACTIVITY_RAW, classname); // 同時保存原來的Main3Activity
   try {
      @SuppressLint("PrivateApi")
      Method method = instrumentation.getClass().getDeclaredMethod("execStartActivity",
            Context.class, IBinder.class, IBinder.class, Activity.class, Intent.class, int.class, Bundle.class);
      if (!Modifier.isPublic(method.getModifiers())) {
         method.setAccessible(true);
      }
      return (ActivityResult) method.invoke(instrumentation, who, contextThread, token, target, intent, requestCode, options);
}
複製代碼
  • 建立真正啓動的Activity

上面的替換操做躲過了系統的註冊檢查,程序執行到newActivity()建立活動時,咱們要該回真正啓動的目標活動,不然一切都白忙活了,下面在newActivity()完成替換,在替換完成後執行原來的邏輯;框架

public Activity newActivity(ClassLoader cl, String className, Intent intent) throws InstantiationException, IllegalAccessException {
   String classnameIntent = intent.getStringExtra(ACTIVITY_RAW);
   String packageName = intent.getComponent().getPackageName(); // 獲取Intent中保存的真正Activity包名、類名
   if (className.equals(ProxyActivity.class.getCanonicalName())) {
      ComponentName componentName = new ComponentName(packageName, classnameIntent); // 替換真實Activity的包名和類名
      intent.setComponent(componentName);
      className = classnameIntent;
   }
   Log.d("FixInstrumentation == ", "set activity is original" + className);
   try {
      @SuppressLint("PrivateApi")
      Method method = instrumentation.getClass().getDeclaredMethod("newActivity",
            ClassLoader.class, String.class, Intent.class);
      if (!Modifier.isPublic(method.getModifiers())) {
         method.setAccessible(true);
      }
      return (Activity) method.invoke(instrumentation, cl, className, intent); // 執行原來的建立方法
   }
}
複製代碼

Hook Instrumentation實現Activity插件啓動總結:ide

  1. Hook系統的Instrumentation對象,設置建立的代理類
  2. 在代理類中修改啓動Activity的Intent,將啓動的目標Activity替換爲佔位Activity,從而避免註冊清單的檢查
  3. 在代理類中重寫newActivity()將啓動的活動換回真實目標,而後繼續執行原有邏輯

三、Binder Hook(Hook 系統服務)

上面經過Hook技術修改了活動的啓動過程,屬於應用程序的Hook,下面嘗試Hook Android的系統服務,修改系統的功能,在Hook以前仍是先了解一下系統服務的獲取過程,並嘗試尋找Hook點;函數

3.一、系統獲取服務的原理
  • ContextImpl.getSystemService(String name)
@Override
public Object getSystemService(String name) {
     return SystemServiceRegistry.getSystemService(this, name);
}
public static Object getSystemService(ContextImpl ctx, String name) {
     //一、從註冊的SYSTEM_SERVICE_FETCHERS中根據名稱獲取ServiceFetcher
     ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name); 
     return fetcher != null ? fetcher.getService(ctx) : null; //二、ServiceFetcher中建立服務
}
複製代碼

在使用系統服務時會直接調用Context的getSystemService(),最終調用ContextImpl中的方法,在ContextImpl中調用SystemServiceRegistry.getSystemService(),關於SystemServiceRegistry簡單介紹一下,系統在啓動時會向SystemServiceRegistry中註冊一系列服務,在使用過程當中直接根據服務名換獲取服務;學習

  • SYSTEM_SERVICE_FETCHERS中註冊服務(以JOB_SCHEDULER_SERVICE爲例)
//註冊服務
registerService(Context.JOB_SCHEDULER_SERVICE, JobScheduler.class, new StaticServiceFetcher<JobScheduler>() {
           @Override
          public JobScheduler createService() {
             IBinder b = ServiceManager.getService(Context.JOB_SCHEDULER_SERVICE); //從ServiceManager中獲取Binder
             return new JobSchedulerImpl(IJobScheduler.Stub.asInterface(b)); //獲取Binder的代理對象
         }});
private static <T> void registerService(String serviceName, Class<T> serviceClass,ServiceFetcher<T> serviceFetcher) {
    SYSTEM_SERVICE_NAMES.put(serviceClass, serviceName);
    SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher); //以鍵值對的形式保存服務名稱、StaticServiceFetcher實例
}
複製代碼

從上面的註冊過程知道,系統首先將每一個服務的建立過程封裝在對應的ServiceFetcher對象中,而後將ServiceFetcher對象以服務名稱註冊在SYSTEM_SERVICE_FETCHERS中,這也就是爲何獲取服務時傳入服務名稱;fetch

  • ServiceManager.getService():獲取系統中相應服務對應的Binder對象
public JobScheduler createService() throws ServiceNotFoundException {
               IBinder b = ServiceManager.getServiceOrThrow(Context.JOB_SCHEDULER_SERVICE);
               return new JobSchedulerImpl(IJobScheduler.Stub.asInterface(b));
           }
複製代碼

在服務獲取的過程當中會調用ServiceFetcher的createService()方法,在create()中首先獲取系統中保存的Binder對象,而後根據Binder對象調用asInterface()查找代理類,asInterface()會先檢查本進程是否存在Binder對象,若是不存在則建立一個代理對象;ui

  • 總結一下服務的獲取過程:
  1. 在系統開始時,系統會像SYSTEM_SERVICE_FETCHERS註冊封裝服務的ServiceFetcher實例
  2. 在程序調用獲取服務時,根據服務名稱從SYSTEM_SERVICE_FETCHERS查找並返回對應的ServiceFetcher實例
  3. 調用實例的get()獲取服務時,首先從ServerManager中獲取系統中保存服務的Binder
  4. 調用IxxInterface的asInterface()方法查找並返回Binder的代理類
3.二、尋找Hook點
  1. 經過上面的分析知道,能夠操做的地方就是obj.queryLocalInterface(),若是咱們Hook了傳入的Binder對象,修改他的queryLocalInterface就能夠返回替代的對象的代理對象,就可實現代理;
  2. 要想實現目標1就必須確保ServerManager的查找中能返回咱們指定的Binder,好在ServerManager中從系統Map緩存中獲取,咱們只要將代理的Binder放在緩存的Map,而後在查找時便可返回指定的Binder;
3.三、實戰——以剪切版服務爲例
  • 建立服務的動態代理類
public class FixBinder implements InvocationHandler {
    private static final String TAG = "BinderHookHandler";
    // 原來的Service對象 (IInterface)
    Object base;
    public FixBinder(IBinder base, Class<?> stubClass) {
        try {
         Method asInterfaceMethod = stubClass.getDeclaredMethod("asInterface", IBinder.class);//獲取原接口的asInterface
         this.base = asInterfaceMethod.invoke(null, base); //使用原來的Binder反射執行獲取原本服務的代理類
        } catch (Exception e) {
            throw new RuntimeException("hooked failed!");
        }
    }
    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 欺騙系統,使之認爲剪切版上一直有內容
        if ("hasPrimaryClip".equals(method.getName())) {
            return true;
        }
        return method.invoke(base, args); //其他方法使用原Binder代理反射執行
    }
}
複製代碼
  1. 這裏和前面直接保存系統對象不一樣,由於在查找服務時首先得到的是系統的Binder,只有本身利用Binder其查找纔會返回代理類
  2. 在構造函數中傳入系統中查找的Binder對象,而後反射調用asasInterface()獲取並保存系統服務自己的服務的代理類
  3. 攔截剪切方法,攔截hasPrimaryClip()方法返回true,使系統一直認爲剪切板上有內容
  • 建立Binder對象
public class ProxyBinder implements InvocationHandler {
    IBinder base;
    Class<?> stub;
    Class<?> iinterface;
    public ProxyBinder(IBinder base) {
        this.base = base; //(1)
        try {
            this.stub = Class.forName("android.content.IClipboard$Stub」); //(2)
            this.iinterface = Class.forName("android.content.IClipboard");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if ("queryLocalInterface".equals(method.getName())) { //(3) 
            return Proxy.newProxyInstance(proxy.getClass().getClassLoader(),//(4)
                    // asInterface 的時候會檢測是不是特定類型的接口而後進行強制轉換
                    // 所以這裏的動態代理生成的類型信息的類型必須是正確的,即必須是如下3個接口實例
                    new Class[] { IBinder.class, IInterface.class, this.iinterface },
                    new FixBinder(base, stub));
        }
        return method.invoke(base, args);
    }
}
複製代碼

第一建立了系統服務的代理類,由前面分析知道了,代理類的使用是由Binder查詢出來的,因此下一步要建立一個Binder類,而且內部攔截查詢的queryLocalInterface()方法,讓此方法返回第一步的代理類,建立步驟:

  1. 和普通代理同樣,在代理內部保存ServerManager中原來真正的Binder
  2. 利用反射獲取IClipboard$Stub類,用於查找代理類
  3. Hook了queryLocalInterface方法
  4. 在invoke()攔截到方法後,使用動態代理建立並返回IClipboard的代理Binder
  • Hook 替換ServerManager中的Binder
final String CLIPBOARD_SERVICE = "clipboard";
// 下面這一段的意思實際就是: ServiceManager.getService("clipboard");
Class<?> serviceManager = Class.forName("android.os.ServiceManager");
Method getService = serviceManager.getDeclaredMethod("getService", String.class);
// (1)ServiceManager裏面管理的原始的Clipboard Binder對象
IBinder rawBinder = (IBinder) getService.invoke(null, CLIPBOARD_SERVICE);
//(2) Hook 掉這個Binder代理對象的 queryLocalInterface 方法
IBinder hookedBinder = (IBinder) Proxy.newProxyInstance(serviceManager.getClassLoader(),
        new Class<?>[] { IBinder.class },
        new BinderProxyHookHandler(rawBinder));
// (3)把這個hook過的Binder代理對象放進ServiceManager的cache裏面
Field cacheField = serviceManager.getDeclaredField("sCache");
cacheField.setAccessible(true);
Map<String, IBinder> cache = (Map) cacheField.get(null);
cache.put(CLIPBOARD_SERVICE, hookedBinder);
複製代碼

經過前面兩部已經將全部要建立的代理Binder實現了,剩下的就是要將ProxyBinder放入系統ServiceManager的緩存中,這樣在查詢時纔會按咱們的要求返回Binder,後面的套路才能執行下去,具體的Hook過程見代碼註釋;

四、Hook 系統服務AMS(Android 9.0)

上面兩個例子已經將Hook的使用介紹清楚了,接下來再利用Hook技術攔截系統的AMS,改變系統的服務啓動,也是插件化啓動服務的原理,這裏實現啓動未註冊的MyService,關於Service的啓動過程點擊上面的四大組件的連接查看,由於AMS也是經過Binder通訊的因此Hook的第一步要實現Binder的動態代理

  • 建立AMS的代理,實現功能攔截服務的啓動過程
public class HookProxyBinder implements InvocationHandler {
   public static final String HookProxyBinder = "HookProxyBinder";
   Object binder;
   public HookProxyBinder(Object binder) {
      this.binder = binder;
   }
   @Override
   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      Log.e("HookProxyBinder==", method.getName());
      if ("startService".equals(method.getName())) { //攔截啓動服務
         int i = 0;
         Intent intent = null;
         for (int index = 0; index < args.length; index++) {
            if (args[index] instanceof Intent) {
               i = index;
               intent = (Intent) args[index];
               break;
            }
         }
         String packageName = intent.getComponent().getPackageName();
         String className = intent.getComponent().getClassName();
         if (className.equals(MyService.class.getCanonicalName())) {
            intent.setClassName(packageName, ProxyService.class.getCanonicalName());
            intent.putExtra(HookProxyBinder, className);
         }
         args[i] = intent;
      }
      return method.invoke(binder, args);
   }
}
複製代碼

在invoke()方法中攔截startService(),總體實現和上面Activity的啓動同樣,經過在攔截到方法後替換爲註冊的佔位服務,實現服務的啓動

  • Hook系統AMS(以Android 9.0 爲例)
//一、反射獲取ActivityManager類中的靜態實例IActivityManagerSingleton
Class<?> manager = Class.forName("android.app.ActivityManager");
Field field = manager.getDeclaredField("IActivityManagerSingleton");
field.setAccessible(true);
Object object = field.get(null);
//反射獲取Singleton中的mInstance實例,mInstance就是調用create以後建立的對象,此處就是IActivityManager的代理實例
Class<?> singlen = Class.forName("android.util.Singleton");
Field field1 = singlen.getDeclaredField("mInstance");
field1.setAccessible(true);
Object binder = field1.get(object); //二、獲取此IActivityManagerSingleton內部的mInstance
Class<?> iActivityManagerInterface = Class.forName("android.app.IActivityManager");
//三、建立代理IActivityManager
Object binder1 = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[]{iActivityManagerInterface}, new HookProxyBinder(binder));
//四、將重寫的代理IActivityManager設置給mInstance
field1.set(object, binder1);
複製代碼

上面的Hook過程也就是利用反射獲取系統原來的代理類,而後將建立的代理類設置到系統中,只是這裏牽扯到了Singleton類, 系統中的AMS是使用Singleton單例提供的,因此只能反射從Singleton中獲取,詳情見Android進階知識樹——Android四大組件啓動過程

  • 註冊和建立代理服務
public class ProxyService extends Service {
   public ProxyService() {
   }
   @Override
   public IBinder onBind(Intent intent) {
      throw null;
   }
   @Override
   public int onStartCommand(Intent intent, int flags, int startId) {
      String service = intent.getStringExtra(HookProxyBinder.HookProxyBinder);
      Log.e("============++++", service);
      return super.onStartCommand(intent, flags, startId);
   }
}
複製代碼
  • 啓動服務
Intent intentService = new Intent(MainActivity.this, MyService.class); //此處啓動服務並未註冊
startService(intentService);
複製代碼

插件啓動Service過程:

  1. 經過Hook技術將系統的AMS替換爲代理類,攔截啓動服務的方法
  2. 在攔截方法中將啓動的目標Service替換爲佔位Service,同時保存目標服務
  3. 在佔位服務啓動後獲取目標服務並調用其相應方法,實現插件服務的啓動

本文從基礎只是和實戰的角度介紹下Hook技術,Hook技術也是插件化中必備的技術,Hook從技術角度上仍是比較簡單,但須要對目標原理和過程進行理解,肯定合理的Hook點,才能實現真正的效果;

相關文章
相關標籤/搜索