Undutmaning Misc Writeup

Botten: Misc Challenge

This writeup reconstructs the entire solve path for the Botten misc challenge: from decoding the text hints, identifying the correct port, understanding why the stream was not TLS, extracting the Morse layer from the framed transmission, and finally decoding the second-stage Base32 payload into the real flag. It also includes a clearer account of how the solve progressed across multiple sessions, where several early ideas were useful for narrowing the problem even though they were not the final decoding method.

Final Flag undut{remorse}

1. Challenge Setup

The story says Mildred Milwaukee is listening to an undersea communications cable near CASCADA. We only get a hostname, challs.undutmaning.se, but not the exact port. We are told the challenge is not running on CTFd, which matters later because it means a normal undut{...} submission assumption is not always valid during the middle of the solve.

In practice, this solve happened over several sessions. The first session was mostly about understanding the hints and establishing a reliable way to talk to the service. Later sessions focused on collecting larger captures, testing framing theories, and separating the misleading intermediate message from the actual final payload.

Given

  • Host: challs.undutmaning.se
  • Binary: sc
  • Two free hints

Unknown

  • Correct port
  • Whether the service uses TLS or plaintext
  • How the signal is encoded
  • What the final answer format is

2. Free Hints

The challenge provides two hint strings:

xyzxyzxdyzjxyzxuyzxypzetxyäzrxyzpxoyzxrteyzxyzxyznxyz
ÅTdjTIOuTRpeEÅtäTrpoTIrOten

Hint 1

Remove every xyz from the first string:

djupetärporten

This translates to: “the depth is the port”.

Hint 2

The second string is mixed-case. Taking the lowercase letters again gives djupetärporten. Taking the uppercase letters gives:

ÅTTIOTREÅTTIO

That is Swedish for eighty-three eighty, so the port is:

Port = 8380

3. Finding The Port

The intended first solve step is simply to derive 8380 from the hints. At this point the natural first try was to use the supplied binary:

./sc challs.undutmaning.se:8380

That failed with a TLS error:

tls: first record does not look like a TLS handshake

This was important: the port was correct, but sc was the wrong transport for this service.

4. Protocol Mismatch

The local sc binary is a TLS/SNI tunneling helper. Its help output shows options such as -bind, -servername, and -insecure. So when it connected to 8380, it tried to speak TLS.

The remote side answered in raw bytes, not TLS. So the correct client for initial exploration was plain nc:

nc challs.undutmaning.se 8380

This produced a continuous binary-looking stream rather than readable text.

5. Capturing The Signal

Dumping the stream to files was the only practical next step. Early captures were:

nc challs.undutmaning.se 8380 | head -c 8192 > a.bin
nc challs.undutmaning.se 8380 | head -c 8192 > b.bin
cmp -l a.bin b.bin

The first observation was that a.bin and b.bin were identical. That suggested a fixed stream, but a larger capture later showed the more accurate interpretation: each new connection starts at the same place, but the stream keeps progressing if you stay connected.

This was one of the main session-to-session corrections. In the early pass, the equal small captures made the service look like a short repeating loop. After returning with longer recordings, it became clear that the challenge behaves more like a broadcast that rewinds to the start on reconnect. That changed the goal from “find the loop” to “stay connected long enough to reach the rest of the message.”

Larger captures

nc challs.undutmaning.se 8380 | head -c 65536 > long.bin
nc challs.undutmaning.se 8380 | head -c 167848 > huge.bin
Important correction: the stream is not an 8 KB loop. The initial equality of a.bin and b.bin happened because both connections started at the beginning of the same broadcast.

6. Framing Analysis

The data did not match any normal file format. file and signature scans found nothing useful. The crucial structural observation was that the stream splits cleanly into 33-byte frames.

The dominant idle frame was:

d820a2e15a50004924924924500049249249245000492492492450004924924924

Active payload appeared in long bursts separated by large idle regions, with three tiny singleton frames between most major bursts. At first this looked like some kind of display or lane-coded modem stream. A large amount of exploratory work went into:

  • bitmaps from raw bits
  • XOR against idle
  • triplet/ternary interpretations
  • burst-family averaging
  • marker-frame overlays

Those visualizations were useful for understanding the structure, but the solve breakthrough came from ignoring the image interpretation and looking at the timing of each major burst.

That detail is worth stressing because it explains the investigative path. Several sessions were spent building images, comparing burst families, and checking whether the data hid some sort of rasterized text. None of that directly produced the flag, but it did show that the stream was highly structured and that the bursts were consistent enough to support a timing analysis.

7. Decoding The Morse Layer

Each major active burst can be reduced to a 1-bit signal over time. The on-run lengths naturally cluster into short and long pulses, with small off-gaps between them. That is exactly Morse-style timing.

Example mapping

Burst Pulse lengths Morse Letter
(0, 64) [17, 6, 8, 7] -... B
(116, 54) [7, 18, 7] .-. R
(221, 45) [7, 17] .- A

Doing that across the initial section gave:

B R A J O B B A T A V K O D A

or:

BRA JOBBAT AVKODA

At first it was tempting to treat that phrase as the final answer. That was wrong. It is only an instruction: “Good job, decode …”

Longer capture

With the longer capture, the Morse transmission continued beyond the first phrase and decoded to:

BRAJOBBATAVKODANUOVXGI5LUPNZGK3LPOJZWK7I

The key insight is to split this as:

BRA JOBBAT AVKODA NU OVXGI5LUPNZGK3LPOJZWK7I

In Swedish, NU means now. So the message reads: “Good job, decode now …”

This was the point where the later session finally resolved the biggest ambiguity from the earlier work. The first recovered phrase looked polished enough to be a possible answer, but the longer recording showed that it was only an instruction embedded in the transmission. Without that extra session and the longer capture, it would have been easy to stop one layer too early.

8. Second-Stage Ciphertext

The real second-stage payload is therefore not the whole tail after BRAJOBBATAVKODA, but the substring after NU:

OVXGI5LUPNZGK3LPOJZWK7I

This immediately becomes suspicious because:

  • it is uppercase alphanumeric
  • it fits the Base32 alphabet
  • its length is 23, which becomes valid Base32 with one = pad

The next test is therefore:

python3 - <<'PY'
import base64
s = "OVXGI5LUPNZGK3LPOJZWK7I"
print(base64.b32decode(s + "="))
PY

9. Final Base32 Decode

The result is:

b'undut{remorse}'
Final Flag: undut{remorse}

Why the earlier guesses were wrong

  • BRA JOBBAT AVKODA was only the first-layer instruction.
  • The alphanumeric string including NU was not the final token either.
  • NU was part of the instruction text, not part of the Base32 ciphertext.

10. Step-By-Step Summary

  1. Use the two free hints to derive the port 8380.
  2. Notice that sc fails because it expects TLS, while the service is plaintext.
  3. Connect with nc and save the output to binary files.
  4. Discover that the stream uses a 33-byte frame structure with a dominant idle frame.
  5. Separate the stream into major active bursts and tiny singleton marker frames.
  6. Reduce each major burst to a binary timing trace and decode it as Morse.
  7. Read the first-layer message: BRA JOBBAT AVKODA.
  8. Capture a longer stream and continue the Morse decode.
  9. Read the extended message: BRAJOBBATAVKODANUOVXGI5LUPNZGK3LPOJZWK7I.
  10. Interpret NU as Swedish “now”, separating the actual ciphertext: OVXGI5LUPNZGK3LPOJZWK7I.
  11. Base32-decode that ciphertext with one padding character.
  12. Recover the real flag: undut{remorse}.

11. Useful Commands

# Find the signal
nc challs.undutmaning.se 8380 | head -c 65536 > long.bin

# Get a larger capture
nc challs.undutmaning.se 8380 | head -c 167848 > huge.bin

# Base32 decode final payload
python3 - <<'PY'
import base64
s = "OVXGI5LUPNZGK3LPOJZWK7I"
print(base64.b32decode(s + "=").decode())
PY