HTB: ZombieNet Challenge

ZombieNet - HackTheBox Challenge Writeup

FieldValue
NameZombieNet
CategoryForensics
DifficultyMedium
Authord3vn0mi

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).

Terminal window
# Extract the real firmware archive
cd /tmp && mkdir -p realfw && cd realfw
unzip -P hackthebox /out/<checkpoint_id>.zip
# Verify the firmware file
file 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

Terminal window
# Locate the squashfs offset and extract
binwalk -e --run-as=root openwrt-ramips-mt7621-xiaomi_mi-router-4a-gigabit-squashfs-sysupgrade.bin
# Or manually extract using dd + 7z
dd 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.squashfs

Step 3: Identify the Persistence Mechanism

Navigate to the extracted filesystem and examine init.d:

Terminal window
cd squashfs-root
# List startup services
ls -la etc/init.d/
# Examine the suspicious "dead-reanimation" service
cat etc/init.d/dead-reanimation

Output reveals:

#!/bin/sh /etc/rc.common
START=95
STOP=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

Terminal window
file sbin/zombie_runner
# Output: ELF 32-bit LSB executable, MIPS, MIPS32 release 2
# Extract and decode the shell script
strings sbin/zombie_runner | grep -E "(dead-reanimation|/usr/bin|system)"

The zombie_runner behavior:

  • Infinite loop that calls /usr/bin/dead-reanimation every 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.

Terminal window
# Identify the XOR key location
python3 << 'EOF'
import binascii
binary = open('usr/bin/dead-reanimation', 'rb').read()
# The first 32-byte blob is the XOR key
key = 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]
EOF

Decode 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 sequences
for 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_jEzOWMtZTUxOS00
  • http://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.

Terminal window
# Add hostname resolution
echo "154.57.164.83 configs.router.htb" >> /etc/hosts
# Fetch the first payload (reanimate.sh) with proper Host header
curl -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_reanimated

Step 7: Extract the Flag Prefix from reanimate.sh

The reanimate.sh script contains the flag prefix encoded in an authentication cookie:

Terminal window
# Search for the auth_token cookie
grep -ao 'auth_token=[^"]*' /tmp/reanimate.sh
# Output: auth_token=SFRCe1owbWIxM3NfaDR2M19pbmY
# Decode the base64 cookie
python3 -c "
import base64
cookie = '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:

  1. A command to create a hidden root user (zombie_lord, uid=0, gid=0)
  2. An RC4-encrypted blob containing the flag suffix
import hashlib
# The RC4 key is derived from a SHA1 string found in the binary
key_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 generation
def 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 + suffix
print(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 backdoor

Tools Used

ToolPurpose
binwalkFirmware image analysis and extraction
7zSquashfs filesystem decompression
stringsExtract printable strings from binaries
xxdHex dump analysis for offset identification
curlHTTP requests to C2 server with custom headers
Python3XOR/RC4 cryptographic operations, base64 decoding
grepPattern matching in binary/text data

Key Learnings

  1. OpenWRT Persistence via Init Services
    The START=95 priority ensures the zombie service launches after critical services, making detection harder. The service continuously re-executes the C2 agent every 600 seconds for redundancy.

  2. 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.

  3. 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.

  4. 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.

  5. C2 Communication Without Domain Registration
    The /etc/hosts hostname configs.router.htb points to the challenge server rather than requiring actual DNS resolution — a critical IOC for forensic investigation.

  6. 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.