题目要求在提供的 arm64 模拟器上用 CVE-2015-3636 漏洞实现提权。 关于 CVE-2015-3636 的分析网上有很多,可以参考 KeenTeam 的Own your Android! Yet Another Universal Root。
漏洞利用思路
1. 利用 ret2dir(physmap) 覆盖 socket 的 close 函数指针 2. 调用 close(sockfd) 取得 pc 3. ROP 修改 thread_info.addr_limit ,获得任意地址读写 4. 修改 thread_info.task.cred 和 security 提权
漏洞触发地址
直接运行 GitHub 上的 PoC 会发现崩溃地址不是 0x200200 ,而是 0x1360 (为什么还要加个 1 )。
这是一个低地址,一般 mmap 是 map 不到的,需要修改 mmap_min_addr 。一般需要 root 权限才能改,但这里其实 shell 用户也可以改。 修改方法: [Bash shell] 纯文本查看 复制代码 echo 4096 > /proc/sys/vm/mmap_min_addr
绕过 PXN 由于内核开启了 PXN ,不能从内核地址直接跳到用户地址,需要使用 ROP 绕过。关于 ROP 我也不太熟,找了很久才找到一段,像 JOP 多一点。
这一坨,有点长,地址是文件地址,要加上 0xffffffc000080000(用 RopGadget 时忘记加了,又不想跑多一次)
效果就是修改 addr_limit ,泄漏 sp 。修改之后就能在用户态随意读写内核地址了。然后修改 thread_info.task.cred 就能拿到 root 权限。但因为 SELinux 的存在,还是存在很多限制。
相当于 [C] 纯文本查看 复制代码 unsigned long thread_info_addr = sp & 0xFFFFFFFFFFFFC000;
unsigned long addr_limit_addr = thread_info_addr + 8;
*(void *)addr_limit_addr = 0xFFFFFFFFFFFFFFFF;
绕过 SELinux 虽然可以直接设置 security 的 sid 为 1 ,拿到 u:r:kernel:s0 权限,但这不是最高权限。u:r:init:s0 权限最高,但 sid 不固定,需要找到 init 进程获取,如何找 init 进程?找 init_task 。KeenTeam 的这篇 How to Root 10 Million Phones with One Exploit 讲的很详细。
搜索 init_task ,先从 /proc/iomem 读出 kernel data 地址,然后特征搜索。然后遍历 init_task.task ,找到 init 进程。
[C] 纯文本查看 复制代码 for (k = 0xffffffc0005f6000; k < 0xffffffc000687bcf; k += 8) {
read_pipe((void*)k, &init_info, sizeof init_info);
if ((init_info.stack & 0x1ff) == 0 && init_info.usage == 0x2 && init_info.flags == 0x200000) {
// printf("Init_info at %p\n", (void*) k);
init_task_addr = (void *)k;
break;
}
}
修改了sid后可以执行 system(id) ,但不能执行 system(sh) 。估计是跟 sepolicy 有关,感觉又是很麻烦的,就没继续弄了。
第 5 题
搞定这个后第 5 题就简单了,漏洞可以越界读写比 buffer 高的内核堆地址。因为时间关系,暴力解决了。就是fork一堆子进程,利用漏洞将找到所有 task_info 的 addr_limit 改掉,找到有权限的子进程继续提权。当然这种方法很不稳定。可以通过设置查找 comm 来精确定位。
后记
比赛之前刚好在看 KeenTeam 的那几篇文章,结果比赛题目就出这个,只能说运气比较好。其实我是想来做 CrackMe 的,结果调这个就调了两天。因为之前没写过漏洞exp,时间都花在看文档上,代码都是到处抠的,乱到自己都不想看 (*つ _ ⊂* ),就不贴了。
最后感谢吾爱和 360 举办这次比赛,感谢 KeenTeam 的分享。
|