1. Hook technology
In program development, method invocation and execution are executed in sequence. If we want to modify or access the execution of the program, we must insert our own program in the execution process, but at the same time, it does not affect the execution of the original program. This is Hook technology. Cut open the original program and Hook the two ends. The program is still executed as a whole, but all the data is passed through the Hook. Since all the data is passed through the Hook, we can change the poles in this process.
- Hook classification
- According to the API language of Hook, it is divided into Hook Java and Hook Native
- According to the Hook process, it can be divided into application process Hook and global Hook
- The proxy pattern
- The proxy pattern is the basic prototype of the Hook pattern, which provides proxy execution for operations of a class
- The significance of proxy mode is that there is no need to modify the original program structure, add or extend the functionality of the program
- Implementation of proxy class: declare a functional interface, both the proxy class and the real class to implement this functional interface, in the proxy class to save the object of the real class, when calling the proxy class execution method, internal call real object execution;
interface Function { // Declare the method interface
fun function(a)
}
class RealFunction : Function {// Create a real implementation class that needs to execute concrete logic
override fun function(a) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.}}class ProxyFunction(val realFunction: Function) : Function { // Proxy class, passed in a real instance internally
override fun function(a) {
// doSomething.....
realFunction.function()
}
}
Copy the code
- Use of proxy mode
val realFunction = RealFunction()
val proxyFunction = ProxyFunction(realFunction)
proxyFunction.realFunction // Execute the proxy method
Copy the code
- Dynamic proxies, see another article Java Dynamic proxies for the use and principles of dynamic proxies
From the above proxy mode and dynamic proxy can be seen, we can completely through the proxy code to intercept and modify the logic of the original program, here for the class they created, the source code for the system or framework? Is that possible? The answer is yes, but on the face can be seen in the Hook set to, we entered to the stability of the Hook procedure, it must find the most appropriate and will be performed, which is to find a place to Hook, it is also important and difficult place, generally choose not too transform the object as a Hook point, such as: the singleton, static objects, etc;
2, Instrumentation in Hook system
Through the above learning, I have a general understanding of the principle and use of Hook. The rest of the learning has taken the common use of Hook in Android as an example to deeply understand and use Hook technology. Now we use Hook technology to modify the startup process of Activity, which is known from the Advanced Knowledge tree of Android — the startup process of the four major Components of Android. The Activity starts with Instrumentation and creates objects at the end of the Instrumentation class, and the Instrumentation object in the ActivityThread is only one instance in the process, which meets the Hook point. The following together to achieve Hook Instrumentation, the specific steps are as follows:
- Create an Instrumentation agent class that internally saves the original Instrumentation of the system
public class FixInstrumentation extends Instrumentation {
private Instrumentation instrumentation;
private static final String ACTIVITY_RAW = "raw_activity";
public FixInstrumentation(Instrumentation instrumentation) {
this.instrumentation = instrumentation; }}Copy the code
- Hook system 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);
Copy the code
- First the object of the ActivityThread in the fetching process is reflected, and then the mInstrumentation object in the fetching system is reflected from that object
- Create a FixInstrumentation object that internally holds an instance of the mInstrumentation obtained in the previous step
- Reflection sets the FixInstrumentation object to the ActivityThread, where the mInstrumentation in the process is the FixInstrumentation
- Rewrite the Instrumentation method to replace the proxy Activity
Now, I want to implement the FixInstrumentation to start the Main3Activity, but the Main3Activity is a plug-in activity that was loaded in, it’s not registered in the program’s registry, and it will throw an exception if it starts normally. Here want to use Hook Instrumentation object will start the object to replace the car into a registered activity, so as to avoid the system inspection, this is also the Android plug-in technology solution, we will replace in execStartActivity () method
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")) { // Check whether it is Main3Activity
intent.setClassName(who, ProxyActivity.class.getCanonicalName()); // Replace the registered ProxyActivity to start
}
intent.putExtra(ACTIVITY_RAW, classname); // Save the original Main3Activity as well
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);
}
Copy the code
- Create the Activity that actually starts
When the program starts the newActivity () creation activity, we need to return to the target activity that was actually started. Otherwise, all the work is in vain. Now replace newActivity () and execute the original logic after the replacement.
public Activity newActivity(ClassLoader cl, String className, Intent intent) throws InstantiationException,
IllegalAccessException {
String classnameIntent = intent.getStringExtra(ACTIVITY_RAW);
String packageName = intent.getComponent().getPackageName(); // Get the package name and class name of the actual Activity stored in the Intent
if (className.equals(ProxyActivity.class.getCanonicalName())) {
ComponentName componentName = new ComponentName(packageName, classnameIntent); // Replace the package name and class name of the real 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); // Execute the original creation method}}Copy the code
Hook Instrumentation to implement Activity plug-in start summary:
- Hook system Instrumentation object, set the created proxy class
- Modify the Intent that starts the Activity in the proxy class to replace the target Activity with a placeholder Activity to avoid checking the registry
- Override newActivity () in the proxy class to switch the initiated activity back to the real target, and then continue with the original logic
3, Binder Hook (Hook system service)
Above, the activation process of the activity is modified by Hook technology, which belongs to the Hook of the application program. Below, we try to Hook the Android system service and modify the system function. Before Hook, we should first understand the acquisition process of the system service and try to find the Hook point.
3.1 Principle of system obtaining service
- ContextImpl.getSystemService(String name)
@Override
public Object getSystemService(String name) {
return SystemServiceRegistry.getSystemService(this, name);
}
public static Object getSystemService(ContextImpl ctx, String name) {
Fetch ServiceFetcher by name from the registered SYSTEM_SERVICE_FETCHERSServiceFetcher<? > fetcher = SYSTEM_SERVICE_FETCHERS.get(name);returnfetcher ! =null ? fetcher.getService(ctx) : null; // create a service in ServiceFetcher
}
Copy the code
When using the system service directly invoke the Context of getSystemService (), the final call of ContextImpl method, ContextImpl invokes the SystemServiceRegistry. GetSystemService (), The SystemServiceRegistry registers a series of services with the SystemServiceRegistry at startup. During use, the system directly obtains services based on the service name change.
- Register services in SYSTEM_SERVICE_FETCHERS (JOB_SCHEDULER_SERVICE as an example)
// Register service
registerService(Context.JOB_SCHEDULER_SERVICE, JobScheduler.class, new StaticServiceFetcher<JobScheduler>() {
@Override
public JobScheduler createService(a) {
IBinder b = ServiceManager.getService(Context.JOB_SCHEDULER_SERVICE); // Obtain Binder from ServiceManager
return new JobSchedulerImpl(IJobScheduler.Stub.asInterface(b)); // Get the proxy object for 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); // Save the service name and StaticServiceFetcher instance as key-value pairs
}
Copy the code
As you can see from the registration process above, the system first encapsulates the creation of each service in the corresponding ServiceFetcher object, and then registers the ServiceFetcher object with the service name in SYSTEM_SERVICE_FETCHERS, which is why the service name is passed in when obtaining the service.
- ServiceManager. GetService () : get the corresponding service system corresponding to the Binder object
public JobScheduler createService(a) throws ServiceNotFoundException {
IBinder b = ServiceManager.getServiceOrThrow(Context.JOB_SCHEDULER_SERVICE);
return new JobSchedulerImpl(IJobScheduler.Stub.asInterface(b));
}
Copy the code
The createService () method of ServiceFetcher is called. Create () retrieves the Binder objects stored in the system, and then calls asInterface () to find the proxy classes. AsInterface () checks for Binder objects and creates a proxy object if not.
- To summarize the service acquisition process:
- At system start, the system registers an instance of ServiceFetcher that encapsulates the service like SYSTEM_SERVICE_FETCHERS
- When the program calls to get the service, it looks up from SYSTEM_SERVICE_FETCHERS based on the service name and returns the corresponding ServiceFetcher instance
- When an instance’s GET () is called to get the service, the Binder that holds the service in the system is first retrieved from ServerManager
- Call IxxInterface’s asInterface () method to find and return Binder’s proxy classes
3.2. Look for Hook points
- If we Hook the Binder object, modify its queryLocalInterface to return the proxy object of the substitute object, then we can implement the proxy object.
- To achieve goal 1, we must ensure that the ServerManager lookup returns our specified Binder, which is retrieved from the system Map cache in ServerManager. We simply place the agent’s Binder in the cached Map and then return the specified Binder when searching.
3.3 actual combat — take shear version service as an example
- Create a dynamic proxy class for the service
public class FixBinder implements InvocationHandler {
private static final String TAG = "BinderHookHandler";
// The original Service object (IInterface)
Object base;
public FixBinder(IBinder base, Class
stubClass) {
try {
Method asInterfaceMethod = stubClass.getDeclaredMethod("asInterface", IBinder.class);// Obtain the asInterface of the original interface
this.base = asInterfaceMethod.invoke(null, base); // Use the original Binder reflection to get the proxy class of the original service
} 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 {
// Tricking the system into thinking there is always something on the clipped version
if ("hasPrimaryClip".equals(method.getName())) {
return true;
}
return method.invoke(base, args); // The rest of the methods are executed using the original Binder proxy reflection}}Copy the code
- Binder will return the proxy class only if you use Binder
- Binder objects found in the system are passed in the constructor, and the proxy class of the service that calls asasInterface () gets and holds the system service itself
- Intercepting the clipboard method, intercepting the hasPrimaryClip () method returns true so that the system always thinks there is something on the clipboard
- Create Binder objects
public class ProxyBinder implements InvocationHandler { IBinder base; Class<? > stub; Class<? > iinterface; public ProxyBinder(IBinder base) { this.base = base; / / (1) the try {this. Stub = Class. Class.forname (" android. Content. IClipboard $stub "); / / (2) enclosing iinterface = Class. 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 will check if it is a specific type of interface and cast // So the type information generated by the dynamic proxy here must be of the correct type, New Class[] {ibinder. Class, iInterface. Class, this. IInterface}, new FixBinder(Base, stub)); } return method.invoke(base, args); }}Copy the code
Binder > queryLocalInterface (); Binder > queryLocalInterface (); Binder > queryLocalInterface ();
- The original real Binder in ServerManager is stored inside the agent as with normal agents
- Use reflection to get the IClipboard$Stub class to find the proxy class
- Hook the queryLocalInterface method
- After invoke () intercepts the method, the dynamic proxy is used to create and return IClipboard’s proxy Binder
- Hook Replace Binder in ServerManager
final String CLIPBOARD_SERVICE = "clipboard";
/ / the following this paragraph mean actually is: ServiceManager. GetService (" clipboard ");Class<? > serviceManager = Class.forName("android.os.ServiceManager");
Method getService = serviceManager.getDeclaredMethod("getService", String.class);
// (1) The original Clipboard Binder object managed by the ServiceManager
IBinder rawBinder = (IBinder) getService.invoke(null, CLIPBOARD_SERVICE);
// (2) Hook the queryLocalInterface method of the Binder proxy object
IBinder hookedBinder = (IBinder) Proxy.newProxyInstance(serviceManager.getClassLoader(),
newClass<? >[] { IBinder.class },new BinderProxyHookHandler(rawBinder));
// Add the hook agent object to the ServiceManager cache
Field cacheField = serviceManager.getDeclaredField("sCache");
cacheField.setAccessible(true);
Map<String, IBinder> cache = (Map) cacheField.get(null);
cache.put(CLIPBOARD_SERVICE, hookedBinder);
Copy the code
Through the first two have all the ProxyBinder to create the implementation, the rest is to put ProxyBinder into the system ServiceManager cache, so that the query will be returned to Binder according to our requirements, the following routine can be carried out, the specific Hook process see code notes;
4. AMS (Android 9.0) Hook system service
The above two examples have clearly introduced the use of Hook. Then, Hook technology is used to intercept AMS of the system and change the Service startup of the system, which is also the principle of plug-in startup Service. Here, unregistered MyService is started. Because AMS also communicates with Binder, the first step in Hook is to implement dynamic proxies with Binder
- Create a proxy for AMS to implement a feature interception service startup process
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())) { // Intercepts start services
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;
}
returnmethod.invoke(binder, args); }}Copy the code
StartService () is intercepted in the invoke () method, and the overall implementation is the same as the Activity started above, by intercepting the method and replacing it with a registered placeholder service
- AMS Hook System (taking Android 9.0 as an example)
/ / 1, reflection for ActivityManager IActivityManagerSingleton the static instanceClass<? > manager = Class.forName("android.app.ActivityManager");
Field field = manager.getDeclaredField("IActivityManagerSingleton");
field.setAccessible(true);
Object object = field.get(null);
// Reflection gets an instance of mInstance in the Singleton. MInstance is the object created after the call to Create, which is the proxy instance of IActivityManagerClass<? > singlen = Class.forName("android.util.Singleton");
Field field1 = singlen.getDeclaredField("mInstance");
field1.setAccessible(true);
Object binder = field1.get(object); / / 2, obtain the mInstance IActivityManagerSingleton internalClass<? > iActivityManagerInterface = Class.forName("android.app.IActivityManager");
// create IActivityManager
Object binder1 = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[]{iActivityManagerInterface}, new HookProxyBinder(binder));
// Execute mInstance to IActivityManager
field1.set(object, binder1);
Copy the code
The Hook process above is to use reflection to obtain the original proxy class of the system, and then set the created proxy class into the system, but it involves Singleton class. AMS in the system is provided by Singleton Singleton, so only reflection can be obtained from Singleton. For details, see the Android Advanced Knowledge Tree — The startup process of Android’s four major components
- Register and create the proxy service
public class ProxyService extends Service {
public ProxyService(a) {}@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); }}Copy the code
- Start the service
Intent intentService = new Intent(MainActivity.this, MyService.class); // The startup service is not registered here
startService(intentService);
Copy the code
The plug-in starts the Service process:
- The AMS of the system is replaced by a proxy class by Hook technology to intercept the method of starting the service
- Replace the started target Service with a placeholder Service in the interception method, and save the target Service
- After the placeholder service is started, the target service is obtained and its corresponding method is called to realize the start of plug-in service
This paper introduces Hook technology from the perspective of basic and actual combat. Hook technology is also a necessary technology in plug-in. Hook is relatively simple from the technical point of view, but it is necessary to understand the target principle and process and determine reasonable Hook points to achieve real effects.