Hook机制之Binder Hook

2024-05-27 20:32
文章标签 机制 hook binder

本文主要是介绍Hook机制之Binder Hook,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

Android系统通过Binder机制给应用程序提供了一系列的系统服务,诸如ActivityManagerServiceClipboardManager, AudioManager等;这些广泛存在系统服务给应用程序提供了诸如任务管理,音频,视频等异常强大的功能。

插件框架作为各个插件的管理者,为了使得插件能够无缝地使用这些系统服务,自然会对这些系统服务做出一定的改造(Hook),使得插件的开发和使用更加方便,从而大大降低插件的开发和维护成本。比如,Hook住ActivityManagerService可以让插件无缝地使用startActivity方法而不是使用特定的方式(比如that语法)来启动插件或者主程序的任意界面。

我们把这种Hook系统服务的机制称之为Binder Hook,因为本质上这些服务提供者都是存在于系统各个进程的Binder对象。因此,要理解接下来的内容必须了解Android的Binder机制。

系统服务的获取过程

我们知道系统的各个远程service对象都是以Binder的形式存在的,而这些Binder有一个管理者,那就是ServiceManager;我们要Hook掉这些service,自然要从这个ServiceManager下手,不然星罗棋布的Binder广泛存在于系统的各个角落,要一个个找出来还真是大海捞针。

回想一下我们使用系统服务的时候是怎么干的,想必这个大家一定再熟悉不过了:通过Context对象的getSystemService方法;比如要使用ActivityManager


ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);

可是这个貌似跟ServiceManager没有什么关系啊?我们再查看getSystemService方法;(Context的实现在ContextImpl里面):

public Object getSystemService(String name) {ServiceFetcher fetcher = SYSTEM_SERVICE_MAP.get(name);return fetcher == null ? null : fetcher.getService(this);
}

很简单,所有的service对象都保存在一张map里面,我们再看这个map是怎么初始化的:

registerService(ACCOUNT_SERVICE, new ServiceFetcher() {public Object createService(ContextImpl ctx) {IBinder b = ServiceManager.getService(ACCOUNT_SERVICE);IAccountManager service = IAccountManager.Stub.asInterface(b);return new AccountManager(ctx, service);}});

ContextImpl的静态初始化块里面,有的Service是像上面这样初始化的;可以看到,确实使用了ServiceManager;当然还有一些service并没有直接使用ServiceManager,而是做了一层包装并返回了这个包装对象,比如我们的ActivityManager,它返回的是ActivityManager这个包装对象:

registerService(ACTIVITY_SERVICE, new ServiceFetcher() {public Object createService(ContextImpl ctx) {return new ActivityManager(ctx.getOuterContext(), ctx.mMainThread.getHandler());}});

但是在ActivityManager这个类内部,也使用了ServiceManager;具体来说,因为ActivityManager里面所有的核心操作都是使用ActivityManagerNative.getDefault()完成的。那么这个语句干了什么呢?

private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() {protected IActivityManager create() {IBinder b = ServiceManager.getService("activity");IActivityManager am = asInterface(b);return am;}};

因此,通过分析我们得知,系统Service的使用其实就分为两步:

IBinder b = ServiceManager.getService("service_name"); // 获取原始的IBinder对象
IXXInterface in = IXXInterface.Stub.asInterface(b); // 转换为Service接口

寻找Hook点

在Hook机制之动态代理里面我们说过,Hook分为三步,最关键的一步就是寻找Hook点。我们现在已经搞清楚了系统服务的使用过程,那么就需要找出在这个过程中,在哪个环节是最合适hook的。

由于系统服务的使用者都是对第二步获取到的IXXInterface进行操作,因此如果我们要hook掉某个系统服务,只需要把第二步的asInterface方法返回的对象修改为为我们Hook过的对象就可以了。

asInterface过程

接下来我们分析asInterface方法,然后想办法把这个方法的返回值修改为我们Hook过的系统服务对象。这里我们以系统剪切版服务为例,源码位置为android.content.IClipboard,IClipboard.Stub.asInterface方法代码如下:

public static android.content.IClipboard asInterface(android.os.IBinder obj) {if ((obj == null)) {return null; }android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); // Hook点if (((iin != null) && (iin instanceof android.content.IClipboard))) {return ((android.content.IClipboard) iin);}return new android.content.IClipboard.Stub.Proxy(obj);
}

这个方法的意思就是:先查看本进程是否存在这个Binder对象,如果有那么直接就是本进程调用了;如果不存在那么创建一个代理对象,让代理对象委托驱动完成跨进程调用。

观察这个方法,前面的那个if语句判空返回肯定动不了手脚;最后一句调用构造函数然后直接返回我们也是无从下手,要修改asInterface方法的返回值,我们唯一能做的就是从这一句下手:


android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); // Hook点

我们可以尝试修改这个obj对象的queryLocalInterface方法的返回值,并保证这个返回值符合接下来的if条件检测,那么就达到了修改asInterface方法返回值的目的。

而这个obj对象刚好是我们第一步返回的IBinder对象,接下来我们尝试对这个IBinder对象的queryLocalInterface方法进行hook。

getService过程

上文分析得知,我们想要修改IBinder对象的queryLocalInterface方法;获取IBinder对象的过程如下:

IBinder b = ServiceManager.getService("service_name");

因此,我们希望能修改这个getService方法的返回值,让这个方法返回一个我们伪造过的IBinder对象;这样,我们可以在自己伪造的IBinder对象的queryLocalInterface方法作处理,进而使得asInterface方法返回在queryLocalInterface方法里面处理过的值,最终实现hook系统服务的目的。

在跟踪这个getService方法之前我们思考一下,由于系统服务是一系列的远程Service,它们的本体,也就是Binder本地对象一般都存在于某个单独的进程,在这个进程之外的其他进程存在的都是这些Binder本地对象的代理。因此在我们的进程里面,存在的也只是这个Binder代理对象,我们也只能对这些Binder代理对象下手。(如果这一段看不懂,建议不要往下看了,先看Binder学习指南)

然后,这个getService是一个静态方法,如果此方法什么都不做,拿到Binder代理对象之后直接返回;那么我们就无能为力了:我们没有办法拦截一个静态方法,也没有办法获取到这个静态方法里面的局部变量(即我们希望修改的那个Binder代理对象)。

接下来就可以看这个getService的代码了:

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;
}

天无绝人之路!ServiceManager为了避免每次都进行跨进程通信,把这些Binder代理对象缓存在一张map里面。

我们可以替换这个map里面的内容为Hook过的IBinder对象,由于系统在getService的时候每次都会优先查找缓存,因此返回给使用者的都是被我们修改过的对象,从而达到瞒天过海的目的。

总结一下,要达到修改系统服务的目的,我们需要如下两步:

  1. 首先肯定需要伪造一个系统服务对象,接下来就要想办法让asInterface能够返回我们的这个伪造对象而不是原始的系统服务对象。
  2. 通过上文分析我们知道,只要让getService返回IBinder对象的queryLocalInterface方法直接返回我们伪造过的系统服务对象就能达到目的。所以,我们需要伪造一个IBinder对象,主要是修改它的queryLocalInterface方法,让它返回我们伪造的系统服务对象;然后把这个伪造对象放置在ServiceManager的缓存map里面即可。

我们通过Binder机制的优先查找本地Binder对象的这个特性达到了Hook掉系统服务对象的目的。因此queryLocalInterface也失去了它原本的意义(只查找本地Binder对象,没有本地对象返回null),这个方法只是一个傀儡,是我们实现hook系统对象的桥梁:我们通过这个“漏洞”让asInterface永远都返回我们伪造过的对象。由于我们接管了asInterface这个方法的全部,我们伪造过的这个系统服务对象不能是只拥有本地Binder对象(原始queryLocalInterface方法返回的对象)的能力,还要有Binder代理对象操纵驱动的能力。

接下来我们就以Hook系统的剪切版服务为例,用实际代码来说明,如何Hook掉系统服务。

Hook系统剪切版服务

伪造剪切版服务对象

首先我们用代理的方式伪造一个剪切版服务对象,关于如何使用代理的方式进行hook以及其中的原理,可以查看插件框架原理解析——Hook机制之动态代理。

具体代码如下,我们用动态代理的方式Hook掉了hasPrimaryClip()getPrimaryClip()这两个方法:

public class BinderHookHandler implements InvocationHandler {private static final String TAG = "BinderHookHandler";// 原始的Service对象 (IInterface)Object base;public BinderHookHandler(IBinder base, Class<?> stubClass) {try {Method asInterfaceMethod = stubClass.getDeclaredMethod("asInterface", IBinder.class);// IClipboard.Stub.asInterface(base);this.base = asInterfaceMethod.invoke(null, base);} catch (Exception e) {throw new RuntimeException("hooked failed!");}}@TargetApi(Build.VERSION_CODES.HONEYCOMB)@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 把剪切版的内容替换为 "you are hooked"if ("getPrimaryClip".equals(method.getName())) {Log.d(TAG, "hook getPrimaryClip");return ClipData.newPlainText(null, "you are hooked");}// 欺骗系统,使之认为剪切版上一直有内容if ("hasPrimaryClip".equals(method.getName())) {return true;}return method.invoke(base, args);}
}

注意,我们拿到原始的IBinder对象之后,如果我们希望使用被Hook之前的系统服务,并不能直接使用这个IBinder对象,而是需要使用asInterface方法将它转换为IClipboard接口;因为getService方法返回的IBinder实际上是一个裸Binder代理对象,它只有与驱动打交道的能力,但是它并不能独立工作,需要人指挥它;asInterface方法返回的IClipboard.Stub.Proxy类的对象通过操纵这个裸BinderProxy对象从而实现了具体的IClipboard接口定义的操作。

伪造IBinder 对象

在上一步中,我们已经伪造好了系统服务对象,现在要做的就是想办法让asInterface方法返回我们伪造的对象了;我们伪造一个IBinder对象:

public class BinderProxyHookHandler implements InvocationHandler {private static final String TAG = "BinderProxyHookHandler";// 绝大部分情况下,这是一个BinderProxy对象// 只有当Service和我们在同一个进程的时候才是Binder本地对象// 这个基本不可能IBinder base;Class<?> stub;Class<?> iinterface;public BinderProxyHookHandler(IBinder base) {this.base = base;try {this.stub = Class.forName("android.content.IClipboard$Stub");this.iinterface = Class.forName("android.content.IClipboard");} catch (ClassNotFoundException e) {e.printStackTrace();}}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {if ("queryLocalInterface".equals(method.getName())) {Log.d(TAG, "hook queryLocalInterface");// 这里直接返回真正被Hook掉的Service接口// 这里的 queryLocalInterface 就不是原本的意思了// 我们肯定不会真的返回一个本地接口, 因为我们接管了 asInterface方法的作用// 因此必须是一个完整的 asInterface 过的 IInterface对象, 既要处理本地对象,也要处理代理对象// 这只是一个Hook点而已, 它原始的含义已经被我们重定义了; 因为我们会永远确保这个方法不返回null// 让 IClipboard.Stub.asInterface 永远走到if语句的else分支里面return Proxy.newProxyInstance(proxy.getClass().getClassLoader(),// asInterface 的时候会检测是否是特定类型的接口然后进行强制转换// 因此这里的动态代理生成的类型信息的类型必须是正确的new Class[] { IBinder.class, IInterface.class, this.iinterface },new BinderHookHandler(base, stub));}Log.d(TAG, "method:" + method.getName());return method.invoke(base, args);}
}

我们使用动态代理的方式伪造了一个跟原始IBinder一模一样的对象,然后在这个伪造的IBinder对象的queryLocalInterface方法里面返回了我们第一步创建的伪造过的系统服务对象;注意看注释,详细解释可以看代码

替换ServiceManager的IBinder对象

现在就是万事具备,只欠东风了;我们使用反射的方式修改ServiceManager类里面缓存的Binder对象,使得getService方法返回我们伪造的IBinder对象,进而asInterface方法使用伪造IBinder对象的queryLocalInterface方法返回了我们伪造的系统服务对象。代码较简单,如下:

接下来,在app里面使用剪切版,比如长按进行粘贴之后,剪切版的内容永远都是you are hooked了;这样,我们Hook系统服务的目的宣告完成!详细的代码参见 github。

也许你会问,插件框架会这么hook吗?如果不是那么插件框架hook这些干什么?插件框架当然不会做替换文本这么无聊的事情,DroidPlugin插件框架管理插件使得插件就像是主程序一样,因此插件需要使用主程序的剪切版,插件之间也会共用剪切版;其他的一些系统服务也类似,这样就可以达到插件和宿主程序之间的天衣服缝,水乳交融!另外,ActivityManager以及PackageManager这两个系统服务虽然也可以通过这种方式hook,但是由于它们的重要性和特殊性,DroidPlugin使用了另外一种方式,我们会单独讲解。

 

这篇关于Hook机制之Binder Hook的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1008511

相关文章

Spring使用@Retryable实现自动重试机制

《Spring使用@Retryable实现自动重试机制》在微服务架构中,服务之间的调用可能会因为一些暂时性的错误而失败,例如网络波动、数据库连接超时或第三方服务不可用等,在本文中,我们将介绍如何在Sp... 目录引言1. 什么是 @Retryable?2. 如何在 Spring 中使用 @Retryable

JVM 的类初始化机制

前言 当你在 Java 程序中new对象时,有没有考虑过 JVM 是如何把静态的字节码(byte code)转化为运行时对象的呢,这个问题看似简单,但清楚的同学相信也不会太多,这篇文章首先介绍 JVM 类初始化的机制,然后给出几个易出错的实例来分析,帮助大家更好理解这个知识点。 JVM 将字节码转化为运行时对象分为三个阶段,分别是:loading 、Linking、initialization

Java ArrayList扩容机制 (源码解读)

结论:初始长度为10,若所需长度小于1.5倍原长度,则按照1.5倍扩容。若不够用则按照所需长度扩容。 一. 明确类内部重要变量含义         1:数组默认长度         2:这是一个共享的空数组实例,用于明确创建长度为0时的ArrayList ,比如通过 new ArrayList<>(0),ArrayList 内部的数组 elementData 会指向这个 EMPTY_EL

【编程底层思考】垃圾收集机制,GC算法,垃圾收集器类型概述

Java的垃圾收集(Garbage Collection,GC)机制是Java语言的一大特色,它负责自动管理内存的回收,释放不再使用的对象所占用的内存。以下是对Java垃圾收集机制的详细介绍: 一、垃圾收集机制概述: 对象存活判断:垃圾收集器定期检查堆内存中的对象,判断哪些对象是“垃圾”,即不再被任何引用链直接或间接引用的对象。内存回收:将判断为垃圾的对象占用的内存进行回收,以便重新使用。

【Tools】大模型中的自注意力机制

摇来摇去摇碎点点的金黄 伸手牵来一片梦的霞光 南方的小巷推开多情的门窗 年轻和我们歌唱 摇来摇去摇着温柔的阳光 轻轻托起一件梦的衣裳 古老的都市每天都改变模样                      🎵 方芳《摇太阳》 自注意力机制(Self-Attention)是一种在Transformer等大模型中经常使用的注意力机制。该机制通过对输入序列中的每个元素计算与其他元素之间的相似性,

如何通俗理解注意力机制?

1、注意力机制(Attention Mechanism)是机器学习和深度学习中一种模拟人类注意力的方法,用于提高模型在处理大量信息时的效率和效果。通俗地理解,它就像是在一堆信息中找到最重要的部分,把注意力集中在这些关键点上,从而更好地完成任务。以下是几个简单的比喻来帮助理解注意力机制: 2、寻找重点:想象一下,你在阅读一篇文章的时候,有些段落特别重要,你会特别注意这些段落,反复阅读,而对其他部分

【Tools】大模型中的注意力机制

摇来摇去摇碎点点的金黄 伸手牵来一片梦的霞光 南方的小巷推开多情的门窗 年轻和我们歌唱 摇来摇去摇着温柔的阳光 轻轻托起一件梦的衣裳 古老的都市每天都改变模样                      🎵 方芳《摇太阳》 在大模型中,注意力机制是一种重要的技术,它被广泛应用于自然语言处理领域,特别是在机器翻译和语言模型中。 注意力机制的基本思想是通过计算输入序列中各个位置的权重,以确

FreeRTOS内部机制学习03(事件组内部机制)

文章目录 事件组使用的场景事件组的核心以及Set事件API做的事情事件组的特殊之处事件组为什么不关闭中断xEventGroupSetBitsFromISR内部是怎么做的? 事件组使用的场景 学校组织秋游,组长在等待: 张三:我到了 李四:我到了 王五:我到了 组长说:好,大家都到齐了,出发! 秋游回来第二天就要提交一篇心得报告,组长在焦急等待:张三、李四、王五谁先写好就交谁的

UVM:callback机制的意义和用法

1. 作用         Callback机制在UVM验证平台,最大用处就是为了提高验证平台的可重用性。在不创建复杂的OOP层次结构前提下,针对组件中的某些行为,在其之前后之后,内置一些函数,增加或者修改UVM组件的操作,增加新的功能,从而实现一个环境多个用例。此外还可以通过Callback机制构建异常的测试用例。 2. 使用步骤         (1)在UVM组件中内嵌callback函

Smarty模板引擎工作机制(一)

深入浅出Smarty模板引擎工作机制,我们将对比使用smarty模板引擎和没使用smarty模板引擎的两种开发方式的区别,并动手开发一个自己的模板引擎,以便加深对smarty模板引擎工作机制的理解。 在没有使用Smarty模板引擎的情况下,我们都是将PHP程序和网页模板合在一起编辑的,好比下面的源代码: <?php$title="深处浅出之Smarty模板引擎工作机制";$content=