在程序開發中方法的調用和執行都是按順序執行的,那若是咱們想修改或接入程序的執行,就必須在執行的過程當中插入本身的程序,但同時又不影響原來程序的執行,這就是Hook 技術,Hook中文意思是鉤子,它是一個兩頭的鉤子,切開原來的程序而後用Hook勾住兩端,是程序仍然總體執行但數據都同過鉤子傳遞,既然數據都要經過鉤子,那咱們就能夠在這個過程當中偷樑換柱了;java
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 // 執行代理方法
複製代碼
從上面的代理模式和動態代理能夠看出,咱們徹底能夠經過代理的代碼去攔截和修改原來程序的邏輯,這裏針對的是本身建立的類,那針對系統或框架的源碼呢?是否是也能夠實現呢?答案是確定的,但從上面對Hook的定以中能夠看出,咱們入過要想穩定的Hook程序,那必須找到最合適且必定會執行的地方,也就是尋找Hook點,這也是重點和難點的地方,通常選擇不太變換的對象最做爲Hook點,如:單例、靜態對象等;android
經過上面的學習大概知道Hook的原理和使用,剩下部分已安卓中常見的Hook使用爲例深刻理解和使用Hook技術,咱們如今利用Hook技術修改掉Activity的啓動過程,由Android進階知識樹——Android四大組件啓動過程知道,Activity在啓動的開始和最後建立對象時都是經過Instrumentation類,並且Instrumentation在ActivityThread中的對象即進程中只有一個實例,正好符合Hook點,下面就一塊兒實現Hook Instrumentation,具體步驟以下:緩存
public class FixInstrumentation extends Instrumentation {
private Instrumentation instrumentation;
private static final String ACTIVITY_RAW = "raw_activity";
public FixInstrumentation(Instrumentation instrumentation) {
this.instrumentation = 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);
複製代碼
如今要在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);
}
複製代碼
上面的替換操做躲過了系統的註冊檢查,程序執行到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
上面經過Hook技術修改了活動的啓動過程,屬於應用程序的Hook,下面嘗試Hook Android的系統服務,修改系統的功能,在Hook以前仍是先了解一下系統服務的獲取過程,並嘗試尋找Hook點;函數
@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中註冊一系列服務,在使用過程當中直接根據服務名換獲取服務;學習
//註冊服務
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
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
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代理反射執行
}
}
複製代碼
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()方法,讓此方法返回第一步的代理類,建立步驟:
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的使用介紹清楚了,接下來再利用Hook技術攔截系統的AMS,改變系統的服務啓動,也是插件化啓動服務的原理,這裏實現啓動未註冊的MyService,關於Service的啓動過程點擊上面的四大組件的連接查看,由於AMS也是經過Binder通訊的因此Hook的第一步要實現Binder的動態代理
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的啓動同樣,經過在攔截到方法後替換爲註冊的佔位服務,實現服務的啓動
//一、反射獲取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過程:
本文從基礎只是和實戰的角度介紹下Hook技術,Hook技術也是插件化中必備的技術,Hook從技術角度上仍是比較簡單,但須要對目標原理和過程進行理解,肯定合理的Hook點,才能實現真正的效果;