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!
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.
md5sum, read strings with strings, disassemble with objdump -d.vmem capture of the host → not needed for any question hereTimestamp note: No question asks for a timestamp, so there is no Forensic Timeline for this case.
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
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.
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]+/?$'
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.
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])"
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.
| Phase | Action |
|---|---|
| Execution | Insider runs the ubuntu-client ELF with the XOR key as its first argument |
| Defense Evasion | Configuration in .data is decoded at runtime with the repeating XOR key |
| Command and Control | Client contacts a DigitalOcean-hosted API to retrieve its key |
| Exfiltration | Files are sent to the same host through an upload route |
| Persistence | A systemd unit is written under /etc/systemd/system/ to keep the client running |
| Type | Value |
|---|---|
| MD5 | a2444b61b65be96fc2e65924dee8febd |
| XOR key | xGonnaGiveIt2Ya |
| URL (key retrieval) | hxxps[://]plankton-app-3qigq[.]ondigitalocean[.]app/connect |
| URL (upload) | hxxps[://]plankton-app-3qigq[.]ondigitalocean[.]app/upload/ |
| Service | ubuntu_running.service |
| Technique | ID | Description |
|---|---|---|
| Obfuscated Files or Information | T1027 | C2 URL and unit file XOR-encoded in .data with a runtime key |
| Application Layer Protocol: Web Protocols | T1071.001 | HTTPS API on a cloud app platform for key retrieval |
| Exfiltration Over C2 Channel | T1041 | File upload route on the same C2 host |
| Create or Modify System Process: Systemd Service | T1543.002 | ubuntu_running.service installed for persistence |
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.
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.