JailhouseRock
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 variablekey- generates a random URL-safe key using thesecretsmodule
The python file chal.py given in this challenge contains following functions -
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
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.
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.
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
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
tempfilemodule 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.
sing(code)
- This function is responsible for executing the user-provided Rockstar code.
- It adds the line
let something be arguments at 0at 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 somethingandshout libertyat 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_lyricsfunction - 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
somethingin 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.
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
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
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 -
Explanation of the Rockstar code
-
In the payload of decrypt function, I have used
null minus nullto represent 0true minus nullto represent 1null minus oneto represent -1- also avoid using any special characters or digits
- This is because the
jailfunction 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 reversedis used to reverse the string. Multiplying by -1 effectively reverses the string in Rockstar. -
Initialize an empty list
unscrambledto hold the decrypted characters. (Rock unscrambled) -
The
cutoperation 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 linecut 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
burnoperation is used to convert a character to its ASCII value. - Burn the value of
charintocodeto 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
wowintoyooto convert it back to a character. (burn wow into yoo) - Append the character
yooto theunscrambledlist. (unscrambled is with yoo) - Increment the counter by 1. (
Build the counter up, yeah)
- For each character (
-
After the loop, join the characters in
unscrambledto 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)