justCatTheFish 2020

A few of us came together as a team to tackle the challenges put together by this Polish team. Despite teammates living around the world in different time zones, we had a lot of fun! Here are a few of the challenges that we were able to solve together.


PDF is broken, and so is this file

This PDF contains the flag, but you’ll probably need to fix it first to figure out how it’s embedded. Fortunately, the file contains everything you need to render it. Follow the clues to find the flag, and hopefully learn something about the PDF format in the process.

We are given a file that you can download below if you want to give it a try:

We know this is presented as a corrupted PDF file, but it doesn’t hurt to throw the usual commands at it. Unfortunately file and TrID don’t help identify what this file can be. strings, however, shows a few strange things right off the bat:

Pretty strange for a PDF file to include Ruby code

This PDF contains Ruby code, gives a small hint about file and readelf and shows that there will be a web server involved. Nothing else seems interesting here. I’m tempted to explore the file with binwalk, maybe foremost after that. Running binwalk shows a few parts and I notice a few zip files as well. Let’s extract all this with binwalk -e challenge.pdf and see what we find:

Quite a few files have spawned here along with a directory at the botton called “feelies”. Let’s check it out:

This image has an empty alt attribute; its file name is image-1.png

Reading false_flag.md shows these hints:

The Youtube video is about identifying file formats, check it out here.
I set out to try the tip involving mutool since the tool was conveniently placed right there in the same directory:

Ok let’s check out what’s in this rendered.png file. Probably another set of tips, what a wild goose chase!

This image contains a few tips that I didn’t understand at that time. One of them being that the PDF specifications allow for 6 kinds of “space” characters and a 7th one has been added here in this modified version of mutool: ACK 0x06. Anyway, as I had explored all the tips and was in a dead end, I decided to examine all the files that had been extracted by binwalk. One of them reveals more hints!

Unfortunately I wasn’t able to take advantage of this tip because I hadn’t fixed the file yet.

As I continue my recon of the extracted files, I find one that contains exclusively hex codes:

This is a bit suspicious, so I whip out my xxd tool and run xxd -p -r B5FFD > test to convert the file from hex to binary and see what kind of file it is:

It’s an image! Let’s take a look at what it is. Maybe another set of hints? (yay I guess)

There’s our flag! Finally!

Ok you know how lazy I am and how long that flag is. So in some cases I like to use tesseract to OCR the flag out of an image:


Forgotten Name

We forgot what our secret domain name was... We remember that it starts with 6a... Can you help me recover it?
Hint: the domain name is not bruteforcable

As we started working on this challenge, we looked for subdomains for justctf.team which was the domain hosting the ctf site. But we couldn’t find anything interesting there. Continuing our enumeration of domains, we found the jctf.pro domain that was used for a few challenges. Bingo!

There’s something interesting that starts by 6a just like the challenge hint says. But where’s the flag? Hmmm… that string looks awfully hexy 🙂 Let’s try to convert it with our old friend xxd:

And there’s our flag! Cert Leaks Oops 🙂


That’s not crypto

This is very simple RE task, but you may need some other skills as well. 🙂

We are given a file that you can download below if you want to give it a try:

The file that we need to study has a .pyc extension meaning that it’s the compiled bytecode of a python script. A nifty little tool to decompile these is uncompyle6 (sudo pip3 install uncompyle6):

Now we have the original script and can examine it. To make it shorter, I removed a lot of numeric values that were in the a list (line 31).

What we can see in the main function is that we have a list a that contains very large numbers. These are then all multiplied by 4919 (which is a prime number… but why?) on line 36. Then on line 37 the user is asked to input a value through the command line and spaces are removed around this value. It looks like it’s actually the flag that we’re supposed to supply here. But of course we don’t know the flag, so there’s no use entering anything manually here. This value is then sent to the make_correct_array function on line 3.
If we look at the make_correct_array function, all it does is take the value input by the user, transforms each character into an integer and then applies calculations on them, creating an array of integers.
Back to the main function, we now have a test on line 39 using the validate function that will compare the a array with the one that was processed by make_correct_array.
This is where it gets interesting. In the validate function, we see this:

    if len(a) != len(xs) + 1:
        return False

This means that as long as the lengths of these 2 arrays won’t be the same, we will never get a validation. So let’s start by finding the length of the flag that we’re supposed to input. I added 1 “debugging print” so that I could see what the value of len(a) was and from there deduct what the value of len(xs) should be:

    print("len(a) = " + str(len(a)))
    if len(a) != len(xs) + 1:
        return False

Running the script now and entering a bogus flag of “dfdf” outputs this:

Now we know that the flag is 57 characters long (len(a) = len(xs) + 1). This probably includes the 9 characters justCTF{}.

So we need to determine what the 48 remaining characters inside the curly braces are:
justCTF{xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx}

Now that we can bypass the length test inside the validate function, we can make the program execute the final validation test that will check if the characters are the correct ones:

    if len(a) != len(xs) + 1:
        return False
    else:
        for x in xs:
            value = poly(a, x)
            if value != 24196561:
                return False

This highlighted line is where the magic happens. If this is verified for each character in the xs array, then it means that we have the correct 48 characters.

What’s interesting is that the test is performed on each character, one after the other. This is going to allows us to use brute-forcing to find the flag.

All we need to do now is add some code to test each of the 48 positions with characters which we choose to be lower case, upper case, digits (in case of leet speak), and a few special characters like underscore, etc. If these characters don’t end up producing the flag, then we’ll extend them to more character classes. We went with the strings.printable method which includes digits, ascii letters, punctuation, and whitespace:

char_pool = [''.join(i) for i in product(printable, repeat=1)]

This creates an array of all these characters and we will iterate through all of them for each position of the flag, hoping that we get a positive match.

We added a correct += 1 to the final validation part of the script so that we know when we’ve found the correct character for a given position:

    else:
        for x in xs:
            value = poly(a, x)
            if value != 24196561:
                return False
            correct += 1

And at the end of the script, in the main function, we’ve removed the input since we’re feeding the characters though our brute-forcing function. Until we guess all 57 characters correctly, we’re testing all the characters from the char_pool array, adding them to the first section of the flag that has already been solved (current[:i]) and then appending bogus characters (current[i+1:]) until we reach the length of 57 characters.

    # flag_str = input('flag: ').strip()
    while correct < 56:
        for char in char_pool:
            current = current[:i] + char + current[i+1:]
            flag = make_correct_array(current)
            if validate(a, flag):
                print('Yes, this is the flag!')
            # else:
            #     print('Incorrect, sorry. :(')
            if(i < correct):
                known = current
                i += 1
                print(current)

The script was written by the excellent Gilad Tabul in a few minutes, impressive 🙂

This is what the brute-forcing script looks like in action:


My Little Pwny

Ponies like only one type of numbers!
nc mylittlepwny.nc.jctf.pro 1337

This was a pwn type of challenge with no binary provided, so all the testing had to be done while being connected to the server.
When we connected, all we could see was a chevron cursor waiting for our input. Entering any digits, numbers, letters or words would only proceed to display an ascii art animal (pretty cool BTW). We tried a few special characters and were getting different responses from the server but couldn’t find anything really special until our teammate gabri3ls3c found the character that led us to victory: the backtick or backquote or command substitution! When gabri3ls3c submitted a backtick to the server, an error message appeared:

/bin/sh: 2: Syntax error: EOF in backquote substitution

Reading up on this, he found that we could execute commands between 2 backticks and they would be processed and the result would be echoed back to the output like all the other inputs that we tried before that.
Running ls provided us with a satisfying list of files:

Ok we have a few directories and files. Let’s see what’s in bin:

Ok let’s stop the recon a bit and see if we can read the flag that’s sitting there. After all we have limited time to work on all the challenges so let’s pounce on the low hanging fruits, aka the flag file that we found with ls.
Alas, running commands such as cat, more, less, tail, or head just trigger custom responses for each of these commands, and none of them reveal the contents of the flag file to us. So we researched what other bash commands we could use to read this file and gabri3ls3c came up with nl which displays files with line numbers prepended to each line. Another command that we realized worked as well was simply strings. Here’s a quick video of us fooling around trying to solve this challenge (but mostly admiring the ascii art ❤):

And there’s our flag: justCTF{p0nY_t4lEs_b3giN5_h3r3}


Thank you justCatTheFish! We had a lot of fun chewing on your challenges! Can’t wait for the next event!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s