PicoCTF 2025 Reverse Engineering—Quantum Scrambler Writeup

Welcome back to my writeup! Today, I will show the step-by-step on how I solved Quantum Scrambler from picoCTF 2025!

In this challenge we are provided with a Python code, let's analyze it

import sys

def exit():
  sys.exit(0)

def scramble(L):
  A = L
  i = 2
  while (i < len(A)):
    A[i-2] += A.pop(i-1)
    A[i-1].append(A[:i-2])
    i += 1

  return L

def get_flag():
  flag = open('flag.txt', 'r').read()
  flag = flag.strip()
  hex_flag = []
  for c in flag:
    hex_flag.append([str(hex(ord(c)))])

  return hex_flag

def main():
  flag = get_flag()
  cypher = scramble(flag)
  print(cypher)

if __name__ == '__main__':
  main()

This Python script reads a flag from flag.txt, converts each character of the flag into its hexadecimal representation (as a list of one string), and passes that list to the scramble function. The scramble function then modifies the list in-place by popping and merging elements while inserting sublists, effectively scrambling the structure and contents of the flag list in a non-obvious way. The scrambled result is printed to the console.

This is the challenge

At first glance, this might look really confusing, right? Haha. But the challenge is intentionally designed like this because of the scramble function in the Python code. So, the real question is—how do we go about retrieving the original flag?

When analyzing the challenge, the first thing I noticed was that the scramble() function heavily modified the structure of the original list of hex-encoded flag characters. Instead of encrypting or transforming the actual contents, it nested and reshaped the list using pop() and append(), making it appear confusing and unreadable. So, my first goal in planning the solution is to understand what kind of transformation is happening—it's not encryption, it's obfuscation through deep list nesting.

Given that, my strategy is to write a function that can reverse the nesting caused by scramble(). I plan to treat the scrambled list as a tree-like structure and traverse it completely to extract all leaf values. Since we’re dealing with potentially multiple layers of lists within lists, recursion or queue-based traversal will help. Once I gather all the hex values from the deeply nested list, I can then convert them back to ASCII characters using chr(int(hex_value, 16)).

Before that, I’ll need to connect to the CTF server, receive the scrambled output, and parse it properly. Since the output seems to be a Python-like list, I can safely evaluate it using ast.literal_eval() to convert the string into an actual Python data structure. Once that's done, I can feed it into my unscramble() function, collect the characters, and reconstruct the original flag. So, the plan is: receive → parse → flatten → decode → print.

Here's our solution

from pwn import *
import ast

def unscramble(L):
    result = []
    queue = [L]
    hex_values = []

    while queue:
        item = queue.pop(0)
        if isinstance(item, list):
            for sub_item in item:
                queue.append(sub_item)
        else:
            try:
                hex_values.append(item)
                result.append(chr(int(item, 16)))
            except ValueError as e:
                print(f"Error converting {item}: {e}")
                continue
    print(f"Extracted hex values: {hex_values}")
    return ''.join(result)

host = 'verbal-sleep.picoctf.net'
port = 60759

print(f"[+] Opening connection to {host} on port {port}")
conn = remote(host, port)

print("[+] Receiving all data")
ciphertext = conn.recvall().decode().strip()

print("[*] Closing connection")
conn.close()

with open('ciphertext.txt', 'w') as f:
    f.write(ciphertext)
print("[+] Ciphertext saved to 'ciphertext.txt'")

try:
    scrambled_flag_list = ast.literal_eval(ciphertext)
except Exception as e:
    print(f"Error converting ciphertext to list: {e}")
    exit(1)

print(f"cipher[15]: {scrambled_flag_list[15]}")
print(f"cipher[16]: {scrambled_flag_list[16]}")

flag = unscramble(scrambled_flag_list)
print(f"Unscrambled Flag: {flag}")

Run and we should get the decoded version of the flag!

FLAG

The flag is actually right at the very top, haha. Even the decoded output looks like a complete mess, xD.

Last updated