macOS内核提权:利用CVE-2016-1758获取kernel slide(Part1)
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内核代码
xnu-2782.40.9
找到/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 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的调用参数
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内核代码
xnu-2782.40.9
找到/bsd/net/if.c里的if_clone_list方法 www.wnhack.com
/*
* 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 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 */ copyright 无奈人生
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的调用参数