1、Hook技術
在程序開發中方法的調用和執行都是按順序執行的,那如果我們想修改或接入程序的執行,就必須在執行的過程中插入自己的程序,但同時又不影響原來程序的執行,這就是Hook 技術,Hook中文意思是鉤子,它是一個兩頭的鉤子,切開原來的程序然後用Hook勾住兩端,是程序仍然整體執行但數據都同過鉤子傳遞,既然數據都要通過鉤子,那我們就可以在這個過程中偷樑換柱了;
- Hook分類
- 根據Hook的API語言劃分,分爲Hook Java 和 Hook Native
- 根據Hook進程劃分,分爲應用進程Hook 和 全局 Hook
- 代理模式
- 代理模式是Hook模式的基礎原型,代理模式指爲某個類的操作提供代理執行
- 代理模式的意義在於無需修改原來的程序結構,增加或擴展程序的功能
- 代理類的實現:聲明一個功能接口,代理類和真實類都實現這個功能接口,在代理類中保存真實類的對象,當調用代理類執行方法時,內部調用真實對象執行;
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()
}
}
- 代理模式的使用
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、
- 首先反射獲取進程中ActivityThread的對象,然後以此對象反射獲取系統中的mInstrumentation對象
- 創建FixInstrumentation對象,內部保存上一步獲取的mInstrumentation的實例
- 反射將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插件啓動總結:
- Hook系統的Instrumentation對象,設置創建的代理類
- 在代理類中修改啓動Activity的Intent,將啓動的目標Activity替換爲佔位Activity,從而避免註冊清單的檢查
- 在代理類中重寫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對象,如果不存在則創建一個代理對象;
- 總結一下服務的獲取過程:
- 在系統開始時,系統會像SYSTEM_SERVICE_FETCHERS註冊封裝服務的ServiceFetcher實例
- 在程序調用獲取服務時,根據服務名稱從SYSTEM_SERVICE_FETCHERS查找並返回對應的ServiceFetcher實例
- 調用實例的get()獲取服務時,首先從ServerManager中獲取系統中保存服務的Binder
- 調用IxxInterface的asInterface()方法查找並返回Binder的代理類
3.2、尋找Hook點
- 通過上面的分析知道,要想獲取到代理的Binder,可以操作的地方就是obj.queryLocalInterface(),如果我們Hook了傳入的Binder對象,修改他的queryLocalInterface()就可以返回替代的對象的代理對象,就可實現代理;
- 要想實現目標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代理反射執行
}
}
- 這裏和前面直接保存系統對象不同,因爲在查找服務時首先獲得的是系統的Binder,只有自己利用Binder其查找纔會返回代理類
- 在構造函數中傳入系統中查找的Binder對象,然後反射調用asasInterface()獲取並保存系統服務本身的服務的代理類
- 攔截剪切方法,攔截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()方法,讓此方法返回第一步的代理類,創建步驟:
- 和普通代理一樣,在代理內部保存ServerManager中原來真正的Binder
- 利用反射獲取IClipboard$Stub類,用於查找代理類
- Hook了queryLocalInterface方法
- 在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過程:
- 通過Hook技術將系統的AMS替換爲代理類,攔截啓動服務的方法
- 在攔截方法中將啓動的目標Service替換爲佔位Service,同時保存目標服務
- 在佔位服務啓動後獲取目標服務並調用其相應方法,實現插件服務的啓動
本文從基礎只是和實戰的角度介紹下Hook技術,Hook技術也是插件化中必備的技術,Hook從技術角度上還是比較簡單,但需要對目標原理和過程進行理解,確定合理的Hook點,才能實現真正的效果;