On this page
Kyber ransomware sounds like post-quantum branding until the crypto starts lining up.
The ransom note says the files were encrypted with AES-256-CTR, and that X25519 plus Kyber1024 were used for key generation. That could have been branding. Ransom notes are full of borrowed cryptography words.
This sample is not that. Local analysis confirms a Rust ransomware build with AES-CTR-style file encryption, Kyber1024-sized encapsulation material, active Curve25519/X25519 arithmetic, HMAC-SHA256-style key and IV derivation, and a fixed 0x744 trailer on every encrypted file.
The recovery result is blunt. Instrumented analysis recovered a fixture key and IV and decrypted a seeded test file, which validates the model. That is not a reusable decryptor. From the sample and encrypted files alone, local analysis did not find a practical recovery path.
The short version: the note is not empty post-quantum dressing. The code and file format both point to real hybrid crypto.
If you operate a threat intelligence platform with API access and can provide a researcher account, please reach out to [email protected]. Additional data sources directly increase the quality and coverage of the threat intel published here.
Public reporting
Rapid7 published a side-by-side Kyber analysis (opens in new tab) from a March 2026 incident response case. They recovered two payloads in the same environment: a VMware ESXi encryptor and a Windows file-server encryptor. The samples shared a campaign ID and Tor infrastructure, but not the same cryptographic reality.
The ESXi note claimed AES, X25519, and Kyber. Rapid7 found ChaCha8 with RSA-4096 key wrapping instead. The Windows line is different: the Rust variant implements the advertised Kyber1024 and X25519 key-protection path while AES-CTR handles bulk file encryption.
This post focuses on the older Windows sample 4ed176edb75ae2114cda8cfb3f83ac2ecdc4476fa1ef30ad8c81a54c0a223a29. The contribution here is the file-format and runtime proof: the 0x744 trailer, the 0x620 Kyber-sized field, the AES counter handling, and the fixture decryption check.
BleepingComputer reported (opens in new tab) that, as of April 22, 2026, it found one listed victim on the Kyber extortion portal: a large American defense contractor and IT services provider. PCrisk had also documented (opens in new tab) the .#~~~ extension and READ_ME_NOW.txt ransom note in December 2025, which lines up with Kyber being visible before the March 2026 Rapid7 case.
Sample overview
| Field | Value |
|---|---|
| Sample | 4ed176edb75ae2114cda8cfb3f83ac2ecdc4476fa1ef30ad8c81a54c0a223a29.exe |
| Triage run | 251018-c56cbag16d (opens in new tab) |
| SHA256 | 4ed176edb75ae2114cda8cfb3f83ac2ecdc4476fa1ef30ad8c81a54c0a223a29 |
| MD5 | 18498b1ff111ee9d9a037c280f75b720 |
| SHA1 | 0e9a47782e39741a2c161bf639252d33ad3a428a |
| Type | PE32+ Windows x64 console executable |
| Size | 1,907,200 bytes |
| PDB hint | encrypt.pdb |
| Encrypted extension | .#~~~ |
| Ransom note | read_me_now.txt |
The binary carries Rust module strings that match the job it performs: encrypt::crypto, encrypt::walker, encrypt::csprng, encrypt::icons, and encrypt::services.
The dependency strings are also direct:
| Crate string | Why it matters |
|---|---|
pqc_kyber-0.7.1 | Kyber implementation path |
curve25519-dalek-4.1.3 | Curve25519 arithmetic path |
aes-0.8.4 | AES implementation path |
sha2-0.10.8 | SHA/HMAC-style derivation support |
rayon-1.10.0 / crossbeam-channel-0.5.13 | threaded file processing |
Those strings alone would not be enough. Rust binaries can drag in unused code. The proof is that runtime probes hit the key paths during file encryption.
What the encryptor does
The controlled run reached normal ransomware behavior.
It wrote ransom notes, registered the encrypted-file class, wrote a custom icon, logged its own thread pool and random-number setup, and launched the usual recovery-inhibition commands. Encrypted files used the .#~~~ extension.
Host changes included:
| Area | Observed behavior |
|---|---|
| Ransom note | read_me_now.txt written to drive and user roots |
| File class | HKCR\.#~~~, HKCR\fucked.file, HKCR\fucked.file\DefaultIcon |
| Icon | C:\fucked_icon\processed_file.ico |
| Shadow copies | vssadmin, PowerShell WMI deletion, and wmic SHADOWCOPY DELETE command launches |
| Recovery settings | bcdedit recovery changes and wbadmin system-state backup deletion |
| Logs | wevtutil cl event-log clearing loop |
| Services | patterns for sql*, msexchange*, vss*, backup*, and veeam* |
Ransom note scope
The ransom note provides the operator contact points:
| Type | Endpoint |
|---|---|
| Chat | mlnmlnnrdhcaddwll4zqvfd2vyqsgtgj473gjoehwna2v4sizdukheyd[.]onion/chat/f9e1d038b1f5220e888b56e97881937f |
| Blog | kyblogtz6k3jtxnjjvluee5ec4g3zcnvyvbgsnq5thumphmqidkt7xid[.]onion |
The extracted note path was C:\fucked_icon\READ_ME_NOW.txt. The note itself is weirdly useful because it advertises the same crypto stack the binary later backs up, while also sounding like someone ran a standard double-extortion script through a bored group chat. The section headers are doing a lot of accidental comedy: ??WE HAVE A FLASH DRIVE WITH BACKUPS ON THE ADMIN'S NECK??, ??CAN WE TRUST HACKERS??, and ??WHAT ABOUT THE ANONYMITY?? are not subtle.
/
# Hello, if you are seeing this then you have been attacked by Kyber Ransomware.
\
<=> Your files are encrypted with the AES-256-CTR algorithm.
>-- (Explanation) https://en.wikipedia.org/wiki/Advanced_Encryption_Standard
<=> Two asymmetric algorithms X25519 and Kyber1024 were used for key generation.
>-- (Explanation) https://en.wikipedia.org/wiki/Curve25519
>-- (Explanation) https://en.wikipedia.org/wiki/Kyber
<=> Keys are created from several random sources, so do not hope that you will return the files without our help
>-- (Explanation) https://en.wikipedia.org/wiki//dev/random
>-- (Explanation) https://en.wikipedia.org/wiki/RDRAND
>-- (Explanation) https://en.wikipedia.org/wiki/HKDF
(??WE HAVE A FLASH DRIVE WITH BACKUPS ON THE ADMIN'S NECK??)
>========================================================================================
> In addition to encrypting files, a lot of data has been downloaded from your network.
> If you don't write to us, within a week or two your name will end up on our
> blog with example of important data.
>========================================================================================
(??CAN WE TRUST HACKERS??)
>========================================================================================
> If you come to our chat room, you can count on free decryption for three small files.
> and examples of the downloaded data.
>========================================================================================
(??WE DON'T HAVE VALUABLE DATA??)
>========================================================================================
> We take a responsible approach to doing our job.
> We have probably downloaded a lot of personal information from your servers, and could
> cause you HUGE problems by publishing it.
# Documents such as payroll, statements, contracts and others may contain valuable data,
# the publication of which could lead to lawsuits.
>========================================================================================
(??WILL THE POLICE HELP??)
>========================================================================================
> DO NOT try to call the police as they will not save you from
> publishing your data, nor will they help you get your files back,
> they will only ban you from paying.
>========================================================================================
(??WHAT IF I TRIED TO TRICK YOU???)
>========================================================================================
> DO NOT modify the files, you may damage them and make it so
> we can't help you.
>========================================================================================
(??WHAT ABOUT THE ANONYMITY??)
>========================================================================================
> We create unique links to anonymous chat for each company.
> you don't have to worry, all the details of our deal will be kept secret.
> We also have alternative ways to contact us if you are worried and do
> not want to write in the panel.
>========================================================================================
HOW TO CONTACT US:
<=> Download Tor Browser (https://www.torproject.org/download)
<=> Open it
<=> Follow this link: hxxp[:]//mlnmlnnrdhcaddwll4zqvfd2vyqsgtgj473gjoehwna2v4sizdukheyd[.]onion/chat/f9e1d038b1f5220e888b56e97881937f
(Also maybe you would like to visit our blog? Don't be shy!)
<=> Blog: hxxp[:]//kyblogtz6k3jtxnjjvluee5ec4g3zcnvyvbgsnq5thumphmqidkt7xid[.]onion
The file format
Each encrypted output has a simple top-level shape:
encrypted body || 0x744-byte trailer
The trailer is the useful part. It is fixed-size, starts with a four-byte marker, and then holds the metadata needed for attacker-side recovery.
| Trailer offset | Size | Content |
|---|---|---|
0x000 to 0x003 | 4 | magic bytes e1 2f a8 c3 |
0x004 to 0x103 | 0x100 | AES-wrapped metadata |
0x104 to 0x723 | 0x620 | Kyber1024-sized material |
0x724 to 0x743 | 0x20 | plaintext Curve25519/X25519-derived tail |
Keep the two sizes separate. 0x620 is the Kyber1024-sized field inside the trailer. 0x744 is the full appended trailer.
The controlled run produced 12 encrypted internal test fixtures. They are not public IOCs; they are included here only to show the fixed trailer size. Every output was original size plus 0x744. The budget-2026.xlsx fixture, for example, started as 24,576 bytes and became 26,436 bytes:
| Internal fixture | Original size | Encrypted size | Delta |
|---|---|---|---|
budget-2026.xlsx.#~~~ | 24,576 | 26,436 | 0x744 |
customer-list.csv.#~~~ | 8,192 | 10,052 | 0x744 |
quarterly-planning.docx.#~~~ | 32,768 | 34,628 | 0x744 |
Final write watches also saw 0x744 trailer buffers being passed to NtWriteFile. These buffers began with e12fa8c3, the little-endian form of 0xc3a82fe1.
That ties the trailer to the ransomware's write path. It is not a parsing mistake from dumped files.
AES-CTR, with a metadata wrinkle
The sample derives two named fields:
| Field | Size | First watched value |
|---|---|---|
encryption-key | 0x20 bytes | fcca04669f1a9c79786e29914563c772584fba1aebc58ce1fd17c8e11a1266ea |
encryption-iv | 0x10 bytes | df2dba375800d76695d5ca37e5c72a50 |
The labels are not only strings in the binary. Runtime watches show encryption-key and encryption-iv being expanded by HMAC-SHA256-style code. The setup function builds inner and outer SHA256 pad states, then the label expansion function appends the field label and counter byte before finalizing.
The 32-byte key is passed into the AES key schedule at 0x1400628c0. The AES stream routine at 0x140001000 XORs generated keystream against the target buffer, which is the CTR-style shape expected from the ransom note's AES-256-CTR claim.
The IV handling is worth spelling out in plain English:
- the first eight IV bytes are stored as-is in the AES counter state
- the second eight IV bytes are byte-swapped before storage
- the counter starts at zero for the trailer metadata wrap
- body encryption starts at counter
0x10
That last point is the wrinkle. The first 0x100 bytes of trailer metadata consume 16 AES blocks. The file body then continues from counter 0x10, rather than restarting at zero.
The local verifier confirmed the model. Using the watched key and IV, it decrypted the budget fixture metadata and body. The metadata field at raw offset +0x08 decrypted to 0x6000, matching the 24,576-byte body size. The body decrypted back to the fixture's 0x42 fill bytes.
That is a strong validation check. It is not a victim-facing break, because the key and IV came from instrumented execution.
The Kyber part is real
The sample embeds a Kyber1024-sized public key in .rdata.
| Field | Value |
|---|---|
| Address | 0x1401526eb |
| Size | 0x620 / 1,568 bytes |
| SHA256 | 1b66614d63ce9f1b0b9f68464a93d826a3af7e08ccadcbc662f8444f0eaab6b9 |
A Rust lazy initializer copies that key into runtime state. Runtime watches then show the key pointer and 0x620 length being passed into the crypto path for each file.
The binary also contains Kyber-specific error strings:
| String | Why it matters |
|---|---|
Error when trying to calc Kyber (ciphertext, shared_secret) | names the Kyber encapsulation result shape |
Invalid public key size | supports public-key validation logic |
The per-file trailer also has a 0x620 region at offsets 0x104 to 0x723. That size matches Kyber1024 public key and ciphertext material. The field is copied as one contiguous region before the final trailer is written.
Size alone is not the whole argument. The runtime public-key pointer, Kyber crate path, Kyber error strings, 0x620 length checks, and trailer layout all point in the same direction.
The X25519 side
No literal x25519 or montgomery string survived in this build. The Curve25519 evidence is in the code and constants.
The binary contains the source path string curve25519-dalek-4.1.3/src/scalar.rs. More importantly, the active crypto path reaches Curve25519-style field arithmetic at runtime.
Local anchors:
| Evidence | Meaning |
|---|---|
0x14017f960 contains little-endian 0x1db42 / 121666 | Curve25519 Montgomery ladder constant variant |
Function 0x1400bea00 uses 0x7ffffffffffff | 51-bit Curve25519 limb mask |
Runtime reaches 0x14002d09b -> 0x1400bea00 with rdx=0x14017f960 | active arithmetic path during encryption |
Trailer 0x724 to 0x743 equals raw builder bytes before and after metadata wrapping | plaintext 32-byte Curve25519/X25519-derived tail |
For file format work, the 0x20 tail matters because it is not part of the AES-wrapped 0x100 metadata area. It remains readable in the final trailer.
It is also separate from the 64-byte key-derivation input used to derive the AES key and IV. The KDF input is built from two 32-byte pieces, then fed into the HMAC-SHA256-style expansion.
No decryptor path in this sample
The binary can parse existing 0x744 trailers. That is not the same as decrypting them.
The parser at 0x1400221a0 splits the trailer into fields:
| Final trailer field | Parsed destination |
|---|---|
+0x004 to +0x103 | parsed +0x008 |
+0x104 to +0x723 | parsed +0x108 |
+0x724 to +0x743 | parsed +0x728 |
| magic | parsed +0x748 |
The caller checks the magic and routes valid encrypted files into already-encrypted handling. It does not decapsulate Kyber, derive victim AES keys from the trailer, or decrypt the file body.
Static command strings show win_encryptor, path, hyperv, and system. No decrypt or recover mode was found. The File unlocked successfully strings belong to file locking and opening behavior, not cryptographic recovery.
The clean read: this sample is the encryptor. Recovery would need a separate decryptor or service holding private material.
Recovery limits
Local break attempts did not expose a practical decryptor from the sample and encrypted files alone. The trailer does not store plaintext AES key or IV. No embedded private key was found. Across the 12 unique fixed-run encrypted outputs, there was no observed AES key/IV reuse, no repeated aligned 16-byte trailer blocks, and no constant-XOR body prefix hit once duplicate dumps were excluded.
The fixture key/IV recovery is still important. It proves the analysis is reading the AES state correctly. It does not turn into a general decryptor unless the same key material can be recovered from a real victim environment during execution.
That is the line between validating the scheme and breaking it.
Detection shape
The strongest hunt is the combination:
| Signal | Why it matters |
|---|---|
Rust PE32+ x64 console binary with encrypt.pdb | build identity |
pqc_kyber-0.7.1, curve25519-dalek-4.1.3, aes-0.8.4 strings | crypto stack |
.#~~~ encrypted extension | file impact marker |
0x744 trailer beginning e1 2f a8 c3 | encrypted file format |
trailer 0x104 to 0x723 is 0x620 bytes | Kyber1024-sized material |
read_me_now.txt | ransom note name |
C:\fucked_icon\processed_file.ico | custom encrypted-file icon |
HKCR\.#~~~ and HKCR\fucked.file | file class registration |
recovery-inhibition commands via vssadmin, wmic, bcdedit, wbadmin, wevtutil | ransomware impact path |
Compact indicators:
| Type | Value |
|---|---|
| Sample SHA256 | 4ed176edb75ae2114cda8cfb3f83ac2ecdc4476fa1ef30ad8c81a54c0a223a29 |
| Ransom note SHA256 | ef054d22823758290db94aab3c901471a9ebd633f94963030806cc68dd433d8d |
| Icon SHA256 | 5a5f2bfea416f4b9ed4e6e45d82df524c1d9fa5f99c08944f2bacdf5bf9f525d |
| Embedded Kyber1024 public key SHA256 | 1b66614d63ce9f1b0b9f68464a93d826a3af7e08ccadcbc662f8444f0eaab6b9 |
| Extension | .#~~~ |
| Trailer size | 0x744 |
| Trailer magic | e12fa8c3 |
| Chat onion | mlnmlnnrdhcaddwll4zqvfd2vyqsgtgj473gjoehwna2v4sizdukheyd[.]onion |
| Blog onion | kyblogtz6k3jtxnjjvluee5ec4g3zcnvyvbgsnq5thumphmqidkt7xid[.]onion |
Kyber ransomware is crude around the edges. The icon path is ugly, the class name is ugly, and the note reads like every other pressure script with a Tor link. The crypto work is the part worth taking seriously.
For defenders, the read is simple: hunt the extension, the trailer, the Rust crypto strings, and the recovery-inhibition behavior. For recovery, do not assume the Kyber name is empty marketing. In this sample, the scheme is real enough that the encryptor alone did not give up the files.