Note: This is a very early prototype version, originally made as a proof of concept for a competition.
MEDUSA is a kernel-level anti-ransomware system for Linux that uses eBPF (extended Berkeley Packet Filter) to detect ransomware in real-time, freeze the malicious process before it can finish encrypting, intercept the encryption keys directly from the cryptographic API calls, and enable full recovery of encrypted files — all without requiring the attacker's cooperation.
- The Problem
- Theoretical Foundation
- Architecture
- How Each Component Works
- Why This Works Against Most Ransomware
- Project Structure
- Building
- Usage
- Demo
Modern ransomware encrypts files using strong cryptography (AES-256, ChaCha20) that is mathematically impossible to break without the key. Victims must either:
- Pay the ransom (no guarantee of recovery)
- Restore from backups (if they exist)
- Lose their data permanently
MEDUSA introduces a 4th option: intercept the encryption key at the moment the ransomware uses it, freeze the process, and decrypt everything.
Ransomware faces a paradox that MEDUSA exploits:
┌─────────────────────────────────────────────────────────────────┐
│ THE RANSOMWARE PARADOX │
│ │
│ To encrypt files, ransomware MUST: │
│ 1. Generate or receive encryption keys │
│ 2. Call a cryptographic function to encrypt │
│ 3. Access files on disk to read → encrypt → write │
│ │
│ All three steps happen IN MEMORY on the VICTIM'S MACHINE. │
│ If we can observe any of these steps, we win. │
│ │
│ The ransomware cannot encrypt without exposing its key │
│ in the same address space we can monitor. │
└─────────────────────────────────────────────────────────────────┘
The key insight: encryption keys must exist in process memory at the moment of encryption. No matter how sophisticated the ransomware, it cannot encrypt data without having the key accessible in RAM. MEDUSA hooks the exact moment this happens.
Almost all malware — including ransomware written in C, C++, Go, Rust, and Python — uses OpenSSL's libcrypto for encryption. Even custom implementations eventually call into system-provided crypto libraries for performance.
Ransomware Code System Library
───────────────── ──────────────────
encrypt_files() ──────────────────► EVP_EncryptInit_ex()
│
│ ← MEDUSA hooks HERE
│ with a BPF uprobe
│
│ Arguments in CPU registers:
│ RDI = cipher context
│ RSI = cipher type (AES-256)
│ RDX = engine
│ RCX = ★ KEY POINTER ★
│ R8 = IV pointer
│
▼
AES encryption happens
MEDUSA places a uprobe (userspace probe) on EVP_EncryptInit_ex inside libcrypto.so. When ANY process calls this function, MEDUSA's BPF program executes in kernel context, reads the 4th argument (the key pointer) from the RCX register, copies the 32-byte key from the process's memory, and stores it in a per-PID BPF hash map.
The ransomware literally hands us its key.
Detection alone isn't enough — we need to catch ransomware as early as possible. MEDUSA uses strategically placed honeypot files that act as trip wires:
Directory listing (alphabetical order):
────────────────────────────────────────
!00_Recovery_Index.dat ← Honeypot ALPHA (sorts first due to !)
0001_Annual_Report.xlsx ← Real user file
0002_Budget_Q4.xlsx ← Real user file
0003_Client_List.csv ← Real user file
1000_database_backup.xlsx ← Honeypot DECOY (looks like valuable data)
z_system_restore.dat ← Honeypot OMEGA (sorts last)
Why three honeypots?
Most ransomware processes files alphabetically (to be systematic). The three honeypots form a net:
| Honeypot | Position | Purpose |
|---|---|---|
!00_Recovery_Index.dat |
Sorts first (!) | Catches ransomware that starts from the top. Some ransomware skips .dat extensions, so... |
1000_database_backup.xlsx |
Sorts in the middle | The real trap. .xlsx extension makes it look like a high-value target. If the ransomware skipped the .dat file, it will still hit this one after encrypting a few real files. |
z_system_restore.dat |
Sorts last (z) | Catches ransomware that processes in reverse order. |
When ransomware attempts to write to or delete any honeypot, MEDUSA's LSM (Linux Security Module) hook:
- Denies the operation (returns
-EPERM) - Freezes the process (sends
SIGSTOP) - Alerts the daemon to extract keys
The honeypots are protected at the kernel level — the ransomware cannot modify or delete them, because the operation is blocked before it completes.
MEDUSA employs a strict 3-layer defense mechanism. If malware manages to bypass or damage the first layer, the subsequent layers act as traps and recovery safeguards to neutralize the threat and repair the damage.
The first line of defense is a network of invisible tripwires. MEDUSA places decoy files (honeypots) strategically within the file system. When ransomware attempts to encrypt or delete these files, MEDUSA's kernel-level LSM hooks instantly block the operation and freeze the malicious process.
- If it works: The ransomware is stopped before touching real user data.
- If it fails (or is bypassed): The ransomware begins encrypting real files, triggering Layer 2.
If the ransomware skips the honeypots and starts encrypting real files, it eventually must call standard cryptographic libraries (e.g., OpenSSL's libcrypto.so). MEDUSA places a hidden BPF uprobe on EVP_EncryptInit_ex. The moment the ransomware passes its encryption key to the library, MEDUSA quietly copies the 32-byte key from memory.
- If it works: We have the exact encryption key used by the malware. Even if files are damaged, we can perfectly decrypt them.
- If it fails (e.g., statically linked binary): We fall back to Layer 3.
For advanced malware that avoids dynamic libraries entirely, MEDUSA's extraction daemon optionally scans the frozen process's raw memory (/proc/PID/mem) to hunt for AES key schedules. Once any key is recovered (via Layer 2 or memory scan), the Standalone Decryptor automatically traverses the infected directories and restores all damaged files to their original state.
┌──────────────────────────────────────────────────────────────────────┐
│ USER SPACE │
│ │
│ ┌──────────────┐ ┌──────────────────┐ ┌──────────────────┐ │
│ │ Ransomware │ │ medusa_daemon │ │ medusa_decrypt │ │
│ │ (malicious) │ │ (orchestrator) │ │ (recovery tool) │ │
│ └──────┬───────┘ └────────┬─────────┘ └────────┬─────────┘ │
│ │ │ │ │
├─────────┼──────────────────────┼─────────────────────────┼────────────┤
│ │ KERNEL SPACE │ │
│ │ │ │ │
│ ▼ ▼ │ │
│ ┌──────────────┐ ┌──────────────────┐ │ │
│ │ libcrypto.so │ │ medusa.bpf.c │ │ │
│ │ │ │ │ │ │
│ │ EVP_Encrypt │◄────┤ LSM hooks: │ │ │
│ │ Init_ex() │ │ file_permission │ │ │
│ │ │ │ │ inode_unlink │ │ │
│ │ │(uprobe) │ inode_mkdir │ │ │
│ │ ▼ │ │ │ │ │
│ │ Key captured │ │ BPF Maps: │ │ │
│ │ to BPF map │ │ honeypot_inodes │ │ │
│ └──────────────┘ │ frozen_pids │ │ │
│ │ mkdir_events │ │ │
│ └──────────────────┘ │ │
│ │ │
│ ┌─────────────────────┐ │ │
│ │ Keys saved to file │◄───────────────┘ │
│ │ /var/lib/medusa/ │ │
│ │ keys/pid_XXX.txt │ │
│ └─────────────────────┘ │
└───────────────────────────────────────────────────────────────────────┘
File: medusa_kernel/medusa.bpf.c
Three LSM (Linux Security Module) hooks run inside the kernel:
SEC("lsm/file_permission")
int BPF_PROG(detect_honeypot_write, struct file *file, int mask)- Triggers on every file access system-wide
- Checks if the operation is a WRITE (
mask & 2) - Looks up the file's inode number in the
honeypot_inodesBPF map - If it's a honeypot: DENY the write (return
-EPERM), sendSIGSTOPto the process, record the PID infrozen_pids
SEC("lsm/inode_unlink")
int BPF_PROG(detect_honeypot_delete, struct inode *dir, struct dentry *dentry)- Same logic but for file deletion attempts
- Catches ransomware that tries to delete evidence
SEC("lsm/inode_mkdir")
int BPF_PROG(detect_mkdir, struct inode *dir, struct dentry *dentry, umode_t mode)- Catches ALL directory creation (any syscall variant)
- Stores the directory name in
mkdir_eventsmap - Daemon deploys honeypots in new directories automatically
File: EXTRACTOR/extractor-buffer.bpf.c
SEC("uprobe")
int capture_evp_encrypt_key(struct pt_regs *ctx)
{
void *key_ptr = (void *)PT_REGS_PARM4(ctx); // 4th arg = key
bpf_probe_read_user(buf->keys[idx], 32, key_ptr); // Copy 32 bytes
}This is a uprobe — a breakpoint placed on a userspace function. It's attached to EVP_EncryptInit_ex inside libcrypto.so at the correct offset (found via nm -D).
How it captures the key:
- On x86_64, function arguments are passed in CPU registers
- The 4th argument to
EVP_EncryptInit_exis the encryption key pointer - This pointer is in the
RCXregister, accessible viaPT_REGS_PARM4 bpf_probe_read_user()safely copies 32 bytes from the process's memory- The key is stored in a per-PID circular buffer BPF map
Critical: This happens at the entry point of the function — before encryption even begins. The key is captured the instant the ransomware tries to use it.
File: medusa_controller/main.c
The daemon ties everything together:
- Startup: Loads BPF programs, deploys honeypots, registers inode numbers
- Main loop (every 1 second):
- Polls
frozen_pidsmap for new detections - Polls
mkdir_eventsmap for new directories → deploy honeypots - Periodically verifies honeypot integrity
- Polls
- On detection: Extracts keys from BPF map → saves to file → sends notification
Key extraction has two plans:
| Plan | Method | How it works |
|---|---|---|
| A | BPF uprobe | Reads captured keys from the pid_key_buffers BPF map (most reliable) |
| B | Memory scan | Scans /proc/PID/mem for AES key schedules in memory (fallback for statically-linked binaries) |
File: EXTRACTOR/medusa_decrypt.c
After the daemon extracts keys, this tool recovers files:
./medusa_decrypt /var/lib/medusa/keys/pid_1234_keys.txt /home/user/It handles two common encrypted file formats:
| Format | Layout | Used by |
|---|---|---|
| Format 1 | [orig_size (8B)][IV (16B)][ciphertext] |
Many ransomware families |
| Format 2 | [IV (16B)][ciphertext] |
Simpler variants |
Validation: After decryption, the tool verifies correctness by:
- Checking magic bytes (PDF starts with
%PDF, PNG with\x89PNG, etc.) - Checking printable ratio for text files (>75% printable = valid)
- Checking entropy (encrypted data ≈ 8.0, normal data < 6.0)
Every ransomware needs to encrypt files. Whether it uses AES, ChaCha20, RSA, or any other algorithm, it must:
- Have the key in memory
- Call a crypto function
On Linux, >95% of software uses OpenSSL's libcrypto.so for this. Even Go, Rust, and Python programs typically link against system OpenSSL. MEDUSA hooks this shared bottleneck.
Timeline of a ransomware encryption:
┌──── Key generated or received
│
│ ┌── Key passed to EVP_EncryptInit_ex ◄── MEDUSA captures HERE
│ │
│ │ ┌── File encrypted
│ │ │
│ │ │ ┌── Key potentially destroyed
▼ ▼ ▼ ▼
─────●──●──●──●───────────────── time →
│ │
│ └── The key MUST be in a register (RCX) at this point
│ There is no way to avoid this.
│
└── Even if the key is encrypted in memory,
it must be decrypted before use.
No matter how advanced the ransomware's anti-analysis techniques, the key must exist in plaintext in a CPU register when passed to the encryption function. MEDUSA reads it at that exact moment.
The honeypot files:
- Look like normal user files (
.xlsx,.datextensions) - Have realistic file sizes and content
- Cannot be distinguished from real files by the ransomware
- Are protected by kernel-level LSM hooks — the ransomware cannot modify or delete them
Without MEDUSA:
File 1 ──► ENCRYPTED ✗
File 2 ──► ENCRYPTED ✗
File 3 ──► ENCRYPTED ✗
...
File N ──► ENCRYPTED ✗ (all files lost)
With MEDUSA:
!00_Recovery_Index.dat ──► SKIPPED (ransomware ignores .dat)
File 1 ──► encrypted ✗ (but key was captured!)
File 2 ──► encrypted ✗ (but key was captured!)
1000_database_backup.xlsx ──► 🛑 BLOCKED + FROZEN
File 3 ──► never reached ✓
File 4 ──► never reached ✓
...
File N ──► never reached ✓
Result: Files 1-2 encrypted but RECOVERABLE (we have the key)
Files 3-N never touched
MEDUSA doesn't use signatures (like antivirus software). It doesn't need to know what ransomware family is attacking. The detection is behavioral:
- Is something writing to a honeypot? → Malicious
- Is something calling
EVP_EncryptInit_ex? → Capture the key
This means MEDUSA catches zero-day ransomware that no antivirus has ever seen before.
MEDUSA/
├── medusa_kernel/ # BPF programs (kernel-level)
│ ├── medusa.bpf.c # LSM hooks: honeypot detection + mkdir monitor
│ └── Makefile
│
├── medusa_controller/ # Userspace daemon
│ ├── main.c # Orchestrator: key extraction, notifications
│ ├── honeypot_deployer.h # Honeypot file creation and deployment
│ └── Makefile
│
├── EXTRACTOR/ # Key extraction & recovery tools
│ ├── extractor-buffer.bpf.c # BPF uprobe: captures keys from EVP_EncryptInit_ex
│ ├── extractor.h # Plan A: read keys from BPF map
│ ├── extractor-keysched.h # Plan B: memory scan for key schedules
│ ├── medusa_decrypt.c # Standalone decryptor CLI tool
│ ├── decryptor.h # Decryption logic library
│ └── Makefile
│
├── lockbit_replica/ # Test ransomware simulation
│ └── demo/
│ ├── fake_ransom.c # Demo ransomware using OpenSSL
│ ├── setup_demo.sh # Creates test files
│ └── Makefile
│
├── Makefile # Top-level build
└── README.md # This file
# Debian/Ubuntu
sudo apt install clang llvm libbpf-dev linux-headers-$(uname -r) \
libssl-dev bpftool
# Fedora
sudo dnf install clang llvm libbpf-devel kernel-devel \
openssl-devel bpftool# Build everything
make
# Or build components individually
cd medusa_kernel && make # BPF programs
cd medusa_controller && make # Daemon
cd EXTRACTOR && make # Decryptor tool# Interactive mode (see all output)
sudo ./medusa_controller/medusa_daemon
# Daemon mode (background)
sudo ./medusa_controller/medusa_daemon -dYou should see:
[+] MEDUSA loaded! Watching for ransomware...
[+] Deployed 66 honeypots across 22 directories
[+] Registered 78 honeypot inodes for monitoring
[+] Found EVP_EncryptInit_ex at offset 0x241250
[+] Key capture attached to /usr/lib/.../libcrypto.so.3 at offset 0x241250
MEDUSA automatically:
- ❌ Blocks the write/delete operation
- 🧊 Freezes the process (SIGSTOP)
- 🔑 Extracts encryption keys
- 📢 Shows alert notification
- 💾 Saves keys to
/var/lib/medusa/keys/pid_XXXX_keys.txt
# Auto-find latest keys, scan /home
sudo ./EXTRACTOR/medusa_decrypt --auto
# Or specify keys file and directory
sudo ./EXTRACTOR/medusa_decrypt /var/lib/medusa/keys/pid_1234_keys.txt /home/user/cd lockbit_replica/demo
make # Build demo ransomware
./setup_demo.sh ~/test_files # Create test filesTerminal 1 — start MEDUSA:
sudo ./medusa_controller/medusa_daemonTerminal 2 — run fake ransomware:
cd lockbit_replica/demo
./fake_ransom ~/test_filesExpected result:
[SKIP] !00_Recovery_Index.dat ← Skipped (.dat extension)
[ENCRYPT] 0001_Annual_Report.xlsx ← Encrypted (key captured!)
[ENCRYPT] 0002_Budget_Q4.xlsx ← Encrypted (key captured!)
← 🛑 FROZEN when hitting honeypot!
Terminal 2 — recover files:
sudo ./EXTRACTOR/medusa_decrypt --auto ╔══════════════════════════════════════╗
║ Results: 2 recovered, 0 failed ║
╚══════════════════════════════════════╝
GPL-2.0 (required for eBPF programs)
MEDUSA — Because ransomware operators should be the ones who are petrified. 🐍