Equinor CTF is an annual onsite CTF in Norway. I played with my team Norske Nøkkelsnikere, and we placed 5th out of 79 teams and during the CTF I got twofirst bloods!
crypto
👶 Double Dip Dilemma 👶 🩸
Author: starsiv Description: We intercepted some encrypted communication we think is very important. But it appears they are using uncrackable OTP We have two ciphertexts from the communication, and also the plaintext for the first. Can you help decrypt the second?
We are given a intercept.txt file that contains two plaintexts and two ciphertexts. The ciphertexts are encrypted with a One-Time Pad (OTP), which is a very simple form of encryption that relies on bitwise XORing a message with a random binary sequence. Since the OTP is deterministic, we can use the inherent properties of XOR to recover the key.
Vulnerability
OTP key reuse
The one-time pad is theoretically unbreakable when used correctly. However, its security relies on two critical factors:
The key must be truly random
The key must never be reused
In this challenge, the second rule is violated, since we are given two plaintexts and two ciphertexts that are encrypted with the same OTP key. By reusing the OTP key, the system becomes vulnerable to a known-plaintext attack.
The Attack
The attack exploits the properties of the XOR operation used in OTP encryption:
If \(P_1 \oplus K = C_1\), then \(K = P_1 \oplus C_1\)
Once we have \(K\), we can decrypt any message encrypted with this key: \(P_2 = C_2 \oplus K\)
Solution
For first solving the challenge, I used CyberChef since it was the fastest way to get the solution, when I recognized the OTP vulnerability. This lead me to a first blood on this introductory crypto challenge.
For a more technical and indepth writeup, please check out Sithis from bootplug's writeup. In the end I wrote a simple solve.py script that will print out the whole message.
Limit your messages to 100chars to fit the master OTP. Now use this secret: EPT{w3lc0m3_t0_my_kr1b!}
The flag is: EPT{w3lc0m3_t0_my_kr1b!}
LFSXOR
Author: Surprior Description: We are given a out.bmp file and a enc.py script. Upon first glance, we can see that the image is encrypted and contains a lot of noise.
Code analysis
We are presented with a program that encrypts an image using an LFSR for generating PRNG out of a key and XOR that with a pixel from the original image. Something that I noticed straigh away was that if we dismiss the LFSR functionality in the get_rand function that implements the LFSR, and just look at the return value, it will only return a value between 0-255 no matter what the value of x is (in our case the key).
We can see this with the following code:
Since the bitwise AND operation with 0xFF (binary 11111111) effectively masks the result to the least significant 8 bits. Since 8 bits can represent 256 distinct values (\(2^8\)), the function will only return integers from 0 to 255 inclusive, no matter what the value of x is. In our case it was the original key.
This means, we could just directly XOR the encrypted image with values from 0-255 and analyze the noise and hope we see something of interest. We do not aim for a clear image, only a good enough picture where we hopefully can see something.
Solving the challenge
Knowing that the image containing the flag is encrypted by XORing a key with values between 0-255, I wrote a Python script to decrypt the image with every possible key, save the results, and print the time taken for the process, allowing me to work on another challenge while waiting for the script to finish.
Extracting the flag
After XOR-ing the image with all possible keys, I uploaded one of the decrypted images to AperiSolve. The image contained a lot of noise, and I just hoped to find something recognizable. Interestingly, the images revealed the flag when viewed in AperiSolve's superimposed noise view and color view.
Each image corresponds to a different key, resulting in varying levels of noise, hence I just inputted a random image and hoped for the best. This particular image was clear enough to discern the flag amidst the superimposed noise.
From this point, we read out the letters we could see and made an educated guess about the flag's content and submitted it.
Flag: EPT{DOUBLECRYPTOTOFAIL}
misc
Leftovers 🩸
Author: starsiv Description: Our "employee of the week" eloped. He was the only one that knew the master class secret. We managed to extract logs from his last activities. Can you find anything from this?
We are given a terminal log from a user, where the user is encrypting a file called master.txt with openssl. It uses the hostname as the password and the current time as the number of iterations, and the encryption algorithm is AES-256-CBC with PBKDF2 key derivation. At last it is encoded with base64.
AES-256-CBC is a symmetric encryption algorithm, which means that the same key and iterations are used for encryption and decryption.
Since we know the hostname and the iteration count from the terminal log, we can decrypt the file using the same command but with the decryption flag -d .
Save the output to a file called encrypted.txt and run the following command to decrypt it:
Master class secret: EPT{Ach13v3m3nt_Unl0ck3d_293857}
Flag: EPT{Ach13v3m3nt_Unl0ck3d_293857}
Canvas Curve
Author: mattis Description: You know the drill, collect apples for points, profit and flags! Gather up to 4 team members to help collect apples.
To solve this challenge, we need to play the game snake until we collect 1000 apples. But who said we need to play the game?
Analysis
We can read the source code of the game with the browser's developer tools which exposes the game logic. We see that we have a pixel.js file that contains a lot of pixel data and a game.js file that contains the game logic, and upon inspecting this game.js file we can see that the random apple that we need to collect is initialized with the same seed for each run. Having the same seed means that the apples will end up in the same place every time when the game is finished.
this.randomAppl = new randomApple(1234);
With this knowledge, we see that the randomApple function is defined in the randomApple.js and is looking complex and hard to understand.
Solution
Since we can download the game from the challenge page, and run it locally, we can modify the game logic to reveal the flag when we collect 1000 apples, since we know the seed and the output of the randomApple function would be deterministic.
I revealed this with editing the game.js file, and making it automatically collect the current apple and move to the next apple until we have collected 1000 apples, and the game will print the flag.
Flag: EPT{DI3_COMIC_CurV3}
🔥 Pixel Perfect 🔥 🥈
Author: nordbo, tmolberg, null Description: Something in your surroundings just changed. What?!
Decode the message displayed on a wall with 16 static lights and moving color pairs. The key is to interpret the color combinations as ASCII characters in hexadecimal. For making it easier, map out the section where the start of the flag is displayed, EPT{, and begin decoding from there.
While this, also account for the color ambiguities due to lighting conditions lol
Equinor CTF 2024
Published on 2024 Nov 11Equinor CTF is an annual onsite CTF in Norway. I played with my team Norske Nøkkelsnikere, and we placed 5th out of 79 teams and during the CTF I got two first bloods!
crypto
👶 Double Dip Dilemma 👶 🩸
We are given a
intercept.txt
file that contains two plaintexts and two ciphertexts. The ciphertexts are encrypted with a One-Time Pad (OTP), which is a very simple form of encryption that relies on bitwise XORing a message with a random binary sequence. Since the OTP is deterministic, we can use the inherent properties of XOR to recover the key.Vulnerability
OTP key reuse
The one-time pad is theoretically unbreakable when used correctly. However, its security relies on two critical factors:
In this challenge, the second rule is violated, since we are given two plaintexts and two ciphertexts that are encrypted with the same OTP key. By reusing the OTP key, the system becomes vulnerable to a known-plaintext attack.
The Attack
The attack exploits the properties of the XOR operation used in OTP encryption:If \(P_1 \oplus K = C_1\), then \(K = P_1 \oplus C_1\)
Once we have \(K\), we can decrypt any message encrypted with this key: \(P_2 = C_2 \oplus K\)
Solution
For first solving the challenge, I used CyberChef since it was the fastest way to get the solution, when I recognized the OTP vulnerability. This lead me to a first blood on this introductory crypto challenge.
For a more technical and indepth writeup, please check out Sithis from bootplug's writeup. In the end I wrote a simple solve.py script that will print out the whole message.
The flag is:
EPT{w3lc0m3_t0_my_kr1b!}
LFSXOR
Code analysis
We are presented with a program that encrypts an image using an LFSR for generating PRNG out of a key and XOR that with a pixel from the original image. Something that I noticed straigh away was that if we dismiss the LFSR functionality in the
get_rand
function that implements the LFSR, and just look at the return value, it will only return a value between 0-255 no matter what the value ofx
is (in our case the key).We can see this with the following code:
Since the bitwise AND operation with
0xFF
(binary11111111
) effectively masks the result to the least significant 8 bits. Since 8 bits can represent 256 distinct values (\(2^8\)), the function will only return integers from 0 to 255 inclusive, no matter what the value ofx
is. In our case it was the original key.This means, we could just directly XOR the encrypted image with values from 0-255 and analyze the noise and hope we see something of interest. We do not aim for a clear image, only a good enough picture where we hopefully can see something.
Solving the challenge
Knowing that the image containing the flag is encrypted by XORing a key with values between 0-255, I wrote a Python script to decrypt the image with every possible key, save the results, and print the time taken for the process, allowing me to work on another challenge while waiting for the script to finish.
Extracting the flag
After XOR-ing the image with all possible keys, I uploaded one of the decrypted images to AperiSolve. The image contained a lot of noise, and I just hoped to find something recognizable. Interestingly, the images revealed the flag when viewed in AperiSolve's superimposed noise view and color view.
Each image corresponds to a different key, resulting in varying levels of noise, hence I just inputted a random image and hoped for the best. This particular image was clear enough to discern the flag amidst the superimposed noise.
From this point, we read out the letters we could see and made an educated guess about the flag's content and submitted it.
Flag:
EPT{DOUBLECRYPTOTOFAIL}
misc
Leftovers 🩸
We are given a terminal log from a user, where the user is encrypting a file called
master.txt
withopenssl
. It uses the hostname as the password and the current time as the number of iterations, and the encryption algorithm is AES-256-CBC with PBKDF2 key derivation. At last it is encoded with base64.Full command:
Output:
Solution
AES-256-CBC is a symmetric encryption algorithm, which means that the same key and iterations are used for encryption and decryption.
Since we know the hostname and the iteration count from the terminal log, we can decrypt the file using the same command but with the decryption flag
-d
.Save the output to a file called
encrypted.txt
and run the following command to decrypt it:The output is the flag.
Flag:
EPT{Ach13v3m3nt_Unl0ck3d_293857}
Canvas Curve
To solve this challenge, we need to play the game snake until we collect 1000 apples. But who said we need to play the game?
Analysis
We can read the source code of the game with the browser's developer tools which exposes the game logic. We see that we have a
pixel.js
file that contains a lot of pixel data and agame.js
file that contains the game logic, and upon inspecting thisgame.js
file we can see that the random apple that we need to collect is initialized with the same seed for each run. Having the same seed means that the apples will end up in the same place every time when the game is finished.With this knowledge, we see that the
randomApple
function is defined in therandomApple.js
and is looking complex and hard to understand.Solution
Since we can download the game from the challenge page, and run it locally, we can modify the game logic to reveal the flag when we collect 1000 apples, since we know the seed and the output of the
randomApple
function would be deterministic.I revealed this with editing the
game.js
file, and making it automatically collect the current apple and move to the next apple until we have collected 1000 apples, and the game will print the flag.Flag:
EPT{DI3_COMIC_CurV3}
🔥 Pixel Perfect 🔥 🥈
Decode the message displayed on a wall with 16 static lights and moving color pairs. The key is to interpret the color combinations as ASCII characters in hexadecimal. For making it easier, map out the section where the start of the flag is displayed,
EPT{
, and begin decoding from there.While this, also account for the color ambiguities due to lighting conditions lol
Flag:
EPT{c0l0r_m3_h4ppy}