// HackTheBox  ·  writeup

OpSalwarKameez24-3: Snake-Pill

HackTheBox Hard stringsxxdPython
Malware Analysis

Scenario

An attacker has gained access to the supply chain of StoreD and has successfully uploaded malicious firmware to an environmental sensor which has then attempted to connect to many hosts on the network. Your task is to reverse engineer the firmware to discover what the device tries to do.

Executive Summary

A supply-chain compromise put trojanised firmware on a StoreD environmental sensor. The image is a 4 MB flash backup of an ESP32-class device running FreeRTOS, and it keeps its Wi-Fi settings in the NVS configuration partition and again as strings in the application. Alongside the legitimate sensor loop that ships temperature and pressure readings to a collector, a reporting task sweeps the local subnet and fires a canned FTP exploit at the hosts it finds. The exploit is a classic stack overflow: a long run of filler, a hard-coded return address, and a shikata_ga_nai encoded shellcode that connects back to an attacker-controlled address on the HTTPS port. All of it was recovered statically with strings, a hex viewer and a short Python decoder.

Artifacts

Tip: No Xtensa disassembler was available, so everything below comes from strings, offsets and byte patterns. Where an answer rests on that alone, the walkthrough says so.

Discovery

Q1 — What WiFi network does the device connect to?

Run strings over the flash dump and look at the application region first. Wi-Fi settings are also persisted in the NVS partition, so search for the station keys there. sta.ssid sits at offset 0x90e4 and holds the corporate network name, which matches the StoreD strings in the app.

strings -n 6 evidence/fw-backup-4M.bin > work/strings.txt
grep -abo StoreD-Corporate evidence/fw-backup-4M.bin
xxd -s 37092 -l 128 evidence/fw-backup-4M.bin
Click flag to reveal StoreD-Corporate
Q2 — What was the WiFi password?

The password lives next to the SSID in NVS as sta.apinfo at 0x9524, and the same string appears again in the application rodata at 0x13274. Two independent copies agreeing rules out a stray string.

xxd -s 38148 -l 128 evidence/fw-backup-4M.bin
xxd -s 0x13160 -l 0x400 evidence/fw-backup-4M.bin
Click to reveal answer StoreD@2024
Q3 — What IP address do the temperature and pressure measurements get sent to?

Dump the app rodata around 0x12fd8. The telemetry code’s format strings sit there, including Message sent to, with the destination IPv4 address stored as a plain string right beside it.

xxd -s 0x12a00 -l 0x600 evidence/fw-backup-4M.bin
strings -n 4 -t x evidence/fw-backup-4M.bin | awk '$1 >= "12d00" && $1 <= "12e80"'
Click flag to reveal 10.137.244.155
Q5 — How many FreeRTOS tasks does the application create (not including library functions)?

Without a disassembler, count the task-name strings the app passes to task creation: udp_send_task, report_task and sensor_task. That gives three.

sed -n 525,566p work/strings.txt

Note: This was reasoned from the three task-name strings, not from counting xTaskCreate calls in disassembly. HTB accepted it, but verify in Ghidra with an Xtensa processor module if you want certainty.

Click flag to reveal 3

Lateral Movement

Q4 — What is the name for the FreeRTOS task that performs the attack?

The same task-name strings show which task does what. report_task is paired with a report log TAG whose messages are about scanning the subnet, so it is the task driving the attack traffic, while sensor_task handles the measurements and udp_send_task ships them.

sed -n 525,566p work/strings.txt

Note: Inferred from task names and the log TAG, not from reading the task body in disassembly. The scanning behaviour and the payload both sit next to the report strings.

Click to reveal answer report_task
Q6 — What protocol is the payload attacking? Give the three letter acronym.

The payload blob in rodata starts with USER BBBB..., an FTP login command with an oversized username. That is a buffer overflow against an FTP server’s USER handler.

grep -a -n -E 'USER|AAAA|\x90\x90' evidence/fw-backup-4M.bin | head
xxd -s 198184 -l 256 evidence/fw-backup-4M.bin
Click to reveal answer FTP

Execution

Q7 — What address does Buffer Overflow attack place in EIP?

Find where the run of B filler (2000 bytes) ends. The next four bytes are 37 50 88 7c, the overwrite for the saved return address. EIP is x86 little-endian, so read them reversed to get the address.

python3 -I - <<'EOF'
d=open('evidence/fw-backup-4M.bin','rb').read()
i=d.find(b'USER '+b'B'*2000)+5+2000
print(d[i:i+4].hex(), hex(int.from_bytes(d[i:i+4],'little')))
EOF

Gotcha: The bytes in the file read 37 50 88 7c left to right; submitting them without reversing gives the wrong value.

Click flag to reveal 0x7C885037

Command and Control

Q8 — To what IP does the attack send a reverse shell?

The shellcode after the return address is not readable as-is: it opens with a shikata_ga_nai decoder stub. Recover the XOR key from the stub (0x427c96fc), decode the body in Python, and the plaintext contains push 0x3fb2a8c0 (the address, little-endian) followed by a port word of 0x01bb, which is 443. The pushed dword c0 a8 b2 3f is 192.168.178.63.

python3 -I - <<'EOF'
# XOR-decode the shikata_ga_nai body with key 0x427c96fc, then disassemble the push imm32 values
EOF

Tip: Common reverse-shell stagers push the IP and port as immediates, so look for push imm32 followed by a push of a port word (sockaddr_in setup) in the decoded bytes.

Click to reveal answer 192.168.178.63

Attack Summary

PhaseAction
Initial AccessTrojanised firmware delivered to the sensor through the vendor supply chain
Credential AccessCorporate Wi-Fi SSID and password stored in NVS and in the app image
Command and ControlLegitimate sensor loop sends temperature and pressure readings to a collector
Discoveryreport_task scans the local subnet for hosts
Lateral MovementHosts are hit with an FTP USER overflow
ExecutionReturn address overwritten with a hard-coded value, shikata_ga_nai shellcode runs
Command and ControlShellcode connects back to an internal address on port 443

IOCs

TypeValue
Filefw-backup-4M.bin
SSIDStoreD-Corporate
Wi-Fi passwordStoreD@2024
IP (telemetry sink)10.137.244[.]155
IP (reverse shell)192.168.178[.]63
Port (reverse shell)443
Return address0x7C885037
FreeRTOS tasksudp_send_task, report_task, sensor_task

MITRE ATT&CK

TechniqueIDDescription
Supply Chain Compromise: Compromise Software Supply ChainT1195.002Malicious firmware pushed to the sensor via the vendor pipeline
Unsecured Credentials: Credentials In FilesT1552.001Wi-Fi password stored in clear in NVS and rodata
Network Service DiscoveryT1046report_task sweeps the subnet
Exploitation of Remote ServicesT1210FTP USER stack overflow against hosts found
Obfuscated Files or InformationT1027Shellcode encoded with shikata_ga_nai
Non-Application Layer ProtocolT1095Raw socket reverse shell to port 443

Defender Takeaways

Firmware images are executables and need provenance checks. The device accepted an image from a compromised supply chain. Signed firmware with secure boot and flash encryption on the ESP32 would have rejected the trojanised build.

Credentials in NVS and rodata are readable to anyone with a flash dump. The Wi-Fi password was recoverable in two places with strings. Use a dedicated IoT SSID with a rotated, per-device key, and enable NVS encryption so a lost or backed-up image does not leak the corporate network key.

IoT devices that scan the subnet are visible on the wire. A sensor that normally talks to one collector suddenly probing many hosts and opening FTP sessions with 2000-byte usernames is easy to catch. Put sensors in a segment that only allows traffic to the collector and alert on any other egress.

Legacy FTP is still the target. The payload exploits a stack overflow in an FTP server’s USER handling. Retire or patch internal FTP services, and use IDS signatures for oversized FTP commands.

A reverse shell on 443 still stands out by destination. The callback goes to an internal address on the HTTPS port. Egress allow-lists keyed on destination, not just port, catch it.

Conclusion

The sensor was turned into an internal scanner with a ready-made FTP exploit and a reverse shell to a fixed address, all hidden in one firmware image. The decisive evidence sat in plain strings and one encoded blob, so signed firmware and segmented IoT networks are what would have stopped it.