# HG changeset patch # User Michael Spacefalcon # Date 1368918493 0 # Node ID ae5337f881e311e85084d07935e962831507811f # Parent 9f3a7b014e6312a734eba895166f88acd7cee8cd MysteryFFS: beginning of the extract utility diff -r 9f3a7b014e63 -r ae5337f881e3 .hgignore --- a/.hgignore Sat May 18 21:09:33 2013 +0000 +++ b/.hgignore Sat May 18 23:08:13 2013 +0000 @@ -1,3 +1,4 @@ re:^mokosrec2bin$ re:^mysteryffs/dump[12]$ +re:^mysteryffs/extract$ re:^mysteryffs/scan1$ diff -r 9f3a7b014e63 -r ae5337f881e3 mysteryffs/Makefile --- a/mysteryffs/Makefile Sat May 18 21:09:33 2013 +0000 +++ b/mysteryffs/Makefile Sat May 18 23:08:13 2013 +0000 @@ -1,6 +1,6 @@ CC= gcc CFLAGS= -O2 -PROGS= dump1 dump2 scan1 +PROGS= dump1 dump2 extract scan1 all: ${PROGS} @@ -9,6 +9,7 @@ dump1: dump1.c dump2: dump2.c +extract: extract.c scan1: scan1.c clean: diff -r 9f3a7b014e63 -r ae5337f881e3 mysteryffs/extract.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mysteryffs/extract.c Sat May 18 23:08:13 2013 +0000 @@ -0,0 +1,282 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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); +} + +dump_dir(firstent, path_prefix) +{ + int ent; + struct objinfo obj; + int typechar; + u32 length; + + 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); + /* mkdir will go here */ + dump_dir(obj.descend, strlen(workpath)); + continue; + case 0xF1: + /* regular file */ + printf("file: %s\n", workpath); + /* file extraction will go here */ + 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); +}