Brixel CTF 2020 write up
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!
- no peeking!
- Seacode
- Don’t be salty
- Keep walking
- punchcard
- Flat eath
- Android app
- Are you fast enough?
- Quizbot
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
:
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.
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:
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?
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!
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?
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}