Lockpick 3.0 | HTB Forensics (Hard)

3 February 2026 | 3 min read | justruss.tech

Lockpick 3.0 is a Hard-rated HackTheBox forensics challenge involving a ransomware infection. You are given a disk image and asked to recover five flags. The encryption scheme looks custom at first glance but turns out to be a textbook implementation mistake that makes full recovery possible.

Initial image triage

sudo mount -o ro,loop,offset=$((512*2048)) lockpick.img /mnt/lockpick
find /mnt/lockpick/Users -newer /mnt/lockpick/Windows/System32/ntoskrnl.exe \
  -not -path "*/AppData/Local/Temp/Low/*" 2>/dev/null | head -20

The ransomware binary is still in AppData\Local\Temp as svchost32.exe, named to blend in with the legitimate svchost.exe processes in System32.

Unpacking the binary

die svchost32.exe
# PE32: compiler: Python 3.x (PyInstaller)

python3 pyinstxtractor.py svchost32.exe
uncompyle6 svchost32.exe_extracted/svchost32.pyc > svchost32.py

The encryption flaw

The decompiled source shows the key derivation:

import hashlib
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad

VICTIM_ID = open("C:\\ProgramData\\victim_id.txt").read().strip()

def get_key_iv(victim_id):
    key = hashlib.sha256(victim_id.encode()).digest()
    iv  = hashlib.md5(victim_id.encode()).digest()
    return key, iv

The victim ID is stored in plaintext on disk and is also in the ransom note. Both the AES key and the IV are deterministically derived from that value alone with no additional entropy. Knowing the victim ID means knowing the key.

Decrypting the files

python3 << EOF
import hashlib, glob
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad

victim_id = open("/mnt/lockpick/ProgramData/victim_id.txt").read().strip()
key = hashlib.sha256(victim_id.encode()).digest()
iv  = hashlib.md5(victim_id.encode()).digest()

for locked in glob.glob("/mnt/lockpick/Users/victim/Documents/*.locked"):
    with open(locked, "rb") as f:
        data = f.read()
    cipher = AES.new(key, AES.MODE_CBC, iv)
    try:
        plain = unpad(cipher.decrypt(data), AES.block_size)
        with open(locked.replace(".locked", ""), "wb") as f:
            f.write(plain)
        print(f"Decrypted: {locked}")
    except Exception as e:
        print(f"Failed {locked}: {e}")
EOF

Flags 1 through 4 are in the decrypted document contents. Flag 5 is in the Windows registry run key written by the ransomware during execution, recoverable by parsing the NTUSER.DAT hive with regipy.

The lesson

Deterministic key derivation from attacker-visible inputs is a fundamental ransomware design flaw. A properly implemented scheme generates a random symmetric key per victim, encrypts that key with the attacker’s public RSA key, and discards the symmetric key. Without the attacker’s private key decryption is impossible. Using the victim ID as the sole entropy source, stored on the victim’s own machine, makes the entire scheme trivially reversible once you understand the implementation.