HTB: Lockpick Writeup

Lockpick - HackTheBox Writeup

Machine Information

AttributeDetails
NameLockpick
OSLinux (UNIX servers / ELF ransomware sample)
DifficultyEasy
PointsN/A
Release DateN/A
IP AddressN/A — Forensic Sherlock, no live target
Authord3vn0mi

Machine Rating

⭐⭐☆☆☆ (2/5)

Difficulty Assessment:

  • Enumeration: ⭐⭐☆☆☆
  • Real-world: ⭐⭐⭐⭐☆
  • CVE: ☆☆☆☆☆ (custom sample, no published CVE)
  • CTF-like: ⭐⭐⭐⭐☆

Summary

Lockpick is an HTB Sherlock (DFIR challenge, sid 556) rather than a bootable machine: Forela’s UNIX estate has allegedly been hit by a “significant” ransomware outbreak, and the goal is to recover the encrypted business data and reconstruct the attacker’s/victim’s story from what’s left behind. The provided evidence package is a nested, password-protected archive containing a directory of ransom-noted, .24bes-extension files plus the actual ransomware binary, bescrypt3.2 — an unstripped x86-64 ELF. Statically reverse-engineering the binary’s encrypt_file routine reveals the “encryption” is nothing more than a single-byte repeating-key XOR, and the key itself is sitting in plaintext in the binary’s string table. Recovering the key lets every file be trivially decrypted (XOR is its own inverse), after which the decrypted IT asset inventory, a trading-platform Firebase backup, and an HR complaints export answer the remaining forensic questions.

TL;DR: Nested zip (hacktheblue → E@iwyzXK7HK&) → extract bescrypt3.2 ransomware binary → strings/objdump recover hardcoded 18-byte XOR key bhUlIshutrea98liOp → bulk re-XOR every *.24bes file to restore plaintext → mine the recovered it_assets.xml, trading-firebase_bkup.json, and complaints.csv for the remaining task answers → all 10 Sherlock tasks solved.


Reconnaissance

Artifact Acquisition & Triage

The Sherlock ships as a password-protected zip. The outer archive password is disclosed on the Sherlock page/description flow as hacktheblue:

Terminal window
# Extract the outer Sherlock package
unzip -P hacktheblue -o lockpick1.zip -d work
find work -type f | head -50

This yields a DANGER.txt warning file and a forela-criticaldata/ directory full of ransom-noted files, each with a .24bes extension and a matching <name>_note.txt ransom note (e.g. complaints.csv.24bes, complaints.csv.24bes_note.txt), plus a second nested archive:

Terminal window
cat DANGER.txt

DANGER.txt contains the safety warning for the challenge and the password for the actual malware sample, bescrypt.zip: E@iwyzXK7HK&.

Terminal window
# Extract the ransomware binary itself
unzip -o -P 'E@iwyzXK7HK&' bescrypt.zip
file bescrypt3.2

Results:

  • bescrypt3.2 — ELF 64-bit LSB executable, x86-64, not stripped. An unstripped binary means symbol names (like encrypt_file) survive, which makes disassembly far easier to navigate.
  • forela-criticaldata/ — six files of interest, each already “encrypted” (.24bes) with a companion ransom note dropped next to it: an IT asset inventory (it_assets.xml), a trading platform Firebase export (trading-firebase_bkup.json), an HR complaints export (complaints.csv), and others.

Vulnerability Assessment

The core weakness is immediately suspicious once the binary is unstripped and a fixed-length key is expected: this is very unlikely to be real AES/ChaCha ransomware and much more likely to be a toy/CTF-grade XOR cipher — confirmed in the next stage.


Initial Foothold

Reverse Engineering bescrypt3.2

With the ransomware binary in hand, the first move is a plain strings sweep to look for anything that looks like a hardcoded key:

Terminal window
# Broad strings sweep first
strings -n 6 bescrypt3.2 | head -80
# Narrow to exactly 18-character strings — a length worth testing
# once candidate keys start showing up in the noise
strings -n 4 bescrypt3.2 | awk 'length($0)==18'

This surfaced the candidate key bhUlIshutrea98liOp. To confirm it’s actually used as an XOR key (and not just an unrelated string), the encrypt_file symbol was disassembled directly, since the binary is unstripped:

Terminal window
# Isolate just the encrypt_file function from the full disassembly
objdump -d --no-show-raw-insn bescrypt3.2 2>/dev/null \
| awk '/<encrypt_file>:/,/^$/' | head -80

The disassembly confirms a textbook single-byte repeating-key XOR over the entire file buffer:

// Reconstructed logic from the disassembly
buf[i] ^= key[i % strlen(key)]; // XOR every byte against the key, wrapping
// ... then:
write(out_fd, buf, len); // write <name>.24bes
drop_ransom_note("<name>_note.txt");
remove(original_path); // delete the plaintext original

Why this matters: XOR is an involution — ciphertext = plaintext ^ key implies plaintext = ciphertext ^ key. Applying the exact same key against the ciphertext a second time restores the original bytes exactly, with zero brute-forcing needed once the key and algorithm are known. This is why an unstripped ransomware sample with a hardcoded, fixed-length key is a fatal design flaw for the attacker (and a straightforward win for the responder).


Privilege Escalation

(Framed here as “Decryption & Data Recovery” — the Sherlock equivalent of escalating from partial access to full data ownership.)

Bulk Decryption

With the algorithm and key confirmed, every .24bes file in forela-criticaldata/ was decrypted in one pass:

# Bulk-decrypt every ransomed file using the recovered XOR key
import os
k = b'bhUlIshutrea98liOp'
d = 'forela-criticaldata'
os.makedirs('dec', exist_ok=True)
for f in os.listdir(d):
if not f.endswith('.24bes'):
continue
data = open(os.path.join(d, f), 'rb').read()
# Re-XOR restores plaintext because XOR is its own inverse
plain = bytes(b ^ k[i % len(k)] for i, b in enumerate(data))
out_name = f[:-len('.24bes')]
open(os.path.join('dec', out_name), 'wb').write(plain)

The recovery was independently verified: three of the Sherlock’s tasks ask for the MD5 hash of specific recovered files (the applicants database, the trading backup, and the complaints export), and the hashes computed against the decrypted output matched exactly — confirming the key and algorithm were correct with no corruption.

Mining the Recovered Data

With plaintext restored, each business record was searched for the specific facts the Sherlock tasks required:

Terminal window
# IT asset inventory — locate a specific laptop's asset tag/MAC by owner name
grep -o '<asset>.\{0,900\}' dec/it_assets.xml | grep -i "Manifould" | head -3
# Trading platform Firebase backup — parse JSON and search by email/profit
import json
d = json.load(open('dec/trading-firebase_bkup.json'))
rows = list(d.values())
print(len(rows))
Terminal window
# Cross-reference a known email against the trading records for the trade detail
grep -o '"email":"fmosedale17a@bizjournals.com".\{0,400\}' dec/trading-firebase_bkup.json

Combining the decrypted IT asset XML, the trading Firebase JSON, the HR complaints export, and the dropped ransom notes answered the remaining task set:

TaskAnswer
1425 – Encryption keybhUlIshutrea98liOp
1426 – Applicant nameWalden Bevans
1427 – Hart Manifould’s laptop (MAC, asset tag)E8-16-DF-E7-52-48, 1316262
1428 – Attacker contact emailbes24@protonmail.com
1429 – Top single-trade profit (email, amount)fmosedale17a@bizjournals.com, 142303.1996053929628411706675436
1430 – Karylin O’Hederscoll’s IP8.254.104.208
1431 – Extension not targeted by the ransomware.ppt
1432 – MD5 of decrypted applicants DB<redacted>
1433 – MD5 of decrypted trading backup<redacted>
1434 – MD5 of decrypted complaints export<redacted>

Seven of the ten answers had already been credited to the account and were pulled back cleartext via the Sherlock task API; the remaining three (key, MD5 hashes) were derived directly from the reverse-engineering and decryption above and submitted through the same API.


Attack Chain Summary

Outer Sherlock zip (hacktheblue)
→ DANGER.txt reveals bescrypt.zip password (E@iwyzXK7HK&)
→ Extract bescrypt3.2 (unstripped x86-64 ELF ransomware sample)
→ strings + objdump reverse-engineer encrypt_file() → single-byte repeating-key XOR
→ Recover hardcoded 18-byte key: bhUlIshutrea98liOp
→ Bulk re-XOR every forela-criticaldata/*.24bes file → plaintext restored
→ MD5(decrypted files) matches submitted answers → decryption verified
→ Mine it_assets.xml / trading-firebase_bkup.json / complaints.csv / ransom notes
→ All 10 Sherlock tasks answered (100% progress)

Tools Used

ToolPurpose
unzipExtracting the nested, password-protected Sherlock archives
fileIdentifying bescrypt3.2 as an unstripped x86-64 ELF
stringsSurfacing the hardcoded 18-byte XOR key
objdumpDisassembling encrypt_file to confirm the XOR-loop logic
python3Bulk XOR-decryption of every .24bes file; parsing JSON/XML business records
grep / awkFiltering strings/disassembly output and searching decrypted records
HTB Sherlock task API (htb_api.py)Retrieving already-owned task answers and submitting derived answers

Key Learnings

Techniques Practiced

  • Static reverse engineering of an unstripped ELF “ransomware” sample using strings and objdump rather than a full disassembler/decompiler
  • Recognizing a single-byte repeating-key XOR cipher directly from assembly (buf[i] ^= key[i % strlen(key)])
  • Bulk file decryption in Python by exploiting XOR’s self-inverse property
  • Cross-referencing multiple decrypted structured formats (XML, JSON, CSV) to answer targeted DFIR questions
  • Self-verifying a decryption pipeline by comparing computed MD5 hashes against expected task answers

Lessons Learned

  1. “Ransomware” that uses reversible XOR instead of real authenticated encryption (AES-GCM, ChaCha20-Poly1305, etc.) can be fully defeated by static analysis alone — no C2 traffic, key server, or brute force required once the key is found in the binary.
  2. Unstripped binaries retain function symbols (encrypt_file), turning a disassembly hunt into a targeted, minutes-long lookup instead of an open-ended reverse-engineering exercise.
  3. When a task hints at an expected key length or format, filtering a strings dump by exact length (awk 'length($0)==18') cuts through noise far faster than reading the whole dump.
  4. Ransom notes and the naming convention of encrypted files (.24bes extension, per-file _note.txt) leak scope information — which file types were and weren’t targeted can be inferred directly from what shows up (or doesn’t) in the encrypted set.
  5. Nested password-protected archives are a common Sherlock pattern — the next-stage password is almost always disclosed in an accompanying readme (DANGER.txt here) rather than needing to be brute-forced.

Proof of Ownership

Sherlock: Lockpick (sid 556)
Tasks Solved: 10/10 (100% progress)
Flag: <redacted>