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);
}