A byte-level .Funder salvage — from unbreakable crypto to a rebuilt business
The client is one of India's leading insurance-survey and loss-assessment firms — the
people who inspect and value claims for insurers. Their entire working archive lived on a single 8 TB drive:
years of survey reports, spreadsheets of assessments, scanned bills, claim photographs, e-mail, and their
Tally accounting books. One morning it was all gone — every file renamed with a
.Funder extension and a ransom note in every folder. The business had, in effect, stopped.
The encrypted drive was couriered to Straightarc Technologies. This is the honest, step-by-step account of how we got the data back — without ever decrypting a single file — and it is written so that another victim, or another responder, can follow the same path. Every step names the tool we used and shows exactly how to run it.
127,852 files encrypted · 197.39 GB of live data · 0 files decrypted · 39,636 recovered in full
How to read this article. Wherever you see a hex screenshot, each colour marks a different field and a legend explains it in plain English. Wherever you see a Run it card, that's the actual command — with variants for a whole drive, a single file, or a chosen subset. The full toolkit is linked at the end.
I will be candid, because the story only makes sense this way. When the drive first landed on my desk, I did the obvious things, and found nothing usable. The files were high-entropy noise. There were no backups I could restore, no shadow copies, no recovery tool that returned anything but garbage. I genuinely believed the data was gone, told myself the case was closed, and set it aside.
A few days later I couldn't let it go. I decided to stop trying to recover files and instead understand the enemy — to read the ransomware at the level of individual bytes and let the malware tell me what it had, and had not, actually done. That decision is where every recovery in this article begins.
First you name what hit you. You cannot plan a recovery against a strain you can't identify.
Ransomware leaves a calling card. Two things tell you which family you're dealing with: the
extension it appends to every file (here, .Funder) and the ransom
note it drops (here, Funder_Help.txt). The note also carries a victim
ID and the attackers' contact e-mails — your first indicators of compromise (IOCs).
We took the note and one encrypted sample to ID Ransomware — a free service that matches these fingerprints against a database of known families. It identified the strain as Proxima (also called BlackShadow) and, importantly, confirmed there is no free decryptor.
| Family | Proxima / BlackShadow |
|---|---|
| Extension | .Funder |
| Ransom note | Funder_Help.txt |
| Victim ID | 341E7BDECD7F760E |
| Cipher | ChaCha20 + Curve25519 (secure) |
| Public decryptor | None |
Before anything clever, you exhaust the easy wins. We searched for a vendor decryptor, a leaked key, a flaw write-up — nothing existed for this build. Proxima's cryptography (we'll dissect it in Part D) is done correctly, which means the only key that can decrypt these files is one held by the attackers. So we crossed decryption off the list and asked a different question: *what survived?*
Every responder tries these first. We show them failing honestly, because knowing why they fail is what points you at the method that works.
Windows quietly keeps point-in-time snapshots called Volume Shadow Copies — the machinery behind 'Restore previous versions'. If they survive an attack, recovery is trivial. Most ransomware deletes them first. We checked anyway.
Undelete/carving tools (the kind you'd use after an accidental format) look for *deleted originals* still sitting in free space. They returned nothing meaningful here — and understanding why was the turning point of the whole case.
There are two ways ransomware can encrypt a file. The naive way is copy-then-delete: read the original, write a new encrypted copy, delete the original — which leaves the original recoverable in free space. The efficient way is in-place: overwrite the file's own bytes where they already sit, then rename it. Proxima does the second.
We proved it from timestamps. If the file were a freshly-written copy, its creation time would be the attack date. Instead, 98.8% of the encrypted files kept their original, pre-attack creation date — they are the same files, overwritten in place. The consequence is blunt: there are no deleted originals to carve back. Conventional recovery was always going to fail.
The client ran Quick Heal, whose continuous-backup feature keeps copies of protected files.
Our best hope. But when we looked, the backup archives themselves carried the .Funder extension —
the ransomware had encrypted the backups as well, including the backup's own index. We
contacted Quick Heal; their answer was that the backup was encrypted and there was no chance.
To learn what the malware really did, we stop using the operating system's view of the files and read the raw disk directly.
NTFS keeps a change journal (the USN journal) — a running log of every file created, renamed, or deleted. Even after an attack it often survives, and it gave us two things: a list of the tools the attacker dropped, and the timing/naming pattern of the encryption itself. It's the first place we read for clues before touching the file contents.
# Whole journal (from the mounted volume's $Extend\$UsnJrnl:$J) python3 usn_parse.py UsnJrnl_J.bin # Filter to one file's history python3 usn_parse.py UsnJrnl_J.bin --name "report.docx" # Filter to a keyword subset (e.g. attacker tools) python3 usn_parse.py UsnJrnl_J.bin --grep "mimikatz|anydesk|.Funder"
To read the disk beneath the operating system we mounted it as a raw block device in Linux (via WSL). In plain terms: instead of asking Windows for 'files', we open the disk as one long ribbon of bytes and interpret the filesystem ourselves.
# take the disk offline in Windows so WSL can own it Set-Disk -Number <N> -IsOffline $true wsl --mount \\.\PHYSICALDRIVE<N> --bare # in WSL, make the partition readable sudo chmod o+r /dev/sdX2
Then we extracted and parsed the $MFT (Master File Table) — NTFS's index of every file: its name, size, dates, and exactly which disk clusters hold its bytes. Windows' own tools were far too slow across 1.3 million records, so we pulled the raw $MFT and parsed it in Python. This table is the map every later step depends on.
# Whole volume python3 parse_mft.py /dev/sdX2 > mft_meta.json # Inspect one file's record python3 parse_mft.py /dev/sdX2 --inode 290315 # List a subset by extension python3 parse_mft.py /dev/sdX2 --ext .Funder
This is the heart of it. Once we understood precisely how Proxima encrypts, the whole recovery strategy fell out of it.
In plain terms: for every file, the malware invents a brand-new secret number, uses it to scramble the file, then throws that secret away — keeping only a locked copy that only the attacker's master key can open. Because the secret is unique per file and is destroyed after use, there is no shortcut: no shared key, no pattern, nothing to guess.
In detail: for each file Proxima generates a fresh Curve25519 key pair and a ChaCha20 nonce using CryptGenRandom (Windows' strong random-number generator). It performs an ECDH key exchange between that throwaway private key and the attackers' public key (baked into the malware) to derive a 32-byte shared secret, and uses that secret directly as the ChaCha20 key. After encrypting, it wipes the throwaway private key and the shared secret from memory, and writes only the throwaway *public* key into a footer. To decrypt, the attacker recombines their *private* master key with that public key — which no one else has.
We identified this scheme by dissecting the footer (below) and matching it to public analysis of the Proxima/BlackShadow family. See RFC 8439 (ChaCha20) and RFC 7748 (Curve25519) for the primitives.
Why we could never decrypt. Each file has its own key; the key generator is cryptographically strong; the same file never reuses a keystream; and known-plaintext doesn't break ChaCha20. Every route to the key is a dead end — we tested them all.
Here's the insight that saved the case. Entropy measures randomness: encrypted data looks perfectly random (entropy near 8.0 bits/byte); ordinary file data is structured and measures lower (~6.7). By scanning entropy across a file, we can literally *see* where the ciphertext ends and the original, untouched plaintext begins.
That seam sits at a fixed offset — 0x60000, i.e. 393,216 bytes (384 KiB). Proxima encrypts only the first 384 KiB of a typical file and leaves the rest alone. For anything larger than 384 KiB, the tail is your original data, verbatim.
One subtlety we had to get right: Proxima does not always stop at 384 KiB. The footer (next section) holds a chunk counter, and the amount encrypted grows with it. Small files: just the first 384 KiB. Large files: multiple encrypted regions, and beyond a point the whole file. This is why recovery has to be driven by the file's structure, not a fixed offset — we scan each file for surviving format markers rather than blindly cutting at 384 KiB.
| Example file | Size | Chunk counter | What's encrypted |
|---|---|---|---|
| 526 KB | 1 | first 384 KiB; rest is original | |
| JPEG | 6.4 MB | 2 | header region; tail intact |
| ZIP | 2.1 MB | 9 | effectively the whole file |
| Disk image | 3.7 GB | 14,340 | encrypted throughout |
Appended to the very end of every encrypted file is a 72-byte footer — the malware's own record of how to (for the attacker) unlock that file. Reading it is what let us confirm the whole scheme.
The first real wins: pull back everything the header-only encryption left exposed.
Now that we knew large files keep an intact plaintext tail, we swept the whole drive and extracted every surviving tail — 44,240 files, 172 GB of original bytes hiding behind the encrypted headers.
# Whole drive python3 extract_all.py /dev/sdX2 --out ./tails # One file python3 extract_all.py /dev/sdX2 --file "video.mp4.Funder" # A subset by type python3 extract_all.py /dev/sdX2 --ext .jpg,.pdf,.mp4
A tail on its own isn't a working file — it's missing the *header* the encryption ate. For
videos, we synthesised a fresh container header and re-attached the intact moov
index from the tail. The rebuilt clips play after a few seconds of glitch (the encrypted
lead-in), then run clean. For images, we carved embedded JPEGs directly from the plaintext
regions.
File-format references used here: ISO/IEC 14496-12 (MP4/MOV base media format) and ITU-T T.81 (JPEG).
# One video from its tail python3 reconstruct_video.py video.tail --ref sample.mp4 -o video_fixed.mp4 # Batch a folder of tails python3 reconstruct_video.py --dir ./tails/video --ref sample.mp4 # Whole recovered set python3 reconstruct_video.py --dir ./tails/video --ref sample.mp4 --all
Modern Office files (.docx, .xlsx, .pptx) are really ZIP
archives of XML and media. The bad news: the XML that holds your text and numbers
lives at the front of the file — inside the encrypted 384 KiB — so the actual document text was
unrecoverable. The good news: embedded images often sit further in, in the
plaintext, so we could rebuild picture-only versions of many documents. We recovered images extensively and
Word
files with their pictures intact.
But there was a conspicuous hole: we could not recover a single Excel spreadsheet this way — and for a loss-assessment firm, the spreadsheets *are* the work. That gap is exactly what sent us back to the 'hopeless' Quick Heal backups. Format references: ECMA-376 / Office Open XML and the PKWARE ZIP APPNOTE.
# One document python3 repair_files2.py "report.docx.Funder" -o ./repaired # A subset/folder python3 repair_files2.py --dir ./tails/office -o ./repaired # Whole drive via the MFT map python3 repair_files2.py --mft mft_meta.json --dev /dev/sdX2
The backups were encrypted the same way as everything else — which meant they had to contain surviving plaintext too. We just had to understand their structure.
The missing Excel files nagged at me. Then it clicked: the Quick Heal backups were encrypted by the same ransomware, the same header-only way. If ordinary files kept a plaintext tail, the backup archives had to keep one too — and a backup archive is nothing but a *wrapper around an original file*. If we could understand that wrapper's structure, we could reach inside and pull the originals out. So we set out to reverse-engineer the backup file format.
Each Quick Heal backup file is a .cfa archive. By comparing many of them at the byte level we
recovered the format completely. There are two variants. Small files are stored compressed: a
4-byte magic QHBK, a 4-byte original size, then a standard
zlib/DEFLATE stream of the whole original file. Large files are stored
raw — the original's bytes verbatim, no wrapper.
The compressed payload is a zlib stream — the 78 9C you see is its header;
inside
is a DEFLATE bitstream and a trailing Adler-32 checksum. Specs: RFC 1950 (zlib) and RFC 1951 (DEFLATE).
Recovery
is then trivial and lossless: skip the 8-byte header, inflate, and you have a byte-perfect
original. Full field tables are in Appendix A.
With the format understood, we swept every backup archive and extracted the originals — deduplicating the many historical versions Quick Heal keeps down to unique files. This single step recovered the bulk of the business: 39,636 original files in full fidelity — including the Excel spreadsheets that had eluded every other method.
23,530 PDFs · 9,481 Word / other docs · 6,740 spreadsheets · 39,636 total, full fidelity
# Whole backup store (all types) python3 cfa_extract.py /dev/sdX2 --out ./recovered # Only certain types (e.g. Excel) python3 cfa_extract.py /dev/sdX2 --types xls,xlsx # A single backup file python3 cfa_extract.py --file "Bill.xls_177660.cfa"
Tens of thousands of files is not the same as a working business. The files had to go back where they belonged.
Tally (India's ubiquitous accounting software) stores each company's books as a
set
of cryptically-named data files (TranMgr.900, Company.900, and so on). We had
recovered all of them from the backups — but they arrived as a giant, undifferentiated pile. Which
file
belonged to which company? Without that, the accounts were just noise.
Quick Heal keeps a master index (cfrdb) that maps every backup archive to its
origin — the snapshot it came from, the original filename, and a sequential backup ID. The index was itself
encrypted, but header-only, so its record table survived in the plaintext tail. We
reverse-engineered the record layout and read it: each ~98-byte record ties a backup ID to a path like
snapshot \ set \ originalname_ID.cfa.
The backup ID was the thread that stitched everything together. It appears both in the index and in each
.cfa filename, so — with the index decoded — we could place every recovered file back into its
original folder structure, and regroup the Tally files into their 28 company
folders by snapshot cluster. We rebuilt the client's entire E:\ layout as it had
existed before the attack.
# Rebuild Tally companies python3 tally_reconstruct.py --index cfrdb.tail --out ./tally # Rebuild the full tree python3 tree_rebuild.py --recovered ./recovered --index cfrdb.tail --out ./recovered_tree # One company / subset python3 tally_reconstruct.py --index cfrdb.tail --company "<name>"
What we walked away with, and the principle that got us there.
0 files decrypted · 39,636 recovered in full · 42.9 GB partially salvaged · 28 Tally companies rebuilt
Modern ransomware cryptography is not a lock you pick. Proxima's ChaCha20/Curve25519 scheme is sound; we could not, and did not, break it. The recovery came from everything the malware left exposed — the plaintext tails its header-only encryption couldn't reach, and above all the backup store it encrypted the same lazy way, which therefore still contained recoverable originals.
If you take one thing from this: don't fight the crypto — enumerate the untouched. Shadow copies, security-software backup stores, plaintext tails of large files, deleted-but-present data. The biggest win in this case came from the one place everyone — including the backup vendor — had written off as hopeless.
For fellow victims. Every script referenced in this article is
published
as a toolkit so you can attempt the same recovery on a .Funder-hit drive. See Appendix B for the
full list and the download link. Work on a copy of the disk, never the original.
One last carve, for completeness: we turned the same deleted-space recovery on the intruder's *own* tools. Evidence of mimikatz and the NirSoft PassView credential suite surfaced in unallocated clusters — confirming what was run on the machine — but the attacker had wiped the kit: the toolkit archive and the ransomware encryptor binary itself had been deleted and their clusters overwritten, so those could not be recovered. In other words, the attacker's toolkit was not found intact on the system; only the traces it left behind.
This article covered how we got the data back. It deliberately did *not* cover how the attackers got in — the intrusion, how long they lurked, the tools they used, and the attribution. That investigation is a story of its own, and we will publish it separately in our next article, focused on the DFIR (Digital Forensics & Incident Response) analysis of the compromise.
— *Prince Komal Boonlia, CEO, Straightarc Technologies.*
This appendix is the byte-level reference behind the recovery. Each structure below is
documented with its fields; the annotated hex figures appear in the relevant sections.
1) .Funder ENCRYPTED FILE
[0x000000 .. 0x060000) ChaCha20 ciphertext (counter=1; more regions for large files)
[0x060000 .. EOF-72) ORIGINAL plaintext (this is what we salvage)
EOF-72 .. EOF-40 (32 B) ephemeral Curve25519 public key
EOF-40 .. EOF-8 (32 B) ChaCha20 nonce + reserved
EOF-8 .. EOF ( 8 B) chunk counter (u64, little-endian)
2) QUICK HEAL .cfa (Variant A - compressed / small files)
0x00 (4 B) magic 'QHBK' (51 48 42 4B)
0x04 (4 B) original file size (u32, little-endian) - equals decompressed length
0x08 (..) zlib stream of the complete original file
QUICK HEAL .cfa (Variant B - stored / large files) = raw original bytes, no header
3) zlib / DEFLATE (the compressed payload of a Variant-A .cfa)
byte 0 CMF : 0x78 (CM=8 = DEFLATE, CINFO=7 = 32 KiB window)
byte 1 FLG : 0x9C (FCHECK so (CMF*256+FLG) % 31 == 0; FDICT=0; FLEVEL=2 default)
then one or more DEFLATE blocks (RFC 1951): each has a 3-bit header
(BFINAL + BTYPE), BTYPE 01=fixed Huffman, 10=dynamic Huffman, 00=stored
end Adler-32 checksum (4 B, big-endian) of the uncompressed data
Specs: RFC 1950 (zlib), RFC 1951 (DEFLATE)
4) QUICK HEAL INDEX 'cfrdb' (~98-byte records)
link(5 B) markers(9 B, 69 02 06 06 06 ..) then the ASCII path:
<DDMMYYYY_HHMMSS> \ cfrbackup_<N> \ <original-name> _ <backup-ID> .cfa then meta
The backup-ID is the join key; it also appears in the .cfa filename.
5) FORMATS THE MALWARE INTERACTS WITH (spec links)
DOCX/XLSX/PPTX = ZIP of XML+media - ECMA-376 (Office Open XML)
ZIP container - PKWARE APPNOTE.TXT
PDF - ISO 32000
MP4/MOV (moov atom) - ISO/IEC 14496-12
NTFS $MFT - Microsoft NTFS on-disk structures
ChaCha20 - RFC 8439 ; Curve25519 - RFC 7748
All scripts are published for other victims of .Funder / Proxima. Work on a COPY of the disk image, never the original media. Download: https://github.com/straightarc/funder-recovery parse_mft.py - map every file on the volume from the raw $MFT usn_parse.py - read the NTFS USN change journal (tool + timing clues) extract_all.py - carve surviving plaintext tails + recover deleted files repair_files2.py - recover intact members from partial ZIP/OOXML files reconstruct_video.py - rebuild playable MP4/MOV from an intact tail reconstruct_docx.py - rebuild image-only versions of Word documents cfa_extract.py - extract byte-perfect originals from Quick Heal .cfa backups tally_reconstruct.py - regroup Tally files into company folders tree_rebuild.py - rebuild the original directory tree from the index Each script accepts a whole device, a single file, or a subset (see the per-step cards).
- Public/vendor decryptor .......... none exists for this build - Breaking the cipher (ECB/pattern) . ChaCha20 stream; 33 repeats in 1,536 blocks = not ECB - Keystream reuse across files ...... XOR of two files -> entropy 7.18, no structure - Fixed/predictable IV or key ....... 16 footers sampled, all unique, 0 constant bytes - Weak RNG shortcut ................. keys from CryptGenRandom (strong CSPRNG) - Known-plaintext attack ............ irrelevant to ChaCha20 - Volume Shadow Copies .............. all deleted (zero snapshots) - Conventional undelete/carving ..... nothing (in-place encryption; no deleted originals) - Standalone .zip/.rar repair ....... 43 of 47 fully encrypted, unrecoverable - Office document TEXT .............. always inside the encrypted header (media only survived) - Quick Heal's own verdict .......... 'backup encrypted, no chance' - later proven recoverable
ID Ransomware .......... https://id-ransomware.malwarehunterteam.com ChaCha20 ............... RFC 8439 https://www.rfc-editor.org/rfc/rfc8439 Curve25519 ............. RFC 7748 https://www.rfc-editor.org/rfc/rfc7748 zlib ................... RFC 1950 https://www.rfc-editor.org/rfc/rfc1950 DEFLATE ................ RFC 1951 https://www.rfc-editor.org/rfc/rfc1951 Office Open XML ........ ECMA-376 https://ecma-international.org/publications-and-standards/standards/ecma-376/ ZIP .................... PKWARE APPNOTE.TXT PDF .................... ISO 32000 MP4 / MOV .............. ISO/IEC 14496-12 JPEG ................... ITU-T T.81