简介
什么是 Hook
Hook 又叫“钩子”,它能够在事件传送的过程中截获并监控事件的传输,将本身的代码与零碎办法进行融入。这样当这些办法被调用时,也就能够执行咱们本人的代码,这也是面向切面编程的思维(AOP)。
Hook 分类
1.依据Android开发模式,Native模式(C/C++)和Java模式(Java)辨别,在Android平台上
- Java层级的Hook;
- Native层级的Hook;
2.根 Hook 对象与 Hook 后处理事件形式不同,Hook还分为:
- 音讯Hook;
- API Hook;
3.针对Hook的不同过程上来说,还能够分为:
- 全局Hook;
- 单个过程Hook;
常见 Hook 框架
在Android开发中,有以下常见的一些Hook框架:
1 . Xposed
通过替换 /system/bin/app_process 程序控制 Zygote 过程,使得 app_process 在启动过程中会加载 XposedBridge.jar 这个 Jar 包,从而实现对 Zygote 过程及其创立的 Dalvik 虚拟机的劫持。 Xposed 在开机的时候实现对所有的 Hook Function 的劫持,在原 Function 执行的前后加上自定义代码。
2 . Cydia Substrate
Cydia Substrate 框架为苹果用户提供了越狱相干的服务框架,当然也推出了 Android 版 。Cydia Substrate 是一个代码批改平台,它能够批改任何过程的代码。不论是用 Java 还是 C/C++(native代码)编写的,而 Xposed 只反对 Hook app_process 中的 Java 函数。
3 . Legend
Legend 是 Android 免 Root 环境下的一个 Apk Hook 框架,该框架代码设计简洁,通用性高,适宜逆向工程时一些 Hook 场景。大部分的性能都放到了 Java 层,这样的兼容性就十分好。 原理是这样的,间接结构出新旧办法对应的虚拟机数据结构,而后替换信息写到内存中即可。
Hook 必须把握的常识
- 反射
如果你对反射还不是很相熟的话,倡议你先温习一下 java 反射的相干常识。有趣味的,能够看一下我的这一篇博客 Java 反射机制详解
- java 的动静代理
动静代理是指在运行时动静生成代理类,不须要咱们像动态代理那个去手动写一个个的代理类。在 java 中,咱们能够应用 InvocationHandler 实现动静代理,有趣味的,能够查看我的这一篇博客 java 代理模式详解
本文的次要内容是解说单个过程的 Hook,以及怎么 Hook。
Hook 应用实例
Hook 抉择的关键点
- Hook 的抉择点:尽量动态变量和单例,因为一旦创建对象,它们不容易变动,非常容易定位。
- Hook 过程:
- 寻找 Hook 点,准则是尽量动态变量或者单例对象,尽量 Hook public 的对象和办法。
- 抉择适合的代理形式,如果是接口能够用动静代理。
- 移花接木——用代理对象替换原始对象。
- Android 的 API 版本比拟多,办法和类可能不一样,所以要做好 API 的兼容工作。
简略案例一: 应用 Hook 批改 View.OnClickListener 事件
首先,咱们先剖析 View.setOnClickListener 源码,找出适合的 Hook 点。能够看到 OnClickListener 对象被保留在了一个叫做 ListenerInfo 的外部类里,其中 mListenerInfo 是 View 的成员变量。ListeneInfo 外面保留了 View 的各种监听事件。因而,咱们能够想方法 hook ListenerInfo 的 mOnClickListener 。
1public void setOnClickListener(@Nullable OnClickListener l) { 2 if (!isClickable()) { 3 setClickable(true); 4 } 5 getListenerInfo().mOnClickListener = l; 6} 7 8static class ListenerInfo { 910 ---1112 ListenerInfo getListenerInfo() {13 if (mListenerInfo != null) {14 return mListenerInfo;15 }16 mListenerInfo = new ListenerInfo();17 return mListenerInfo;18 }1920 ---21}
接下来,让咱们一起来看一下怎么 Hook View.OnClickListener 事件?
大略分为三步:
- 第一步:获取 ListenerInfo 对象
从 View 的源代码,咱们能够晓得咱们能够通过 getListenerInfo 办法获取,于是,咱们利用反射失去 ListenerInfo 对象
- 第二步:获取原始的 OnClickListener事件办法
从下面的剖析,咱们晓得 OnClickListener 事件被保留在 ListenerInfo 外面,同理咱们利用反射获取
- 第三步:移花接木,用 Hook代理类 替换原始的 OnClickListener
1public static void hookOnClickListener(View view) throws Exception { 2 // 第一步:反射失去 ListenerInfo 对象 3 Method getListenerInfo = View.class.getDeclaredMethod("getListenerInfo"); 4 getListenerInfo.setAccessible(true); 5 Object listenerInfo = getListenerInfo.invoke(view); 6 // 第二步:失去原始的 OnClickListener事件办法 7 Class<?> listenerInfoClz = Class.forName("android.view.View$ListenerInfo"); 8 Field mOnClickListener = listenerInfoClz.getDeclaredField("mOnClickListener"); 9 mOnClickListener.setAccessible(true);10 View.OnClickListener originOnClickListener = (View.OnClickListener) mOnClickListener.get(listenerInfo);11 // 第三步:用 Hook代理类 替换原始的 OnClickListener12 View.OnClickListener hookedOnClickListener = new HookedClickListenerProxy(originOnClickListener);13 mOnClickListener.set(listenerInfo, hookedOnClickListener);14}
1public class HookedClickListenerProxy implements View.OnClickListener { 2 3 private View.OnClickListener origin; 4 5 public HookedClickListenerProxy(View.OnClickListener origin) { 6 this.origin = origin; 7 } 8 9 @Override10 public void onClick(View v) {11 Toast.makeText(v.getContext(), "Hook Click Listener", Toast.LENGTH_SHORT).show();12 if (origin != null) {13 origin.onClick(v);14 }15 }1617}
执行以下代码,将会看到当咱们点击该按钮的时候,会弹出 toast “Hook Click Listener”
1mBtn1 = (Button) findViewById(R.id.btn_1);2mBtn1.setOnClickListener(this);3try {4 HookHelper.hookOnClickListener(mBtn1);5} catch (Exception e) {6 e.printStackTrace();7}
简略案例二:HooK Notification
发送音讯到告诉栏的外围代码如下:
1NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);2notificationManager.notify(id, builder.build());
跟踪 notify 办法发现最终会调用到 notifyAsUser 办法
1public void notify(String tag, int id, Notification notification)2{3 notifyAsUser(tag, id, notification, new UserHandle(UserHandle.myUserId()));4}
而在 notifyAsUser 办法中,咱们惊喜地发现 service 是一个单例,因而,咱们能够想办法 hook 住这个 service,而 notifyAsUser 最终会调用到 service 的 enqueueNotificationWithTag 办法。因而 hook 住 service 的 enqueueNotificationWithTag 办法即可
1public void notifyAsUser(String tag, int id, Notification notification, UserHandle user) 2{ 3 // 4 INotificationManager service = getService(); 5 String pkg = mContext.getPackageName(); 6 // Fix the notification as best we can. 7 Notification.addFieldsFromContext(mContext, notification); 8 if (notification.sound != null) { 9 notification.sound = notification.sound.getCanonicalUri();10 if (StrictMode.vmFileUriExposureEnabled()) {11 notification.sound.checkFileUriExposed("Notification.sound");12 }13 }14 fixLegacySmallIcon(notification, pkg);15 if (mContext.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1) {16 if (notification.getSmallIcon() == null) {17 throw new IllegalArgumentException("Invalid notification (no valid small icon): "18 + notification);19 }20 }21 if (localLOGV) Log.v(TAG, pkg + ": notify(" + id + ", " + notification + ")");22 final Notification copy = Builder.maybeCloneStrippedForDelivery(notification);23 try {24 service.enqueueNotificationWithTag(pkg, mContext.getOpPackageName(), tag, id,25 copy, user.getIdentifier());26 } catch (RemoteException e) {27 throw e.rethrowFromSystemServer();28 }29}3031private static INotificationManager sService;3233static public INotificationManager getService()34{35 if (sService != null) {36 return sService;37 }38 IBinder b = ServiceManager.getService("notification");39 sService = INotificationManager.Stub.asInterface(b);40 return sService;41}
综上,要 Hook Notification,大略须要三步:
- 第一步:失去 NotificationManager 的 sService
- 第二步:因为 sService 是接口,所以咱们能够应用动静代理,获取动静代理对象
- 第三步:移花接木,应用动静代理对象 proxyNotiMng 替换零碎的 sService
于是,咱们能够写出如下的代码
1public static void hookNotificationManager(final Context context) throws Exception { 2 NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); 3 4 Method getService = NotificationManager.class.getDeclaredMethod("getService"); 5 getService.setAccessible(true); 6 // 第一步:失去零碎的 sService 7 final Object sOriginService = getService.invoke(notificationManager); 8 9 Class iNotiMngClz = Class.forName("android.app.INotificationManager");10 // 第二步:失去咱们的动静代理对象11 Object proxyNotiMng = Proxy.newProxyInstance(context.getClass().getClassLoader(), new12 Class[]{iNotiMngClz}, new InvocationHandler() {1314 @Override15 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {16 Log.d(TAG, "invoke(). method:" + method);17 String name = method.getName();18 Log.d(TAG, "invoke: name=" + name);19 if (args != null && args.length > 0) {20 for (Object arg : args) {21 Log.d(TAG, "invoke: arg=" + arg);22 }23 }24 Toast.makeText(context.getApplicationContext(), "检测到有人发告诉了", Toast.LENGTH_SHORT).show();25 // 操作交由 sOriginService 解决,不拦挡告诉26 return method.invoke(sOriginService, args);27 // 拦挡告诉,什么也不做28 // return null;29 // 或者是依据告诉的 Tag 和 ID 进行筛选30 }31 });32 // 第三步:移花接木,应用 proxyNotiMng 替换零碎的 sService33 Field sServiceField = NotificationManager.class.getDeclaredField("sService");34 sServiceField.setAccessible(true);35 sServiceField.set(notificationManager, proxyNotiMng);3637}
Hook 应用进阶
Hook ClipboardManager
第一种办法
从下面的 hook NotificationManager 例子中,咱们能够得悉 NotificationManager 中有一个动态变量 sService,这个变量是远端的 service。因而,咱们尝试查找 ClipboardManager 中是不是也存在雷同的相似动态变量。
查看它的源码发现它存在 mService 变量,该变量是在 ClipboardManager 构造函数中初始化的,而 ClipboardManager 的构造方法用 @hide 标记,表明该办法对调用者不可见。
而咱们晓得 ClipboardManager,NotificationManager 其实这些都是单例的,即零碎只会创立一次。因而咱们也能够认为 ClipboardManager 的 mService 是单例的。因而 mService 应该是能够思考 hook 的一个点。
1public class ClipboardManager extends android.text.ClipboardManager { 2 private final Context mContext; 3 private final IClipboard mService; 4 5 /** {@hide} */ 6 public ClipboardManager(Context context, Handler handler) throws ServiceNotFoundException { 7 mContext = context; 8 mService = IClipboard.Stub.asInterface( 9 ServiceManager.getServiceOrThrow(Context.CLIPBOARD_SERVICE));10 }11}
接下来,咱们再来一个看一下 ClipboardManager 的相干办法 setPrimaryClip , getPrimaryClip
1public void setPrimaryClip(ClipData clip) { 2 try { 3 if (clip != null) { 4 clip.prepareToLeaveProcess(true); 5 } 6 mService.setPrimaryClip(clip, mContext.getOpPackageName()); 7 } catch (RemoteException e) { 8 throw e.rethrowFromSystemServer(); 9 }10}1112/**13 * Returns the current primary clip on the clipboard.14 */15public ClipData getPrimaryClip() {16 try {17 return mService.getPrimaryClip(mContext.getOpPackageName());18 } catch (RemoteException e) {19 throw e.rethrowFromSystemServer();20 }21}
能够发现这些办法最终都会调用到 mService 的相干办法。因而,ClipboardManager 的 mService 的确是一个能够 hook 的一个点。
hook ClipboardManager.mService 的实现
大略须要三个步骤
- 第一步:失去 ClipboardManager 的 mService
- 第二步:初始化动静代理对象
- 第三步:移花接木,应用 proxyNotiMng 替换零碎的 mService
1public static void hookClipboardService(final Context context) throws Exception { 2 ClipboardManager clipboardManager = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE); 3 Field mServiceFiled = ClipboardManager.class.getDeclaredField("mService"); 4 mServiceFiled.setAccessible(true); 5 // 第一步:失去零碎的 mService 6 final Object mService = mServiceFiled.get(clipboardManager); 7 8 // 第二步:初始化动静代理对象 9 Class aClass = Class.forName("android.content.IClipboard");10 Object proxyInstance = Proxy.newProxyInstance(context.getClass().getClassLoader(), new11 Class[]{aClass}, new InvocationHandler() {1213 @Override14 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {15 Log.d(TAG, "invoke(). method:" + method);16 String name = method.getName();17 if (args != null && args.length > 0) {18 for (Object arg : args) {19 Log.d(TAG, "invoke: arg=" + arg);20 }21 }22 if ("setPrimaryClip".equals(name)) {23 Object arg = args[0];24 if (arg instanceof ClipData) {25 ClipData clipData = (ClipData) arg;26 int itemCount = clipData.getItemCount();27 for (int i = 0; i < itemCount; i++) {28 ClipData.Item item = clipData.getItemAt(i);29 Log.i(TAG, "invoke: item=" + item);30 }31 }32 Toast.makeText(context, "检测到有人设置粘贴板内容", Toast.LENGTH_SHORT).show();33 } else if ("getPrimaryClip".equals(name)) {34 Toast.makeText(context, "检测到有人要获取粘贴板的内容", Toast.LENGTH_SHORT).show();35 }36 // 操作交由 sOriginService 解决,不拦挡告诉37 return method.invoke(mService, args);3839 }40 });4142 // 第三步:移花接木,应用 proxyNotiMng 替换零碎的 mService43 Field sServiceField = ClipboardManager.class.getDeclaredField("mService");44 sServiceField.setAccessible(true);45 sServiceField.set(clipboardManager, proxyInstance);4647}
第二种办法
对 Android 源码有根本理解的人都晓得,Android 中的各种 Manager 都是通过 ServiceManager 获取的。因而,咱们能够通过 ServiceManager hook 所有零碎 Manager,ClipboardManager 当然也不例外。
1public final class ServiceManager { 2 3 4 /** 5 * Returns a reference to a service with the given name. 6 * 7 * @param name the name of the service to get 8 * @return a reference to the service, or <code>null</code> if the service doesn't exist 9 */10 public static IBinder getService(String name) {11 try {12 IBinder service = sCache.get(name);13 if (service != null) {14 return service;15 } else {16 return getIServiceManager().getService(name);17 }18 } catch (RemoteException e) {19 Log.e(TAG, "error in getService", e);20 }21 return null;22 }23}
老套路
- 第一步:通过反射获取剪切板服务的近程Binder对象,这里咱们能够通过 ServiceManager getService 办法取得
- 第二步:创立咱们的动静代理对象,动静代理原来的Binder对象
- 第三步:移花接木,把咱们的动静代理对象设置进去
1public static void hookClipboardService() throws Exception { 2 3 //通过反射获取剪切板服务的近程Binder对象 4 Class serviceManager = Class.forName("android.os.ServiceManager"); 5 Method getServiceMethod = serviceManager.getMethod("getService", String.class); 6 IBinder remoteBinder = (IBinder) getServiceMethod.invoke(null, Context.CLIPBOARD_SERVICE); 7 8 //新建一个咱们须要的Binder,动静代理原来的Binder对象 9 IBinder hookBinder = (IBinder) Proxy.newProxyInstance(serviceManager.getClassLoader(),10 new Class[]{IBinder.class}, new ClipboardHookRemoteBinderHandler(remoteBinder));1112 //通过反射获取ServiceManger存储Binder对象的缓存汇合,把咱们新建的代理Binder放进缓存13 Field sCacheField = serviceManager.getDeclaredField("sCache");14 sCacheField.setAccessible(true);15 Map<String, IBinder> sCache = (Map<String, IBinder>) sCacheField.get(null);16 sCache.put(Context.CLIPBOARD_SERVICE, hookBinder);1718}
1public class ClipboardHookRemoteBinderHandler implements InvocationHandler { 2 3 private IBinder remoteBinder; 4 private Class iInterface; 5 private Class stubClass; 6 7 public ClipboardHookRemoteBinderHandler(IBinder remoteBinder) { 8 this.remoteBinder = remoteBinder; 9 try {10 this.iInterface = Class.forName("android.content.IClipboard");11 this.stubClass = Class.forName("android.content.IClipboard$Stub");12 } catch (Exception e) {13 e.printStackTrace();14 }15 }1617 @Override18 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {19 Log.d("RemoteBinderHandler", method.getName() + "() is invoked");20 if ("queryLocalInterface".equals(method.getName())) {21 //这里不能拦挡具体的服务的办法,因为这是一个近程的Binder,还没有转化为本地Binder对象22 //所以先拦挡咱们所知的queryLocalInterface办法,返回一个本地Binder对象的代理23 return Proxy.newProxyInstance(remoteBinder.getClass().getClassLoader(),24 new Class[]{this.iInterface},25 new ClipboardHookLocalBinderHandler(remoteBinder, stubClass));26 }2728 return method.invoke(remoteBinder, args);29 }30}