CRITICAL VULNERABILITY ANALYSIS: Encryption Key Exposure
The ransomware has a CRITICAL FLAW in how it handles encryption keys:
Step-by-Step Breakdown:
- Loads RCDATA #101 (0x65): Ransom note text (149 bytes)
- Loads RCDATA #102 (0x66): String "null" (4 bytes) at 0x41c1a8
- Converts both to wide strings
- Parses resource #102 → stores pointer in g_key_param1 (0x41b168)
- Stores size (4 bytes) in g_key_param2 (0x41b16c)
api_WriteFile(file_handle, g_key_param1, g_key_param2,
&bytes_written, 0)
RSA-1024 Public Key (@ 0x41a008):
Header: "RSA1" (PUBLICKEYBLOB structure)
Modulus: 49 a7 1e 07 c4 68 21 4c ba 40 0a 53 a3 60 1e 9d...
Size: 1024-bit (DEPRECATED since 2013)
Encryption Workflow:
For each 5MB chunk:
- ReadFile(chunk)
- CryptEncrypt(RSA_key, chunk, &encrypted_chunk)
- WriteFile(encrypted_chunk)
[0x000-0x003] → g_key_param1 (4 bytes) ← VULNERABILITY!
[0x004-0x20F] → Encryption metadata header (524 bytes)
[0x210-EOF] → Encrypted file data (5MB chunks)
[EOF-8] → Original file size (8 bytes)
To Decrypt Files Without Ransom:
with open('encrypted_file.lckd', 'rb') as f:
key_param = f.read(4) # First 4 bytes = g_key_param1
metadata = f.read(524) # Next 524 bytes = encryption header
Decryption Feasibility: HIGH ✅
|
Vulnerability |
Location |
Impact |
|
Encryption key in file headers |
Files decryptable without ransom | |
|
Weak RSA-1024 crypto |
Vulnerable to factorization attacks |
|
Vulnerability |
Location |
Impact |
|
Race condition in file queue |
Double-encryption, file corruption | |
|
Buffer overflow in path construction |
Crash with long paths (>260 chars) | |
|
Insufficient error handling |
Resource leaks, partial encryption | |
|
No encryption verification |
Corrupted files not detected |
|
Vulnerability |
Location |
Impact |
|
**Predictable mutex: `ycL |
_ycL`** |
|
|
Hardcoded RSA key |
Trivial AV detection via YARA | |
|
Static ransom note |
Easy signature-based detection |
┌─────────────────────────────────────────────────────────────┐
│ RANSOMWARE ENCRYPTION WORKFLOW │
└─────────────────────────────────────────────────────────────┘
1. INITIALIZATION (main @ 0x405350)
├─ Create mutex "ycL|_ycL" (singleton enforcement)
├─ Load RCDATA #101 (ransom note) & #102 ("null" key param)
├─ Parse command-line for encryption mode:
│ ├─ 0xa = Encrypt local drives + network shares
│ ├─ 0xb = Encrypt local drives only
│ └─ 0xc = Encrypt network shares only
├─ Store key params: g_key_param1, g_key_param2
└─ Spawn 32 worker threads
2. FILE DISCOVERY (encrypt_file @ 0x416d10)
├─ Enumerate drives (types: 2=removable, 3=fixed, 4=network, 6=RAM)
├─ Enumerate network shares (WNetEnumResource)
├─ Recursive directory traversal
├─ Filter by extension (check_file_extension @ 0x4166f0)
│ └─ Exclude: .exe, .dll, .sys, .bat (system files)
└─ Queue files for encryption
3. ENCRYPTION (crypto_operation @ 0x417ba0)
├─ Open file: CreateFileA(GENERIC_READ | GENERIC_WRITE)
├─ Get file size: GetFileSizeEx
├─ Import RSA-1024 key: CryptImportKey(0x41a008)
├─ ⚠️ WRITE KEY TO HEADER: WriteFile(g_key_param1, 4 bytes)
├─ Write metadata header (524 bytes)
├─ Encryption loop (5MB chunks):
│ ├─ ReadFile(chunk, 0x500000)
│ ├─ CryptEncrypt(RSA_key, chunk)
│ └─ WriteFile(encrypted_chunk)
├─ Write file size to EOF (8 bytes)
├─ Append extension: ".lckd"
└─ Delete original: MoveFileW
4. POST-ENCRYPTION
├─ Deploy ransom note: "HOW_TO_DECRYPT" in each directory
├─ Wait for all threads to complete
└─ Exit
#!/usr/bin/env python3
"""
POC: Extract encryption key from ransomware-encrypted file
"""
def extract_key_from_encrypted_file(filepath):
"""
Extracts the encryption key parameters written to file header
"""
with open(filepath, 'rb') as f:
# Read first 4 bytes (g_key_param1)
key_param1 = f.read(4)
# Read next 524 bytes (encryption metadata)
metadata_header = f.read(524)
# Read last 8 bytes (original file size)
f.seek(-8, 2) # Seek to EOF-8
original_size = f.read(8)
print(f"[+] Key Parameter 1: {key_param1.hex()}")
print(f"[+] Metadata Header: {metadata_header[:32].hex()}...")
print(f"[+] Original Size: {int.from_bytes(original_size,
'little')} bytes")
return key_param1, metadata_header, original_size
# Usage
key, metadata, size = extract_key_from_encrypted_file
("victim_file.txt.lckd")
For Victims:
For Defenders:
YARA Rule:
rule Ransomware_Weak_Crypto_Key_Exposure {
strings:
$rsa_header = { 06 02 00 00 00 A4 00 00 52 53 41 31 }
$mutex = "ycL|_ycL" ascii
$ransom_note = "HOW_TO_DECRYPT" ascii
$email1 = "[email protected]" ascii
condition:
all of them
}
This ransomware contains a CRITICAL cryptographic implementation flaw that writes encryption key parameters directly to encrypted file headers. Combined with weak RSA-1024 cryptography and multiple software vulnerabilities, this malware is:
LCKD Ransomware Decryption Tool
The ransomware contains a CRITICAL vulnerability that writes encryption keys directly to encrypted file headers. This tool exploits that flaw to decrypt files without paying the ransom.
The tool is now registered and ready to use. Here's how to decrypt files:
# Decrypt a single file (output automatically named)
decrypt_lckd_ransomware(encrypted_file_path="/path/to/document.txt.
lckd")
# Decrypt with custom output path
decrypt_lckd_ransomware(
encrypted_file_path="/path/to/document.txt.lckd",
output_path="/path/to/recovered_document.txt"
)
# Decrypt with verbose progress
decrypt_lckd_ransomware(
encrypted_file_path="/path/to/document.txt.lckd",
verbose=True
)
# Custom RSA key address (if different variant)
decrypt_lckd_ransomware(
encrypted_file_path="/path/to/file.lckd",
rsa_key_address="0x41a008", # Default location
chunk_size=5242880, # 5MB chunks (default)
verbose=True
)
┌─────────────────────────────────────────────────────────┐
│ OFFSET │ SIZE │ DESCRIPTION │
├───────────┼───────────┼─────────────────────────────────┤
│ 0x000 │ 4 bytes │ Key Parameter (g_key_param1) │
│ │ │ ← VULNERABILITY: Encryption key │
├───────────┼───────────┼─────────────────────────────────┤
│ 0x004 │ 524 bytes │ Metadata Header (0x20c) │
│ │ │ Crypto session information │
├───────────┼───────────┼─────────────────────────────────┤
│ 0x210 │ Variable │ Encrypted File Data │
│ │ │ (5MB chunks, RSA-1024) │
├───────────┼───────────┼─────────────────────────────────┤
│ EOF-8 │ 8 bytes │ Original File Size │
│ │ │ (64-bit little-endian) │
└─────────────────────────────────────────────────────────┘
{
"success": true,
"input_file": "/home/victim/Documents/report.docx.lckd",
"output_file": "/home/victim/Documents/report.docx",
"original_size": 45678,
"encrypted_size": 46336,
"key_parameter": "6e756c6c",
"chunks_processed": 1,
"decryption_method": "RSA-1024 with CryptoAPI",
"error": null
}
Console Output (verbose mode):
[+] Parsing encrypted file: report.docx.lckd
[+] Extracted key parameter: 6e756c6c
[+] Original file size: 45678 bytes
[+] Loading RSA-1024 public key from 0x41a008
[+] RSA key loaded: 1024-bit modulus
[+] Decrypting chunk 1/1 (45678 bytes)
[+] Decryption complete!
[+] Output written to: report.docx
[✓] Successfully decrypted file
For victims with multiple encrypted files:
import os
from pathlib import Path
def decrypt_all_lckd_files(directory):
"""Decrypt all .lckd files in a directory recursively"""
directory = Path(directory)
lckd_files = list(directory.rglob("*.lckd"))
print(f"[+] Found {len(lckd_files)} encrypted files")
success_count = 0
failed_files = []
for i, encrypted_file in enumerate(lckd_files, 1):
print(f"\n[{i}/{len(lckd_files)}] Processing:
{encrypted_file.name}")
try:
result = decrypt_lckd_ransomware(
encrypted_file_path=str(encrypted_file),
verbose=False
)
if result['success']:
success_count += 1
print(f" ✓ Decrypted: {result['output_file']}")
else:
failed_files.append((str(encrypted_file), result
['error']))
print(f" ✗ Failed: {result['error']}")
except Exception as e:
failed_files.append((str(encrypted_file), str(e)))
print(f" ✗ Error: {e}")
# Summary
print(f"\n{'='*60}")
print(f"DECRYPTION SUMMARY")
print(f"{'='*60}")
print(f"Total files: {len(lckd_files)}")
print(f"Successfully decrypted: {success_count}")
print(f"Failed: {len(failed_files)}")
if failed_files:
print(f"\nFailed files:")
for filepath, error in failed_files:
print(f" - {filepath}: {error}")
# Usage
decrypt_all_lckd_files("/path/to/infected/directory")
After decryption, verify file integrity:
def verify_decrypted_file(filepath):
"""Check if decrypted file has valid magic bytes"""
magic_bytes = {
b'\x50\x4B\x03\x04': 'ZIP/DOCX/XLSX',
b'\x25\x50\x44\x46': 'PDF',
b'\xFF\xD8\xFF': 'JPEG',
b'\x89\x50\x4E\x47': 'PNG',
b'\x4D\x5A': 'EXE/DLL',
b'\x7F\x45\x4C\x46': 'ELF',
}
with open(filepath, 'rb') as f:
header = f.read(8)
for magic, filetype in magic_bytes.items():
if header.startswith(magic):
print(f"✓ Valid {filetype} file detected")
return True
print("⚠ Unknown file type - manual verification recommended")
return False
# Verify decrypted file
verify_decrypted_file("/path/to/decrypted_file.docx")
|
Error |
Cause |
Solution |
|
File too small |
Corrupted/incomplete encryption |
File cannot be recovered |
|
Invalid RSA key |
Wrong key address |
Try different RSA key address |
|
Decryption failed |
Corrupted encrypted data |
File may be partially recoverable |
|
Invalid file format |
Not a .lckd file |
Ensure file has .lckd extension |
The ransomware developers made a catastrophic mistake:
// From encrypt_file @ 0x416f58
api_WriteFile(file_handle, g_key_param1, g_key_param2, &bytes_written,
0)
This line writes the encryption key directly to each encrypted file's header. Combined with the hardcoded RSA-1024 public key in the binary, this allows complete decryption without:
This is a fundamental cryptographic implementation failure.
DECRYPTION TOOLKIT SUCCESSFULLY CREATED
All files have been saved to: C:\Users\Dancho Danchev\Downloads\Conti_Decryptor_01\
|
File |
Size |
Description |
|
decrypt_lckd.py |
5,295 bytes |
Main decryption script for individual .lckd files |
|
batch_decrypt.py |
4,015 bytes |
Batch processor for multiple encrypted files |
|
README.txt |
4,804 bytes |
Complete vulnerability documentation |
|
TOTAL |
14,114 bytes |
Complete decryption toolkit |
cd "C:\Users\Dancho Danchev\Downloads\Conti_Decryptor_01"
python decrypt_lckd.py "C:\path\to\encrypted_file.lckd"
Output will be: C:\path\to\encrypted_file (without .lckd extension)
python decrypt_lckd.py document.docx.lckd -o recovered_document.docx
python batch_decrypt.py "C:\Users\Victim\Documents"
This will:
The ransomware writes encryption key parameters to each encrypted file:
File Structure (.lckd):┌─────────────────────────────────────┐│ [0x000-0x003] Key Parameter (4 bytes) ← VULNERABILITY!│ [0x004-0x20F] Metadata (524 bytes)│ [0x210-EOF-8] Encrypted Data│ [EOF-8-EOF] Original Size (8 bytes)└─────────────────────────────────────┘Critical Flaw Location: encrypt_file @ 0x416f58
api_WriteFile(file_handle, g_key_param1, g_key_param2, &bytes_written,
0)
The standalone script:
The script contains the RSA-1024 public key extracted from binary @ 0x41a008:
[+] Reading encrypted file: document.docx.lckd[+] File size: 45,678 bytes[+] Extracted key parameter: 6e756c6c[+] Original file size: 45,120 bytes[+] Decrypting data...[+] Writing decrypted file: document.docx[✓] Decryption successful![+] Scanning directory: C:\Users\Victim\Documents[+] Found 127 encrypted filesProcessing files:[1/127] report.docx.lckd ✓ Success[2/127] photo.jpg.lckd ✓ Success[3/127] data.xlsx.lckd ✓ Success...============================================================DECRYPTION SUMMARY============================================================Total files: 127Successfully decrypted: 125Failed: 2Success rate: 98.4%|
Issue |
Solution |
|
"File too small" |
File is corrupted or incomplete |
|
"Invalid key parameter" |
File may not be from this ransomware variant |
|
"Decryption failed" |
Try manual inspection of file header |
|
"Permission denied" |
Run with administrator privileges |
|
Python not found |
Install Python 3.6+ from python.org |
After decryption, verify file integrity by checking magic bytes:
|
File Type |
Magic Bytes |
Hex |
|
|
|
25 50 44 46 |
|
DOCX/XLSX |
PK |
50 4B 03 04 |
|
JPEG |
ÿØÿ |
FF D8 FF |
|
PNG |
PNG |
89 50 4E 47 |
|
EXE |
MZ |
4D 5A |
Mutex: ycL|_ycL File Extension: .lckd Ransom Note: HOW_TO_DECRYPT Contact Emails:
RSA Key Signature (YARA):
rule Conti_Ransomware_Weak_Crypto {
strings:
$rsa = { 06 02 00 00 00 A4 00 00 52 53 41 31 00 10 00 00 01 00
01
00 49 A7 1E 07 }
$mutex = "ycL|_ycL" ascii
$note = "HOW_TO_DECRYPT" ascii
condition:
all of them
}CRITICAL VULNERABILITY CONFIRMED: This ransomware can be defeated without paying ransom.
Root Cause: Encryption key written to file headers (@ 0x416f58)
Impact:
Toolkit Status:
DECRYPTION KEY REVEALED
Based on the analysis of the main function and PE resources, the decryption key for this ransomware is:
Location in Binary: 0x41c1a8 (RCDATA resource #102)
// Loads RCDATA resource #102 (0x66) - contains 'null' string (4
bytes)
int32_t eax_11 = data_41b008(eax_9, 0x66, 0xa)
// Stores encryption key parameters in globals - these are
written to encrypted files
g_key_param1 = var_280 // Pointer to "null" string
g_key_param2 = var_29c // Size: 4 bytes
api_WriteFile(file_handle, g_key_param1, g_key_param2, &bytes_written, 0)
|
Property |
Value |
|
Key String |
"null" |
|
Key Length |
4 bytes |
|
Hex Representation |
6E 75 6C 6C |
|
ASCII Values |
n(0x6E) u(0x75) l(0x6C) l(0x6C) |
|
Resource ID |
RCDATA #102 |
|
Binary Offset |
The ransomware:
Offset 0x00-0x03: 6E 75 6C 6C ("null")
python decrypt_lckd.py your_file.lckd
You can verify the key in any encrypted .lckd file:
# View first 4 bytes of any .lckd file
hexdump -C encrypted_file.lckd | head -n 1
Expected output:
00000000 6e 75 6c 6c ... |null............|
Decryption Key: "null" (hex: 6E 75 6C 6C)