If you found this because your ESXi host is showing a ransom banner, every VM is
powered off, and your virtual disks now end in .babyk: the situation is much
better than it looks. Take five minutes to read this before you do anything
irreversible.
Two things are true at once.
You will not decrypt these files. The crypto is Curve25519 ECDH with a
per-file ephemeral key from /dev/urandom and Sosemanuk for the bulk. It is
implemented correctly. There is no flaw to attack and no key to find. Anyone who
tells you otherwise is selling something.
You have probably lost almost nothing. The encryptor stops a few hundred megabytes into each file and moves on. In the first incident documented here, 6,911.95 GiB of "encrypted" virtual disks contained 6,894.93 GiB of perfectly readable plaintext — 99.754% of the bytes were never touched. Most Linux guests came back in under ten minutes each.
Since then the same procedure has recovered six hosts and around 150 damaged disks: a fleet of thirty guests brought back in a single pass, a 6.7 TB media volume whose root directory had been destroyed, and — as of August 2026 — Windows Server guests running SQL Server, with zero data loss.
The rest of this is how.
What this covers: ESXi ransomware recovery and VMware ransomware recovery
end to end, from .babyk VMDK recovery on VMFS datastores through GPT partition
table rebuild, then either LVM and ext4 recovery inside a Linux guest or
NTFS recovery inside a Windows one, plus the ransomware incident response steps
around all of it (evidence, IOCs, cleaning the host). Babuk ransomware and its
ESXi descendants specifically, though the damage model applies to any locker
that stops early in a file.
Run this with an AI agent
This repository ships an agent skill. Point Claude Code — or any coding agent — at it and you get the whole procedure: triage, the damage model, the tooling, and a symptom-to-fix table for the failures that look like total data loss and are not.
That last part is why it is worth doing. Several failure modes in this work look identical to "your data is gone" while being one command away from readable, and an agent that has read the table will not tell you to give up.
Claude Code
git clone https://github.com/Ki4nb/VSphereRansomwareRecovery.git
cd VSphereRansomwareRecovery
claude
The skill in .claude/skills/ is picked up automatically while you work inside
the repository. To make it available everywhere instead:
mkdir -p ~/.claude/skills
cp -r VSphereRansomwareRecovery/.claude/skills/esxi-ransomware-recovery ~/.claude/skills/
Then just describe the situation — "our ESXi host got hit, the vmdks are all .babyk now" — and it will load.
Any other agent
Codex, Cursor, Copilot, Gemini, or your own harness: clone the repository and
point the agent at AGENTS.md. Many read it automatically.
Everything is plain Markdown and plain POSIX shell — there is no runtime, no
plugin and no dependency to install.
You are helping with an ESXi ransomware recovery.
Read AGENTS.md in this repository first, then
.claude/skills/esxi-ransomware-recovery/SKILL.md.
What to expect
It will ask for read-only output before suggesting anything that writes, tell
you which commands modify what, and refuse to declare data lost before the
checks that routinely disprove it. Everything with a --commit flag is dry-run
by default.
An agent is a good pair of hands here and a bad decision-maker. It will move faster than you can check it. Keep damaged disks attached Independent — non-persistent until you have seen a dry run you believe.
Measure the boundary. Do not assume it.
Whoever built this made a speed choice. On a hypervisor you don't need to encrypt a 2 TB disk to take it hostage. You only need to destroy the front of it, where the partition table and the bootloader live, and then the VM won't boot and the disk looks like garbage. So the encryptor writes a fixed number of bytes, appends a 32-byte key, and moves to the next file.
Here is a 100 GiB disk after it ran:
[ ~520 MiB destroyed ][ -------------- 99.5 GiB, untouched -------------- ]
partition table /boot, LVM, NTFS $MFT, databases, everything
EFI bootloader
How many bytes is not a constant. This actor has shipped at least two builds
with a byte-identical run.sh, the same ransom note, the same contacts and the
same master public key, differing only here:
| Build | Bytes destroyed per file | |
|---|---|---|
| A | 0x20000000 |
512 MiB |
| B | 0x20800000 |
520 MiB |
Eight megabytes sounds like nothing. It is the difference between correctly reporting a database file as damaged and confidently reporting it as intact — an error in the only direction that costs you data. So the first thing you establish is the number, not the procedure:
python3 tools/measure-boundary.py /vmfs/volumes/*/*/*-flat.vmdk.babyk
It finds the transition by entropy, without being told what to look for, and
refines to the sector. Encrypted output is uniform random — high entropy, almost
no zero bytes — while real filesystem data is structured, sparse or both. On
build B the last encrypted sector begins at 0x207FFE00 and clean plaintext
resumes at 0x20800000, on every file, on every disk, on two separate hosts.
It stops dead at the boundary, every time. It just is not always the same boundary.
Filesystems keep spare copies of the parts that died
This is the part that turns a bad day into a long afternoon. The structures sitting in that damaged head are exactly the ones filesystem designers decided were too important to keep only one copy of, and the spares live at the end of the volume, where the encryptor never reaches.
| What died | Where the spare is | Usable? |
|---|---|---|
| GPT partition table (LBA 1) | last sector of the disk | yes, gdisk rebuilds from it |
| NTFS boot sector | last sector of the partition | yes |
NTFS $MFT |
not a spare — it just starts ~3 GiB in | yes, and it is the whole ballgame |
NTFS $UpCase, $AttrDef |
identical on every NTFS volume | yes, donate from an intact one |
| NTFS root directory index | nowhere | no — and this is why Windows says RAW |
| ext2/3/4 superblock | block groups 1, 3, 5, 7, 9, 25… | yes, though the first two are usually inside the damage |
| LVM2 PV label | no spare, but PVs normally start at 2–3 GiB | intact if the PV starts past the boundary |
| ext4 inode tables | nowhere | this is the one that hurts |
So recovery is mechanical: read the backup structure, rebuild the primary from it, mount, copy the data out. Where there is no spare, work around the missing structure instead of trying to recreate it.
One number tells you how bad each VM is
Where does the filesystem start?
If it starts past the boundary — Ubuntu's default guided install with LVM puts
the root PV around 2–3 GiB in, and NTFS puts $MFT around 3 GiB in — then the
important structures are completely untouched. You lost the bootloader and
metadata that has spares, and you are close to having your files back.
If it starts at 1 MiB, which is what a plain-ext4 Debian install gives you, then
the root inode and the first ~131,000 inodes were inside the blast radius.
ext4's flex_bg packs the inode tables for 16 block groups together at the front
of the filesystem, and unlike superblocks, inode tables have no backup copies
anywhere. The file data is still on the disk. The names and the directory tree
are not. That guest needs e2fsck and then reassembly out of lost+found.
Both are recoverable. One is coffee, the other is an afternoon.
First 15 minutes
Everything here reads and never writes.
# How many times was each file encrypted? VMFS flat files are 512-byte aligned,
# so the appended keys show up in the remainder.
# 0 = renamed but NEVER encrypted
# 32 = encrypted once
# 64 = encrypted twice
ls -l /vmfs/volumes/*/*/*-flat.vmdk.babyk | awk '{print $5 % 512, $NF}'
Do that one first. It is free, and it sometimes ends the job: the attacker's script runs the encryptor several times in a row, and any disk still held open by a running VM gets renamed without being encrypted. On one host that was five disks and 325 GiB; on another, 33 guests. They needed a new descriptor file and nothing else.
python3 tools/measure-boundary.py /vmfs/volumes/*/*/*-flat.vmdk.babyk
sh tools/esxi-recon.sh # host state, IOCs, datastores, pass counts
sh tools/make-descriptors.sh --write # only ever creates new files
sh tools/remaining-report.sh # classify the fleet
The descriptor problem, and the trick that solves it
You cannot attach a .babyk file to a VM. ESXi wants a descriptor file, and the
original descriptor was a 360-byte text file, so it sat entirely inside the
damage and is gone for good.
Writing a new one takes four numbers, and it fixes a second problem at the same
time. A VMDK descriptor declares its extent length in sectors. Declare
original_size / 512 and two things happen: the 32 or 64 appended key bytes fall
outside the declared extent so nothing reads them, and the extent goes back to
being 512-byte aligned, which those appended bytes had broken and which quietly
confuses every recovery tool you will otherwise reach for.
After that, ESXi treats the encrypted file as an ordinary flat disk. Nothing gets renamed, copied or modified.
Windows guests
For a long time this repository said no Windows guest had been recovered here. That is no longer true, and the procedure is in docs/windows-recovery.md.
The disk-level work is identical. What is different is NTFS, and one thing about it will waste your afternoon if nobody tells you:
Windows reports the volume as RAW, and that word means nothing. A volume
missing its boot sector, a volume missing every file, and a volume that is
perfectly readable except for one 4 KiB index block all look the same in Disk
Management. Ask Linux instead and you get the actual reason in one command:
ntfsfix -n /dev/sdb2 # -n checks, writes nothing
Corrupt index block signature: vcn 0 inode 5
Failed to open $Secure: No such file or directory
Inode 5 is the root directory. Its index block is in the damaged head. The
$Secure line is not a second fault — ntfs-3g finds system files by name
through that index, so one broken structure produces two alarming messages.
The important part: you do not need the index. Every file's $FILE_NAME
attribute records its parent directory, so the whole tree can be rebuilt from the
MFT alone — and $MFT normally starts around 3 GiB into the volume, far past any
early-stop encryptor.
python3 tools/ntfs_triage.py disk-flat.vmdk.babyk 545259520 # what died
python3 tools/ntfs_extract.py disk-flat.vmdk.babyk <off> <end> <dmg> /out --list
ntfs_extract.py reads through the backup boot sector, so it never writes to
the damaged disk, and it runs on the ESXi host's own Python 3 — no rescue VM
required to get the data off. It verifies every file against the size recorded in
its MFT record and lists any file whose clusters overlapped the destroyed head,
which is the one output you actually have to read.
Two more things worth knowing before you start: $UpCase and $AttrDef are
byte-identical on every NTFS volume of the same Windows version, so an intact
volume can donate them — and Windows helpfully puts a recovery partition at the
end of the disk, hundreds of gigabytes past the damage. And SAM and
SECURITY die while SYSTEM and SOFTWARE survive, with copies of all four
usually sitting in \Windows\System32\config\RegBack\.
Four rules that keep this safe
Attach damaged disks as Independent — non-persistent. Every write the
rescue guest makes goes into a redo log that gets thrown away at power off. The
original file physically cannot change, which means you can experiment, get it
wrong, and start over. Worth knowing: writes in this mode still succeed, they
are just discarded later, so a successful write proves nothing about
persistence. Confirm the setting in the UI.
Clean the host before you power anything on. The encryptor is usually still
sitting in /var/run/, executable. A VM you just restored is a fresh target, and
this time the new writes land in the damaged head where nothing survives. On both
August 2026 hosts the encryptor re-ran days later, while responders were
already working the incident.
Never run the encryptor to "check" something. On its final pass in the first
incident it reported 0 files encrypted and 916 skipped, which looks harmless
until you understand it skipped them only because they already had the .babyk
extension. Anything you restore is fair game.
Assume every credential on those guests is burned. SSH keys, .env files,
database passwords, .git-credentials. They survived the encryption, which means
the attacker had hours of root access to read them.
Before you write anything off
Run the free No More Ransom Babuk decryptor against a copy of one encrypted file. Fifteen Babuk private keys are public: fourteen from the 2021 source-code leak, plus the Tortilla key that Cisco Talos recovered and handed to Avast in 2024. The tool tries all of them in about ten minutes. If you are facing a private fork it will fail, but ten minutes is cheap and assuming is not.
Then report the incident, and include the master public key from the binary. Every key in that public decryptor exists because someone reported. When an actor is eventually arrested and their keys are recovered, victims get matched by exactly that value. If yours was never filed, nobody can match it to you.
And do not trust "unrecoverable" until you have checked four specific things. Each of these looks exactly like total data loss and is not:
- an LVM root in a rescue environment that lacks
lvm2reads as unformatted space; - a filesystem that refuses to mount with
orphan file block N: bad magicafter a cleane2fsckis intact, and onetune2fs -O ^orphan_filefrom readable; - a disk reporting "no backup GPT" may have been expanded in VMware, leaving its secondary table stranded mid-device rather than absent;
- an NTFS volume reading as
RAWis usually refusing over its root directory index, while$MFTand every file sit intact 3 GiB further in.
Questions people ask at hour one
Can Babuk ransomware be decrypted?
Not this variant, and almost certainly not yours. Curve25519 ECDH with a
per-file ephemeral key drawn from /dev/urandom, SHA-256 to derive, Sosemanuk
to encrypt. It is implemented correctly and the master private key sits with the
attacker. Fifteen Babuk private keys are public, from the 2021 source leak plus
the Tortilla key Cisco Talos recovered, so run the No More Ransom decryptor
against a copy of one file before you accept that answer. It takes ten minutes.
What is a .babyk file, and can I open it?
It is one of your files with the first few hundred megabytes overwritten and 32 bytes of key appended. Nothing opens it directly. On a virtual disk, everything past the boundary is still your original data, which is why the recovery works.
My ESXi VMs won't boot after ransomware. Is the data gone?
Very probably not. What died is the partition table and the bootloader, both of which sit in the first megabytes of the disk. The filesystem and your files are further in. Follow the rescue VM guide and you will usually be looking at a directory listing within the hour.
How do I recover a VMDK encrypted by ransomware?
Generate a fresh descriptor so ESXi will attach the encrypted flat file as an
ordinary disk, attach it to a rescue VM in non-persistent mode, rebuild the GPT
from the backup copy at the end of the disk, activate LVM, and mount read-only.
tools/make-descriptors.sh handles the first step for every damaged file on the
host.
How do I rebuild a GPT partition table after ransomware?
The primary table at LBA 1 is destroyed, but GPT keeps a full backup in the last
sector of the disk and gdisk restores one from the other:
printf '1\nr\nb\nw\nY\n' | gdisk /dev/sda. The leading 1 answers gdisk's
"found invalid MBR" prompt. If there is no backup header at all, the disk is
MBR-partitioned and you want testdisk with a deeper search instead.
tools/repair-disk-head.py does the same thing offline against a flat file, and
restores the NTFS boot sectors in the same pass.
My Windows volume shows as RAW. Is it gone?
Almost certainly not. Run ntfsfix -n against it from a Linux rescue and read
the actual error. If it names vcn 0 inode 5, that is the root directory index,
and your files are fine — see docs/windows-recovery.md.
Extract with tools/ntfs_extract.py, which does not need the volume to mount.
How do I recover an LVM volume after ransomware?
If the physical volume starts past the boundary, which is normal on an Ubuntu
guided install, the LVM label and metadata are untouched. vgscan --mknodes,
vgchange -ay, lvs, then mount. If the rescue environment has no lvm2
installed, the volume shows up as unformatted space and looks exactly like total
loss. That mistake is the single most common false negative in this work.
What if ext4 says bad superblock?
The primary superblock at offset 1024 was inside the damage. ext4 keeps backups
at block groups 1, 3, 5, 7, 9, 25 and so on, but the first two usually sit inside
the blast radius too. Block 163840 is normally the first usable one:
e2fsck -b 163840 -B 4096 /dev/sdaN. tools/find_fs.py will scan and tell you
rather than making you guess.
Should I pay?
Nobody can make that call for you, but do the measurement first. Run the triage
in the section above and find out how much of your data is actually damaged
before anyone quotes you a price for it. In the first incident the answer was
0.246%. In the most recent one, the entire loss on one host landed on a tempdb
file that SQL Server rebuilds at every startup.
e2fsck finished but the filesystem still will not mount
If the error is orphan file block N: bad magic, the filesystem is intact.
e2fsck rebuilt ext4's optional orphan_file using block bitmaps that are
garbage in the destroyed groups, so it allocated blocks past the end of the
device. Drop the feature and re-check:
tune2fs -O ^orphan_file /dev/sdaN && e2fsck -fy /dev/sdaN. This one is worth
knowing because at that moment the volume looks completely destroyed and is one
command from readable.
Documentation
- docs/recovery-runbook.md — the working procedure. Triage, both recovery paths, and the mistakes that cost hours.
- docs/windows-recovery.md — Windows and NTFS: why the volume reads as RAW, what can be donated, and how to extract without mounting anything.
- docs/batch-recovery.md — thirty guests instead of one. One rescue VM, many disks, and how to tell identical clones apart.
- docs/environment-gotchas.md — the ESXi shell, SSH and the workstation. Everything here returns a confident wrong answer rather than an error.
- docs/case-media-server.md — a hard-path recovery start to finish, including the wrong turns.
- docs/rescue-vm-guide.md — click-by-click in the ESXi web UI, then the console commands.
- docs/analysis.md — what the malware does, the full cryptographic assessment, and hardening.
- docs/iocs.md — hashes, paths and behavioural indicators.
Tools
Host-side tooling is Python 3 standard library and POSIX shell only, because it runs on the ESXi host where you cannot install anything.
| Tool | Writes? | What it does |
|---|---|---|
measure-boundary.py |
no | find where the encryptor stopped, without assuming |
esxi-recon.sh |
no | host triage: IOCs, datastores, pass counts |
babuk_triage.py |
no | classify files, total the surviving plaintext |
babuk_mapdisk.py |
no | rebuild one disk's layout from its backup structures |
babuk_fleetscan.py |
no | the mapper across the whole host, CSV out |
ntfs_triage.py |
no | NTFS: what died, what survived, which files are damaged |
ntfs_extract.py |
no | NTFS: pull files out via the MFT, no mount needed |
find_fs.py |
no | signature scan when the head is gone entirely |
find-backup-gpt.sh |
no | find the backup GPT on a disk expanded in VMware |
remaining-report.sh |
no | what is left; checks power state before reading |
final-status.sh |
no | per-VM power/tools/IP table |
make-descriptors.sh / .py |
creates only | the -recovered.vmdk descriptors |
repair-disk-head.py |
yes | protective MBR, GPT and NTFS boot sectors from backups |
repair-ntfs-meta.py |
yes | donate $UpCase/$AttrDef, rebuild $MFTMirr |
recover-easy-path.sh |
yes | the easy path end to end; dry-run by default |
repair-ubuntu-efi.sh |
yes | the same by hand, as a worked example |
rebuild-bootable.sh |
yes | hard path: reassemble lost+found onto a fresh disk |
make-rescue-vm.sh |
yes | provision a rescue VM around one disk |
make-batch-rescue-vm.sh |
yes | provision one rescue VM around many disks |
batch-repair.sh |
yes | drive the repair across every attached disk |
bringup-recovered-vm.sh |
yes | repoint the .vmx, register, boot |
bringup-sequential.sh |
yes | the same for a list, one at a time |
fix-suspended-vms.sh |
yes | guests suspended with an encrypted .vmem |
esxi_run.py, batch_driver.py |
n/a | run things on a host from your workstation |
windows/ |
n/a | PowerShell, for password-only access from Windows |
Scope
Linux guest recovery here is written against Ubuntu and Debian, since that is what the fleets ran. Windows guest recovery is written against Windows Server 2022 with NTFS and SQL Server. The disk-level work applies to any guest regardless.
ReFS is partly covered, and honestly so. A Veeam backup repository on a 14 TB ReFS volume was worked in August 2026. Selecting which files to extract and verifying whether an extracted file is real both worked well and are written up in docs/refs-veeam-recovery.md, along with the two tools. Reading the remaining bytes out did not: DMDE names every file and gets every size right, then fills roughly 8% of the extents with zeros, and no setting changed that. Nothing here repairs an ReFS volume.
Still untested: XFS and btrfs, where the backup-superblock story differs; dynamic disks, Storage Spaces and BitLocker, none of which the NTFS work touches; and NTFS with 4 KiB physical sectors. For those, R-Studio, DMDE and UFS Explorer remain the sensible commercial fallback. R-Studio and UFS Explorer are also the untried lever on ReFS, since they reconstruct containers independently of DMDE. See docs/analysis.md for the full list of what this does and does not cover.
Contributions welcome. The gaps above are the obvious places to start, as is any locker with a different damage size — the technique holds for anything that stops early in a file, only the constant changes, and this actor already shipped two builds that differ by 8 MiB. Two conditions, both in CLAUDE.md: host-side tools stay POSIX shell and Python standard library, and victim-side detail never reaches git history. CI enforces the mechanical half of that.
Hashes, contacts and the attacker's master public key are published on purpose; that is what makes an IOC list useful. Host addresses, VM names and credentials from the incidents are not, for reasons that should be obvious.
There is no malware here and nothing that helps anyone write any. This is the other half of the problem: work out what survived, and go get it.
Comments