> For the complete documentation index, see [llms.txt](https://simon-6.gitbook.io/simoncyber/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://simon-6.gitbook.io/simoncyber/ctf-writeups/picoctf-2025/reverse-engineering/flag-hunter-easy.md).

# Flag Hunter (Easy)

Before going to the challenge, this is my first Reverse Engineering CTF! Reverse Engineering always sounded scary to me. Hopefully this doesn't turn out too hard.&#x20;

***

<figure><img src="/files/qgEOxIrYemltOD8LMFxh" alt=""><figcaption></figcaption></figure>

The source code is added below:

```python
import re
import time


# Read in flag from file
flag = open('flag.txt', 'r').read()

secret_intro = \
'''Pico warriors rising, puzzles laid bare,
Solving each challenge with precision and flair.
With unity and skill, flags we deliver,
The ether’s ours to conquer, '''\
+ flag + '\n'


song_flag_hunters = secret_intro +\
'''

[REFRAIN]
We’re flag hunters in the ether, lighting up the grid,
No puzzle too dark, no challenge too hid.
With every exploit we trigger, every byte we decrypt,
We’re chasing that victory, and we’ll never quit.
CROWD (Singalong here!);
RETURN

[VERSE1]
Command line wizards, we’re starting it right,
Spawning shells in the terminal, hacking all night.
Scripts and searches, grep through the void,
Every keystroke, we're a cypher's envoy.
Brute force the lock or craft that regex,
Flag on the horizon, what challenge is next?

REFRAIN;

Echoes in memory, packets in trace,
Digging through the remnants to uncover with haste.
Hex and headers, carving out clues,
Resurrect the hidden, it's forensics we choose.
Disk dumps and packet dumps, follow the trail,
Buried deep in the noise, but we will prevail.

REFRAIN;

Binary sorcerers, let’s tear it apart,
Disassemble the code to reveal the dark heart.
From opcode to logic, tracing each line,
Emulate and break it, this key will be mine.
Debugging the maze, and I see through the deceit,
Patch it up right, and watch the lock release.

REFRAIN;

Ciphertext tumbling, breaking the spin,
Feistel or AES, we’re destined to win.
Frequency, padding, primes on the run,
Vigenère, RSA, cracking them for fun.
Shift the letters, matrices fall,
Decrypt that flag and hear the ether call.

REFRAIN;

SQL injection, XSS flow,
Map the backend out, let the database show.
Inspecting each cookie, fiddler in the fight,
Capturing requests, push the payload just right.
HTML's secrets, backdoors unlocked,
In the world wide labyrinth, we’re never lost.

REFRAIN;

Stack's overflowing, breaking the chain,
ROP gadget wizardry, ride it to fame.
Heap spray in silence, memory's plight,
Race the condition, crash it just right.
Shellcode ready, smashing the frame,
Control the instruction, flags call my name.

REFRAIN;

END;
'''

MAX_LINES = 100

def reader(song, startLabel):
  lip = 0
  start = 0
  refrain = 0
  refrain_return = 0
  finished = False

  # Get list of lyric lines
  song_lines = song.splitlines()
  
  # Find startLabel, refrain and refrain return
  for i in range(0, len(song_lines)):
    if song_lines[i] == startLabel:
      start = i + 1
    elif song_lines[i] == '[REFRAIN]':
      refrain = i + 1
    elif song_lines[i] == 'RETURN':
      refrain_return = i

  # Print lyrics
  line_count = 0
  lip = start
  while not finished and line_count < MAX_LINES:
    line_count += 1
    for line in song_lines[lip].split(';'):
      if line == '' and song_lines[lip] != '':
        continue
      if line == 'REFRAIN':
        song_lines[refrain_return] = 'RETURN ' + str(lip + 1)
        lip = refrain
      elif re.match(r"CROWD.*", line):
        crowd = input('Crowd: ')
        song_lines[lip] = 'Crowd: ' + crowd
        lip += 1
      elif re.match(r"RETURN [0-9]+", line):
        lip = int(line.split()[1])
      elif line == 'END':
        finished = True
      else:
        print(line, flush=True)
        time.sleep(0.5)
        lip += 1



reader(song_flag_hunters, '[VERSE1]')
```

Quite a lot of nonsensical poems, but this should be relatively easy.

### The Source Code

The code reads a flag from `flag.txt` and embeds it into song lyrics, then uses a simple interpreter to execute the lyrics as a program. However, what we're trying to solve is to print out the secret intro. Since we always start at VERSE, we don't really start from the beginning,. So let's go it through it one by one to figure it out.&#x20;

```python
  lip = 0
  start = 0
  refrain = 0
  refrain_return = 0
  finished = False
```

This are the initial variables. lip is short for line in progress. These are used to store the lines to return to.&#x20;

```python
  for i in range(0, len(song_lines)):
    if song_lines[i] == startLabel:
      start = i + 1
    elif song_lines[i] == '[REFRAIN]':
      refrain = i + 1
    elif song_lines[i] == 'RETURN':
      refrain_return = i
```

This part of the reader function finds where to start for the variables mentioned above.&#x20;

```python
 line_count = 0
  lip = start
  while not finished and line_count < MAX_LINES:
    line_count += 1
    for line in song_lines[lip].split(';'):
      if line == '' and song_lines[lip] != '':
        continue
      if line == 'REFRAIN':
        song_lines[refrain_return] = 'RETURN ' + str(lip + 1)
        lip = refrain
      elif re.match(r"CROWD.*", line):
        crowd = input('Crowd: ')
        song_lines[lip] = 'Crowd: ' + crowd
        lip += 1
      elif re.match(r"RETURN [0-9]+", line):
        lip = int(line.split()[1])
      elif line == 'END':
        finished = True
      else:
        print(line, flush=True)
        time.sleep(0.5)
        lip += 1
```

These track the current line number, and checks if it contains some specific words. If it does, it'll go back to that line like in refrain, or in CROWD it takes user input. However, this is the place in where we'll put our exploit. Let's analysis more of the conditional statements.

```python
for line in song_lines[lip].split(';'):
      if line == '' and song_lines[lip] != '':
        continue
```

This line splits it by a semicolon into a list. So now something like String;StringA will become \["String","StringA"]. Then they check if it's empty, if it contains the words, etc...&#x20;

One if statement we're particularly interested in is the return elif statement. This allows us to return to any line. &#x20;

```python
 elif re.match(r"RETURN [0-9]+", line):
        lip = int(line.split()[1])
```

The first line basically just checks if there is a number after the return.&#x20;

The "lip = int(line.split()\[1]) works like this:&#x20;

```python
Let's say line is "RETURN 42"
Step 1: line.split()

Splits the string by whitespace
"RETURN 42".split() → ['RETURN', '42']

Step 2: [1]

Gets the element at index 1 (the second element)
['RETURN', '42'][1] → '42'

Step 3: int(...)

Converts the string to an integer
int('42') → 42

Step 4: lip = ...

Sets lip (Line In Progress) to that number
Execution will continue from line 42
```

Combining all of this, you simply crack this by entering a string + ; + RETURN 0.&#x20;

### How it works

When you do this: song\_lines\[lip] = 'Crowd: lala ; RETURN 0' it goes through the conditional statements again. It checks the line 'Crowd: lala". This doesn't match any parameters. It then checks Return 0. It gets picked up by the later line, and goes back to 0!&#x20;

The reason why RETURN 0 doesn't work is because it will be outputted as "'Crowd: RETURN 0', which won't match any.&#x20;
