BCACTF: reload decode
Challenge
Author: Nikhil
Points: 50 points
Solves: 19
Category: misc
Description:
Greetings neighbor. My name’s Crazy Dave but you can just call me Crazy Dave. I found this weird website that seems to represent something. Says something about taco as a reward Can you find whatever the code is and hand it to me? I could really go for a taco right now
Resources
- Web servers: challs.bcactf.com:30831
- Static resources: main.py
challs.bcactf.com:30831
A website with a /flag endpoint whose response is an ASCII art of a flag represented using only 1s and 0s:
011000 100000 011010110011 011000 011001011100110011011111 000100 011001101110011010111011111011101110 001100 000001011101110110000011011101010010011000000001 010101 011100000010001110110011001001010111011000110011001011010101 010010 000000001111011101011101000000011001010001111000110011011010 111111 101011011101011000111011000001110111011010001000 001010 001000011000100000001011101110010100 110010 011110101000010110011001 110010 001000011110 101010 011110 101110 010010 011000 010010 001100 011100 000100 011001 100110 011000 100110 010100 110111 010011 101010 011100 000100 010110 101110 011110 011101
main.py
from flask import Flask, render_template
import random
app = Flask(__name__)
@app.route('/')
def index():
return render_template('index.html')
@app.route('/flag')
def getFlag():
# Get flag from flag.txt
with open("flag.txt") as f:
flag = bytearray(f.read().encode())
flag_str = ""
for b in flag:
b = b << 4 ^ ((b << 4 & 0xff) >> 4)
bm = 1 << random.randint(0, 11)
cb = b ^ bm
flag_str += bin(cb)[2:].zfill(12)
return render_template('index.html', joined_flag = flag_str)
if __name__ == '__main__':
app.run(host="0.0.0.0", port=3000)Initial thoughts and Analysis
In the main.py file, we can see that the only endpoint that returns any relevant information is the /flag route. I don’t like the look of the getFlag() function, though.
The getFlag() function reads the contents of flag.txt and transforms it into a bytearray before saving it to the flag variable. Each character of the flag goes through a sequence of bitshifts and XORs before they are stored in flag_str. The most important bit here is that each character of the flag is bitshifted left by a random amount between 0 and 11 (random.randint(0, 11)). This means that a single character in the flag plaintext can turn into 12 different outputs as random.randint is inclusive:
def randint(self, a, b):
"""Return random integer in range [a, b], including both end points.
"""
return self.randrange(a, b+1)When the entire flag has been transformed/encrypted it is displayed on the website in a series of <h6> tags which together form an ascii art flag.
Vulnerability
The vulnerability in this challenge lies in the fact that we can “encrypt” any input we want using a modified version of the same algorithm. I’ve created a function that will create a dictionary that lets me look up a plaintext character given an encrypted character:
def create_lookup() -> dict[str, list[str]]:
printable_range = range(0x20, 0x7f)
lookup = {}
for character in printable_range:
modified_character = character << 4 ^ ((character << 4 & 0xff) >> 4)
for i in range(0, 12):
random_shift = 1 << i
possible_output = bin(modified_character ^ random_shift)[2:].zfill(12)
if possible_output not in lookup:
lookup[possible_output] = []
lookup[possible_output].append(character)
return lookupExploitation
The only tools I needed to use for this challenge were me, myself, and my trusty snake (python).
Initially, I copied and manually formatted the binary output of http://challs.bcactf.com:30831, but that ended up being too time consuming, and too prone to errors. Resorted to fetching the contents of the website, and collecting all the binary data with BeautifulSoup.
I managed to get what I thought to be the correct flag multiple times, however due to a bitshift added to the ciphertext, the resulting plaintext was different each time. These were some of the invalid flags I got:
bcactf{n0w-HEr3's-@-taco-chbnRzIHzIHpvbWJp}
bcactf{n0w-HEr3's-@-taco-xhbnRzIHzIHpvbWJp}
bcactf{n0w-HEr3's-@tco-cGhbnRzIHZzIHpbWJp}
bcactf{n0w-HEr3s-@-taco-cGxhbnRzIHZzIHpvbJpZ}
bcactf{n0w-HEr3's-@-taco-cGxbRzIHZzIHpvbWJpZ}I tweaked the script to compare multiple different plaintexts, and generate a new plaintext containing only the most common letter at each position.
Final solve script
# Function to fetch the binary representation of
# the encrypted flag from the ascii art flag from
# the `/flag` endpoint
def get_bins(count: int) -> list[str]:
from requests import get
from bs4 import BeautifulSoup as bs
bins = []
for _ in range(count):
response_text = get("http://challs.bcactf.com:30831/flag?").text
soup = bs(response_text, "html.parser")
# All the binary data are stored in <h6> tags
splits = soup.find_all("h6")
string = "".join([s.text for s in splits])
bins.append(string)
return bins
def create_lookup() -> dict[str, list[str]]:
printable_range = range(0x20, 0x7f)
lookup = {}
for character in printable_range:
modified_character = character << 4 ^ ((character << 4 & 0xff) >> 4)
for i in range(0, 12):
random_shift = 1 << i
possible_output = bin(modified_character ^ random_shift)[2:].zfill(12)
if possible_output not in lookup:
lookup[possible_output] = []
lookup[possible_output].append(character)
return lookup
# Get 5 versions of the encrypted flag
bins = get_bins(5)
# Create a lookup containing all possible ciphertext <-> plaintext combinations
lookup = create_lookup()
i = 0
chunk_size = 12
streams = []
for i in range(len(bins[0])//chunk_size):
for i_bins in range(len(bins)):
chunk = bins[i_bins][i * chunk_size: (i + 1) * chunk_size]
try:
letters = [chr(character) for character in lookup[chunk]]
except KeyError:
continue
if i_bins == 0:
streams.append([])
streams[-1] += letters
# Get the most common letter at each index
# and collect them to get the flag
for s in streams:
counts = {}
for a in s:
if a in counts:
counts[a] += 1
else:
counts[a] = 1
res = [key for key in counts if all(counts[temp] <= counts[key] for temp in counts)]
print(" ".join(res))
# The output will be a vertical list of each character in the flag
# Some indices may have multiple possible characters
# Example:
# b
# c t
# a c
# c
# t
# f
# {
# etc.The output of this script is the flag:
bcactf{n0w-HEr3's-@-taco-cGxhbnRzIHZzIHpvbWJpZ}