FreeCalypso > hg > freecalypso-reveng
view mysteryffs/extract.c @ 389:623316d1ece7
compal/iMelody: capturing initial observations
author | Mychaela Falconia <falcon@freecalypso.org> |
---|---|
date | Thu, 31 Mar 2022 20:56:56 +0000 |
parents | d19b4e20ff9f |
children |
line wrap: on
line source
/* * This program is the logical culmination of the MysteryFFS reverse eng * experiments: it walks the FFS directory tree from the root down, * recreates a matching tree in the local Unix file system, and * extracts the full content of all data files. * * All acquired understanding of the MysteryFFS structure is tested * in the process. */ #include <sys/types.h> #include <sys/file.h> #include <sys/stat.h> #include <endian.h> #include <ctype.h> #include <stdio.h> #include <string.h> #include <strings.h> #include <stdlib.h> #include <unistd.h> typedef unsigned char u8; typedef unsigned short u16; typedef unsigned int u32; u8 mysteryffs_hdr[6] = {'F', 'f', 's', '#', 0x10, 0x02}; /* actual MysteryFFS on-media structure */ struct mysteryffs_index { u16 len; u8 unknown_b1; u8 type; u16 descend; u16 sibling; u32 dataptr; u16 unknown_w1; u16 unknown_w2; }; /* our own struct for convenience */ struct objinfo { u16 entryno; struct mysteryffs_index *idxrec; u8 *dataptr; u32 offset; u16 len; u8 type; u16 descend; u16 sibling; }; char *imgfile; u32 eraseblk_size; int total_blocks; u32 total_img_size; u8 *image, *indexblk; char workpath[512]; read_img_file() { int fd; struct stat st; fd = open(imgfile, O_RDONLY); if (fd < 0) { perror(imgfile); exit(1); } fstat(fd, &st); if (!S_ISREG(st.st_mode)) { fprintf(stderr, "%s is not a regular file\n", imgfile); exit(1); } if (st.st_size < total_img_size) { fprintf(stderr, "%s has fewer than 0x%x bytes\n", imgfile, total_img_size); exit(1); } image = malloc(total_img_size); if (!image) { perror("malloc"); exit(1); } read(fd, image, total_img_size); close(fd); } find_index_block() { int i; u8 *ptr; for (ptr = image, i = 0; i < total_blocks; i++, ptr += eraseblk_size) { if (bcmp(ptr, mysteryffs_hdr, 6)) continue; if (ptr[8] != 0xAB) continue; printf("Found index in erase block #%d (offset %x)\n", i, ptr - image); indexblk = ptr; return(0); } fprintf(stderr, "could not find a MysteryFFS index block in %s\n", imgfile); exit(1); } get_index_entry(oi) struct objinfo *oi; { struct mysteryffs_index *le; if (oi->entryno >= (eraseblk_size >> 4)) { fprintf(stderr, "error: index block pointer %x past the erase block size!\n", oi->entryno); exit(1); } le = (struct mysteryffs_index *) indexblk + oi->entryno; oi->idxrec = le; oi->len = le16toh(le->len); oi->type = le->type; oi->descend = le16toh(le->descend); oi->sibling = le16toh(le->sibling); return(0); } validate_chunk(oi) struct objinfo *oi; { u32 dptr; if (oi->len & 0xF || !oi->len) { fprintf(stderr, "index entry #%x: invalid chunk length\n", oi->entryno); exit(1); } dptr = le32toh(oi->idxrec->dataptr); if (dptr > 0x0FFFFFFF) { invdptr: fprintf(stderr, "index entry #%x: invalid data pointer\n", oi->entryno); exit(1); } dptr <<= 4; if (dptr >= total_img_size - oi->len) goto invdptr; oi->offset = dptr; oi->dataptr = image + dptr; return(0); } validate_obj_name(oi) struct objinfo *oi; { u8 *cp; int cnt; for (cp = oi->dataptr, cnt = 0; ; cp++, cnt++) { if (cnt >= oi->len) { fprintf(stderr, "object at index %x: name expected at %x: length overrun\n", oi->entryno, oi->offset); exit(1); } if (!*cp) break; if (*cp < '!' || *cp > '~') { fprintf(stderr, "object at index %x: name expected at %x: bad character\n", oi->entryno, oi->offset); exit(1); } } if (!cnt) { fprintf(stderr, "object at index %x: name expected at %x: null string\n", oi->entryno, oi->offset); exit(1); } return(0); } name_safe_for_extract(oi) struct objinfo *oi; { char *s; s = (char *)oi->dataptr; if (!isalnum(*s) && *s != '_') return(0); for (s++; *s; s++) if (!isalnum(*s) && *s != '_' && *s != '.') return(0); return(1); } u8 * find_end_of_chunk(ch) struct objinfo *ch; { u8 *p; int i; p = ch->dataptr + ch->len; for (i = 1; i <= 16; i++) { if (!p[-i]) return(p - i); if (p[-1] != 0xFF) break; } fprintf(stderr, "chunk starting at %x (index entry %x): no valid termination found\n", ch->offset, ch->entryno); exit(1); } void dump_head_chunk(fd, ch) struct objinfo *ch; { u8 *endname, *endchunk; endname = (u8 *) index((char *)ch->dataptr, '\0') + 1; endchunk = find_end_of_chunk(ch); if (endchunk <= endname) return; write(fd, endname, endchunk - endname); } void dump_extra_chunk(fd, ch) struct objinfo *ch; { u8 *endchunk; endchunk = find_end_of_chunk(ch); write(fd, ch->dataptr, endchunk - ch->dataptr); } extract_file(head) struct objinfo *head; { int fd; int ent; struct objinfo ch; fd = open(workpath + 1, O_WRONLY|O_CREAT|O_TRUNC, 0666); if (fd < 0) { perror(workpath + 1); exit(1); } dump_head_chunk(fd, head); for (ent = head->descend; ent != 0xFFFF; ent = ch.descend) { ch.entryno = ent; get_index_entry(&ch); if (ch.type != 0xF4) { fprintf(stderr, "file continuation object at index %x: type %02X != expected F4\n", ent, ch.type); exit(1); } validate_chunk(&ch); dump_extra_chunk(fd, &ch); if (ch.sibling != 0xFFFF) printf("warning: file continuation object (index %x) has a non-nil sibling pointer\n", ent); } close(fd); } dump_dir(firstent, path_prefix) { int ent; struct objinfo obj; for (ent = firstent; ent != 0xFFFF; ent = obj.sibling) { obj.entryno = ent; get_index_entry(&obj); if (!obj.type) /* skip deleted objects w/o further validation */ continue; validate_chunk(&obj); validate_obj_name(&obj); if (path_prefix + strlen(obj.dataptr) + 2 > sizeof workpath) { fprintf(stderr, "handling object at index %x, name \"%s\": path buffer overflow\n", obj.entryno, (char *)obj.dataptr); exit(1); } sprintf(workpath + path_prefix, "/%s", (char *)obj.dataptr); switch (obj.type) { case 0xF2: /* directory */ printf("dir: %s\n", workpath); if (!name_safe_for_extract(&obj)) { printf("name contains unsafe characters; subtree skipped\n"); continue; } if (mkdir(workpath + 1, 0777) < 0) { perror(workpath + 1); exit(1); } dump_dir(obj.descend, strlen(workpath)); continue; case 0xF1: /* regular file */ printf("file: %s\n", workpath); if (!name_safe_for_extract(&obj)) { printf("name contains unsafe characters; file skipped\n"); continue; } extract_file(&obj); continue; case 0xE1: /* special .journal file */ if (path_prefix == 0 && !strcmp((char *)obj.dataptr, ".journal")) printf("skipping /.journal\n"); else printf("skipping unexpected E1 file: %s\n", workpath); continue; default: printf("%s (index entry #%x): unexpected type %02X; skipping\n", workpath, obj.entryno, obj.type); continue; } } } dump_root() { struct objinfo root; root.entryno = 1; get_index_entry(&root); validate_chunk(&root); validate_obj_name(&root); printf("Root node name: %s\n", (char *)root.dataptr); if (root.type != 0xF2) { fprintf(stderr, "error: index entry #1 (expected root dir) is not a directory\n"); exit(1); } if (root.sibling != 0xFFFF) printf("warning: root entry has a non-nil sibling pointer\n"); dump_dir(root.descend, 0); } main(argc, argv) char **argv; { if (argc != 4) { fprintf(stderr, "usage: %s imgfile blksize nblocks\n", argv[0]); exit(1); } imgfile = argv[1]; eraseblk_size = strtoul(argv[2], 0, 0); total_blocks = strtoul(argv[3], 0, 0); total_img_size = eraseblk_size * total_blocks; read_img_file(); find_index_block(); dump_root(); exit(0); }