15、Binder驱动中的权限检查绕过:binder_ioctl权限校验逻辑、CAP_SYS_ADMIN滥用、命名空间逃逸
Binder驱动,说白了就是Android系统的“血管”。所有跨进程通信都得走它。但血管一旦有漏洞,攻击者就能顺着它流遍全身。今天聊的这几个点——binder_ioctl权限校验、CAP_SYS_ADMIN滥用、命名空间逃逸——是我在实际漏洞挖掘和内核开发中反复踩过的坑。
15.1 binder_ioctl的权限校验逻辑:看似严密,实则暗藏玄机
先看binder_ioctl。这是Binder驱动的入口函数,用户态通过它发起各种操作。内核里长这样:
static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
struct binder_proc *proc = filp->private_data;
// ... 各种校验
switch (cmd) {
case BINDER_WRITE_READ:
// 读写操作
break;
case BINDER_SET_CONTEXT_MGR:
// 设置上下文管理器
break;
// ...
}
}
这里有个关键点:权限校验是在进入switch之前做的吗? 不是。很多早期内核版本里,校验是分散在各个case里的。这就给了攻击者机会。
核心问题:某些ioctl命令(比如BINDER_SET_CONTEXT_MGR)只检查了调用者的uid是否为0,却没检查它是否真的拥有CAP_SYS_ADMIN能力。换句话说,只要你是root用户(uid=0),就能执行这个操作。但root用户和拥有CAP_SYS_ADMIN是两码事。
我记得有一次做内核审计,发现某个厂商的定制内核里,binder_ioctl甚至没做任何权限检查。任何进程都能调用BINDER_SET_CONTEXT_MGR。你想想看,这意味着什么?一个普通App就能把自己伪装成ServiceManager,拦截所有系统服务。
15.2 CAP_SYS_ADMIN滥用:万能钥匙还是潘多拉魔盒?
CAP_SYS_ADMIN在Linux内核里是个“万能能力”。拥有它,你几乎能干任何事。但在Binder场景下,它被滥用了。
举个例子:BINDER_GET_NODE_INFO_FOR_REF这个ioctl命令,用来获取Binder节点的信息。早期实现里,它只检查调用者是否有CAP_SYS_ADMIN。但问题是,很多系统服务(比如system_server)默认就拥有这个能力。攻击者只要控制了这些服务,就能随意调用这个命令。
避坑指南:我曾经在分析一个内核漏洞时发现,攻击者通过CAP_SYS_ADMIN绕过了所有Binder节点访问控制。修复方案很简单:不要依赖单一能力,要结合selinux上下文和uid做多重校验。但很多厂商为了省事,直接放行了。
为什么会这样?说白了,CAP_SYS_ADMIN的设计初衷是给系统管理员用的。但在Android的沙箱模型里,每个App都是隔离的。你给一个系统服务这个能力,就等于给了它一把万能钥匙。一旦这把钥匙被偷,整个系统就完了。
15.3 命名空间逃逸:突破Binder的“墙”
命名空间(Namespace)是Linux内核用来隔离进程的机制。每个Binder节点都属于某个命名空间。正常情况下,进程只能访问自己命名空间内的Binder节点。
但漏洞就出在命名空间边界检查不严格上。比如binder_transaction函数在处理跨命名空间的事务时,没有正确验证目标节点是否属于当前命名空间。
static void binder_transaction(struct binder_proc *proc,
struct binder_thread *thread,
struct binder_transaction_data *tr)
{
struct binder_node *target_node = NULL;
// 获取目标节点
target_node = binder_get_node_ref(proc, tr->target.handle);
// 这里缺少命名空间检查!
// 攻击者可以传入一个属于其他命名空间的handle
// 导致跨命名空间访问
}
我遇到过这样一个案例:一个App通过Binder向system_server发送事务,但它在事务中嵌入了一个指向其他命名空间的Binder引用。由于内核没做检查,这个引用被成功解析,导致App能访问到本不该看到的系统服务。
个人经验:修复这类漏洞时,我习惯在binder_get_node_ref函数里加一个参数,传入当前进程的命名空间ID。然后在解析节点时,强制检查节点所属命名空间是否匹配。虽然性能有轻微损耗,但安全第一。
15.4 知识体系与核心逻辑
下面这张图总结了Binder驱动权限检查绕过的三个核心维度:
15.5 实战中的避坑指南
讲几个我实际踩过的坑,希望能帮你少走弯路:
- 别信“root用户就安全”:很多开发者觉得只要进程是root,权限就够用了。但Binder的权限模型比这复杂得多。root用户只是uid=0,不代表它拥有所有能力。攻击者可以通过
capset系统调用精确控制能力集。 - 命名空间检查要前置:我习惯在
binder_ioctl入口处就做命名空间检查,而不是等到具体命令处理时。这样能避免遗漏。曾经有个漏洞就是因为检查放在了switch的某个case里,其他case全没检查。 - 日志审计不能省:在
binder_ioctl里加日志,记录每次权限检查的结果。我遇到过生产环境里,日志被关掉导致漏洞潜伏了半年才被发现。嗯,这很要命。
总结一下:Binder驱动的权限检查绕过,核心问题就三个:校验逻辑不完整、能力滥用、边界检查缺失。修复思路也很明确:多重校验、最小权限原则、前置检查。别偷懒,安全没有捷径。