Flag Hunters

Challenge Overview

Lyrics jump from verses to the refrain kind of like a subroutine call. There's a hidden refrain this program doesn't print by default. Can you get it to print it? There might be something in it for you.
The program's source code can be downloaded here.
Connect to the program with netcat:
$ nc verbal-sleep.picoctf.net 56688

Solution

wget https://challenge-files.picoctf.net/c_verbal_sleep/9f2b86c1e1068d492f783b106f4535aeb137b0c0e31e43351f8cb82a39456a84/lyric-reader.py

After downloading the source code these are the important parts to look at:

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 +\
'''

From this section, you can see that there is a secret intro variable that has the flag attached to it and song_flag_hunters has it added to the very start before anything.

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]')

The reader function has two parameters song, startLabel which at the end can be seen to be song_flag_hunters which was declared before and the startLabel is set to '[VERSE1]' which is after the secret_intro.

The next important thing to consider is for line in song_lines[lip].split(';') which is in the while loop. You can consider the lip variable as the current line that is being processed through the loop and then checked with many different if statements to determine the correct action. For instance when it gets to REFRAIN; then it changes the lip variable to refrain to do those lines. One of the lines near the start says CROWD (Singalong here!); which activates the elif re.match(r"CROWD.*", line): statement which lets the user put an input. That input will be taken into the for loop that takes in the line.

Let's say for instance you wanted to end the program and when it gets to Crowd: to put an input you put in END with the thought that during the next iteration of Crowd triggering it would end the program it wouldn't actually do that. That is because this is the statement, elif line == 'END':, which needs it to end exactly END whereas it is taking in Crowd: END as the line that is being processed. In the for loop it splits based on semicolons so whenever you run it put in something like ;END and in the next iteration it would end.

Similarly, you can use RETURN with the value of 0 to get to the secret_intro that holds the flag with this ;RETURN 0. Once you wait for the next verse that calls Crowd it will execute Crowd: and RETURN 0 where the second part will output the secret intro and then at the end of that intro you will see the flag. It doesn't really matter what you put in front of the semicolon so it could even be nsaidnsaiodnas;2132131isaodnsoiadioas;niohiwoeuwoqie;RETURN 0;hsaod980123;z as long as one of them contain RETURN 0 that will only be executed as that then it will output the flag.

Flag: picoCTF{70637h3r_f0r3v3r_750...}