JailhouseRock

jailhouse

Challenge Description

After an untold number of tequilas, you found yourself arrested and thrown into jail. But don’t fret, I’ve got a plan — their security is laughable. Unfortunately, you’re going to have to help me rock you out of this mess.

nc 35.224.150.112 1337

Author: @drgn Category: Misc

Handout

The handout to this challenge contains

- chal.py
- Dockerfile

chal.py

import sys
import re
import secrets
import os
import subprocess
import tempfile
import base64

FLAG = os.getenv("FLAG")
key = secrets.token_urlsafe(32)

def encrypt_key(key):
    scrambled = [] 
    for i, c in enumerate(key):
        shifted_char = chr((ord(c) + (i + 1)) % 256)
        scrambled.append(shifted_char)
    scrambled_str = ''.join(scrambled)
    result = scrambled_str[::-1]
    return result

def prison_gate():
    gate = '''
     ________
    |        |
    |  ____  |
    |  |  |  |
    |  |  |  |
    |  |__|  |
    |________|
    |   |    |
    |   |    |
    |   |    |
    |   |    |
    |___|____|
    (c) Hard Rock Penitentiary 

    
Enter your decryption function:
Finish your input with $$END$$ on a newline
___________________________________________
    '''
    print(gate)


def print_open_jail():
    print(f"""
          
          YOU DID IT
     _____________________
    |  _________________  |
    | |    _________    | |
    | |   |         |   | |
    | |   |   __    |   | |
    | |   |  |__|   |   | |
    | |   |_________|   | |
    | |                 | |
    | |                 | |
    | |_________________| |
    |_____________________|
${base64.b64decode(FLAG)}
    """)

def jail(code):
    symbol_pattern = r'[^\w\t\n\s,]'
    for line in code:
        symbols = re.findall(symbol_pattern, line)
        if symbols:
            if line.strip() != "$$END$$":
                print(f"How am I supposed to sing that...")
                return False
            
        for char in line:
            if char.isdigit():
                print(f"Where do you think you're at? In a math class? You're a rockstar, be poetic!")
                return False
    return True

def write_down_lyrics(strings, suffix='.rock'):
    with tempfile.NamedTemporaryFile(delete=False, mode='w', suffix=suffix) as temp_file:
        for string in strings:
            temp_file.write(string)
        return temp_file

def sing(code):
    code.insert(0,"let something be arguments at 0\n")
    code.extend(["let liberty be decrypt taking something\n","shout liberty\n"])
    encrypted_key = encrypt_key(key)
    file = write_down_lyrics(code)
    try:
        result = subprocess.run(['/rockstar', file.name, encrypted_key], capture_output=True, text=True)
    except Exception as e:
        print(f"Something went really wrong {e}")
        exit(1)
    os.remove(file.name)
    return result.stdout.strip()


def read_until():
    line = ""
    code = []
    while True:
        line = sys.stdin.readline()
        if "$$END$$" in line:
            break
        code.append(line)
    return code

def main():
    prison_gate()
    input_text = read_until()
    valid = jail(input_text)
    if valid:
        if sing(input_text) == key:
            print_open_jail()
        else:
            print("Aw... what happen to your voice..")
    else:
        print("Look on the bright side, you have only 24 years left.")
    exit

if __name__ == "__main__":
    main()

Dockerfile

FROM python:3.11-slim
WORKDIR /

# In case you are wondering, this is a flag! Might not work in CTFD though :)
ENV FLAG="MDdDVEZ7ZG9uJ3RfcHVuY2hfdGhpc19vbmVfaW5faXRfYWludF90aGF0fQ=="

RUN apt-get update && apt-get install -y \
    curl \
    libicu-dev \
    && rm -rf /var/lib/apt/lists/*

RUN curl -L -o rockstar.tar.gz https://github.com/RockstarLang/rockstar/releases/download/v2.0.29/rockstar-v2.0.29-linux-x64.tar.gz \
    && tar -xf rockstar.tar.gz --strip-components=1  \
    && rm rockstar.tar.gz

COPY chal.py  /

ENTRYPOINT ["python", "/chal.py"]
CMD [""]

Overview of challenge

In this challenge we have given a python script that simulates a jail break scenario.

The script prompts the user to input a decryption function in a Rockstar programming language, which is designed to look like song lyrics.

The goal is to write a Rockstar function that correctly decrypts an encrypted key, allowing the user to “escape” the jail and retrieve the flag.

Handout Overview

There are two files in the handout - chal.py and Dockerfile

chal.py

Let’s break down the chal.py file to understand its functionality.

It begins with initializing necessary imports and two variables

  • FLAG - retrieves the flag from an environment variable
  • key - generates a random URL-safe key using the secrets module

The python file chal.py given in this challenge contains following functions -

  1. encrypt_key(key)
  • This function takes a key as input
  • Scrambles it by shifting each character based on its position in the string, position = index + 1
  • Then reverses the entire string to produce the final encrypted key.

For example, if key is “abc”, the function will shift ‘a’ by 1, ‘b’ by 2, and ‘c’ by 3, resulting in “bdf”, which is then reversed to “fdb”.

chr() function is used to convert ASCII values back to characters, while ord() function is used to get the ASCII value of a character.

The mod operation with 256 ensures that the resulting ASCII values wrap around within the valid range of byte values.

# encrypt_key function
def encrypt_key(key):
    scrambled = [] # initialize an empty list to hold the scrambled characters
    for i, c in enumerate(key): # enumerate to get both index and character as i and c respectively
        shifted_char = chr((ord(c) + (i + 1)) % 256) # shift character by its position (i + 1) and wrap around using mod 256
        scrambled.append(shifted_char) # append the shifted character to the list
    scrambled_str = ''.join(scrambled) # join the list into a string without any separator ''
    result = scrambled_str[::-1] # reverse the string
    return result # got the final encrypted key
  1. prison_gate()
  • This function prints an ASCII art representation of a prison gate
  • Nothing special here, just a visual element to set the scene for the challenge.
  1. print_open_jail()
  • This function prints an ASCII art representation of an open jail
  • It also decodes and displays the flag using base64 decoding.
  • The flag is stored in an environment variable named “FLAG” and is base64 encoded for obfuscation (can be seen in Dockerfile).
  • We’ll later see how this function will be called to retrieve the flag.
  1. jail(code)
  • This function checks the user-provided Rockstar code for any disallowed characters.
  • It uses a regular expression (regex) to find any characters that are not alphanumeric, whitespace, tabs, or commas.
  • If any disallowed characters are found, it prints an error message and returns False, except for the special line "$$END$$" which is used to signal the end of input.
  • It also checks for digits in the code and disallows them, enforcing a poetic style of coding.

So overall it performs sanitization of the user input, later we’ll se how the input is taken and passed to this function.

def jail(code):
    symbol_pattern = r'[^\w\t\n\s,]' # regex pattern to match any character that is not alphanumeric, whitespace, tab or comma
    for line in code: # iterate through each line of the provided code
        symbols = re.findall(symbol_pattern, line) # find all disallowed symbols in the line
        if symbols: # if any disallowed symbols are found
            if line.strip() != "$$END$$": # allow the special end marker "$$END$$"
                print(f"How am I supposed to sing that...")
                return False
            
        for char in line: # iterate through each character in the line
            if char.isdigit(): # if the character is a digit
                print(f"Where do you think you're at? In a math class? You're a rockstar, be poetic!")
                return False  # disallow digits, return False
    return True # if no disallowed characters are found, return True
  1. write_down_lyrics(strings, suffix='.rock')
  • This function writes the user-provided Rockstar code to a temporary file with a specified suffix (default is ‘.rock’).
  • It uses Python’s tempfile module to create a temporary file that is automatically deleted when closed.
  • The function returns the temporary file object, which can be used later to execute the Rockstar code.
  1. sing(code)
  • This function is responsible for executing the user-provided Rockstar code.
  • It adds the line let something be arguments at 0 at the beginning of the code to capture the first argument passed to the Rockstar script (which will be the encrypted key).
  • It also add thes lines let liberty be decrypt taking something and shout liberty at the end of the code to perform the decryption and print the result.
  • It writes the modified code to a temporary file using the write_down_lyrics function
  • Overall lyrics are prepared to be executed in Rockstar language is as follows -
let something be arguments at 0
[User provided Rockstar code]
let liberty be decrypt taking something
shout liberty
  • Use python subprocess module to run the Rockstar interpreter (/rockstar) with the temporary file and the encrypted key as arguments.
  • The encrypted key is passed as agrument to the Rockstar script, which will be stored in the variable something in the Rockstar code (argument at 0).
  • It also captures the output of the Rockstar script execution, which is expected to be the decrypted key.
  • Finally, it removes the temporary file and returns the output of the Rockstar script.
def sing(code):
    code.insert(0,"let something be arguments at 0\n") # add line to capture the first argument (encrypted key)
    code.extend(["let liberty be decrypt taking something\n","shout liberty\n"]) # add lines to perform decryption and print result
    encrypted_key = encrypt_key(key) # get the encrypted key 
    file = write_down_lyrics(code) # write the modified code to a temporary file
    try:
        result = subprocess.run(['/rockstar', file.name, encrypted_key], capture_output=True, text=True) # execute the Rockstar script with the encrypted key as argument
    except Exception as e:
        print(f"Something went really wrong {e}")
        exit(1)
    os.remove(file.name) # remove the temporary file
    return result.stdout.strip() # return the output of the Rockstar script (has to be decrypted key)

From the above rockstar code that is passed to the interpreter, our goal is to input a decryption function (decrypt) that takes the encrypted key (stored in something) and returns the original key.

let liberty be decrypt taking something
shout liberty

These lines appended at the end of user provided code will call the decrypt function with the encrypted key stored in something and print the result.

  1. read_until()
  • This function reads user input line by line until it encounters the special line "$$END$$".
  • Uses python standard input to read the lines.
  • It appends each line to a list and returns the list of lines once the end marker is found.
def read_until():
    line = ""
    code = []
    while True:
        line = sys.stdin.readline() # read a line from standard input
        if "$$END$$" in line: # check for the end marker
            break
        code.append(line) # append the line to the list
    return code # return the list of lines
  1. main()
  • This is the main function that orchestrates the flow of the program.
  • It first calls prison_gate() to display the prison gate ASCII art.
  • Then it calls read_until() to read the user-provided Rockstar code.
  • It passes the code to the jail() function for sanitization. If the code is not valid, it prints an error message and exits.
  • If the code is valid, it calls the sing() function to execute the Rockstar code and get the output (decrypeted key).
  • If the output matches the original key, it calls print_open_jail() to display the open jail ASCII art and the flag.
  • If the output does not match, it prints an error message.
def main():
    prison_gate() # display the prison gate ASCII art
    input_text = read_until() # read user-provided Rockstar code
    valid = jail(input_text) # sanitize the code
    if valid:
        if sing(input_text) == key: # execute the Rockstar code and check if output decrypted key matches the original key
            print_open_jail() # display open jail ASCII art and flag
        else:
            print("Aw... what happen to your voice..")
    else:
        print("Look on the bright side, you have only 24 years left.")
    exit

This is about all the functionality of the chal.py file.

Additionally, the scripts begins with calling the main function when executed directly.

See this part -

if __name__ == "__main__":
    main()

Dockerfile

The Dockerfile sets up the environment to run the chal.py script.

  • It uses the python:3.11-slim base image.
  • Sets the working directory to root (/).
  • Sets an environment variable FLAG with a base64 encoded flag value.
  • Installs necessary dependencies including curl and libicu-dev.
  • Downloads and installs the Rockstar programming language interpreter.
  • Copies the chal.py script to the root directory.
  • Sets the entrypoint to run the chal.py script using python.

This is all about the handout files.

Now let’s see how to solve this challenge.

Solution

To solve this challenge, we need to write a Rockstar function that correctly decrypts the encrypted key produced by the encrypt_key function in the chal.py script.

We know that after validation from jail function, the user-provided Rockstar code will be executed with the following lines added -

let something be arguments at 0
[User provided Rockstar code]
let liberty be decrypt taking something 
shout liberty

We’ll have to define the decrypt function in our user provided Rockstar code.

(I have spent 3 hours in total to figure out the decryption logic and write the Rockstar code, the rockstar docs also helped a lot.)

The decrypt function needs to reverse the operations performed by the encrypt_key function.

  • First, it needs to reverse the string.
  • Then, it needs to shift each character back by its position in the string.
  • Finally, it needs to return the original key.

Just for testing purpose let’s take a random key hello1234 and encrypt it using the encrypt_key function to see what the encrypted key looks like. It’ll output "=;97tpogi".

Now our goal is to write a Rockstar code with a function decrypt, that upon calling with “=;97tpogi” as argument, returns “hello1234”.

Here’s the Rockstar code that accomplishes this:

something is "=;97tpogi"

decrypt takes anything
    put null minus true into ohminusone
    put true minus null into ohone
    put ohminusone times anything into reversed
    Rock unscrambled
    cut reversed into pieces
    The counter is null minus null
    While the counter aint pieces
    put pieces at the counter into char
    burn char into code
    put code minus the counter minus ohone into wow
    burn wow into yoo
    unscrambled is with yoo
    Build the counter up, yeah
    join unscrambled
    Give back unscrambled


let liberty be decrypt taking something
shout liberty

Let’s trying running this code in the Rockstar interpreter

rockstar_interpreter

It correctly outputs “hello1234”. yayyy!

This means our decryption logic is correct.

Now we can use this Rockstar code in the challenge.

When we run the chal.py script or the remote instance (nc 35.224.150.112 1337), we can input the above Rockstar code of decrypt function ending by $$END$$ to signal the end of input.

Final payload to input in the challenge -

decrypt takes anything
    put null minus true into ohminusone
    put true minus null into ohone
    put ohminusone times anything into reversed
    Rock unscrambled
    cut reversed into pieces
    The counter is null minus null
    While the counter aint pieces
    put pieces at the counter into char
    burn char into code
    put code minus the counter minus ohone into wow
    burn wow into yoo
    unscrambled is with yoo
    Build the counter up, yeah
    join unscrambled
    Give back unscrambled

$$END$$

By passing this code to the script, the final code that is getting executed in Rockstar interpreter will be -

let something be arguments at 0

decrypt takes anything
    put null minus true into ohminusone
    put true minus null into ohone
    put ohminusone times anything into reversed
    Rock unscrambled
    cut reversed into pieces
    The counter is null minus null
    While the counter aint pieces
    put pieces at the counter into char
    burn char into code
    put code minus the counter minus ohone into wow
    burn wow into yoo
    unscrambled is with yoo
    Build the counter up, yeah
    join unscrambled
    Give back unscrambled

let liberty be decrypt taking something
shout liberty

When the Rockstar interpreter executes this code with the encrypted key as argument, it will call the decrypt function, which will reverse the encryption process and return the original key.

The output of the Rockstar script will be the original key, which will match the key generated in the chal.py script.

When the output matches, the script will call print_open_jail() function, which will display the open jail ASCII art and the flag.

When we run the above payload in the challenge, we get the flag -

flag

Explanation of the Rockstar code

  • In the payload of decrypt function, I have used

    • null minus null to represent 0
    • true minus null to represent 1
    • null minus one to represent -1
    • also avoid using any special characters or digits
    • This is because the jail function in chal.py performs sanitization checks to disallow any special characters or digits in the user-provided Rockstar code.
  • The line put ohminusone times anything into reversed is used to reverse the string. Multiplying by -1 effectively reverses the string in Rockstar.

  • Initialize an empty list unscrambled to hold the decrypted characters. (Rock unscrambled)

  • The cut operation is used to split the string into individual characters. (i.e. "=;97tpogi" into pieces will result in ["=", ";", "9", "7", "t", "p", "o", "g", "i"] can be seen in the line cut reversed into pieces)

  • Initialize a counter to 0 (The counter is null minus null)

  • A while loop to iterate through each character in the pieces.(While the counter aint pieces), the flow of while loop is as follows -

    • For each character (char), calculate the original character by subtracting its position (counter + 1) from its ASCII value.
    • The burn operation is used to convert a character to its ASCII value.
    • Burn the value of char into code to get its ASCII value.
    • Subtract the position (counter + 1) from the ASCII value to get the original ASCII value. (put code minus the counter minus ohone into wow)
    • Burn the original ASCII value stored in wow into yoo to convert it back to a character. (burn wow into yoo)
    • Append the character yoo to the unscrambled list. (unscrambled is with yoo)
    • Increment the counter by 1. (Build the counter up, yeah)
  • After the loop, join the characters in unscrambled to form the original string. (join unscrambled)

  • Return the original string. (Give back unscrambled)

This is how we solve the JailhouseRock challenge and retrieved the flag!

Shoutout to @drgn for creating this fun challenge! (Now you can dump your rockstar knowledge to /dev/null :D)