Skip to content
bhadri01Public

About

Strictly read-only deleted-file recovery and filesystem forensics in Rust: hand-written FAT/exFAT/NTFS/ext/APFS parsers, signature carving, ratatui TUI.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

Β 

History

19 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

NullVeil

A strictly read-only, cross-platform deleted-file recovery and filesystem-analysis tool β€” built entirely from scratch in Rust.

πŸ“– Full documentation site β†’ Β Β (source)

NullVeil reads disk images and live block devices and recovers data via two independent strategies:

  1. Signature carving (carve) β€” filesystem-agnostic recovery of files from their on-disk byte signatures. Works on any source, any filesystem, any OS. The right tool when metadata is gone.
  2. Per-filesystem undelete (ls, recover) β€” reads the actual filesystem and recovers files by name and path. Cleanest where filesystem semantics allow it (FAT, NTFS); best-effort where they don't (ext, APFS).

Plus an interactive TUI (tui) with multi-select basket, search, and hex-and-ASCII preview β€” and a mobile-recovery on-ramp (unwrap-ab) that unpacks Android Backup files for triage.

Built as a learning project per a "from-scratch / the learning is the point" spec: every filesystem parser, every partition table parser, every byte-level structure is hand-rolled. No fatfs, ntfs, gpt, or similar crates.


Quick links

If you want to… Go here
Get running in 5 minutes Quick start
Install on macOS / Linux / Windows docs/src/install/
Recover from a USB stick / external drive / SD card External-storage tutorials
Recover from Android Android tutorial
Recover from iPhone iOS tutorial
Look up a CLI flag CLI reference
Understand the read-only guarantee Read-only by construction

What it does

Command Purpose Status
info <source> Show partition table + detected filesystems All platforms, all FS
list-devices Enumerate live disks attached to this OS macOS, Linux
carve <source> --out <dir> Universal signature carving All platforms
ls <source> List directory entries FAT, NTFS, ext, APFS (volume-list)
ls --deleted <source> List deleted entries FAT, NTFS, ext (rec_len gaps)
recover <source> --out <dir> Per-filesystem undelete FAT (byte-exact), NTFS (byte-exact), ext (best-effort)
tui <source> Interactive terminal-UI browser All platforms
unwrap-ab <backup.ab> Unpack an Android Backup (mobile workflow) All platforms

Supported sources

  • Disk image files (.dd, .img, .raw) β€” every platform
  • Live raw devices:
    • macOS β€” /dev/rdiskN via DKIOCGETBLOCKSIZE/DKIOCGETBLOCKCOUNT
    • Linux β€” /dev/sdX, /dev/nvme* via BLKSSZGET/BLKGETSIZE64 (code written, untested locally)
    • Windows β€” recipe documented; implementation deferred (see src/device/raw_windows.rs)

Supported filesystems

Filesystem Detection List Undelete Notes
FAT12 / FAT16 / FAT32 βœ“ βœ“ βœ“ byte-exact (contiguous) Long filename (LFN) support; FAT32 + FAT12/16 root region
exFAT βœ“ βœ“ root dir βœ“ byte-exact (root, contiguous) UTF-16LE names, NoFatChain support; sub-directory recursion is a follow-up
NTFS βœ“ βœ“ βœ“ byte-exact MFT walker, USA fixup, resident + non-resident data, data-run decoding
ext2 / ext3 / ext4 βœ“ βœ“ best-effort Extents + legacy block pointers; deleted-name resurrection via rec_len gaps
APFS βœ“ βœ“ (volume list) β€” Container superblock + omap B+-tree walking; per-volume FS-tree deferred

For deleted data on ext4 and APFS, where metadata is typically zeroed at delete time, the principled fallback is carve (M1) β€” it doesn't care about the filesystem and works on whatever bytes still exist on the volume.


Architecture

 CLI (clap)
 ─────────────────────────────────────────────────────────────
 Recovery orchestration Β· Export (out-dir only) Β· Audit log
 ───────────────────────────────┬─────────────────────────────
 Carving engine                 β”‚ Per-filesystem
 (FS-agnostic, universal)       β”‚ FAT Β· NTFS Β· ext Β· APFS
 ───────────────────────────────┴─────────────────────────────
 Filesystem trait + detection
 ─────────────────────────────────────────────────────────────
 Partition layer  (MBR / GPT / superfloppy)
 ─────────────────────────────────────────────────────────────
 BlockReader trait (READ-ONLY β€” no write method on the trait)
   backends:  ImageFile  β”‚  macOS dev  β”‚  Linux dev  β”‚  Windows dev

Read-only safety, structurally

The BlockReader trait deliberately has no write method. No backend can be asked to mutate a source through it. Defense-in-depth: backends also open OS handles read-only (O_RDONLY / GENERIC_READ). Exporter::open refuses output directories that:

  • equal the source path,
  • contain the source,
  • live inside the source (when source is a directory), or
  • (on Unix) live on a filesystem whose backing block device matches the source.

Every demo in this README ends by verifying shasum of the source is unchanged after the run.


Quickstart

Build from source

git clone <repo>     # once you create one
cd NullVeil
cargo build --release
./target/release/nullveil --help

Requires Rust 1.95+ stable, edition 2024. Only direct dependencies: clap, anyhow, thiserror, serde + serde_json, sha2, and (Unix only) libc for raw-device ioctls.

info β€” what's on this source?

$ nullveil info evidence.img
Use only on devices and systems you own or are authorized to analyze.
Source : evidence.img
Size   : 64.0 MiB (67108864 bytes)
Sector : 512 bytes
Scheme : GPT

  #1  LBA 40..131032  64.0 MiB  [GPT 7C3457EF-0000-11AA-AA11-00306543ECAC]
        filesystem: APFS, 4096-byte blocks, 64.0 MiB volume

list-devices β€” what's attached?

$ nullveil list-devices
  PATH                     KIND                   SIZE  RAW PATH
  /dev/disk0               disk                      ?  /dev/rdisk0
  /dev/disk0s1             partition                 ?  /dev/rdisk0s1
  ...
  /dev/disk4               disk               32.0 MiB  /dev/rdisk4

System disks show ? for size when they're not user-readable (they require root); a freshly attached USB / loopback shows its real size.

carve β€” recover files by their signatures

$ nullveil --log audit.jsonl carve evidence.img --out recovered --types jpg,png,pdf --verify
  scanned 64.0 MiB / 200.0 MiB (32.0%), 5 carved, 325.3 KiB recovered
  scanned 128.0 MiB / 200.0 MiB (64.0%), 11 carved, 520.3 KiB recovered
  scanned 192.0 MiB / 200.0 MiB (96.0%), 16 carved, 846.1 KiB recovered
  scanned 200.0 MiB / 200.0 MiB (100.0%), 16 carved, 846.1 KiB recovered
Carved : 16 file(s)
Bytes  : 846.1 KiB
Dedupe : 2 duplicate(s) skipped
Out    : recovered/
Manifest: recovered/manifest.json

Carving recognises JPEG, PNG, GIF, BMP, PDF, ZIP (incl. .docx/.xlsx/.jar), and MP4 (incl. .mov/.m4a). BMP and JPEG signatures include post-header validators that drop ~99% of random-data false positives.

--verify re-reads every written file and confirms its SHA-256 matches the streaming hash. --types filters to specific extensions. --keep-duplicates disables the default SHA-256 dedupe. The audit log is JSON-lines with carve_start / carved / carve_end events.

tui β€” interactive terminal browser

Launch the interactive forensic browser:

nullveil tui evidence.img
# or, for a single-file recover shortcut on `r`:
nullveil tui evidence.img --out /tmp/recovered

Built on ratatui. Two-pane layout:

  • Source view (default): partition list + selected-partition details
  • Browser view (after Enter): directory listing with a live/deleted toggle

Keybindings:

Key Action
↑↓ / j k move cursor
Enter / m mount partition (Source view)
Enter descend into directory (Browser view)
⌫ / Left / h ascend to parent directory
Tab toggle Live ↔ Deleted
r recover highlighted file to --out dir
Esc back to Source view (from Browser)
? help overlay
q quit

The TUI inherits all the read-only safety guarantees from the engine β€” it never holds a writable handle to a source. The r action streams the file through the same Exporter+SHA-256-manifest pipeline as the CLI's recover command, so a TUI recovery is forensically equivalent.

ls and recover β€” name-based recovery

$ nullveil ls /tmp/fat32.img
  KIND           SIZE  NAME                                      PATH
  file         512 KiB  IMAGE.JPG                                 /IMAGE.JPG
  dir             0 B  DOCS                                      /DOCS

$ nullveil ls --deleted /tmp/fat32.img
  file         128 KiB  ?DOC.PDF                                  /?DOC.PDF [deleted]

$ nullveil recover /tmp/fat32.img --out /tmp/out --include-deleted
Recovered: 2 file(s)
Bytes    : 640.0 KiB
Out      : /tmp/out
Manifest : /tmp/out/manifest.json

The leading ? is the byte we lost to FAT's 0xE5 delete marker β€” the original glyph is gone. recover --include-deleted then performs contiguous-allocation undelete (byte-exact for the common case of small, non-fragmented files).


Operational safety notes

  • Always recover to a different device than the source. NullVeil refuses via ExportError::SameAsSource, but the responsibility for picking a sane --out is yours.
  • Live raw devices typically require root (sudo nullveil …) or full-disk-access on macOS.
  • --log writes a JSON-lines audit trail with ts_ms, source identity, byte offsets read, per-file SHA-256 β€” keep this for chain-of-custody.
  • Forensic cardinal rule: treat the source as a witness. Don't write to it; clone first if possible (dd or dc3dd). NullVeil never opens with write access; the BlockReader trait has no write method, period.

Limitations and deferred work

  • exFAT β€” detected, but full read is a separate filesystem from FAT (allocation bitmap, upcase table, fragmented allocation) and is left as a follow-up.
  • NTFS β€” list_dir walks the MFT by parent_ref linear-scan; B+-tree index walking (the proper approach) is a follow-up.
  • ext β€” deleted-file data recovery is fundamentally best-effort because the inode's block pointers are typically zeroed at delete time. Names survive in directory rec_len gaps; data needs the carver.
  • APFS β€” per-volume directory walking (FS-tree B+-tree with variable-length keys and 13+ record types, multi-version COW, optional encryption) is a whole milestone on its own; nullveil carve is the practical recovery path today.
  • Windows live devices β€” recipe documented in source; needs a Windows host + the windows-sys crate to wire up.
  • Fragmented FAT undelete β€” currently contiguous-allocation only.

Project layout

src/
  lib.rs                 # Public engine API
  main.rs                # clap CLI
  audit/                 # JSON-lines audit log
  carve/                 # Universal carver + signature registry
    mod.rs, signature.rs
  device/                # BlockReader + backends
    mod.rs, image.rs, raw_macos.rs, raw_linux.rs, raw_windows.rs, enumerate.rs
  export/                # Streaming writer + manifest + dedupe + verify
    mod.rs, manifest.rs
  fs/                    # Filesystem detection + per-FS impls
    mod.rs
    fat/  ntfs/  ext/  apfs/    # Each: detect + Filesystem trait impl
  partition/             # MBR + GPT + superfloppy + CRC32
    mod.rs, mbr.rs, gpt.rs, crc32.rs

tests/                   # 27 integration tests across 8 e2e binaries
  info_e2e.rs            carve_e2e.rs
  m3_device_e2e.rs       m4_fat_e2e.rs   m4_demo.rs
  m5_ntfs_e2e.rs         m6_ext_e2e.rs   m7_apfs_e2e.rs

tools/                   # Sample-image generator
  gen-samples/

Project history at a glance

See CHANGELOG.md for the per-milestone log. The eight milestones, in order:

  • M0 β€” BlockReader trait, ImageReader, MBR/GPT, filesystem detection, info
  • M1 β€” universal signature carving + SHA-256 manifest + audit log
  • M2 β€” carving polish: progress, dedupe, --verify, --quiet
  • M3 β€” live raw devices (macOS), list-devices, --device flag, same-device write guard
  • M4 β€” FAT12/16/32 undelete with LFN reassembly + ls/recover CLI
  • M5 β€” NTFS undelete with MFT walker, USA fixup, attribute decoder, data runs
  • M6 β€” ext2/3/4 read + best-effort deleted-name surfacing via rec_len gaps
  • M7 β€” APFS container parser + object map B+-tree + volume superblock reader

116 tests across 89 unit + 27 e2e binaries, ~7,000 lines of Rust.


License

Dual-licensed under either of

at your option.

About

Strictly read-only deleted-file recovery and filesystem forensics in Rust: hand-written FAT/exFAT/NTFS/ext/APFS parsers, signature carving, ratatui TUI.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages