PAYATHON 2026

如何从 userspace 获取 Android Binder 的 handle?

技术老齐

可以在 userspace 中获取 handle:通过 ServiceManager 查询服务,或在一次 Binder 调用中接收目标 Binder 对象,然后读取驱动交付的 flat_binder_object.handle,填入 tr.target.handle。

handle 属于调用方的 Binder 引用表。同一个 binder_node 在不同客户端中可能对应不同的 handle,因此无法根据目标进程的 PID、节点地址或调试编号直接算出这个值。

handle 与 binder_node 的关系

发送普通 transaction 时,驱动按以下关系查找目标:

调用方 binder_proc
    → 根据 target.handle 查找 binder_ref
    → binder_ref 指向 binder_node
    → binder_node 所属的目标 binder_proc

目标进程拥有 binder_node,调用方持有指向该节点的引用,handle 就是调用方引用表中的描述符。驱动收到 BC_TRANSACTION 后,会在发送方的引用表中查找这个 handle。对应的驱动实现

这些对象和引用的创建顺序是:

  • 打开 Binder 设备时,驱动创建对应的 binder_proc。
  • 通过 transaction 将本地 Binder 对象传给其他进程时,驱动按需创建 binder_node。向 ServiceManager 注册服务就是其中一种方式。
  • 客户端收到这个对象后,驱动为客户端建立引用,并交付客户端可用的 handle。

因此,addService() 不会返回一个所有进程通用的 handle;客户端也不需要先注册自己的服务才能发送 transaction。

通过 ServiceManager 获取目标引用

如果目标已经注册为命名服务,直接使用 ioctl 的客户端需要完成以下步骤:

  1. 打开目标服务所在 Binder context 的设备,并通过 mmap 映射接收缓冲区。
  2. 向 target.handle = 0 发送服务查询请求。0 是该 context 的 context manager 的特殊入口,Android 中通常由对应的 ServiceManager 担任 context manager。
  3. 按目标 Android 版本的协议编码服务名、interface token 等数据,并使用对应查询方法的 transaction code。
  4. 通过 BINDER_WRITE_READ 读取返回命令,等待 BR_REPLY,检查查询是否成功。
  5. 按回复的 Parcel 布局找到服务对象,取得类型为 BINDER_TYPE_HANDLE 的 flat_binder_object,读取其 handle。

ServiceManager 的查询方法和回复布局需要与设备源码一致。不同实现可能直接返回 IBinder,也可能把服务对象包装在包含元数据的结构中。因此,不能直接照搬其他版本的 transaction code,也不能假定回复的前四个字节就是 handle。ServiceManager 接口定义

如果可以使用平台 C++ libbinder,可以让它处理服务查询:

#include <binder/IBinder.h>
#include <binder/IServiceManager.h>
#include <utils/String16.h>

android::sp<android::IBinder> service =
    android::defaultServiceManager()->checkService(
        android::String16("your.service.name"));

if (service == nullptr) {
    // 查询未取得服务,检查注册状态、名称和访问权限。
}

这类平台接口不等同于普通应用可直接使用的 NDK API。如果只是调用服务,可以继续使用 IBinder::transact();如果必须自行调用 ioctl,获取引用和发送 transaction 就必须使用同一个 Binder 连接。C++ ServiceManager 接口

从原始回复中读取 handle

Binder 对象跨进程传递时,驱动会转换它的表示。例如,服务端发送的 BINDER_TYPE_BINDER,到远端客户端时可以变成 BINDER_TYPE_HANDLE,其中的 handle 属于接收方。对象转换实现

下面的函数从已收到的 transaction 中读取一个远端对象的 handle。使用前,需要按回复协议确认目标服务对象的字节偏移 object_offset,并确保接收缓冲区尚未释放。

#include <linux/android/binder.h>
#include <stdbool.h>
#include <stdint.h>
#include <string.h>

static bool read_remote_handle(
        const struct binder_transaction_data *reply,
        binder_size_t object_offset,
        uint32_t *handle)
{
    if (reply->offsets_size % sizeof(binder_size_t) != 0)
        return false;

    if (object_offset > reply->data_size ||
        reply->data_size - object_offset <
            sizeof(struct flat_binder_object))
        return false;

    const unsigned char *offsets =
        (const unsigned char *)(uintptr_t)reply->data.ptr.offsets;
    bool listed = false;

    for (binder_size_t i = 0; i < reply->offsets_size;
         i += sizeof(binder_size_t)) {
        binder_size_t offset;
        memcpy(&offset, offsets + i, sizeof(offset));
        if (offset == object_offset) {
            listed = true;
            break;
        }
    }

    if (!listed)
        return false;

    struct flat_binder_object object;
    const unsigned char *data =
        (const unsigned char *)(uintptr_t)reply->data.ptr.buffer;
    memcpy(&object, data + object_offset, sizeof(object));

    if (object.hdr.type != BINDER_TYPE_HANDLE)
        return false;

    *handle = object.handle;
    return true;
}

data.ptr.offsets 指向 Binder 对象偏移数组,每个元素的类型是 binder_size_t。offsets_size 是数组占用的字节数,每个数组元素则是对象相对于数据缓冲区起点的字节偏移。Binder UAPI 定义

取得 handle 并正确保留引用后,就可以设置这些字段:

struct binder_transaction_data tr = {0};

tr.target.handle = handle;
tr.code = method_code;  // 使用目标服务协议规定的 transaction code。
tr.data_size = buffer_size;
tr.offsets_size = offsets_size;
tr.data.ptr.buffer = (binder_uintptr_t)(uintptr_t)data_buffer;
tr.data.ptr.offsets = (binder_uintptr_t)(uintptr_t)off_buffer;

这个函数只负责读取描述符,没有实现 ServiceManager 请求编码、BR_* 命令解析和引用生命周期管理,不能单独构成完整客户端。

原始 ioctl 调用需要注意什么

获取引用和发送 transaction 必须使用同一个 Binder 连接。通过 libbinder 获取 handle 后,如果再独立 open("/dev/binder", ...) 得到另一个 fd,就会创建另一份 binder_proc 状态。即使仍在同一个 Linux 进程中,也不能直接复用前一个连接的 handle;不同 Binder context 之间同样不能复用。

复制 handle 数值不会保留引用。如果要在释放接收缓冲区后继续使用它,需要先按 Binder 引用协议取得强引用,例如发送并处理 BC_ACQUIRE,使用结束后再通过 BC_RELEASE 释放。完整实现还需要处理相关引用通知和 BC_FREE_BUFFER。使用 libbinder 时,通常由库维护这些状态。引用及缓冲区命令定义

原代码中的两处设置也需要结合协议检查:

  • tr.code = 0 没有通用的“调用服务”含义。应使用目标服务定义的方法编号,并按其 Parcel 格式编码参数。
  • bwr.read_size = 0 只提交写入。ioctl() 返回成功后,仍需读取并处理 BR_REPLY、BR_DEAD_REPLY、BR_FAILED_REPLY 等结果;BR_TRANSACTION_COMPLETE 也不代表远端方法已经执行成功。

服务端还必须运行 Binder 读取循环来处理 transaction,注册和访问服务也需要满足设备上的权限策略。有效 handle 只能确定路由目标,不能替代服务协议或访问权限。

备注:内容仅供参考。