Skip to main content

scrambled-bytes picoMini by redpwn Solution

A forensics challenge requiring you to reconstruct scrambled network data and decrypt it to reveal a hidden image.

Published: April 2, 2026Updated: August 13, 2026

Description

I sent my secret flag over the wires, but the bytes got all mixed up! Recover the flag from the pcapng.

Download capture.pcapng from the challenge page.

Install Wireshark and Python libraries: pip install dpkt scapy

bash
pip install dpkt scapy

Solution

Want to try it yourself first?

The guided walkthrough reveals hints one step at a time.

Walk me through it
  1. Step 1Examine the capture in Wireshark
    Observation
    The description says the bytes in the capture are mixed up. Open it in Wireshark and identify the protocol and payload structure before writing any code.
    Open capture.pcapng in Wireshark. The packets are UDP. Each packet carries a portion of XOR-encrypted data whose bytes have been shuffled using a seeded random number generator.
    Learn more

    pcapng (Packet Capture Next Generation) is the standard file format for network packet captures, used by Wireshark, tcpdump, and most network analysis tools. It stores raw packet data including headers and payloads for every packet captured during a network session. pcapng is the successor to the older pcap format, adding features like multiple interfaces, per-packet timestamps, and comments.

    UDP (User Datagram Protocol) is a connectionless transport protocol - unlike TCP, it has no handshaking, acknowledgment, or guaranteed ordering. Each UDP datagram is independent: they may arrive out of order or not at all. This is relevant to the challenge because the bytes are shuffled across packets, and the correct order must be reconstructed using the shuffle algorithm rather than packet sequence numbers.

    Wireshark's Protocol Hierarchy Statistics (Statistics → Protocol Hierarchy) gives an overview of what protocols are in the capture. Following a UDP stream (Analyze → Follow → UDP Stream) shows all related packets together. Wireshark can also export packet payload bytes for further analysis with external tools.

  2. Step 2Extract the random seed from the first packet
    Observation
    The shuffle seeds Python's random module from the clock, at one-second precision. That seed is recoverable from the packet timestamp, not from anything in the payload.
    The sender's script seeded Python's random module with int(time()) at the moment of transmission. This seed is not transmitted in any packet payload - instead, you recover it from the pcapng file itself: the first packet's arrival timestamp (visible in Wireshark as Epoch Time, or extractable via dpkt as the ts value) truncated to an integer gives the seed.
    python
    python3 -c "
    import dpkt, socket
    with open('capture.pcapng','rb') as f:
        cap = dpkt.pcapng.Reader(f)
        for ts, buf in cap:
            eth = dpkt.ethernet.Ethernet(buf)
            print(eth.data.data.data[:16].hex())
            break
    "
    What didn't work first

    Tried: Reading the seed from the packet payload directly instead of the pcapng timestamp

    The payload is XOR-encrypted and shuffled, so scanning it for a recognizable integer returns garbage. The seed is never sent in any packet; it survives only in the capture timestamp, which records when the sender read the clock before seeding.

    Tried: Using scapy's rdpcap and pkt.time instead of dpkt's ts for the seed

    scapy exposes the same timestamp as a float, matching dpkt. It loads pcapng files more slowly, though, and its layer indexing returns a Raw object rather than bytes, so converting it raises a type error. dpkt gives the timestamp directly and chains cleanly into the payload.

    Learn more

    dpkt is a Python library for parsing raw network packet data. It understands the Ethernet frame structure (dpkt.ethernet.Ethernet), which contains an IP packet (.data), which contains a UDP datagram (.data), which contains the application payload (.data). Each layer is accessed via the .data attribute, following the network stack from layer 2 (Ethernet) down to the application payload.

    The seed is not explicitly transmitted -- it is recoverable from the pcapng packet timestamp, because the sender used int(time()) which has only second-level precision. This is a design flaw in the key-generation scheme: using a low-entropy, externally-observable value (system time) as the PRNG seed means anyone who captures the traffic can determine the seed from the capture metadata alone, without any key material appearing in the packet payloads.

    scapy is an alternative Python packet library that is more user-friendly for interactive use but slower for bulk processing. rdpcap('file.pcapng') reads all packets, and individual layers are accessed via indexing: pkt[UDP].payload for UDP payload data. Both libraries are standard tools in network forensics.

  3. Step 3Reverse the shuffle
    Observation
    Wireshark shows two stacked transformations, an XOR and a byte shuffle, and the Mersenne Twister is fully deterministic given its seed. Re-seed with the recovered timestamp and both invert.
    Use the recovered seed to reconstruct the same random shuffle sequence. XOR each byte with the corresponding value from the PRNG stream (successive random.randrange(256) calls using the recovered seed) to recover the ordered plaintext bytes.
    python
    python3 solve.py
    What didn't work first

    Tried: Applying the inverse shuffle before XOR-decrypting instead of XOR-decrypting first

    The sender encrypted first and shuffled second, so undo the shuffle first and then the XOR. The wrong order gives a buffer of the right length and no meaning: no PNG magic at offset 0, and file reports it as data. Inverses run in reverse order.

    Tried: Generating the XOR keystream with random.random() or random.randint(0, 255) instead of random.randrange(256)

    random.random returns a float below 1, not a byte value, so using it as a keystream without scaling shifts the key range and desynchronizes everything from the second byte onward. randint and randrange are identical in CPython, since the first delegates to the second, so either is fine.

    Learn more

    Python's random module uses a Mersenne Twister PRNG (pseudorandom number generator). Given the same seed, random.seed(n) followed by the same sequence of random calls produces identical output every time - it is entirely deterministic and reproducible. The sender seeded the PRNG with int(time()) at send time, generated a shuffle permutation and XOR stream from it, and applied both to the bytes. The attacker recovers the same seed from the pcapng timestamp and repeats this exactly to reconstruct the permutation and XOR stream, then reverses them.

    XOR decryption is the inverse of XOR encryption: plaintext XOR key = ciphertext, so ciphertext XOR key = plaintext. If the key is known (transmitted or guessable), XOR-encrypted data is trivially decrypted by XORing again with the same key. The combination of XOR and shuffle in this challenge adds two layers of obfuscation, but both are reversible once the seed is known.

    The solve script must: (1) collect payload bytes from all packets in order, (2) seed Python's random module with the integer timestamp extracted from the first packet, (3) generate the same shuffle indices and XOR values the sender used, (4) apply the inverse permutation, and (5) XOR each byte with its corresponding PRNG value. The result is the original PNG image bytes containing the flag.

  4. Step 4View the resulting PNG
    Observation
    The recovered buffer starts with the PNG magic bytes, which confirms it worked. The flag is drawn in the image rather than sitting in it as text.
    The decrypted and un-shuffled bytes form a valid PNG image. Save it and open it - the flag is rendered visually inside the image.
    python
    python3 -c "
    with open('flag.png','wb') as f:
        f.write(decrypted_bytes)
    "
    bash
    eog flag.png

    Expected output

    picoCTF{n0_t1m3_t0_w4st3_5hufflin9_ar0und}
    Learn more

    Writing the recovered bytes to a file and running file flag.png confirms whether the recovery was successful - a valid PNG starts with the bytes 89 50 4E 47 0D 0A 1A 0A (the PNG magic signature). If the output is invalid, the seed extraction or shuffle reversal had an error.

    This challenge combines several disciplines: network forensics (pcap analysis), cryptography (XOR), randomness (PRNG seeding), and steganography (flag in image). Multi-discipline challenges are common at higher CTF difficulty levels - the skills required span tools and concepts from different security domains. Building fluency in each domain separately makes combined challenges approachable.

Interactive tools
  • File Magic IdentifierIdentify file types from magic numbers. Paste hex bytes or drop a file to detect PNG, JPEG, ZIP, PDF, ELF, PCAP, SQLite, and dozens of other formats.
Alternate Solution

Once you have un-shuffled the bytes and know the XOR key, use the XOR Cipher tool on this site to apply the final decryption step - paste the shuffled bytes and the key to confirm the recovered PNG magic bytes (89 50 4E 47) appear correctly.

Flag

Reveal flag

picoCTF{n0_t1m3_t0_w4st3_5hufflin9_ar0und}

When randomization is seeded with a low-entropy, externally-observable value (such as the epoch time readable from pcapng metadata), the shuffle is fully deterministic and reversible by anyone who captures the traffic.

Key takeaway

A pseudorandom generator is deterministic: whoever knows the seed reproduces the whole stream and undoes anything it produced. Seeding from the system clock is fatal, because that value is either recorded in metadata, as it is in a capture timestamp, or guessable within a narrow window. Session tokens, cryptographic nonces, and shuffle-based obfuscation all break the same way when the seed has low entropy and is visible from outside.

Related reading

Tools used in this challenge

Where to go next