This year I participated in the Brixel CTF winter edition along with another player from the Darknet Diaries Discord community. Despite some stability issues on the server side this CTF had some fun puzzles although some more challenging puzzles would be appreciated for a future installment. Below is my write up of a few of them – I ended up solving a few more but I didn’t keep any notes on them.

Cookieee!

This stupid cookie clicker game…

Legend has it there is a reward when you reach 10000000 or more clicks

Can you think of a way to get that many clicks?

This is a perfect use case for CheatEngine, scan on the initial value 1:

cookie_1

Back in the game, click the cookie a number of times until it hits 5 (or any other arbitrary value) and scan for that value in memory. The list of addresses shortens until there are only a few candidates left. Now make sure we can write to the address by changing the value to something like 42 and click the cookie again to see it increase to 43 in the game.

cookie_2

Now change it to 10000000, click the cookie and grab the flag from the message which is now displayed:

Congratulations! Flag = brixelCTF{m3m0ry}

no peeking!

Hidden inside this exe file is a flag

Up to you to find it

file(1) reports the binary to be an Intel 80386 Mono/.net assembly file so we can use a tool like ILSpy or dotPeek to analyze and decopile the file.

I went with dotPeek on this one and found a large base64-encoded object which turned out to be an embedded TTF file. Peeking further the showFlag() object stands out:

peek_1

And indeed it contains the flag:

public object showFlag()
   {
     int num1 = (int) Interaction.MsgBox((object) "Hey, stop looking at my innards!");
     int num2 = (int) Interaction.MsgBox((object) "The flag is brixelCTF{d0tP33K}");
     int num3 = (int) Interaction.MsgBox((object) "Happy holidays!");
     return (object) true;
   }

Seacode

beep beep beeeep…

This one should be fairly straight forward

The audio file sounds like morse so either we could manually transcribe the beeps to morse or use something like this morse code audio decoder to convert it for us:

THE FLAG FOR THIS CHALLENGE IS SEAGULL

Don’t be salty

Our l33t hackers hacked a bulletin board and gained access to the database. We need to find the admin password.

The user’s database info is:

Username:admin

Passwordhash:2bafea54caf6f8d718be0f234793a9be

Salt:04532@#!!

We know from the source code that the salt is put AFTER the password, then hashed. We also know the user likes to use lowercase passwords of only 5 characters long.

The flag is the plaintext password.

Start by analyzing the hash which comes back as MD5. Since we know the format and characterset of the password we can resort to bruteforcing this one. The hashcat documentation provides the format to store the hash and salt so let’s get cracking:

hashcat -a 3 -m 10  /tmp/pass.txt "?l?l?l?l?l"

Since the searchspace is so limited this was cracked quickly on a modest CPU:

2bafea54caf6f8d718be0f234793a9be:04532@#!!:brute

Session..........: hashcat
Status...........: Cracked
Hash.Name........: md5($pass.$salt)
Hash.Target......: 2bafea54caf6f8d718be0f234793a9be:04532@#!!
Time.Started.....: Sun Dec 27 13:51:55 2020 (1 sec)
Time.Estimated...: Sun Dec 27 13:51:56 2020 (0 secs)
Guess.Mask.......: ?l?l?l?l?l [5]
Guess.Queue......: 1/1 (100.00%)
Speed.#1.........:   788.0 kH/s (3.84ms) @ Accel:1024 Loops:26 Thr:1 Vec:8
Recovered........: 1/1 (100.00%) Digests
Progress.........: 212992/11881376 (1.79%)
Rejected.........: 0/212992 (0.00%)
Restore.Point....: 4096/456976 (0.90%)
Restore.Sub.#1...: Salt:0 Amplifier:0-26 Iteration:0-26
Candidates.#1....: spale -> xolch

Keep walking…

This is a challenge to test your basic programming skills.

Pseudo code:

Set X = 1

Set Y = 1

Set previous answer = 1

answer = X * Y + previous answer + 3

After that => X + 1 and Y + 1 (‘answer’ becomes ‘previous answer’) and repeat this till you have X = 525.

The final answer is the value of ‘answer’ when X = 525. Fill it in below.

Simply translate the pseudocode to Python:

#!/usr/bin/env python3

(x, y, prev) = 1, 1, 1

while x < 526:
    answer = x * y + prev + 3
    prev = answer
    x += 1
    y += 1

print(answer)

or to actually have a little bit of challenge I decided to write it in RISC-V assembly:

    .global _start

_start:
    li      t0, 1    # x = 1
    li      t1, 1    # y = 1
    li      t2, 1    # prev = 1
    li      t3, 0    # answer = 0

l1:
    mul     t4, t0, t1   # x * y
    addi    t2, t2, 3    # prev + 3
    add     t3, t2, t4   # answer = x * y + prev + 3
    mv      t2, t3       # prev = answer
    addi    t0, t0, 1    # x += 1
    addi    t1, t1, 1    # y += 1


    li      t5, 526      # loop counter
    blt     t0, t5, l1   # yummy, BLT

    ebreak

    li      a0, 0     # rc=0
    li      a7, 93    # service command code 93 (exit)
    ecall

When run with spike it dumps the registers upon hitting ebreak (the answer is stored in t3):

z  0000000000000000 ra 0000000000000000 sp 000000007f7e8b50 gp 0000000000000000
tp 0000000000000000 t0 000000000000020e t1 000000000000020e t2 0000000002e2205b
s0 0000000000000000 s1 0000000000000000 a0 0000000000000000 a1 0000000000000000
a2 0000000000000000 a3 0000000000000000 a4 0000000000000000 a5 0000000000000000
a6 0000000000000000 a7 0000000000000000 s2 0000000000000000 s3 0000000000000000
s4 0000000000000000 s5 0000000000000000 s6 0000000000000000 s7 0000000000000000
s8 0000000000000000 s9 0000000000000000 sA 0000000000000000 sB 0000000000000000
t3 0000000002e2205b t4 00000000000434a9 t5 000000000000020e t6 0000000000000000
pc 00000000000100a8 va 00000000000100a8 insn       ffffffff sr 8000000200046020
Breakpoint!

punchcard

I found this old punchcard

it seems to be classified

can you figure out what’s on there?

punchcard

Upload the punchcard to an online punchcard reader to scan the image and read back the result:

THE FLAG IS BRIXELCTF(M41NFR4M3) -- THANK YOU FOR PLAYING BRIXELCTF --        1A

Flat earth

These idiots… I heard there is a rally of flat earth believers tomorrow

We should access their admin panel and stop that rally from happening!

http://timesink.be/flatearth/

The page source contains a hidden link (black text on black background) to admin.php. That login form is vulnerable to a simple SQLi. Set the username to 'or 1=1--' and it returns the flag:

That should do the trick, the flag is brixelCTF{aroundtheglobe}

Android app

This little android app requires a password, can you find it?

The download is an Android apk file which is just a JAR file which is just a ZIP file. So unzip it and see if the flag is stored in plain text:

% rabin2 -z *.dex | grep brixelCTF
2084  0x0037ebbd 0x0037ebbd 116 116  data    ascii /tmp/1602666767984_0.27958933214667514-0/youngandroidproject/../src/appinventor/ai_kevin_erna/brixelCTF/Screen1.yail
2085  0x0037ec33 0x0037ec33 116 116  data    ascii /tmp/1602666767984_0.27958933214667514-0/youngandroidproject/../src/appinventor/ai_kevin_erna/brixelCTF/Screen2.yail
2086  0x0037eca9 0x0037eca9 116 116  data    ascii /tmp/1602666767984_0.27958933214667514-0/youngandroidproject/../src/appinventor/ai_kevin_erna/brixelCTF/Screen3.yail
23668 0x0042027b 0x0042027b 9   9    data    ascii brixelCTF
23669 0x00420286 0x00420286 43  43   data    ascii brixelCTF{th3_4ndr0ids_y0u_4r3_l00k1ng_f0r}
%

Are you fast enough?

Can you program something that is fast enough to submit the solution before the time runs out?

http://timesink.be/speedy

Simple case of parsing the HTML and posting the result back.

#!/usr/bin/env python3
#
# Brixel 2020 Winter CTF "Are you fast enough?"

import requests
from bs4 import BeautifulSoup


SPEEDY = 'http://timesink.be/speedy/'

def main():
    # Use a session to pass cookies around
    s = requests.Session()
    r = s.get(SPEEDY)

    if r.status_code != 200:
        print('[-] Failed to GET')
        return

    rndstring = ''

    bs = BeautifulSoup(r.text, 'html.parser')
    for div in bs.find_all('div'):
        id = div.attrs.get('id')
        if id and id == 'rndstring':
            rndstring = div.contents[0]

    print(f'[+] Got the random string: {rndstring}')
    r = s.post(SPEEDY, data = {'inputfield': rndstring})

    if r.status_code != 200:
        print('[-] Failed to POST')
        return

    bs = BeautifulSoup(r.text, 'html.parser')
    flag = bs.find('b').contents[0]

    print(f'[+] Flag: {flag}')

if __name__ == '__main__':
    main()

Important is to use a session otherwise the backend doesn’t know which value belongs to whom:

[+] Got the random string: 9z4q5zttIo
[+] Flag: brixelCTF{sp33d_d3m0n}

Quizbot

Legend has it there’s a flag at the end when you have a perfect score http://timesink.be/quizbot

This challenge requires one to answer the 1000 questions correctly. Since the website gives you the correct answer after you’ve incorrectly answered the question we can simply go through all questions and build our knowledge base. Then go through it and post all correct answers as we go.

#!/usr/bin/env python3
#
# Brixel 2020 Winter CTF "Quizbot"

import requests
from bs4 import BeautifulSoup
from progress.bar import Bar


QUIZ = 'http://timesink.be/quizbot/'

def main():
    # Use a session to pass cookies around
    s = requests.Session()

    # Start the quiz and discard the first question
    r = s.get(QUIZ)

    if r.status_code != 200:
        print('[-] Failed to GET')
        return

    kb = {}

    # Run the quiz and collect all the answers
    bar = Bar('Collecting answers', max=1000)
    for q in range(1, 1001):
        r = s.post(QUIZ, data={'insert_answer': '', 'submit': 'answer'})

        if r.status_code != 200:
            print(f'[-] Failed to POST question {q}')
            return

        bs = BeautifulSoup(r.text, 'html.parser')
        for div in bs.find_all('div'):
            id = div.attrs.get('id')
            if id and id == 'answer':
                answer = div.contents[0]
                kb[q] = answer
                bar.next()
                continue

    bar.finish()

    # Run the quiz again, this time posting the collected answers
    s = requests.Session()

    bar = Bar('Submitting answers', max=1000)
    for q in range(1, 1001):
        r = s.post(QUIZ, data={'insert_answer': kb[q], 'submit': 'answer'})

        if r.status_code != 200:
            print(f'[-] Failed to POST question {q}')
            return

        if 'Correct!' in r.text:
            bar.next()
            continue
        else:
            print('[!] Incorrect answer submitted?!')
            return

    bar.finish()
    print(r.text)


if __name__ == '__main__':
    main()

And progressbars are neat :-)

(env) basalt:1667 brixelctf % python3 quizbox.py
Collecting answers |################################| 1000/1000
Submitting answers |################################| 1000/1000
<div align="center">Correct!</div>
Congratulations, you have defeated the mighty QuizB0t, your flag is: brixelCTF{kn0wl3dg3}