HTB: Lockpick Writeup
Lockpick - HackTheBox Writeup
Machine Information
| Attribute | Details |
|---|---|
| Name | Lockpick |
| OS | Linux (UNIX servers / ELF ransomware sample) |
| Difficulty | Easy |
| Points | N/A |
| Release Date | N/A |
| IP Address | N/A — Forensic Sherlock, no live target |
| Author | d3vn0mi |
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:
# Extract the outer Sherlock packageunzip -P hacktheblue -o lockpick1.zip -d workfind work -type f | head -50This 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:
cat DANGER.txtDANGER.txt contains the safety warning for the challenge and the password for the actual malware sample, bescrypt.zip: E@iwyzXK7HK&.
# Extract the ransomware binary itselfunzip -o -P 'E@iwyzXK7HK&' bescrypt.zipfile bescrypt3.2Results:
bescrypt3.2— ELF 64-bit LSB executable, x86-64, not stripped. An unstripped binary means symbol names (likeencrypt_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:
# Broad strings sweep firststrings -n 6 bescrypt3.2 | head -80
# Narrow to exactly 18-character strings — a length worth testing# once candidate keys start showing up in the noisestrings -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:
# Isolate just the encrypt_file function from the full disassemblyobjdump -d --no-show-raw-insn bescrypt3.2 2>/dev/null \ | awk '/<encrypt_file>:/,/^$/' | head -80The disassembly confirms a textbook single-byte repeating-key XOR over the entire file buffer:
// Reconstructed logic from the disassemblybuf[i] ^= key[i % strlen(key)]; // XOR every byte against the key, wrapping// ... then:write(out_fd, buf, len); // write <name>.24besdrop_ransom_note("<name>_note.txt");remove(original_path); // delete the plaintext originalWhy 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 keyimport 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:
# IT asset inventory — locate a specific laptop's asset tag/MAC by owner namegrep -o '<asset>.\{0,900\}' dec/it_assets.xml | grep -i "Manifould" | head -3# Trading platform Firebase backup — parse JSON and search by email/profitimport json
d = json.load(open('dec/trading-firebase_bkup.json'))rows = list(d.values())print(len(rows))# Cross-reference a known email against the trading records for the trade detailgrep -o '"email":"fmosedale17a@bizjournals.com".\{0,400\}' dec/trading-firebase_bkup.jsonCombining the decrypted IT asset XML, the trading Firebase JSON, the HR complaints export, and the dropped ransom notes answered the remaining task set:
| Task | Answer |
|---|---|
| 1425 – Encryption key | bhUlIshutrea98liOp |
| 1426 – Applicant name | Walden Bevans |
| 1427 – Hart Manifould’s laptop (MAC, asset tag) | E8-16-DF-E7-52-48, 1316262 |
| 1428 – Attacker contact email | bes24@protonmail.com |
| 1429 – Top single-trade profit (email, amount) | fmosedale17a@bizjournals.com, 142303.1996053929628411706675436 |
| 1430 – Karylin O’Hederscoll’s IP | 8.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
| Tool | Purpose |
|---|---|
unzip | Extracting the nested, password-protected Sherlock archives |
file | Identifying bescrypt3.2 as an unstripped x86-64 ELF |
strings | Surfacing the hardcoded 18-byte XOR key |
objdump | Disassembling encrypt_file to confirm the XOR-loop logic |
python3 | Bulk XOR-decryption of every .24bes file; parsing JSON/XML business records |
grep / awk | Filtering 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
stringsandobjdumprather 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
- “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.
- 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. - When a task hints at an expected key length or format, filtering a
stringsdump by exact length (awk 'length($0)==18') cuts through noise far faster than reading the whole dump. - Ransom notes and the naming convention of encrypted files (
.24besextension, 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. - Nested password-protected archives are a common Sherlock pattern — the next-stage password is almost always disclosed in an accompanying readme (
DANGER.txthere) rather than needing to be brute-forced.
Proof of Ownership
Sherlock: Lockpick (sid 556)Tasks Solved: 10/10 (100% progress)Flag: <redacted>