Brief introduction
What is the Hook
Hook called "hook", it can intercept and monitor the transmission of events in the course of events in the transmission of the code itself and system integration methods. So that when the method is called, you can execute our own code, which is oriented programming section of thought (AOP).
Hook Category
1. The development model Android, Native mode (C / C ++) and Java mode (Java) distinction, the Android platform
Java level Hook;
Native level Hook;
2. Hook root objects with different ways to handle events after the Hook, Hook is also divided into:
Message Hook;
API Hook;
3. Hook for the different processes of it, can be divided into:
Global Hook;
A single process Hook;
Hook common framework
In Android development, following some common framework Hook:
Xposed
By replacing the / system / bin / app_process Zygote process control program, so that app_process during the boot process will load XposedBridge.jar this Jar package, thus completing the hijacking of the Dalvik virtual machine Zygote process and created.
Xposed complete hijacking of all Hook Function at boot time, before and after the execution of the original Function add custom code.Cydia Substrate
Cydia Substrate framework provides related services framework jailbreak for Apple users, of course, also launched the Android version. Cydia Substrate is a code modification platform, it can modify the code in any process. Either in Java or C / C ++ (native code) written, but Xposed Hook app_process only supports the Java function.
Legend
Legend is a free Android Apk Hook framework of Root environment, the framework code simple design, high versatility, while for reverse engineering some of the Hook scene. Most of the functions are put on Java layer, so compatibility is very good.
Such a principle is directly constructed virtual machine data structures corresponding to the old and new methods, and then replace the information is written to memory.
Hook must master knowledge
reflection
If you are not very familiar with the reflection, I suggest you brush up on knowledge of java reflection.
Dynamic proxy is a proxy class generated dynamically at run time, like we do not need to manually write the static agent that one of the proxy class. In java, we can use dynamic InvocationHandler agent.
The main content of this article is to explain the individual processes Hook, and how Hook.
Hook Use Case
Hook selection of key points
Hook selection point: try to static variables and single cases, because once an object is created, they are not easy to change, very easy to locate.
Hook process:
Looking Hook point, a static variable or principle is to singleton object, try the public Hook objects and methods.
Select the appropriate proxy mode, if the agent is an interface with dynamic.
Substitution - replacing the original object with the proxy object.
Android API version is more, methods and classes may be different, so do the work of compatible API.
A simple case: Use Hook modify View.OnClickListener event
First, let's analyze View.setOnClickListener source, Hook find the right point. You can see OnClickListener objects are stored in an internal class called the ListenerInfo, which mListenerInfo is a member variable of View. ListeneInfo which holds a variety of events View of listening. Therefore, we can think of ways to hook ListenerInfo of mOnClickListener.
public void setOnClickListener(@Nullable OnClickListener l) { if (!isClickable()) { setClickable(true); } getListenerInfo().mOnClickListener = l; }static class ListenerInfo { --- ListenerInfo getListenerInfo() { if (mListenerInfo != null) { return mListenerInfo; } mListenerInfo = new ListenerInfo(); return mListenerInfo; } --- }
Next, let us look at how Hook View.OnClickListener event?
Roughly divided into three steps:
The first step: Get ListenerInfo objects
View from the source code, we know that we can obtain by getListenerInfo methods, so we obtained by reflection objects ListenerInfo
Step Two: Get original OnClickListener event method
From the above analysis, we know OnClickListener events are saved in ListenerInfo inside, the same way we use reflection to obtain
The third step: substitution, replacing the original proxy class OnClickListener with Hook
public static void hookOnClickListener (View view) throws Exception {// Step: reflecting objects obtained ListenerInfo Method, getListenerInfo = View.class.getDeclaredMethod ( "getListenerInfo"); getListenerInfo.setAccessible (to true); Object listenerInfo = getListenerInfo.invoke (View ); // Step two: get the original OnClickListener event method Class listenerInfoClz = Class.forName ( "android.view.View $ ListenerInfo"); <?> Field, mOnClickListener = listenerInfoClz.getDeclaredField ( "mOnClickListener"); mOnClickListener.setAccessible (to true); View.OnClickListener originOnClickListener = (View.OnClickListener) mOnClickListener.get (listenerInfo); // third step: replacing the original proxy class OnClickListener with Hook View.OnClickListener hookedOnClickListener = new HookedClickListenerProxy(originOnClickListener); mOnClickListener.set(listenerInfo, hookedOnClickListener); }
public class HookedClickListenerProxy implements View.OnClickListener { private View.OnClickListener origin; public HookedClickListenerProxy(View.OnClickListener origin) { this.origin = origin; } @Override public void onClick(View v) { Toast.makeText(v.getContext(), "Hook Click Listener", Toast.LENGTH_SHORT).show(); if (origin != null) { origin.onClick(v); } } }
Execute the following code, you'll see when we click the button, will pop up the toast "Hook Click Listener"
mBtn1 = (Button) findViewById(R.id.btn_1); mBtn1.setOnClickListener(this);try { HookHelper.hookOnClickListener(mBtn1); } catch (Exception e) { e.printStackTrace(); }
Simple Case II: HooK Notification
Core code sends a message to the notification column:
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); notificationManager.notify(id, builder.build());
Tracking notify method calls finally found a method to notifyAsUser
public void notify(String tag, int id, Notification notification){ notifyAsUser(tag, id, notification, new UserHandle(UserHandle.myUserId())); }
In notifyAsUser method, we were pleasantly surprised to find that service is a singleton, so we can think of ways to hook live this service, but eventually will be called to enqueueNotificationWithTag notifyAsUser method of service. Therefore enqueueNotificationWithTag method of service to live hook
public void notifyAsUser(String tag, int id, Notification notification, UserHandle user){ // INotificationManager service = getService(); String pkg = mContext.getPackageName(); // Fix the notification as best we can. Notification.addFieldsFromContext(mContext, notification); if (notification.sound != null) { notification.sound = notification.sound.getCanonicalUri(); if (StrictMode.vmFileUriExposureEnabled()) { notification.sound.checkFileUriExposed("Notification.sound"); } } fixLegacySmallIcon(notification, pkg); if (mContext.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1) { if (notification.getSmallIcon() == null) { throw new IllegalArgumentException("Invalid notification (no valid small icon): " + notification); } } if (localLOGV) Log.v(TAG, pkg + ": notify(" + id + ", " + notification + ")"); final Notification copy = Builder.maybeCloneStrippedForDelivery(notification); try { service.enqueueNotificationWithTag(pkg, mContext.getOpPackageName(), tag, id, copy, user.getIdentifier()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } }private static INotificationManager sService;static public INotificationManager getService(){ if (sService != null) { return sService; } IBinder b = ServiceManager.getService("notification"); sService = INotificationManager.Stub.asInterface(b); return sService; }
In summary, to Hook Notification, it takes about three steps:
The first step: get the sService NotificationManager
Step Two: Because sService is the interface, so we can use the dynamic agent, to obtain a dynamic proxy object
The third step: substitution using a dynamic proxy object proxyNotiMng alternative system sService
So, we can write the following code
public static void hookNotificationManager(final Context context) throws Exception { NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); Method getService = NotificationManager.class.getDeclaredMethod("getService"); getService.setAccessible(true); // 第一步:得到系统的 sService final Object sOriginService = getService.invoke(notificationManager); Class iNotiMngClz = Class.forName("android.app.INotificationManager"); // 第二步:得到我们的动态代理对象 Object proxyNotiMng = Proxy.newProxyInstance(context.getClass().getClassLoader(), new Class[]{iNotiMngClz}, new InvocationHandler() { @Override Invoke Object public (Object Proxy, Method, Method, Object [] args) {throws the Throwable Log.d (the TAG,. "Invoke () Method:" + Method); String name = method.getName (); Log.d (the TAG , "Invoke: name =" name +); IF (args = null && args.length> 0) {for (Arg Object: args) {! Log.d (the TAG, "Invoke: Arg =" + Arg); } } Toast.makeText (context.getApplicationContext (), "was issued a notice of detected", Toast.LENGTH_SHORT) .show (); // referred sOriginService processing operation, does not intercept notification return method.invoke (sOriginService, args); // intercept notice and do nothing // return null; // or notifications according Tag ID and screening } }); // third step: substitution, the alternative system using proxyNotiMng Sservice Field, sServiceField = NotificationManager.class.getDeclaredField ( "Sservice"); sServiceField.setAccessible (to true) ; sServiceField.set (the NotificationManager, proxyNotiMng); }
Hook the use of advanced
Hook ClipboardManager
the first method
NotificationManager hook from the above example, we can see that there is a static variable NotificationManager sService, this variable is a remote service. Therefore, we try to find ClipboardManager is not there the same similar static variables.
View its source found to exist mService variable, the variable is initialized in the constructor ClipboardManager, and ClipboardManager construction method with @hide mark, indicating that the method is not visible to the caller.
And we know ClipboardManager, NotificationManager fact, these are single cases, ie, the system creates only once. Therefore, we can also think
ClipboardManager of mService are singletons. So mService should be able to consider a point of the hook.
public class ClipboardManager extends android.text.ClipboardManager { private final Context mContext; private final IClipboard mService; /** {@hide} */ public ClipboardManager(Context context, Handler handler) throws ServiceNotFoundException { mContext = context; mService = IClipboard.Stub.asInterface( ServiceManager.getServiceOrThrow(Context.CLIPBOARD_SERVICE)); } }
Next, let us look at a related method ClipboardManager of setPrimaryClip, getPrimaryClip
public void setPrimaryClip(ClipData clip) { try { if (clip != null) { clip.prepareToLeaveProcess(true); } mService.setPrimaryClip(clip, mContext.getOpPackageName()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } }/** * Returns the current primary clip on the clipboard. */public ClipData getPrimaryClip() { try { return mService.getPrimaryClip(mContext.getOpPackageName()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } }
You can find these methods will eventually call the relevant methods of mService. Therefore, ClipboardManager of mService indeed be a hook of a point.
hook ClipboardManager.mService implementation
It takes about three steps
The first step: get the mService ClipboardManager
Step two: a dynamic proxy object initialization
The third step: substitution, the alternative system using proxyNotiMng mService
public static void hookClipboardService(final Context context) throws Exception { ClipboardManager clipboardManager = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE); Field mServiceFiled = ClipboardManager.class.getDeclaredField("mService"); mServiceFiled.setAccessible(true); // 第一步:得到系统的 mService final Object mService = mServiceFiled.get(clipboardManager); // 第二步:初始化动态代理对象 Class aClass = Class.forName("android.content.IClipboard"); Object proxyInstance = Proxy.newProxyInstance(context.getClass().getClassLoader(), new Class[]{aClass}, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Log.d(TAG, "invoke(). method:" + method); String name = method.getName(); if (args != null && args.length > 0) { for (Object arg : args) { Log.d(TAG, "invoke: arg=" + arg); } } if ("setPrimaryClip".equals(name)) { Object arg = args[0]; if (arg instanceof ClipData) { ClipData clipData = (ClipData) arg; int itemCount = clipData.getItemCount(); for (int i = 0; i < itemCount; i++) { ClipData.Item item = clipData.getItemAt(i); Log.i (the TAG, "Invoke: Item =" Item +); } } Toast.makeText (context, "detects that a person is provided clipboard contents", Toast.LENGTH_SHORT) the .Show (); } the else IF ( "getPrimaryClip" .equals (name)) { Toast.makeText (context, "was detected to obtain the content of the clipboard", Toast.LENGTH_SHORT) the .Show (); } // referred sOriginService processing operation, does not intercept notification return method.invoke (MSERVICE, args); } }); // third step: substitution, the alternative system using proxyNotiMng MSERVICE Field, sServiceField = ClipboardManager.class.getDeclaredField ( "MSERVICE"); sServiceField.setAccessible (to true); sServiceField.set (clipboardManager , proxyInstance); }
image
第二种方法
对 Android 源码有基本了解的人都知道,Android 中的各种 Manager 都是通过 ServiceManager 获取的。因此,我们可以通过 ServiceManager hook 所有系统 Manager,ClipboardManager 当然也不例外。
public final class ServiceManager { /** * Returns a reference to a service with the given name. * * @param name the name of the service to get * @return a reference to the service, or <code>null</code> if the service doesn't exist */ public static IBinder getService(String name) { try { IBinder service = sCache.get(name); if (service != null) { return service; } else { return getIServiceManager().getService(name); } } catch (RemoteException e) { Log.e(TAG, "error in getService", e); } return null; } }
老套路
第一步:通过反射获取剪切板服务的远程Binder对象,这里我们可以通过 ServiceManager getService 方法获得
第二步:创建我们的动态代理对象,动态代理原来的Binder对象
第三步:偷梁换柱,把我们的动态代理对象设置进去
public static void hookClipboardService() throws Exception { //通过反射获取剪切板服务的远程Binder对象 Class serviceManager = Class.forName("android.os.ServiceManager"); Method getServiceMethod = serviceManager.getMethod("getService", String.class); IBinder remoteBinder = (IBinder) getServiceMethod.invoke(null, Context.CLIPBOARD_SERVICE); //新建一个我们需要的Binder,动态代理原来的Binder对象 IBinder hookBinder = (IBinder) Proxy.newProxyInstance(serviceManager.getClassLoader(), new Class[]{IBinder.class}, new ClipboardHookRemoteBinderHandler(remoteBinder)); //通过反射获取ServiceManger存储Binder对象的缓存集合,把我们新建的代理Binder放进缓存 Field sCacheField = serviceManager.getDeclaredField("sCache"); sCacheField.setAccessible(true); Map<String, IBinder> sCache = (Map<String, IBinder>) sCacheField.get(null); sCache.put(Context.CLIPBOARD_SERVICE, hookBinder); }
public class ClipboardHookRemoteBinderHandler implements InvocationHandler { private IBinder remoteBinder; private Class iInterface; private Class stubClass; public ClipboardHookRemoteBinderHandler(IBinder remoteBinder) { this.remoteBinder = remoteBinder; try { this.iInterface = Class.forName("android.content.IClipboard"); this.stubClass = Class.forName("android.content.IClipboard$Stub"); } catch (Exception e) { e.printStackTrace(); } } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Log.d ( "RemoteBinderHandler", method.getName ( ) + "() is invoked"); if ( "queryLocalInterface" .equals (method.getName ())) {// Here specific services not intercept method, as this is a remote Binder, not yet translated into local Binder object // so we know first interception queryLocalInterface method returns a local proxy object Binder return Proxy.newProxyInstance (remoteBinder.getClass (). getClassLoader (), new Class [] {} this.iInterface, new new ClipboardHookLocalBinderHandler (remoteBinder, stubClass)); } return Method.invoke (remoteBinder, args); } }