// HackTheBox  ·  writeup

Harvest Ransomware

HackTheBox Easy objdumpstringsnm
Malware Analysis

Scenario

Perform static analysis of the provided binary to uncover its configuration, encryption logic, signal handling, and self-deletion behavior. Use IDA Pro or Ghidra to answer the questions. Do not execute the sample.

Executive Summary

The sample is a Linux ransomware binary built around a hardcoded four-digit victim ID. On start it reads the local account database, writes the collected host details to a temp file named after that ID, and uploads the result over FTP after normalising filenames. A command-line option table supplies the extensions to encrypt, and a signal handler can either confirm or abort the directory traversal. Files are encrypted in fixed-size chunks, a ransom note pointing to a Tor hidden service is dropped in the home directory, and the binary unlinks itself afterwards unless the operator opts out. All of it is recoverable statically from the disassembly and .rodata.

Artifacts

Tip: The binary keeps its function symbols (lp_generate_id, lp_save_system_info, lp_signal_handler, lp_encrypt_handler, lp_upload_handler). nm gives the map; disassemble each function instead of reading main top to bottom.

Execution

Q1 — What is the hardcoded 4-digit victim ID embedded in the binary and used to name its on-disk artifacts?

The ID is a string literal in .rodata that main copies into its id variable, and every later filename is built from it. Dump .rodata and find the four-digit string, then confirm in main that the address is the one stored into id.

objdump -s -j .rodata harvest
Click flag to reveal 1606
Q5 — What printf-style format string does the ID-generation function use to serialise its random value into the 5-byte buffer it allocates?

Disassemble lp_generate_id. It allocates 5 bytes, takes rand() modulo 10000 and calls snprintf with a size of 5. The format string address loaded before the call points into .rodata. Four digits plus the terminator fills the buffer exactly, which explains the width. This function is the generic ID generator; the ID in Q1 is the one actually hardcoded in main.

objdump -d harvest --disassemble=lp_generate_id
Click flag to reveal %04d

Discovery

Q4 — What system file does the malware read to enumerate user accounts on the victim host?

The system-information routine opens the local account database. The path appears as a string in .rodata next to the other collection strings and is passed straight to the file open call.

strings -a harvest | grep -E "^/(etc|tmp|proc)"
Click to reveal answer /etc/passwd

Collection

Q2 — What is the full absolute path of the file where the malware writes collected system information before exfiltration?

From Q1 the ID is 1606. lp_save_system_info builds its output name with sprintf(filename, "/tmp/%s.txt", id) and opens it for writing, so the path is the format string with the ID substituted. The disassembly shows the format and the fopen on the result.

objdump -d harvest --disassemble=lp_save_system_info
Click to reveal answer /tmp/1606.txt

Exfiltration

Q10 — What character is used to replace spaces in filenames before FTP upload?

lp_upload_handler calls lp_replace(name, ' ', 0x5f) before building the FTP target. The third argument is the replacement byte, 0x5f, which is the underscore.

objdump -d harvest --disassemble=lp_upload_handler
Click to reveal answer _

Impact

Q3 — Which command-line option supplies the list of file extensions the ransomware targets for encryption? Answer with the option name only, without the leading dashes.

The option table in .rodata pairs each long option with a help string. The entry described as target file extensions is the one that feeds the encryption filter.

objdump -s -j .rodata harvest
Click flag to reveal exts
Q7 — What is the chunk size in bytes passed to read() during the per-file encryption loop?

In lp_encrypt_handler the loop reads each file with read(fd, buf, 0x1000) before encrypting the buffer and writing it back. 0x1000 is 4096.

objdump -d harvest --disassemble=lp_encrypt_handler
Click flag to reveal 4096
Q6 — When the confirm flag is nonzero and quit is clear, what signal number causes the malware to clear confirm instead of requesting traversal to stop?

lp_signal_handler checks confirm, then compares the signal number against a constant. A match clears confirm; any other signal falls through to the path that sets the stop request. The compare is against 2, which is SIGINT.

objdump -d harvest --disassemble=lp_signal_handler
Click to reveal answer 2
Q8 — What .onion hostname string is embedded in the ransom note the sample writes to the victim's home directory?

The ransom note text is a single string in .rodata, so strings finds the hidden-service address directly.

strings -a harvest | grep -i onion
Click to reveal answer kd24u5e4ggp2m4k4w23lm5fu1nurmme6ugrwi9btachx83lpw1t6qlxf.onion

Defense Evasion

Q9 — What command-line flag must be passed to PREVENT the malware from deleting itself after execution? Include the leading dashes.

A destruct boolean defaults to true, and at the end of the run main calls unlink(argv[0]) when it is set. The option table has a flag that toggles it off, so self-deletion is the default and the flag is the opt-out. This is the reverse of what the name suggests: passing it does not trigger destruction, it prevents it.

objdump -d harvest --disassemble=main

Gotcha: The flag name reads like an instruction to self-destruct. The default is already destructive, and passing the flag flips it off.

Click flag to reveal --destruct

Attack Summary

PhaseAction
ExecutionBinary starts with a hardcoded victim ID used to name all of its artifacts
DiscoveryReads /etc/passwd to enumerate user accounts
CollectionWrites host information to /tmp/<id>.txt
ExfiltrationNormalises filenames (spaces to underscores) and uploads over FTP
ImpactEncrypts files matching the exts list in 4096-byte chunks
ImpactSignal handler can clear the confirm flag or request traversal stop
ImpactDrops a ransom note with a Tor hidden-service address in the home directory
Defense EvasionUnlinks its own binary unless the opt-out flag is passed

IOCs

TypeValue
Victim ID1606
Staging file/tmp/1606.txt
Tor hidden servicekd24u5e4ggp2m4k4w23lm5fu1nurmme6ugrwi9btachx83lpw1t6qlxf[.]onion
Recon target/etc/passwd
CLI options--exts, --destruct

MITRE ATT&CK

TechniqueIDDescription
Command and Scripting Interpreter: Unix ShellT1059.004Operator-driven command-line options control targeting and behaviour
System Information DiscoveryT1082Host details collected into the staging file
Account Discovery: Local AccountT1087.001Reads /etc/passwd
Data Staged: Local Data StagingT1074.001Output written to /tmp/1606.txt
Exfiltration Over Unencrypted Non-C2 ProtocolT1048.003Upload handler sends files over FTP
Indicator Removal: File DeletionT1070.004unlink(argv[0]) after execution
Data Encrypted for ImpactT1486Chunked file encryption and Tor ransom note

Defender Takeaways

Hunt for unattended FTP from servers. The upload handler speaks plain FTP after cleaning up filenames. Outbound FTP from a Linux host that has no business using it is a clean, low-noise detection, and egress filtering would have cut off the exfiltration path.

Watch for small text files in /tmp named with four digits. The staging file is predictable (/tmp/<id>.txt) and holds host and account information. File-creation monitoring on /tmp with a numeric-name pattern, followed by a network upload, is a usable behavioural rule.

Treat a process that unlinks its own executable as suspicious. Self-deletion is the default here, so the binary is gone from disk after a normal run. Process telemetry that records the image path and hash at launch, plus memory capture, preserves what the filesystem will not.

A hardcoded ID is a pivot for scoping. The same string names the staging file and ties activity to one victim record. Searching fleet-wide for the ID and the ransom-note onion address shows how far a single sample spread.

Conclusion

The binary needs nothing but a disassembler to explain itself: ID, staging path, targeting option, chunk size, ransom note and self-delete default are all plain constants. Self-deletion by default means the sample is rarely on disk afterwards, so the telemetry captured at launch is what matters.