castorsCTF 2020 Writeups

Posted on 01 Jun 2020 by Aadi Bajpai

Last updated 16 Jun 2020 at 12:00 am

My CTF team Pwnzorz recently won castorsCTF20 🥳

Here are the writeups for the challenges I did for verification. I've tried to go into detail where possible but feel free to hit me up for a clarification if you don't understand what I did or the thought process, which, in my opinion, is the most important part of solving a challenge.

Let's get started.


Goose Chase

We're given two pictures from a screenshot of the Untitled Goose Game, with one of them having a distorted horizontal bar of pixels presumably containing the flag and the other having some weird RGB stuff going on:


Since they're the same picture, immediately my mind jumps to the thought that the diff of both the images might show us the distorted part properly. Checking the diff using literally the first website that came up, my suspicions were confirmed.

flag image diff



We're provided a few numbers, and an image.







triangle with numbers

Classic RSA with a twist, seems simple enough! But even after getting the values of p and q, I wasn't getting anywhere, so I DMed the challenge creator. A crosscheck later, it turned out that the value of c posted in the challenge was actually c^2, factoring that into the equation, we got the correct values.

The equations I used were derived from the Pythagoras theorem and area of the triangle formula:

p = \sqrt(c^2/4 + A)

q = \sqrt(p^2-2A)

giving us:

p = 109715490261974447198586988864224528805526352370318545032102915309331780233413
q = 92066738860291183962222763550286379517247445014524023523696633123203716884771

Then, it's a simple matter of plugging the values in RsaCtfTool, and it spits out the flag.


Magic School Bus

nc 14421

Flag is slightly out of format. Add underscores and submit uppercase

Connecting to it, we are greeted with:

Magic School Bus

Of note is the description that all the kids got moved around and we should fill each row, that alludes to the shuffling being length dependent.

Inputting 2 always gives us the same string, Flag bus seating: SCNTGET0SKV3CTNESYS2ISL7AF4I0SC0COM5ORS31RR3AYN1. And inputting 1 allows us to provide our own string that gets shuffled and returned, playing a bit with that confirmed that the shuffling depends on the length, and is the same shuffling that happens to the flag.

So I thought the way we can do this is use the first option to send a string of As equal to the length of the flag string with one character changed and then observing where the changed character ends up in the output and performing that same switch in the encrypted flag string. As in, if we send AAAB and get back ABAA then we know that the second character in the encrypted string was actually the last character in the plaintext.

I was trying it manually, and it seemed to be working, so my teammate Nils whipped up a quick script to do this automatically.

#!/usr/bin/env python

from pwn import *

r = remote('', 14421)

sol = ""

for x in range(46):
    a = ['a'] * 46
    a[x] = 'b'
    sol += encrypted[r.recv().split()[2].decode().index('B')]

It prints CASTORSCTFR3C0N4ISSANCEISK3YTOS0LV1NGMYS73R1E5, and we add underscores and braces to get the flag.



Are you watching the new series on Amazon?

198 291 575 812 1221 1482 1955 1273 1932 2030 3813 2886 1968 4085 3243 5830 5900 5795 5628 3408 7300 4108 10043 8455 6790 4848 11742 10165 8284 5424 14986 6681 13015 10147 7897 14345 13816 8313 18370 8304 19690 22625

Key takeaways from the challenge text are the words "series" and "Amazon", especially because to watch a series on Amazon you'd go to Prime, and prime numbers are common in context of cryptography.

Now, thinking about primes and series, I think it might be referring to the series of first n prime numbers, loading that up, I tried to xor the first number in the challenge with the first prime number and so on, which led to exactly nothing lol, so I tried modulo which spat out a list of 0s. This was interesting because that meant those numbers were divisible by the corresponding prime number. So we divide and convert to characters:

>>> pp = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181]
>>> nn = [198,291,575,812,1221,1482,1955,1273,1932,2030,3813,2886,1968,4085,3243,5830,5900,5795,5628,3408,7300,4108,10043,8455,6790,4848,11742,10165,8284,5424,14986,6681,13015,10147,7897,14345,13816,8313,18370,8304,19690,22625]
>>> for i in range(42):
...     print(chr(int(nn[i]/pp[i])), end='')




sylv3on_ was visiting cybercastors island and thought it'd be funny to bury the flag.txt. Can you help us dig it out?

And there was an Animal Crossing image attached (this tweet has the challenge image

After performing a lot of steganography on the image and coming up with nothing, I understood that it was not a forensics challenge at all. Then the challenge text was updated with the words dig capitalized to DIG. Immediately I make the connection to the linux dig commands and query the DNS records of the domain where we find a TXT record with the flag.



Bane Art

We're greeted by a website with a lot of ascii art and tabs with even more ascii art, when you go to a tab, the link is like which screams LFI.

I was thinking of doing log poisoning to get RCE but when I opened the logs through the LFI, the flag was already there 😛

For the purposes of the writeup, I properly did the challenge and the flag was located at



Our intern, Jeff, received a brief introduction to Golang and decided to write a Web app that quizzes users.

The website has a simple quiz with basic arithmetic questions, playing with the input gives us nothing so we tried gobusting it which again gave nothing, so we tried with a dot prefixed, signifying hidden dirs. That yielded us .backup/, which contained the source code!

One of the functions was interesting:

func super(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
    var file string = "/" + ps.ByName("directory") + "/" + ps.ByName("theme") + "/" + ps.ByName("whynot")
    test, err := os.Open(file)
    handleError(w, err)
    defer test.Close()

    scanner := bufio.NewScanner(test)
    var content string
    for scanner.Scan() {
        content = scanner.Text()

    fmt.Fprintf(w, "Directories: %s/%s\n", ps.ByName("directory"), ps.ByName("theme"))
    fmt.Fprintf(w, "File: %s\n", ps.ByName("whynot"))
    fmt.Fprintf(w, "Contents: %s\n", content)

and this line a bit above it

mux.GET("/test/:directory/:theme/:whynot", super)

which gave us LFI as long as all the three parts of the url existed. A lot of attempts later, the flag was found at




We suspect a user has been typing faster than a computer. Our analyst don't know what to make of it, maybe you will be the one to shine light on the subject.

A USB PCAP was included with it as well. Since I had never done keyboard pcap analysis before, I googled a bit and found this one writeup which was pretty similar.

Dumping all keystrokes to a txt and running a script to decode them back gave us:

what doyo thng yu will fn her? ths? cstos[CAPSLOCK]ctf[CAPSLOCK]{1stiswhatyowant}

which had the flag with one letter missing.


Overall, this was a great CTF for us and a huge shoutout to the extremely helpful organizers, especially hasu and icinta! We had a great time participating so thank you for making it possible :)