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

1、Hook技術

在程序開發中方法的調用和執行都是按順序執行的,那如果我們想修改或接入程序的執行,就必須在執行的過程中插入自己的程序,但同時又不影響原來程序的執行,這就是Hook 技術,Hook中文意思是鉤子,它是一個兩頭的鉤子,切開原來的程序然後用Hook勾住兩端,是程序仍然整體執行但數據都同過鉤子傳遞,既然數據都要通過鉤子,那我們就可以在這個過程中偷樑換柱了;

  • 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點,如:單例、靜態對象等;

2、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); //1、

Field instrumentationField = classes.getDeclaredField("mInstrumentation");
instrumentationField.setAccessible(true);
Instrumentation instrumentationInfo = (Instrumentation) instrumentationField.get(currentThread);//2、

FixInstrumentation fixInstrumentation = new FixInstrumentation(instrumentationInfo);
instrumentationField.set(currentThread, fixInstrumentation); //3、
  1. 首先反射獲取進程中ActivityThread的對象,然後以此對象反射獲取系統中的mInstrumentation對象
  2. 創建FixInstrumentation對象,內部保存上一步獲取的mInstrumentation的實例
  3. 反射將FixInstrumentation對象設置到ActivityThread中,此時進程中的mInstrumentation就是FixInstrumentation
  • 在代理類中重寫Instrumentation方法,用於替換代理Activity

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

public ActivityResult execStartActivity(
      Context who, IBinder contextThread, IBinder token, Activity target,
      Intent intent, int requestCode, Bundle options) {
   
   ComponentName componentName = intent.getComponent();// 獲取啓動的Intent中的Component對象
   String packageName = componentName.getPackageName(); // 獲取包名
   String classname = componentName.getClassName(); // 獲取Class類名
   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()創建活動時,是會啓動代理的ProxyActivity,但我們要改回真正啓動的目標活動,否則一切都白忙活了,由Activity的啓動過程知道,程序最終在newActivity()中創建實例,下面在newActivity()完成替換,在替換完成後執行原來的邏輯;

public Activity newActivity(ClassLoader cl, String className,
                     Intent intent) throws InstantiationException,
      IllegalAccessException {
   String classnameIntent = intent.getStringExtra(ACTIVITY_RAW); // 獲取保存的原請求的Intent對象
   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插件啓動總結:

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

3、Binder Hook(Hook 系統服務)

上面通過Hook技術修改了活動的啓動過程,屬於應用程序的Hook,即程序的所有操作都在同一進程中,下面嘗試Hook Android的系統服務,實現修改系統的功能,在Hook之前還是先了解一下系統服務的獲取過程,並嘗試尋找Hook點;

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

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

  • 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中,這也就是爲什麼獲取服務時傳入服務名稱;

  • 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對象,如果不存在則創建一個代理對象;

  • 總結一下服務的獲取過程:
  1. 在系統開始時,系統會像SYSTEM_SERVICE_FETCHERS註冊封裝服務的ServiceFetcher實例
  2. 在程序調用獲取服務時,根據服務名稱從SYSTEM_SERVICE_FETCHERS查找並返回對應的ServiceFetcher實例
  3. 調用實例的get()獲取服務時,首先從ServerManager中獲取系統中保存服務的Binder
  4. 調用IxxInterface的asInterface()方法查找並返回Binder的代理類
3.2、尋找Hook點
  1. 通過上面的分析知道,要想獲取到代理的Binder,可以操作的地方就是obj.queryLocalInterface(),如果我們Hook了傳入的Binder對象,修改他的queryLocalInterface()就可以返回替代的對象的代理對象,就可實現代理;
  2. 要想實現目標1就必須確保ServerManager的查找中能返回我們指定的Binder,好在ServerManager中從系統Map緩存中獲取,我們只要將代理的Binder放在緩存的Map,然後在查找時即可返回指定的Binder;
3.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過程見代碼註釋;

4、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 爲例)
//1、反射獲取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); //2、獲取此IActivityManagerSingleton內部的mInstance,也是獲取到Binder

Class<?> iActivityManagerInterface = Class.forName("android.app.IActivityManager");
//3、創建代理IActivityManager
Object binder1 = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[]{iActivityManagerInterface}, new HookProxyBinder(binder));
//4、將重寫的代理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點,才能實現真正的效果;

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