A bootable-USB creator for Linux, with the Windows-install tweaks Rufus users miss. Written in Rust.
On Linux there is no obvious way to build a Windows 11 install stick that also does the Rufus things — get past the TPM / Secure Boot checks and skip the Microsoft-account requirement — without booting into Windows or hand-editing answer files. Ferrus fills that gap: it writes any ISO, and it turns a Windows 11 ISO into an install stick with those tweaks applied, from a CLI or a GUI. It is Linux-first (a clean-room successor to Rufus, not a port), designed cross-platform from day one.
- Safe target selection. Devices are eligible by transport (USB/MMC), not
the unreliable
removablesysfs bit; the system disk and critical mounts are filtered out and refused in code. Every destructive operation passes a single, typed checkpoint (SafeTarget), and--dry-runworks everywhere. - Raw ISO write. Byte-for-byte write of any already-bootable image
(
ferrus write), withO_EXCL,fsync, and optional read-back--verify. - Windows 11 install media. GPT with an NTFS main partition (Windows
install.wimexceeds FAT32's 4 GB limit) plus a FAT helper carrying the Secure Boot-signed UEFI:NTFS loader, so UEFI firmware can boot the NTFS partition. The ISO is copied verbatim. autounattend.xmltweaks (file drops, not binary patching), each verified against current sources and parameterized by the options you pick:- bypass the Windows 11 hardware checks (TPM / Secure Boot / RAM / storage / CPU);
- create a local account without a Microsoft account;
- minimize telemetry — reduced to the edition minimum (Required/Basic on Home/Pro; only Enterprise/Education honor "off") plus advertising ID / location / Find My Device / feedback off. This is minimized, not fully off on Home/Pro — see Security;
- disable automatic BitLocker device encryption;
- optional regional preset (language / locale / keyboard).
- Windows-vs-generic detection, unprivileged and without mounting: a read-only UDF/ISO9660 probe used as a hint (the mounted-ISO check remains the authority at write time).
- GUI, never root. The graphical front-end (iced) runs unprivileged; the real write goes through a type-to-confirm gate, then a minimal polkit-elevated helper that re-validates everything on the root side and streams live progress back.
Rendered with the CPU backend (ICED_BACKEND=tiny-skia).
Pick a device and an ISO, tick the tweaks. The Write button is danger-styled and stays disabled until you type the exact device path (type-to-confirm).
The GUI during a write: the live progress bar and the streamed log.
A dry run: the full plan, with nothing written.
Runtime tools Ferrus shells out to (install what your distro calls them):
sfdiskandpartprobe— partitioning + kernel re-read (util-linux / parted);mkfs.ntfs— format the NTFS partition (ntfs-3g / ntfsprogs);- an NTFS read-write driver — kernel
ntfs3(Linux ≥ 5.15) orntfs-3g(FUSE); mount/umount(util-linux);udevadmis used (best-effort) to settle device nodes;- polkit (
pkexec) — only for the GUI's privileged helper.
The GUI additionally needs a desktop session with a polkit agent and an
xdg-desktop-portal backend (for the native file picker). Building from source
needs a Rust toolchain with edition 2024 (Rust ≥ 1.85); building the GUI on
Linux needs libxkbcommon-dev and libgtk-3-dev (the list from iced's own CI).
A plain install target (not distro packaging) sets Ferrus up the way an end user runs it — the GUI unprivileged, elevating a small root-owned helper through named polkit actions:
sudo make install # release build + install
sudo make uninstall # remove everythingIt installs ferrus + ferrus-gui → /usr/bin/, the privileged
ferrus-helper → /usr/libexec/ (root-owned; this path is the exec.path of the
polkit actions, so they stay in lockstep), the polkit actions →
/usr/share/polkit-1/actions/, and two desktop entries → /usr/share/applications/:
Ferrus and Ferrus (software rendering) (the latter forces CPU rendering —
see Troubleshooting).
Once installed, ferrus-gui uses the installed helper and its named polkit
action; $FERRUS_HELPER is ignored, so a compromised environment cannot redirect
the GUI. For development, don't install and point the GUI at your build:
FERRUS_HELPER=target/debug/ferrus-helper cargo run -p ferrus-gui.
CLI (--dry-run on any command prints the plan and touches nothing):
# list plausible target devices (add --all to include large USB volumes)
ferrus list
# raw-write an already-bootable ISO, then read it back to verify
sudo ferrus write --image alpine.iso --target /dev/sdX --verify
# build a Windows 11 install stick with tweaks
sudo ferrus prepare-windows --target /dev/sdX --image Win11.iso \
--bypass-hardware \
--local-account me --local-password 'secret' \
--minimize-telemetry --disable-auto-bitlocker --region fr-FR \
--verifyGUI: launch ferrus-gui (or the Ferrus menu entry). Pick a device and an
ISO, tick the tweaks, type the exact device path to unlock the action, and
choose Simulate (dry-run) or Write.
- Modern Windows ISOs ship
install.wim(> 4 GB), which does not fit on FAT32, so the install partition is NTFS. UEFI can't boot NTFS by itself, so a tiny FAT helper partition carries UEFI:NTFS (pbatard) — a small signed EFI loader that chains into the NTFS partition. - The tweaks are file drops: an
autounattend.xmlat the media root (Windows Setup reads it automatically) plus LabConfig registry keys. No image is patched. Theautounattend.xmlgenerator, parameterized by the ticked options, is the core of the project. Values that drift per Windows build are isolated in a per-build profile and dated against their sources.
Ferrus is validated on real hardware, through Windows 11 25H2:
- safe enumeration + refusal exercised on a real host;
- a generic ISO (Alpine) written raw and booted;
- a real Windows 11 25H2 ISO written (copy byte-identical to the source, 8.5 GB,
install.wim> 4 GB on NTFS) and booted to Windows Setup via the signed UEFI:NTFS loader; - on a real 25H2 install: no TPM wall (hardware bypass), local account created without a Microsoft account, and — in a TPM 2.0 + Secure Boot VM — no automatic BitLocker;
- the GUI's write path, end to end: the destructive path the Write button
invokes — unprivileged GUI → type-to-confirm → the named polkit action → the
root helper re-validating the target → live NDJSON progress — was exercised via
the helper's
writeverb (byte-identical to what the button spawns). A real 8.5 GB write of a Windows 11 25H2 ISO completed and streamed live progress, and the produced stick booted Windows Setup. The type-to-confirm gate is covered by unit tests.
The engine ships with unit tests (including the refusal cases) and CI runs
fmt + clippy -D warnings + tests on every push.
Not done / limits:
-
The fully on-screen click-through has not been recorded yet: clicking Write in the window, seeing the named polkit dialog and the animated bar live. (The write path it drives is proven above; only the in-window capture is pending.)
-
Linux only. Windows and macOS are on the roadmap; the code is platform-abstracted (traits +
cfg) but only the Linux backend is implemented. -
UEFI/GPT only — no legacy BIOS boot.
-
No distro packaging yet (no
.deb/Flatpak);make installis the path. -
Per-user (HKCU) privacy toggles (tailored experiences, inking/typing) are deferred — they need the Default-user hive, not a specialize-pass reg-add.
- The GUI runs unprivileged. Only a small
ferrus-helperruns as root, via polkit; it re-validates every input on the root side (re-enumerates and re-runs theSafeTargetcheckpoint — it never trusts the device path the GUI proposes), reads its request on stdin (so a password never appears in argv or the environment), accepts exactly two subcommands, and hardcodes destructiveness per subcommand (dry_runis never request data). The whole workspace is#![forbid(unsafe_code)]. - The local-account password is written obfuscated, not encrypted. Windows'
autounattend.xmlstores it asbase64(UTF-16LE(password + "Password"))— trivially reversible. Anyone who has the stick can recover it. This is inherent to the Windows unattend mechanism, not a Ferrus choice; treat a stick that carries a password as carrying a recoverable secret. Ferrus never logs the plaintext and redacts it from all diagnostics, but it cannot make an inherently reversible on-disk format secret.
The GUI renders on the GPU (iced's wgpu) by default. On some GPUs/drivers wgpu
initializes fine but renders corrupted text — a driver/GPU issue Ferrus cannot
reliably auto-detect. Switch to the CPU renderer (tiny-skia, always compiled in):
ICED_BACKEND=tiny-skia ferrus-gui # or use the "Ferrus (software rendering)" entrySlower, but pixel-correct. ferrus-gui prints one startup line noting the active
backend and this workaround. (iced 0.14 has no programmatic backend switch, so it
is set via the environment variable.)
GPL-3.0-or-later. Ferrus is a spiritual successor to
Rufus — a clean-room rewrite in Rust, not a fork. It
vendors UEFI:NTFS by Pete Batard
(pbatard), also GPLv3; see res/uefi/NOTICE for the license and
source reference.
- Phase 6 — Windows port.
- Phase 7 — macOS port.
- Polish — per-user (HKCU) privacy toggles, distro packaging (
.deb/Flatpak), a dedicated icon.
rust · bootable-usb · usb · iso · windows · linux · polkit ·
iced · rufus


