Description
I heard some of you don't like Crypto or Web. Lucky for you, there are a bunch of ways to get the flag. Be it via programming, crypto, forensics, you name it.
Hint: A good place to start would be the client.py script. Just let it run and see for yourself.
Attachments
http://20.51.215.194:5000/
https://imaginaryctf.org/r/4C8A-maze.py
https://imaginaryctf.org/r/B5B1-server.py
https://imaginaryctf.org/r/674D-cookie_decoder.py
https://imaginaryctf.org/r/954F-client.py
Writeup
Have a look at ex.py. There are 5 general ideas on how to solve this.
import requests
from time import time
from os import system
from base64 import b64decode
from solver import solve
from server import get_primes_between
from client import get_new_maze, send_steps
from maze import Maze, WALL, CLEAR, PLAYER, FLAG
from flask_session_cookie_manager3 import encode
API = "http://127.0.0.1:9001"
################################################
# implement dijkstras algorithm (or smt similar)
print("---------------------------------------")
print("[*] With dijkstra:")
s = requests.Session()
maze = get_new_maze(s)
directions = solve(maze)
print(send_steps(s, directions))
#########################################################
# with the known secret key of flask, create an easy maze
print("\n\n---------------------------------------")
print("[*] Own maze:")
raw_maze = 4*WALL + "\n" + WALL + PLAYER + FLAG + WALL + "\n" + 4*WALL + "\n"
maze = Maze(data=raw_maze) # the outer walls are just for looks
print(maze)
secret = 'ictf[I_4m_sur3_y0u_c4nt_cr3at3_a_new_5e551on_to_1nst4ntly_s0lv3_th15_ch47!]'
data = f'{{"maze":"{maze.ascii_render()}","tries":0}}'
encoded = encode(secret, data)
cookies = {'session': encoded}
print("Created cookie: " + str(cookies))
res = requests.get(f'{API}/step?directions=d', cookies=cookies).text
print("\n" + res)
##########################################
# find the solve script in the maze source
print("\n\n---------------------------------------")
print("[*] Solver from Source:")
source = b64decode("CiMgc2VhcmNoIGNsb3Nlc3QgcGF0aCB0byBmbGFnCmRlZiBzb2x2ZShtYXplKToKICAgICMgcmVzZXQgYWxsZSBub2RlIGRpc3RhbmNlcyBhbmQgcHJldiBub2RlcwogICAgbWF6ZS5yZXNldF9zb2x2ZSgpCgogICAgIyBpbml0CiAgICBzdGFydF9ub2RlID0gbWF6ZS5nZXRfcGxheWVyX25vZGUoKQogICAgc3RhcnRfbm9kZS5zZXRfZGlzdCgwKQogICAgdGFyZ2V0ID0gbWF6ZS5nZXRfZmxhZ19ub2RlKCkKCiAgICB2aXNpdGVkID0gW10KICAgIG5leHRfbm9kZXMgPSBbc3RhcnRfbm9kZV0KCiAgICB3aGlsZSB0YXJnZXQgbm90IGluIHZpc2l0ZWQ6CiAgICAgICAgIyBjdXJyZW50IG5vZGUgaXMgYWx3YXlzIHRoZSBvbmUgd2l0aCB0aGUgc2hvcnRlc3QgZGlzdCB0byBzdGFydAogICAgICAgIGN1cnJlbnRfbm9kZSA9IG5leHRfbm9kZXMucG9wKDApCgogICAgICAgIG5laWdoYm91cnMgPSBtYXplLmdldF9hY3Rpb25zKG9ubHlfZGlyZWN0aW9ucz1GYWxzZSwgcG9zPWN1cnJlbnRfbm9kZS5nZXRfcG9zKCkpCiAgICAgICAgZm9yIGRpcmVjdGlvbiwgbmVpZ2hib3VyIGluIG5laWdoYm91cnM6CgogICAgICAgICAgICAjIGNoZWNrIGlmIHRoZSBuZXcgd2F5IGlzIHNob3J0ZXIKICAgICAgICAgICAgbmV3X2Rpc3QgPSBjdXJyZW50X25vZGUuZ2V0X2Rpc3QoKSArIDEKCiAgICAgICAgICAgIGlmIG5ld19kaXN0IDwgbmVpZ2hib3VyLmdldF9kaXN0KCk6CiAgICAgICAgICAgICAgICBuZWlnaGJvdXIuc2V0X2Rpc3QobmV3X2Rpc3QpCiAgICAgICAgICAgICAgICBuZWlnaGJvdXIuc2V0X3ByZXYoZGlyZWN0aW9uLCBjdXJyZW50X25vZGUpCiAgICAgICAgICAgICAgICBuZXh0X25vZGVzLmFwcGVuZChuZWlnaGJvdXIpCgogICAgICAgIGlmIGN1cnJlbnRfbm9kZSA9PSB0YXJnZXQ6CiAgICAgICAgICAgIHByaW50KCkKCiAgICAgICAgdmlzaXRlZC5hcHBlbmQoY3VycmVudF9ub2RlKSAgIyBub3cgYWxsIG5laWdoYm91cnMgYXJlIHZpc2l0ZWQsIG5vIG1vcmUgY2hhbmdlcyB0byBjdXJyZW50IG5vZGUKICAgICAgICBuZXh0X25vZGVzID0gc29ydGVkKG5leHRfbm9kZXMsIGtleT1sYW1iZGEgbjogbi5nZXRfZGlzdCgpKQoKICAgICMgYmFja3RyYWNrIGRpcmVjdGlvbnMKICAgIHdheSA9ICIiCiAgICBub2RlID0gbWF6ZS5nZXRfZmxhZ19ub2RlKCkKICAgIHdoaWxlIG5vZGUgaXMgbm90IHN0YXJ0X25vZGU6CiAgICAgICAgZCwgbm9kZSA9IG5vZGUuZ2V0X3ByZXYoKQogICAgICAgIHdheSArPSBkCgogICAgcmV0dXJuIHdheVs6Oi0xXQoKCmlmIF9fbmFtZV9fID09ICJfX21haW5fXyI6CiAgICBmcm9tIG1hemUgaW1wb3J0IE1hemUKCiAgICBtID0gTWF6ZSh2ZXJib3NlPUZhbHNlKQogICAgZGlyZWN0aW9ucyA9IHNvbHZlKG0pCgogICAgbS5zaG93X3NvbHZlKGRpcmVjdGlvbnMpCiAgICBwcmludChkaXJlY3Rpb25zKQ==")
source = source.decode()
open("leaked_solver.py", "w").write(source)
print("Decoded source, safed in leaked_solver.py")
from leaked_solver import solve as leaked_solve
s = requests.Session()
maze = get_new_maze(s)
directions = leaked_solve(maze)
print(send_steps(s, directions))
system("rm leaked_solver.py")
# --------------------------------------------------------------------- #
# this is used later
def decrypt_flag(encryped):
cipher = encryped[1:-1].split(", ")
cipher = [int(c) for c in cipher]
print("Got encripted flag: " + str(cipher[:2])[:-2] + ", ...]")
# (flag_enc[-1] + ord(FLAG[i])) * p1 * p2 = cipher[1]
# (0x1337 + ord("i")) * p1 * p2 = cipher[1]
# p1*p2 = cipher[1] // (0x1337 + ord("i"))
n = int(cipher[1]) // (0x1337 + ord("i"))
print("Got p1*p2: " + str(n))
# (cipher[-1] + ord(FLAG[i])) * n = cipher[i]
# ord(FLAG[i]) = cipher[i] // n - cipher[-1]
flag = ""
for i in range(1, len(cipher)):
flag += chr(cipher[i] // n - cipher[i-1])
return flag
# --------------------------------------------------------------------- #
#########################################################
# get the server to throw an error, then decrypt flag_enc
print("\n\n---------------------------------------")
print("[*] Throw error:")
s = requests.Session()
get_new_maze(s)
flag_encryped = send_steps(s, "waw").split(": ")[-1][:-1]
print("\n" + decrypt_flag(flag_encryped))
##################################################
# solve the maze "manually", then decrypt flag_enc
print("\n\n---------------------------------------")
print("[*] Manual solve:")
# create a mock maze
raw_maze = 5*WALL + "\n" + WALL + PLAYER + CLEAR + FLAG + WALL + "\n" + 5*WALL + "\n"
maze = Maze(data=raw_maze) # the outer walls are just for looks
print(maze)
secret = 'ictf[I_4m_sur3_y0u_c4nt_cr3at3_a_new_5e551on_to_1nst4ntly_s0lv3_th15_ch47!]'
data = f'{{"maze":"{maze.ascii_render()}","tries":0}}'
encoded = encode(secret, data)
cookies = {'session': encoded}
s = requests.Session()
res = s.get(f'{API}/step?directions=d', cookies=cookies).text
maze.step("d") # only for demonstration purposes, the maze in the session is already updated
print(maze)
res = s.get(f'{API}/step?directions=d', cookies=cookies).text
maze.step("d") # only for demonstration purposes, the maze in the session is already updated
print(maze)
msg, flag_encryped, _ = res.split("\n")
print("Second step: " + msg)
print("\n" + decrypt_flag(flag_encryped))
print()```
Flag
ictf{W0w!_th3re_4r3_s0_m4ny_w4y5_t0_g37_the_fl4g,_r1ght?}