H@cktivityCon 2021 CTF Writeups

A few writeups for H@cktivityCon 2021. Most tasks are pretty trivial. Triforce is somewhat interesting though.

Six Four Over Two [Warmup]


In the attachment file there is a text:


To find the flag all we should do is to feed it to CyberChef with magic operator:

Buffer Overflow [Warmup]


So that’s basic buffer overflow challenge. Examining source.c we can see that there is an SIGSEGV handler which will run handler() function once the program detects segmentation fault (occurs when the stack is smashed).

And the handler() function will call give_flag() which in its turn will print the flag. That means all we have to do is just feed really long string to the program to get the flag.

awawa@awawa-pc:~$ nc challenge.ctf.games 30054 
How many bytes does it take to overflow this buffer?

Tsunami [Warmup]


Attachment file tsunami is just a WAVE sound file. To find the flag all we have to do is open it in some audio editor and trigger spectrogram view. I myself used Audacity:

pimple [Warmup]


We have an attachment file pimple. Let’s see what is it. file utility says that it is a GIMP XCF file

awawa@awawa-pc:~/Documents/CTF/HactivityCon$ file pimple  
pimple: GIMP XCF image data, version 011, 1024 x 1024, RGB Color

So we open it in GIMP and see the following:

Here in the right bottom corner we can see there are a lot of layers in the image. Wandering through levels at some point we find the flag:

2ez [Warmup]


We are provided with some file with binary data in it:

awawa@awawa-pc:~/Documents/CTF/HacktivityCon$ file 2ez
2ez: data

Looking at the file’s hex data we can see some odd signature 2e 32

But at the end of the file we can find pretty familiar for us ff d9, which is JPEG file:

So we can assume that the file’s signature ff d8 was just truncated. After adding it back, we can open the file as JPEG image:

awawa@awawa-pc:~/Documents/CTF/HacktivityCon$ python3
Python 3.8.10 (default, Jun 2 2021, 10:49:15)
[GCC 9.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> data = open('2ez', 'rb').read()
>>> open("2ez.jpg", 'wb').write(b"\xff\xd8" + data)
>>> exit()

To Do [Mobile]


So we have the todo.apk file . We can use http://www.javadecompilers.com/apk to decompile it. After that, searching through the files we can find todos.db in resources/assets. There are two rows in todo table in the database.

Decoding first with base64 yields the flag:

N1TP [Crypto]

Task description:

After starting the challenge we are given the hostname and port to connect via nc. So we connect and here’s what we see

We are given the encrypted flag. That’s being said that for encryption nonce was used with length equal to flag length, and that “unbreakable” method was used. So we may now suppose that OTP was used.

Also we can send some arbitrary data to Nina for her to encrypt it. I know that flag starts with flag{ so I sent that. And there are some fascinating results I received back. Well, first of all, we have the confirmation that OTP scheme was used. And second of all, which is far more important, we can see that our ciphertext starts with the same bytes as the encrypted flag. Seems like nonce isn’t really a nonce (Number used only ONCE).

So as long as key is being reused I can send long plaintext to encrypt and calculate the key k from known plaintext p and ciphertext c using property:

c = p ^ k
k = c ^ p

I sent a bunch of a symbols and received some ciphertext. Now, let’s go to CyberChef and calculate key:

After we got the key, we can decrypt the flag:

Hexahedron [Crypto]


Here we have some text file with values n, e, and c:

n = 0x9ffa2a58ad286990fc5fe97b669e8cb2752e81fafa5ac774ea856d8ca124089ba4b06fe21a5d588c1dcb9602838d32cd70e50b85dec21fa79944543176c7a3b8b804ab754af2978f23b09f2905103dd5a4c748df8d9e9a079a5b38f6f69051b3c6582ebc2d2d199b3a97cb7e58af79b90fe08884626d188e194816bd51960a45
e = 0x3
c = 0x10652cdfaa6a6f6f688b98219cd32ce42c4d4df94afaea31cd94dfac50678b1f50f3ab1fd389f9998b6727ffd1a2c06ee6bde21ae85daef63fd0fa694a93f3674dc3f9ea0f2e3283a3d9897137aea12458aa3b8f96c61f3bf74a510bab7e7d8b7af52290d2621f1e06e52e6a7be4896c6465

Looks like an RSA challenge. What we notice here is that e is pretty small and c is also much smaller than n. Here we can assume that no padding were used for encryption so that p^e = c < n. That means (p^e mod n)== (p^e) and we can calculate integer root p^(1 / e) ignoring modulus. Python script for the solution:

Triforce [Crypto]


As an attachment we are given the server.py with the following content:

Also we are given the hostname and port to connect to server via nc.

Examining the the code we can see that the flag is split into 3 parts, each of 16 bytes long. When we connect to server, we are asked to select piece of triforce (each piece corresponds to part of the flag).

After we’ve chosen piece of triforce, server provides 4 options for us:

So let’s look at the encryption and decryption functions:

Encryption is performed with AES-CBC. Decryption is just an inverse of an encryption without any unusual properties. The crucial part of the challenge is that server uses self.triforce (which is the currently selected part of the flag) both as a key and as an IV for cipher initialization. This implementation error will allow us to determine the encryption key. And as long as the key is part of the flag we will find the flag.

To perform the attack we are going to perform several steps:

Following screenshots present determining first part of the flag:

First part of the flag

After that we can choose second and third pieces of triforce and repeat steps from screenshots to get the full flag.

Second part of the flag
Third part of the flag

How does this work?

So earlier I demonstrated the attack itself without explaining how does it actually work. Let’s look at that now. First, consider the CBC decryption scheme.

Notice the following:

T1 = D(C1) ^ K = P1 ^ IV

T2 = D(C2) ^ C1 = P2 ^ C1

T3 = D(C3) ^ C2 = P3 ^ C2,

where T1, T2, T3 — actual plaintexts, C1, C2, C3 — ciphertexts, P1, P2, P3 — result of block cipher (AES) decryption before XORing it with IV — initialization vector.

Now, in our particular case IV is the key, so

T1 = P1 ^ K

Next, let’s change block C2 to Z — all zeroes block. We will get the following

T1 = P1 ^ V

T2' = D(Z) ^ C1 = P2' ^ C1

T3' = P3 ^ Z

Here T2' and T3' are not equal to T2 and T3 that were encrypted in the first place, but we don’t really care.

Also, T3' = P3, as XORing with 0 doesn’t affect P3 anyway. So now we have

T1 = P1 ^ K

T2' = P2' ^ C1

T3'= P3

And the last step, we change C3 to C1. That will result in the following

T1 = P1 ^ K

T2' = P2' ^ C1

T3' = D(C1) ^ Z = P1 ^ Z = P1

Now that’s pretty easy to see that we have two interesting values

T1 = P1 ^ K


T3' = P1

If we XOR them we will get

T1 ^ T3' = P1 ^ P1 ^ K

And as P1 ^ P1 yields 0, we have

T1 ^ T3' = P1 ^ P1 ^ K = 0 ^ K = K

And that’s how we have the encryption key.


I should stress out that what we actually did in this task is we found the initialization vector (IV). This value never should contain sensitive information. It is perfectly fine to keep IV public (it has to be unpredictable to prevent some other attacks on CBC mode, though). And although I said that using key as an IV is crucial, it is not that crucial for this particular task to be solvable, but for the whole CBC scheme to be secure. In fact, we don’t care about the key itself as well as about AES encryption. Task designers could use some random secret value as a key and we still would’ve found the flag (as it is just an IV).



Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store