// HackTheBox  ·  writeup

Lockpick3.0

HackTheBox Hard md5sumstringsobjdumpPython
Malware Analysis

Scenario

The threat actors of the Lockpick variant of Ransomware seem to have increased their skillset. Thankfully on this occasion they only hit a development, non production server. We require your assistance performing some reverse engineering of the payload in addition to some analysis of some relevant artifacts. Interestingly we can’t find evidence of remote access so there is likely an insider threat… Good luck!

Executive Summary

A Lockpick ransomware operator ran a Linux ELF client on a development server. The binary hides its configuration behind a repeating XOR, with the key supplied on the command line instead of stored in the file. Decoding the embedded data section reveals a cloud-hosted C2 base URL, separate endpoints for key retrieval and file upload, and a complete systemd unit file. The malware writes that unit to disk to survive reboots. The whole chain was recoverable statically; the memory image in the case was not needed.

Artifacts

Timestamp note: No question asks for a timestamp, so there is no Forensic Timeline for this case.

Execution

Q1 — Please confirm the file hash of the malware? (MD5)

Hash the ELF directly. The format mask ends in a hex digit, which fits a plain 32-character MD5 of the whole file.

md5sum ubuntu-client
Click flag to reveal a2444b61b65be96fc2e65924dee8febd

Defense Evasion

Q2 — Please confirm the XOR string utilised by the attacker for obfuscation?

strings on the ELF shows no URLs, domains or unit-file text, so the configuration is encoded. Disassembling the decode routine with objdump -d shows the callers pass argv[1] as the XOR key, so the key is not stored in the binary as a constant. The encoded blob sits in .data at file offset 0x5000. Recover the key with known plaintext: the decoded data must start with https:// and contain .ondigitalocean.app/, and XORing those guesses against the ciphertext exposes the repeating key. Decoding the whole blob with the recovered key produces readable output, including a systemd unit whose ExecStart line passes the same key to the binary, which confirms it.

objdump -d ubuntu-client | less
python3 -I -c "
d=open('ubuntu-client','rb').read()[0x5000:]
k=b'<recovered key>'
print(bytes(b^k[i%len(k)] for i,b in enumerate(d[:600])))"

Tip: Known plaintext beats brute force here. The protocol prefix and the hosting provider’s domain suffix give enough aligned bytes to read the key straight off.

Click to reveal answer xGonnaGiveIt2Ya

Command and Control

Q3 — What is the API endpoint utilised to retrieve the key?

From Q2 the decoded .data section gives the C2 base URL on a DigitalOcean app domain. The key-retrieval route is a path in read-only data, appended to that base. Decode, then search the output and the .rodata strings for the route used when the client asks the server for its encryption key. The answer is the base URL plus that path.

strings -a ubuntu-client | grep -E '^/[a-z]+/?$'
Click flag to reveal https://plankton-app-3qigq.ondigitalocean.app/connect

Exfiltration

Q4 — What is the API endpoint utilised for upload of files?

Same method as Q3: the upload route is another path string in .rodata, joined to the decoded base URL. Submit it exactly as the code builds it.

strings -a ubuntu-client | grep -i upload

Gotcha: The route ends with a trailing slash, and the platform compares the string exactly. Dropping the slash, or guessing a share/ prefix, is rejected.

Click to reveal answer https://plankton-app-3qigq.ondigitalocean.app/upload/

Persistence

Q5 — What is the name of the service created by the malware?

The decoded blob from Q2 contains the unit file content and the path the malware writes it to under /etc/systemd/system/. The unit’s file name is the service name.

python3 -I -c "
d=open('ubuntu-client','rb').read()[0x5000:]
k=b'<recovered key>'
print(bytes(b^k[i%len(k)] for i,b in enumerate(d)).split(b'\0')[:20])"
Click flag to reveal ubuntu_running.service
Q6 — What is the technique ID utilised by the attacker for persistence?

Installing a unit file in /etc/systemd/system/ so the payload restarts at boot is Create or Modify System Process: Systemd Service. This comes from reference knowledge of ATT&CK, not from a further artifact.

Click to reveal answer T1543.002

Attack Summary

PhaseAction
ExecutionInsider runs the ubuntu-client ELF with the XOR key as its first argument
Defense EvasionConfiguration in .data is decoded at runtime with the repeating XOR key
Command and ControlClient contacts a DigitalOcean-hosted API to retrieve its key
ExfiltrationFiles are sent to the same host through an upload route
PersistenceA systemd unit is written under /etc/systemd/system/ to keep the client running

IOCs

TypeValue
MD5a2444b61b65be96fc2e65924dee8febd
XOR keyxGonnaGiveIt2Ya
URL (key retrieval)hxxps[://]plankton-app-3qigq[.]ondigitalocean[.]app/connect
URL (upload)hxxps[://]plankton-app-3qigq[.]ondigitalocean[.]app/upload/
Serviceubuntu_running.service

MITRE ATT&CK

TechniqueIDDescription
Obfuscated Files or InformationT1027C2 URL and unit file XOR-encoded in .data with a runtime key
Application Layer Protocol: Web ProtocolsT1071.001HTTPS API on a cloud app platform for key retrieval
Exfiltration Over C2 ChannelT1041File upload route on the same C2 host
Create or Modify System Process: Systemd ServiceT1543.002ubuntu_running.service installed for persistence

Defender Takeaways

A key passed on the command line keeps the payload opaque on disk. The ELF holds only ciphertext, so strings and static IOC scanning find nothing useful. Alert on unknown ELFs launched with a long, random-looking first argument, and capture process command lines on servers.

Known plaintext defeats a repeating XOR quickly. URLs and unit files have predictable prefixes and suffixes, so analysts can recover the key without running the sample. Build this into triage, because it yields the C2 and persistence details safely.

Cloud app platforms make C2 blocking hard by IP. The endpoints sit on a shared DigitalOcean app domain. Egress filtering and proxy logging by hostname, with alerts on servers talking to *.ondigitalocean.app, is the practical control, especially for a development host with no reason to do so.

New unit files in /etc/systemd/system/ are high-signal. A service named to resemble a normal Ubuntu component but created outside package management should trigger an alert. File integrity monitoring on that directory catches this persistence directly.

Conclusion

The payload kept every useful indicator encrypted until it was given its key at launch, but the key, the C2 routes and the persistence unit all fell out of the file without executing it. The sharpest lesson is that a static read of the data section and one known-plaintext guess were enough to produce the full set of IOCs.