H@cktivityCon 2021 CTF Writeups

0awawa0
8 min readSep 19, 2021

--

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

  1. Six Four Over Two [Warmup]
  2. Buffer Overflow [Warmup]
  3. Tsunami [Warmup]
  4. pimple [Warmup]
  5. 2ez [Warmup]
  6. To Do [Mobile]
  7. N1TP [Crypto]
  8. Hexahedron [Crypto]
  9. Triforce [Crypto]

Six Four Over Two [Warmup]

Description:

In the attachment file there is a text:

EBTGYYLHPNQTINLEGRSTOMDCMZRTIMBXGY2DKMJYGVSGIOJRGE2GMOLDGBSWM7IK

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

Buffer Overflow [Warmup]

Description:

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?
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
flag{72d8784a5da3a8f56d2106c12dbab989}

Tsunami [Warmup]

Description:

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]

Description:

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]

Description:

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)
15937
>>> exit()

To Do [Mobile]

Description:

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]

Description:

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]

Description:

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:

  1. Encrypt some arbitrary (hex) string
  2. Decrypt arbitrary hex string
  3. Change the triforce piece
  4. Exit

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:

  1. Encrypt 3 block of arbitrary data to receive ciphertexts C = C1||C2||C3. Actually you can use just one block — C1 as C2 and C3 are not used anyway.
  2. Replace block C2 with Z (all zeroes block), and replace C3 with C1 to get value C’=C1||Z||C1. Here if you encrypt only C1, then you will not receive C2 and C3. Although you will receive a padding block. So after sending 16 bytes (32 hex symbols) to the server you will receive 32 bytes back. In this task we can ignore padding block and use only first 16 bytes.
  3. Decrypt C’ to receive D = D1||D2||D3. Now the IV (which is also an encryption key) is IV = D1 ^ D3.
  4. Repeat for all three parts of the flag.

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

and

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.

Notes

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).

--

--

Responses (1)