18、Binder客户端漏洞:恶意客户端构造、binder_transaction_data伪造、服务端欺骗攻击

好,我们进入第18章。前面聊了很多服务端和驱动层的安全问题,今天换个角度——站在攻击者的立场,看看恶意客户端怎么利用Binder机制搞事情。

说白了,Binder通信不是单向的。服务端会收到客户端的请求,然后处理数据、返回结果。但如果客户端是恶意的呢?它完全可以构造畸形的binder_transaction_data,欺骗服务端执行非预期的操作。我在项目中就遇到过类似的攻击案例,嗯,今天把这些坑都给你拆开讲。

18.1 恶意客户端的基本思路

先理清攻击者的目标。恶意客户端想干什么?无非是:

  • 绕过权限检查:让服务端以为自己有更高的权限
  • 触发服务端崩溃:构造异常数据导致服务端进程挂掉
  • 执行未授权操作:比如读写敏感文件、修改系统设置
  • 提权:从普通App权限提升到system甚至root

你想想看,Binder驱动在kernel层,它只负责数据拷贝和线程管理,不负责业务逻辑的合法性校验。所以只要数据格式符合规范,驱动就会把它送到服务端。至于服务端怎么处理,那就是另一回事了。

核心观点:Binder驱动是“哑巴”传输层,它不关心数据内容是否恶意。攻击者只要构造合法的binder_transaction_data结构,就能把任意数据塞给服务端。

18.2 binder_transaction_data结构剖析

要伪造数据,先得搞清楚binder_transaction_data长什么样。这是Binder通信的“信封”,所有跨进程数据都装在里面。

struct binder_transaction_data {
    /* 通用字段 */
    union {
        __u32    handle;      // 服务端句柄
        binder_uintptr_t ptr; // 内核内部使用
    } target;
    binder_uintptr_t    cookie;    // 服务端附加数据
    __u32              code;      // 事务码(比如TRANSACTION_TYPE)
    
    /* 权限相关 */
    __u32              flags;     // 标志位,包含TF_ONE_WAY等
    pid_t              sender_pid;    // 发送方PID
    uid_t              sender_euid;   // 发送方有效UID
    
    /* 数据区 */
    binder_size_t      data_size;     // 数据大小
    binder_size_t      offsets_size;  // 偏移数组大小
    union {
        struct {
            binder_uintptr_t    buffer;      // 数据缓冲区
            binder_uintptr_t    offsets;     // 偏移数组
        } ptr;
        __u8    buf[8];          // 小数据直接内联
    } data;
};

注意看sender_pidsender_euid这两个字段。它们是由Binder驱动自动填充的,客户端无法伪造。驱动在拷贝数据时会从当前进程的credential中读取这些值,然后写入结构体。

但是——其他字段呢?target.handlecodeflagsdata.bufferdata.offsets……这些全是客户端可控的。尤其是data.buffer指向的数据内容,客户端想写什么就写什么。

避坑指南:我曾经见过一个开发者,他以为sender_pid是客户端传过来的,于是在服务端用这个值做权限校验。结果呢?攻击者根本改不了这个字段,但服务端却误以为可以信任它。其实正确的做法是用Binder.getCallingPid(),那个才是驱动层保证的。

18.3 伪造binder_transaction_data的常见手法

好,现在我们知道哪些字段可控了。攻击者具体怎么操作?我总结了几种典型手法:

18.3.1 伪造Binder对象引用

Binder通信中,客户端可以传递Binder对象给服务端。比如通过Parcel.writeStrongBinder()。攻击者可以伪造一个Binder引用,指向一个恶意服务端。

具体做法:

  • data.offsets中填入偏移量,指向flat_binder_object结构
  • flat_binder_object中设置type = BINDER_TYPE_HANDLE
  • handle字段填上一个恶意服务端的句柄

服务端收到后,如果它信任这个Binder对象并调用它,就会连接到攻击者控制的恶意服务。这就是服务端欺骗攻击的雏形。

18.3.2 伪造文件描述符

Binder支持传递文件描述符(FD)。攻击者可以伪造一个FD,指向敏感文件(比如/data/system/packages.xml)。

构造方式:

  • flat_binder_object中设置type = BINDER_TYPE_FD
  • handle字段填上目标文件的FD编号

但这里有个限制:驱动会校验FD是否属于当前进程。攻击者只能传递自己已经打开的FD。所以这个手法更多用于绕过服务端的FD权限检查——服务端以为FD来自一个可信进程,实际上却是攻击者打开的。

18.3.3 构造超大或超小数据

攻击者可以设置data_size为极大值(比如0xFFFFFFFF),或者极小值(比如0)。这会导致服务端在解析Parcel时出现内存越界空指针解引用

我记得有个经典案例:某系统服务在onTransact()中直接调用Parcel::unmarshall(),没有检查数据长度。攻击者传一个长度为0的数据包,服务端直接崩溃。嗯,这就是典型的拒绝服务攻击

警告:不要以为服务端是系统进程就安全。很多系统服务(比如ActivityManagerServicePackageManagerService)在早期版本中都存在这类漏洞。攻击者可以让系统UI崩溃、让手机重启,甚至触发内核panic。

18.4 服务端欺骗攻击详解

这是本章的重点。什么叫服务端欺骗攻击?

正常情况下,客户端A请求服务端B的服务。攻击者C作为恶意客户端,构造一个请求发给B。B处理完返回结果给C。但攻击者C可以让B以为自己在跟A通信,从而获取本应只给A的数据或权限。

攻击流程:

  1. 攻击者C打开Binder设备,获取服务端B的句柄
  2. 构造binder_transaction_data,其中target.handle指向B
  3. 在数据区中伪造一个Binder对象,指向攻击者自己的服务
  4. 发送事务给B,B处理请求时,会调用这个伪造的Binder对象
  5. 攻击者C在自己的服务中接收回调,获取敏感数据

为什么能成功?因为Binder驱动只保证sender_pidsender_euid的真实性,但不保证数据内容的真实性。服务端如果盲目信任数据中的Binder对象,就会中招。

真实案例:Android 4.x时代,MediaServer进程存在一个漏洞。攻击者可以构造一个恶意客户端,向MediaServer发送伪造的Binder对象。MediaServer在处理过程中会回调这个对象,攻击者就能获取到MediaServer的权限,进而访问摄像头、麦克风等硬件。这就是CVE-2015-6624,一个经典的提权漏洞。

18.5 防御思路与最佳实践

讲了这么多攻击手法,怎么防?我个人的经验是:

  • 永远不要信任客户端传来的Binder对象:如果服务端需要回调客户端,应该使用Binder.getCallingUid()验证对方身份,而不是直接调用传过来的Binder
  • 严格校验数据长度和格式:在onTransact()中检查data.data_sizedata.offsets_size是否合理
  • 使用权限检查机制:在服务端入口处调用checkCallingPermission(),确保调用方有相应权限
  • 避免在Binder回调中执行敏感操作:如果必须回调,先验证回调对象的身份

个人经验:我建议在服务端实现一个白名单机制。只允许特定UID的客户端调用特定接口。对于回调场景,服务端应该自己维护一个回调列表,而不是信任客户端传来的任意Binder对象。这样做虽然麻烦一点,但能从根本上杜绝服务端欺骗攻击。

18.6 本章小结

恶意客户端攻击的核心,就是利用Binder驱动只做传输、不做校验的特点,构造畸形的binder_transaction_data。攻击者可以伪造Binder对象、文件描述符,或者构造异常数据触发服务端崩溃。

服务端欺骗攻击尤其危险——它能让攻击者冒充其他客户端获取敏感数据或提升权限。防御的关键在于:服务端不能盲目信任数据内容,必须结合驱动层提供的PID/UID信息做二次校验。

嗯,这一章的内容就到这里。记住一句话:Binder驱动是管道,不是警察。数据到了服务端,怎么用、信不信,全看服务端自己的实现。

恶意客户端攻击流程 恶意客户端 Binder驱动 服务端 构造binder_transaction_data 伪造Binder对象/FD 设置异常数据大小 发送事务到驱动 接收事务数据 填充sender_pid/euid 拷贝数据到目标进程 唤醒服务端线程 收到onTransact() 解析Parcel数据 信任伪造的Binder对象 执行恶意回调 恶意回调获取敏感数据 图:恶意客户端通过Binder驱动向服务端发送伪造数据,触发服务端欺骗攻击
公众号:蓝海资料掘金营,微信deep3321