Curtin CTF 2024 Writeups


Posted on Sun, Oct 13th, 2024 | 6640 words | ~31.32 minutes read


This is a writeup for Curtin CTF 2024 for the team [ N4S1L3M4K ]
Since it is a 24 hours CTF, and I haven't slept yet (as of this writing), there will be some mistakes, but it should be generally right.

Warmup

Inziption

Defense in Depth is the foundation for robust security, layering protection to deter even the most persistent threats. But what happens when there’s a leak in the armor? Can the sheer depth of protection save the day?

md5sum: c19da4010ecc2f0f734fca5f5c021ec0 sha1sum: a5a3b3a31b7a5b7ecc77dd588daef9594826c42d Author: @flabby

Files: file100.zip

Opening the file, it looks like a zip inside a zip inside a zip..., a zip bomb possibly?

and since there's 100 of them I'm not going extract it one by one so I wrote a script. Though, I realized this after doing it for about 6 times, when I got to file94.zip hence why the code starts from there.

import io

import pyzipper


def second(data: bytes, password: str) -> None:
    with pyzipper.AESZipFile(io.BytesIO(data)) as file:
        comment: str = file.comment.decode()
        print("ZIP Comment:", comment)

        for content in file.filelist:
            print("Extracting:", content.filename)

            if content.filename.endswith(".zip"):
                try:
                    file.extractall("out", pwd=password.encode())
                    second(file.read(content, pwd=password.encode()), comment)
                    print(f"Extracted {content.filename}")
                except RuntimeError as e:
                    print(f"Failed to extract {content.filename}: {e}")
            else:
                print(f"Skipping non-zip file: {content.filename}")


# starts at file94, because i realized extracting it manually the whole way was dumb.
zip_file_path = "file94.zip"

with open(zip_file_path, "rb") as f:
    zip_data = f.read()
    second(zip_data, "vhzygcuwrq")

and by the end of that zip trail, we will get this:

CURTIN_CTF{1_H0P3_YOU_AUT0M4TED_TH1S_Z1P_B0MB}

Cracked PDF

In this file lies a secret that’s been locked away, protected by a rock solid password. But once you gain access, be careful not to let appearances deceive you. Can you see through the illusion and uncover the real message?

md5sum: ed611d39b0c6c1b63df2ecd9ef40e533 sha1sum: 1d3e1797ae53bdd8c53796ef3efe2c828a6e22ec Author: @flabby

Files: cracked.zip

Opening the PDF file inside the zip will give us:

By cracking the password, we can now use PRETTY to open the file.

Which looks like a bunch of morse code, but we tried that and it doesn't work.

But this is where the description actually helps for once, be careful not to let appearances deceive you. So let's try reading it as binary.

Doing a quick ctrl+a ctrl+c from the pdf into a text editor, we can replace . with 0 and - as 1.

and then lining them up in the group of 8, and translating it back to ASCII/UTF-8 would give us the flag:

CURTIN_CTF{UNDERST4NDING_WH1TE_SP4CES_IS_IMP0RT4NT}

CrazySignal

Lost in transmission, a familiar code awaits discovery. But this one wasn't sent the usual way—it’s hidden in a broadcast, distorted by a robotic signal. To recover the message, you’ll need to tune in carefully and decode what’s hidden in plain sight. Can you crack the transmission and reveal the secret

md5sum c648727fd299476d220848ac6367e95a sha1sum 95a13b689f1e2eb9c90df646d146eabd6bcb6755 Author @flabby

Files: crazySignal.zip

In the zip folder there is one audio file. We can use SSTV to decode this, I'm using RX-SSTV to do it.

The flag is inside the QR code which is:

CURTIN_CTF{SP4C3_EXPL0RERS_MU5T_KNOW_AB0U7_SSTV}

IHeader

I received this secret message from a friend, however it looks like someone has tampered with structure of this image. What could this secret message be?

md5sum: 47a2c9c20500fe6227a8b27eff23becd sha1sum: fcf9814ec18acfc9b7a98d944a7a4fc7a1d98b21 Author: @flabby

Files: iHeader.zip

Inside the zip is one broken header.png picture.

A quick look into the hex editor we can see that the data is broken, there's no AAAA in PNG spec, so I assumed it's IHDR (comparing to other normal pictures I had).

Replacing that still results in a broken image, but it turns out there's another out-of-spec header, which is BBBB. Replacing that with IDAT (considering there isn't one at all in the image) seems to fix the picture.

CURTIN_CTF{m4gic_byt3s_are_s0_mag1cal}

OSINT

Car on Fire Part 1

Owner of the car?

Flag Format: CURTIN_CTF{Firstname_Lastname}

Author: RDxR10

Files: car_on_fire.mp4

The file, like the name suggests, is a video of a Tesla car being on fire. Taking a screenshot from the video and doing a reverse-image search leads up to an article.

CURTIN_CTF{Michael_Morris}

Car on Fire Part 2

What kind of air filter does the car come equipped with?

NOTE: Separate the words with underscores

Author: RDxR10

From the previous article, we get to know that the car is a Tesla Model S.

And from that, a quick google search will give us, HEPA (High-Efficiency Particulate Air).

CURTIN_CTF{high_efficiency_particulate_air}

Car on Fire Part 3

VIN of the car?

Author: RDxR10

By painstakingly looking at video closely, we can get the number plate.

Which is 6WZU104, then I'm using [this website] to get the vehicle VIN.

CURTIN_CTF{5YJSA1DP9CFP02519}

Can on Fire Part 4

Name the food store near which the car was spotted.

NOTE: Replace spaces with underscores.

Author: RDxR10

From the article we know the location of where it happened, which is somewhere on Santa Monica Blvd.

So now, the only thing I need to look for is the landmark or something that uniquely identifies the place.

Which, narrowed it down to [here], and so we can answer the question.

CURTIN_CTF{Odessa_Grocery}

Crash Airdrome Part 1

A: What was the engine model of the aircraft?

B: When was the aircraft manufactured? (DD-MM-YYYY)

Flag format: CURTIN_CTF{A_B}

Files: crash_at_the_airdrome.png

CURTIN_CTF{CFM56-7B24E_19-02-2013}

Crash Airdrome Part 2

When was the first flight of this aircraft model?

Flag Format: CURTIN_CTF{DD_MM_YYYY}

CURTIN_CTF{31_07_1997} 

Crash Airdrome Part 3

A: Distance between the airports?

NOTE: Answer includes digits only (km). Ignore decimal values.

B: What was the name of the PIC?

C: What was the part number of the aircraft's Terrain Awareness and Alerting System?

Flag format: CURTIN_CTF{A_BFirstName_BLastName_C}

CURTIN_CTF{861_Mahlup_Gozali_965-1690-055}

Divulger Part 1

A person with the online alias 'greekguy' had leaked details of Turkish Phone Number IDs

What email was the person using?

A quick search on google for intext: greekguy breachforum leads us to

From there, I found a tweet on twitter regarding him.

This leads us to the website, which is where we can find about his online info.

CURTIN_CTF{kpopayda@gmail.com}

Divulger Part 2

We have an intel that the same person had "Self-banned" himself.

When did this happened?

Format: CURTIN_CTF{DD_MM_YYYY}

From the same website we get before, we can get their banned date.

Which is: 1669612052

Converting the time from UNIX time to DD_MM_YY would give us:

CURTIN_CTF{28/11/2022}

Divulger Part 3

It seems greekguy was communicating with Stranger020.

What email was Stranger020 using? When did the person register on the forum?

Format: CURTIN_CTF{email_dd_mm_yyyy}

Crypto

RSA 1

Sohan, the infamous crook, was finally caught and thrown into prison. There, he unexpectedly encounters Officer Kartik, who turns out to be his childhood friend.

Kartik hands him a puzzle and says, "If you solve this, I promise I’ll let you out."

Help Sohan in his bid for freedom.

Files: chall.txt, close_up_rsa_1.py

From the code, we can guess how to reverse the method used:

p, q = gen_primes(4096, 9998)
n = p * q
phi = (p - 1) * (q - 1)
e = 65537
d = pow(e, -1, phi)
m = b'REDACTED'
c = pow(bytes_to_long(m), e, n)

print(f"n = {n}")
print(f"|p - q| = {abs(p - q)}")
print(f"c = {c}")
print(f"e = {e}")

From the past writeup I've seen and some researching, this is what I came up with:

n = 15940880393081622192223979546417668454241001094105696491631845740319699779637868921818468662546538526734159459987929569646758157949726050092327261607567477210838728560641007932591472048341979229227000996158864820326472140369536128940011407532021392192858059263128347413516277627338557773943130473619144266602819768289316894685127758747023625922861716983808957650638526747245869728390450745710053889903365620164261097827034696518433301946463145635387673172039280459108906156350531065067336803176395966402871830286192577088734549689317831281599635132619523883607065807674352024907150268276243231669001306391571889664767000269740644816918518242783451143038482044617197372383969811690923359599584060183246684500682839727818817572623643916667956119711025923398568092705131007640437009630310461215618002687681148614450537994685732723364660314199425405823518030479379304998927941434491041698761309565206754369009655472934744341914827525943272586954251730078614382895749955996740380235107223902027659741996429282861891673085104147370045405961294099732979678272773826599016536412578932433904030505154999176517839833260039964597955076704762201524866516857761405360622930516491985930417948661249000621996356980915282995033583482641405828363443819241154399820186471985527636315446103596671058367866892705878909337399021378085620148416281726544652130913248870503306604860069279648074721897994731666127511897571565176702890871406233528233764474842435353662824803774230985170155102399526678113553185926581547531929458018714491649785151228464354683945162919888238078818142664891976580283043168793532818976641325908588559452360271093536728188468093955635851427400246135873082132120043700880049057114037825152222836929555281795129395718413678801035220085447886467326801984191156595256589818613913229363820848876188943510988155897845879797538033623007410622942705920928531297122355664722115432967894109638655871697467544954570368089729236699347314122966944234115811880321161988925755790917881868951745905665036446036114588885458572324261723207406410695928509493222079503841942661183609924325963288751971513447161960665796967979143891190229029219582316717531133448823477925219559046895187452616687163842512991459142565797945381440167111698245672180570103015392203883682050472726311220309044957019444576289519199490560422919525648997774693485847722051397914538064633576600336792092599951404116951597102344146970626745993023933325515868962556368207329326367871452331540970273987458930797
|p - q| = 19446185665001932891863123565802286467991322014746876964013701369322897657444386633296616696168267366199920908214861667183967127865711481276497998516475134987986234107252897362882360257060819274323493441003279977951185789618655911147638408200310708832863210179986939189277431934841758094681397206297269204946733155416228404475101349159647712893882998644638490972589384447685758296695943696378904740865372847834754012806961145339009705107364366305272446432151468385174168203082509791808125524328759572433089974565599380134406580709133277810467919601298087724371283440367928548920207922728843966255605792638884428157104036042309966679145466135156678157063068138401063208639819013053445933650327463224033606491166029807779049310851321687425814592777852113728121194401684278386654197915094379386331968569071047319013960290729128040322044194139995761548185543015779153245806933487262288281397595328529970986231707560121231845029590674493616017456787804838794787286983295028461473824879025799162116310309820208035189596601045522614202811245581151461360787349910512422879586756376900659196002122475214220004775212001355945966793757926005189263687454876821065812893701132511947238001771844876788216681670235849206704991988163485611260844
c = 8413782726461541602913756039758305440836412287813773153411365960748450871027707633874217383295383481869325038078976781015007513229347097469371279220803204983355347655683513667088588074202501147594395702282334179124794781871517260250525536908326800965888418964918694916006884537543011229484376018147268919406195055917084790592479499766734576144082810933280877054789971671533919533660158408423424185650356318518379636823750893726233499500188843692055868151249687738386488534142892831049124956774899410006084346138710056812199825326206688966306714260750975121581835353985336980114774232067155041193198180698246403870169981563849307541955386369149422881602494929264929737280150329566903466285149020311618967791172538171280546694675534389454765543724780546918668435019028064871754691532110565068615157319889820944033048902255664642773218099048574566017181575258546758883546492803137717155881557338843592209207689463957840581945426930837246385901477752907096376004160861031244782814843210961453696657150665127977305005202771961236999129828758134510593378918560947499235704939556050322382009751568420207568808891407979608313803205850457374203809188547053115181776254415319188681883447021017869446867989346915152852825019616081716802990396062469163243135351547702156800537851107924720649180003285840914409777322049074094783843649569729539066223970441125382806118498958261540756052860910490399082824953032938402516792174368475134992760224198700374390864316120110306938158921418121457620889268565760685238892212999604146465162867640041064499228386006681717471251254990717858562759770905947269460142594521881470262940835078829442425244723815621278919162516682158822798977763435125635963668626156334114464102838126493966355609183208192998531373143867887346353211910003642658203653385244836610494653921249974400825401880566797287387115572727756785284767721336001760816036988439334050351608229131590077522439493925221375259909629016262905916790128638258322194247479997220390050492289058204485034457313809723675617094812771640112394720404929279053643210561537233165368292934623525915746720648655418268587097359836671508455239599508020030055462133024501888962745878358260116256538476487838862369689123290204371877250090186672449934183327283618423893439031497798985471835874040738044268676828950640266922716836786100999406935131991385114491269400938094956780725144002766875279228699473643314965832833403460993564655225664279717054432748451778401574414898003066402168353796733750757
e = 65537
import math
from Crypto.Util.number import long_to_bytes


def reverse_rsa(n, diff, c, e):
    discriminant = diff**2 + 4 * n
    sqrt_disc = int(math.isqrt(discriminant)) 
    p1 = (diff + sqrt_disc) // 2
    p2 = (diff - sqrt_disc) // 2
    p = max(p1, p2)
    q = p - diff
    phi = (p - 1) * (q - 1)
    d = pow(e, -1, phi)
    m = pow(c, d, n)

    return long_to_bytes(m)


n = 159408...
diff = 19...
c = 8413782...
e = 65537

m = reverse_rsa(n, diff, c, e)
print(m.decode("utf-8", errors="ignore")) 

CURTIN_CTF{1s_7h15_50m37h1ng_c10s3_70_m4g1c?}

RSA 2

Things get so intense on close up...but who cares?

Sejal and Sindu, close friends, agree that observation skills won't be necessary for this.

Files: chall.txt, close_up_rsa_2.py

Like before, we can guess work the decryption method until it works, and at the end this is what I got.

n = 38851370026688292718610500714047313275568128923880406032240694321386868708669715834849444005885071039350867771015411960409482332075714895130040955721609469707712307896329759118786126795971608203356413095211390559788430050833157008379312765193252778797982267208761085870346676464332871683194405356959302337269
c = 10261077771725074705657914043269356567599143194523440913396849942218293691678585398731563393613698000832185314817454466653490727661933561044428403796426278758420015350118130960938311896749919783987826137481434137155805830348623564782373093651077789955010753791725635501759033479236281331100713235218526738970
e = 65537
from Crypto.Util.number import long_to_bytes
from sympy import factorint


def decrypt(n, c, e):
    factors = factorint(n)
    p, q = list(factors.keys())

    phi_n = (p - 1) * (q - 1)
    d = pow(e, -1, phi_n)
    m = pow(c, d, n)

    return long_to_bytes(m)



n = 38851370026 ....
c = 10261077771 .....
e = 65537

decrypted_message = decrypt(n, c, e)
print(decrypted_message)

CURTIN_CTF{4_m4gn1fy1ng_gl455_w0n7_b3_n33d3d_for_sur3}

Dance of the stream

Row, row, row your boat, gently down the stream...

Author: RDxR10

Files: ciphertext.txt, o_nonce, challenge.py

Reading from the code (and the classes), it seems that there really isn't any way of doing it (afaik) with O(1), so I had to bruteforce it.

def main():
    nonce = random.getrandbits(64)
    message = b'REDACTED'
    prng = PRNG(random.randint(1, 9999999999999999999999999999999999999999999999))
    prng_key = prng.get_bytes(2)
    encrypted_message = stream_e(prng_key, nonce, message)
    with open("ciphertext.txt", "wb") as f:
        f.write(encrypted_message)
    
    o_nonce = o_value(struct.pack("<Q", nonce))
    with open("o_nonce.txt", "wb") as f:
        f.write(o_nonce)

This is what I ended up with:

import struct

ROUNDS = 20


def o_value(value):
    o_kie = b"leomessi"
    return bytes([b ^ o_kie[i % len(o_kie)] for i, b in enumerate(value)])


def recover_nonce(o_nonce):
    return struct.unpack("<Q", o_value(o_nonce))[0]


def q_r(a, b, c, d):
    a = (a + b) & 0xFFFFFFFF
    d = (d ^ a) << 16 | (d ^ a) >> (32 - 16)
    c = (c + d) & 0xFFFFFFFF
    b = (b ^ c) << 12 | (b ^ c) >> (32 - 12)
    a = (a + b) & 0xFFFFFFFF
    d = (d ^ a) << 8 | (d ^ a) >> (32 - 8)
    c = (c + d) & 0xFFFFFFFF
    b = (b ^ c) << 7 | (b ^ c) >> (32 - 7)
    return a, b, c, d


def stream(key, nonce, counter):
    state = [0] * 16
    state[0] = 0x61707865
    state[1] = 0x3320646E
    state[2] = 0x79622D32
    state[3] = 0x6B206574
    state[4:6] = struct.unpack("<2L", key + b"\x00" * 6)
    state[6:8] = [0] * 2
    state[8:12] = [0] * 4
    state[12] = counter
    state[13] = nonce & 0xFFFFFFFF
    state[14] = (nonce >> 32) & 0xFFFFFFFF
    state[15] = 0

    working_state = state[:]
    for _ in range(ROUNDS // 2):
        working_state[0], working_state[4], working_state[8], working_state[12] = q_r(
            working_state[0], working_state[4], working_state[8], working_state[12]
        )
        working_state[1], working_state[5], working_state[9], working_state[13] = q_r(
            working_state[1], working_state[5], working_state[9], working_state[13]
        )
        working_state[2], working_state[6], working_state[10], working_state[14] = q_r(
            working_state[2], working_state[6], working_state[10], working_state[14]
        )
        working_state[3], working_state[7], working_state[11], working_state[15] = q_r(
            working_state[3], working_state[7], working_state[11], working_state[15]
        )
        working_state[0], working_state[5], working_state[10], working_state[15] = q_r(
            working_state[0], working_state[5], working_state[10], working_state[15]
        )
        working_state[1], working_state[6], working_state[11], working_state[12] = q_r(
            working_state[1], working_state[6], working_state[11], working_state[12]
        )
        working_state[2], working_state[7], working_state[8], working_state[13] = q_r(
            working_state[2], working_state[7], working_state[8], working_state[13]
        )
        working_state[3], working_state[4], working_state[9], working_state[14] = q_r(
            working_state[3], working_state[4], working_state[9], working_state[14]
        )

    output = bytearray()
    for i in range(16):
        output.extend(struct.pack("<L", (working_state[i] + state[i]) & 0xFFFFFFFF))
    return output


def stream_e(key, nonce, message):
    keystream = bytearray()
    for i in range(0, len(message), 64):
        keystream.extend(stream(key, nonce, i // 64))

    return bytes([m ^ k for m, k in zip(message, keystream)])


class PRNG:
    def __init__(self, seed):
        self.state = seed
        self.update()

    def update(self):
        self.state = (self.state * 0x5DEECE66D + 0xB) & 0xFFFFFFFFFFFF
        self.state ^= self.state >> 33
        self.state = (
            self.state * 0xFF51AFD7ED558CCD + 0xC4CEB9B11F7A6F8D
        ) & 0xFFFFFFFFFFFF

    def get_bytes(self, num_bytes):
        result = bytearray()
        for _ in range(num_bytes):
            self.update()
            result.append(self.state & 0xFF)
        return bytes(result)


def main():
    with open("ciphertext.txt", "rb") as f:
        encrypted_message = f.read()

    with open("o_nonce.txt", "rb") as f:
        o_nonce = f.read()

    nonce = recover_nonce(o_nonce)

    for seed in range(1, 9999999999):
        prng = PRNG(seed)
        prng_key = prng.get_bytes(2)

        decrypted_message = stream_e(prng_key, nonce, encrypted_message)

        if decrypted_message.startswith(b"CUR"):
            print(f"Recovered message: {decrypted_message}")
            print(f"Using seed: {seed}")
            break


if __name__ == "__main__":
    main()

CURTIN_CTF{g0_s0lv3_7h3_57r34m_pr0bl3m_0f_1r0d0v_n0w}

Bluff

Files: challenge_bluff.go, enc_flag.txt

The code contains a somewhat readable encryption method, so we can easily bruteforce it.

func main() {
    key := make([]byte, 4)
    _, err := rand.Read(key)
    if err != nil {
        fmt.Println("Error generating key:", err)
        os.Exit(1)
    }

    flag := "REDACTED"
    enc := encrypt(flag, key)
    var encStr []string
    for _, num := range enc {
        encStr = append(encStr, fmt.Sprintf("%d", num))
    }
    fmt.Println("Encrypted Flag:", strings.Join(encStr, ", "))

}

End result:

package main

import (
    "fmt"
    "strings"
)

func encrypt(flag string, key []byte) []int {
    enc := make([]int, len(flag))
    for i := 0; i < len(flag); i++ {
        chunk := i / len(key)
        offset := i % len(key)
        enc[i] = (int(flag[i]) + chunk) ^ int(key[offset])
    }
    return enc
}

func decrypt(enc []int, key []byte) string {
    flag := make([]byte, len(enc))
    for i := 0; i < len(enc); i++ {
        chunk := i / len(key)
        offset := i % len(key)
        flag[i] = byte((enc[i] ^ int(key[offset])) - chunk)
    }
    return string(flag)
}

func isPrintable(s string) bool {
    for _, r := range s {
        if r < 32 || r > 126 {
            return false
        }
    }
    return true
}

func main() {
    enc := []int{66, 188, 199, 131, 75, 166, 245, 147, 87, 161, 232, 166, 114, 223, 228, 181, 118, 222, 226, 239, 115, 209, 241, 235, 63, 212, 169, 236, 65, 214, 173, 239, 59, 108} 
    for b1 := 0; b1 < 256; b1++ {
        for b2 := 0; b2 < 256; b2++ {
            for b3 := 0; b3 < 256; b3++ {
                for b4 := 0; b4 < 256; b4++ {
                    key := []byte{byte(b1), byte(b2), byte(b3), byte(b4)}
                    decryptedFlag := decrypt(enc, key)
                    if isPrintable(decryptedFlag) && strings.HasPrefix(decryptedFlag, "CURT") {
                        fmt.Printf("Key: [%d, %d, %d, %d] Decrypted Flag: %s\n", b1, b2, b3, b4, decryptedFlag)
                        return
                    }
                }
            }
        }
    }
}

CURTIN_CTF{op3n_s3s4m3_7876598112}

Tutto Bene

Somebody call the fire brigade!

Author: RDxR10

Files: chall.txt, challenge.py

This relatively easy looking challege, was in fact, not easy at all.

import os, hashlib, random, binascii

flag = "REDACTED"
h = ""

for i in range(0, len(flag), 2):
    pair = flag[i:i+2]
    a = random.randint(1, 16)
    b = random.randint(1, 16)
    
    h += binascii.hexlify(os.urandom(random.randint(0, 31))).decode('utf-8')
    hash_value = hashlib.sha512(pair.encode('utf-8')).hexdigest()
    if a < len(hash_value) and b < len(hash_value) and a < len(hash_value) - b:
        h += hash_value[a:-b][::-1]
    else:
        h += hash_value[::-1]
    
    h += binascii.hexlify(os.urandom(random.randint(0, 31))).decode('utf-8')

print(h)

This is what I ended up with, which is half broken but it still outputs somewhat legible output:

import hashlib
import itertools

# The encrypted text you have
encrypted = "..."


def generate_pairs():
    characters = "".join(chr(i) for i in range(32, 127)) 
    for pair in itertools.product(characters, repeat=2):
        yield "".join(pair)


def attempt_decrypt(encrypted):
    pairs = []
    found_pairs = set()

    for pair in generate_pairs():
        hash_value = hashlib.sha512(pair.encode("utf-8")).hexdigest()

        # Change these ranges from 16-32-64
        # But it takes time
        # and is harder to read
        for a in range(1, 16):
            for b in range(1, 16):
                if (
                    a < len(hash_value)
                    and b < len(hash_value)
                    and a < len(hash_value) - b
                ):
                    extracted_hash = hash_value[a:-b][::-1]
                else:
                    extracted_hash = hash_value[::-1]

                if extracted_hash in encrypted:
                    start_idx = encrypted.index(extracted_hash)
                    pairs.append((start_idx, pair))
                    found_pairs.add(pair)


    pairs.sort(key=lambda x: x[0])
    ordered_pairs = [pair for _, pair in pairs]
    print(ordered_pairs)


attempt_decrypt(encrypted)
This is what I meant when I say it's hard to read.

CURTIN_CTF{n07h1n9_h4pp3n3d_7h3_cpu_s33m5_70_b3_f1n3}

Amalgam

Missing description

This is end result we got:

from sympy.ntheory import discrete_log

p = 186506814954895414068796533711441426871
g = 2
h = 128780011407215156870232600336696679553
c1 = 156581689710555992734938659724336258165
c2 = 113787733820173627914147318932861607685
x = discrete_log(p, h, g)
s = pow(c1, x, p)
s_inv = pow(s, p - 2, p)
m = (c2 * s_inv) % p
flag = m.to_bytes((m.bit_length() + 7) // 8, 'big').decode()


print(f"CURTIN_CTF{{{flag}}}")

CURTIN_CTF{dlP_50lv3d:)}

RE

PinValidator

Unlock the app with the PIN to get the flag.

Encase the flag within: CURTIN_CTF{}

Author: sandhrab

Files: CurtinPinValidate.zip

A quick glance at the file tells me that this is an .apk file.

After decompiling, we can look at the MainActivity of the program.

public class MainActivity extends AppCompatActivity {
    private static final String HARDCODED_PIN = "7331";
    private static final String HARDCODED_STRING = "^\"u}B~%F'\"x%bU&r%dZ'p%P&d%`%d";
    private static final int KEY = 22;
    private AppBarConfiguration appBarConfiguration;
    private ActivityMainBinding binding;
    private Button checkPinButton;
    private EditText pinInput;
    private TextView resultText;
String enteredPin = MainActivity.this.pinInput.getText().toString();
if (enteredPin.equals(MainActivity.HARDCODED_PIN)) {
    String xorResult = MainActivity.this.xorString(MainActivity.HARDCODED_STRING, 22);
    MainActivity.this.resultText.setText(xorResult);
} else {
    Toast.makeText(MainActivity.this, "Incorrect PIN!", 0).show();
}
public String xorString(String input, int xorValue) {
        StringBuilder result = new StringBuilder();
        for (char c : input.toCharArray()) {
            result.append((char) (c ^ xorValue));
        }
        return result.toString();
    }

With this, I can easily write a short python script to reverse the XOR algorithm used here.

def xor_string(input_string, xor_value):
    result = []
    for c in input_string:
        result.append(chr(ord(c) ^ xor_value))
    return "".join(result)


# Example usage:
input_string = "^\"u}B~%F'\"x%bU&r%dZ'p%P&d%`%d"
xor_value = 22
output_string = xor_string(input_string, xor_value)
print(output_string)

CURTIN_CTF{H4ckTh3P14n3tC0d3rL1f3F0r3v3r}

Misc

Misc 2

SSH as `curtin` and enter the flag
Author: MetaSpoilt
`18.142.44.244`

Files: misc_2

and it gave us a file, misc_2, which is a OpenSSH private key. This will come up a lot in Misc flags.

Now that we have the private key, we can easily connect to the server by:

bash ssh 18.142.44.244 -i misc_2 -l curtin

which would gives us:

Misc 2 ssh 18.142.44.244 -i misc_2 -l curtin
Last login: Sun Oct 13 00:39:53 2024 from 115.135.39.169
curtin@ip-172-31-19-125:~$

running ls -lar to search for the flag:

curtin@ip-172-31-19-125:~$ ls -lar
total 56
-r-------- 1 curtin curtin   20 Oct 12 06:16 flag
-rw-rw-r-- 1 curtin curtin   24 Oct 12 06:01 1.php
-rw------- 1 curtin curtin 6563 Oct 12 05:51 .viminfo
drwx------ 2 curtin curtin 4096 Oct  5 11:53 .ssh
-rw-r--r-- 1 curtin curtin  807 Oct  5 11:47 .profile
drwxrwxr-x 3 curtin curtin 4096 Oct 12 05:19 .local
-rw------- 1 curtin curtin   20 Oct 12 05:58 .lesshst
drwx------ 2 curtin curtin 4096 Oct  5 11:55 .cache
-rw-r--r-- 1 curtin curtin 3769 Oct 12 05:35 .bashrc
-rw-r--r-- 1 curtin curtin  220 Oct  5 11:47 .bash_logout
-rw------- 1 curtin curtin    0 Oct 13 00:40 .bash_history
-rw------- 1 curtin curtin   62 Oct 12 16:06 .Xauthority
drwxr-xr-x 6 root   root   4096 Oct  5 19:54 ..
drwxr-x--- 5 curtin curtin 4096 Oct 13 00:40 .
it's just there in the home directory, so we don't really have to do anything hard, simply

cat flag

and that's it.
curtin@ip-172-31-19-125:~$ cat flag
CURTIN_CTF{N0_P@TH}

CURTIN_CTF{N0_P@TH}

Misc 3

SSH as curtin2 and enter the flag

Author: MetaSpoilt
18.142.44.244

Files: Misc 3

The same file as before, OpenSSH private key.

Checking for any flag in home.

curtin2@ip-172-31-19-125:~$ echo *
1.php flag

That's not it.

curtin2@ip-172-31-19-125:~$ cat flag
Hope_you_are_enjoying_the_ctf} 

Checking with ls -lar instead.

curtin2@ip-172-31-19-125:~$ ls -lar
total 56
-r-x--x--x 1 curtin2 curtin2   65 Oct 12 03:23 flag
-rwxrwxr-x 1 curtin2 curtin2   24 Oct 12 05:55 1.php
-rw------- 1 curtin2 curtin2 6566 Oct 12 15:47 .viminfo
drwx------ 2 curtin2 curtin2 4096 Oct  5 12:08 .ssh
-rw-r--r-- 1 curtin2 curtin2  807 Oct  5 12:07 .profile
drwxrwxr-x 3 curtin2 curtin2 4096 Oct  6 08:36 .local
-rw------- 1 curtin2 curtin2   20 Oct 12 12:32 .lesshst
-rw-rw-r-- 1 curtin2 curtin2    0 Oct 12 03:17 .flag
drwx------ 4 curtin2 curtin2 4096 Oct 12 12:26 .config
drwx------ 2 curtin2 curtin2 4096 Oct  5 12:08 .cache
-rw-r--r-- 1 curtin2 curtin2 3768 Oct  5 12:08 .bashrc
-rw-r--r-- 1 curtin2 curtin2  220 Oct  5 12:07 .bash_logout
-rw------- 1 curtin2 curtin2    0 Oct 13 01:07 .bash_history
drwxr-xr-x 6 root    root    4096 Oct  5 19:54 ..
drwxr-x--- 6 curtin2 curtin2 4096 Oct 13 01:07 .

This doesn't seem right, so I'm just gonna copy the whole directory just in case.

Misc 3 scp -r -i misc_3 curtin2@18.142.44.244:~ home_copy
.viminfo                                                          100% 6566   168.4KB/s   00:00
.bashrc                                                           100% 3768   100.9KB/s   00:00
flag                                                              100%   65     1.8KB/s   00:00
1.php                                                             100%   24     0.7KB/s   00:00
.bash_logout                                                      100%  220     6.1KB/s   00:00
.lesshst                                                          100%   20     0.6KB/s   00:00
htoprc                                                            100% 1669    45.3KB/s   00:00
.profile                                                          100%  807    21.9KB/s   00:00
authorized_keys                                                   100%   82     2.3KB/s   00:00 

Oh!

Flag: CURTIN_CTF{E$c@pe_C0d35}

Misc 4

SSH as curtin3 and enter the flag

Author: MetaSpoilt
18.142.44.244 

Files: misc_4

OpenSSH, you should know by now.

Like [Misc 3], I'm going to download the entire home folder again.

Misc 4 scp -r -i misc_4 curtin3@18.142.44.244:~ home_copy
snap-discard-ns.c                                                 100%  620    16.7KB/s   00:00
snap-update-ns                                                    100%   26KB  75.5KB/s   00:00
.viminfo                                                          100% 7781    44.9KB/s   00:00
pspy64                                                            100% 3032KB 446.2KB/s   00:06
view_flag1.sh                                                     100%   52     1.5KB/s   00:00
view_flag3.sh                                                     100%   45     1.3KB/s   00:00
view_flag.sh                                                      100%   31     0.9KB/s   00:00
.bashrc                                                           100% 3768    96.8KB/s   00:00
trustdb.gpg                                                       100% 1200    24.3KB/s   00:00
pubring.kbx                                                       100%   32     0.2KB/s   00:00
.bash_logout                                                      100%  220     6.5KB/s   00:00
.Xauthority                                                       100%   62     1.0KB/s   00:00
flag                                                              100%   26     0.8KB/s   00:00
.profile                                                          100%  807    23.0KB/s   00:00
known_hosts                                                       100%  142     4.1KB/s   00:00
authorized_keys                                                   100%   81     2.4KB/s   00:00

Listing the files:

home_copy ls -lar
total 3057
-rwxrwxrwx 1 root root      62 Oct 13 20:27 .Xauthority
-rwxrwxrwx 1 root root    7781 Oct 13 20:27 .viminfo
drwxrwxrwx 1 root root       0 Oct 13 20:27 .ssh
-rwxrwxrwx 1 root root 3104768 Oct 13 20:27 pspy64
-rwxrwxrwx 1 root root     807 Oct 13 20:27 .profile
drwxrwxrwx 1 root root       0 Oct 13 20:27 .Private
drwxrwxrwx 1 root root       0 Oct 13 20:27 .local
drwxrwxrwx 1 root root       0 Oct 13 20:27 .gnupg
-rwxrwxrwx 1 root root       0 Oct 13 20:27 flag.txt
drwxrwxrwx 1 root root       0 Oct 13 20:27 bin
-rwxrwxrwx 1 root root    3768 Oct 13 20:27 .bashrc
-rwxrwxrwx 1 root root     220 Oct 13 20:27 .bash_logout
-rwxrwxrwx 1 root root       0 Oct 13 20:27 .bash_history
drwxrwxrwx 1 root root    4096 Oct 13 20:27 ..
drwxrwxrwx 1 root root    4096 Oct 13 20:27 .
drwxrwxrwx 1 root root       0 Oct 13 20:27 -

Finding the flag, with find:

home_copy find . -name flag
./-/flag

Finish.

home_copy cat ./-/flag
CURTIN_CTF{d@$h3d_f0lder}

CURTIN_CTF{d@$h3d_f0lder}

Misc 5

SSH as ubuntu and enter the flag

Author: MetaSpoilt
54.255.1.14 

Files: misc_5

OpenSSH private key, you know the deal.

Listing files:

ubuntu@ip-172-31-46-50:~$ echo *
es flag flag1 john_hash.txt john_hashes.txt john_key john_shadow.txt key.txt new_key new_key.pub rootshell sad snap ubuntu ubuntu.pub unshadowed_password wtf

Reading the flag:

ubuntu@ip-172-31-46-50:~$ cat flag
cat: flag: Permission denied

Unable to read, because if we check the file permission, it's only readable to root

ubuntu@ip-172-31-46-50:~$ ls -lar
total 180
<.......... Cleaned to only show the important file ..........>
lrwxrwxrwx  1 ubuntu ubuntu     4 Oct 12 13:19 flag1 -> flag
-r--------  1 root   root      27 Oct 12 10:52 flag

Using sudo -l we can get get which binary we can use without entering root password.

ubuntu@ip-172-31-46-50:~$ sudo -l
Matching Defaults entries for ubuntu on ip-172-31-46-50:
    env_reset, mail_badpass,
    secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin,
    use_pty

User ubuntu may run the following commands on ip-172-31-46-50:
    (root) NOPASSWD: /bin/more

and, I was right.

ubuntu@ip-172-31-46-50:~$ sudo /bin/more flag
CURTIN_CTF{$ud0_N0_P@s$wd}

CURTIN_CTF{$ud0_N0_P@s$wd}

Misc 6

nc 52.221.246.50 1337 - get bash shell

enter the flag

Author: MetaSpoilt
54.254.174.55 

Files: None

No file this time around, but it's the same concept, we get a shell and we need to find the flag.

Like before, we can't read the flag because we don't have the permission, so gotta find a permission escalation somehow.

ctfuser@c8ca61aeceaa:/$ ls
ls
bin   dev  home  lib64  mnt  proc  run   srv  tmp  var
boot  etc  lib   media  opt  root  sbin  sys  usr
ctfuser@c8ca61aeceaa:/$ cd home/ctf
cd home/ctf
ctfuser@c8ca61aeceaa:/home/ctf$ ls
ls
flag
ctfuser@c8ca61aeceaa:/home/ctf$ cat flag
cat flag
cat: flag: Permission denied
ctfuser@c8ca61aeceaa:/home/ctf$ ls -lar
ls -lar
total 20
-r-------- 1 root root   19 Oct 12 15:08 flag
-rw-r--r-- 1 root root   55 Oct 12 15:08 .bashrc
drwxr-xr-x 1 root root 4096 Oct 12 15:08 ..
drwxr-xr-x 1 root root 4096 Oct 12 15:08 .
ctfuser@c8ca61aeceaa:/home/ctf$

Checking sudo -l doesn't seem to work.

ctfuser@c8ca61aeceaa:/home/ctf$ sudo -l
sudo -l
bash: sudo: command not found

So we gotta do it the long way:

ctfuser@c8ca61aeceaa:/home/ctf$ find / -perm -4000
find / -perm -4000
/usr/bin/chfn
/usr/bin/su
/usr/bin/chsh
/usr/bin/gpasswd
/usr/bin/umount
/usr/bin/newgrp
/usr/bin/passwd
/usr/bin/mount
find: '/home/ubuntu': Permission denied
find: '/root': Permission denied
find: '/var/cache/ldconfig': Permission denied
find: '/var/cache/apt/archives/partial': Permission denied
find: '/proc/tty/driver': Permission denied
find: '/proc/830/task/830/fd/6': No such file or directory
find: '/proc/830/task/830/fdinfo/6': No such file or directory
find: '/proc/830/fd/5': No such file or directory
find: '/proc/830/fdinfo/5': No such file or directory
/etc/security/print_file

The file /etc/security/print_file really stood out for me, so let's try that.

ctfuser@c8ca61aeceaa:/home/ctf$ /etc/security/print_file flag
/etc/security/print_file flag
CURTIN_CTF{$3tU1D}

CURTIN_CTF{$3tU1D}

Web Exploitation

Crumb Trails

The website theme seems to be telling me something ...

Challenge Link: http://18.141.159.205:8002/

Opening the website and checking the Network tab gave us a clue.

Replicating the same network request in API Tester with the Cookie's flavor value being `chocolate` will give us the flag.

CURTIN_CTF{c00kies_cutt3r_is_c00l}

Valuable Feedback

The author wants some feedback on the website quality, make sure to preview and send him some good reviews.

Challenge Link: http://54.255.222.103:8004/

The website contains a textbox which the user can submit, and it will output it back the same.

From here we can see that the server is using Werkzeug and Python so it's most likely running on a Flask server, which uses Jinja.

By employing a basic jinja payload to the textbox, we can get the flag:

CURTIN_CTF{t3mpl4t3s_ar3_e4sy_t0_cr4ck}

Easy Login

Hey, found this super secure login page. Seems like the database admin forgot to do some sanity checks.

From the description, we might be able to use SQL payload injection because it mentioned the databasa admin forgot to do some sanity checks.

By using admin' or '1'='1' as the username and password.

We can get the flag which is:

CURTIN_CTF{sql_1nj3nt1on_v1ct0ry}







|
1/1 | 0:00 / 0:00 | MUSIC-TITLE
|