Post

Odyssey Finals 2025 - DarBox Inwi (DFIR)

Writeup for DarBox Inwi DFIR challenge from Odyssey Finals 2025

Odyssey Finals 2025 - DarBox Inwi (DFIR)

DarBox Inwi - DFIR Challenge Writeup

The challenge presented a router firmware (firmware.bin) and network traffic capture (traffic.pcapng) with the premise that the router was spying on the user.

Firmware Analysis

We first examine the firmware.bin structure and that reveals a SquashFS filesystem at offset 0x40. We extract it using binwalk:

1
binwalk -e firmware.bin

Finding the Backdoor

Next, we search for suspicious startup scripts in _firmware.bin.extracted/squashfs-root/etc/init.d/

I found sysmon, a suspicious init script that starts a hidden binary:

1
cat _firmware.bin.extracted/squashfs-root/etc/init.d/sysmon

The script launches /usr/bin/.sys/upgrade/sysmon - a hidden binary (suspicious!).

Reverse Engineering the Backdoor

This is the backdoor. Upon analyzing it, we find that it exfiltrates data via HTTP POST requests.

Key findings:

  • Function names: send_http_exfil, get_log_entries, get_system_info, read_flag_file
  • Format string: secretfile=%s&key=%s&%s&%s&timestamp=%ld
  • File path: /root/info

We also learn of the decoding mechanism:

  1. Layer 1: XOR with 0x47 (obfuscates the HTTP POST data)
  2. Layer 2: Custom hex digit encoding (0x10-0x19, 0x41-0x46)
  3. Layer 3: Repeating-key XOR with the KEY field

Extracting Network Traffic

We need to get the exfiltrated data and decrypt it:

1
tshark -r traffic.pcapng -Y "http.request.method == POST" -T fields -e http.file_data

Found a POST to /api/data with hex-encoded data:

1
140204150213010e0b025a5056515650065301505251525302530557575357560151535002575353525202...

Decryption Process

Layer 1: XOR with 0x47

1
2
3
import binascii
data = binascii.unhexlify(hex_string)  # hex_string from POST request
decoded = bytes([b ^ 0x47 for b in data])

This revealed readable text:

1
SECRETFILE=...&KEY=...&USERNAME=ROOT&HOSTNAME=loq&SYSNAME=lINUX...

The data was structured as URL parameters separated by 0x06 bytes, with 0x1d as the key-value separator (= sign).

Layer 2: Parse Fields

Split the decoded data by field separators:

1
2
3
4
parts = decoded.split(b'\x06')
for part in parts:
    if b'\x1d' in part:
        field_name, field_value = part.split(b'\x1d', 1)

This got us:

  • SECRETFILE=1711161117411446171516151445144210101410114616141745...
  • KEY=131012411342114313101216131512161311
  • Other fields: USERNAME, HOSTNAME, SYSNAME, LOGS, etc.

Layer 3: Custom Hex Decoding

The SECRETFILE and KEY values used a custom encoding scheme:

  • Bytes 0x10-0x19 represent hex digits 0-9
  • Bytes 0x41-0x46 represent hex digits A-F

Conversion logic:

1
2
3
4
5
6
7
8
def decode_custom_hex(data):
    hex_string = ""
    for b in data:
        if 0x10 <= b <= 0x19:
            hex_string += str(b - 0x10)  # 0x10 -> '0', 0x11 -> '1', etc.
        elif 0x41 <= b <= 0x46:
            hex_string += chr(b)  # 0x41 -> 'A', 0x42 -> 'B', etc.
    return binascii.unhexlify(hex_string)

Applied to KEY:

1
2
3
131012411342114313101216131512161311
→ 302A3B1C3026352631
→ b'0*;\x1c0&5&1'

Applied to SECRETFILE:

1
2
71617A4F75654E4B00401F647E04455E42010058642C5E79414E056F580B6944154779495F525461
→ (binary data)

Layer 4: Repeating-Key XOR

The SECRETFILE was encrypted using repeating-key XOR with the decoded KEY:

1
2
3
4
5
key_bytes = binascii.unhexlify("302A3B1C3026352631")
secretfile_bytes = binascii.unhexlify("71617A4F75654E4B00401F647E04455E42010058642C5E79414E056F580B6944154779495F525461")

decoded = bytes([secretfile_bytes[i] ^ key_bytes[i % len(key_bytes)]
                 for i in range(len(secretfile_bytes))])

Flag

AKASEC{m1p5_b4ckd00r_0n_th4_r0ut3r_xoxo}

This post is licensed under CC BY 4.0 by the author.