The map specified in the challenge statement is as follows. It signifies the location in which the CTF is conducted. Each of the three red circles signifies the initial position of one of the three components required to complete the puzzle box. Each component can be resolved in isolation from the others.
The initial section of the wooden container contained a keypad and a wooden trapdoor that was closed. The provided code below outlines the authentication mechanism employed by the keypad in order to validate the user’s input by comparing it to the anticipated outcome.
6d47dd806bdf0677abfdca674206650aaa58fcb3669e76d1b60fb1fb91d50a54f4483f6a721cc3e8acc87f536f9419d782dcc8e7b443574530
uint64_t validate_code() int64_t var_38 __builtin_strcpy(dest: &var_38, src: ":1N50QU357") int64_t var_8 = 0x19 void var_140 void* var_10 = &var_140 strcpy(var_10, &code, 0x33555130354e313a) strcat(var_10, &var_38) size_t var_18 = 0x10 int64_t var_20 = 0xf md5String(var_10, &var_140 - 0x10) int128_t var_a0 __builtin_strncpy(dest: &var_a0, src: "33c12ea236cc81d7deb97e432aedc9d4", n: 0x21) void* x0_6 int64_t x1_2 x0_6, x1_2 = hexstr_to_char(&var_a0) void var_78 fastpbkdf2_hmac_sha512(&var_140 - 0x10, var_18, x0_6, x1_2, 0x64, &var_78, 0x40) void* x0_9 int64_t x1_5 x0_9, x1_5 = hexstr_to_char("6d47dd806bdf0677abfdca674206650a…") if (x1_5 == 0x40) int32_t x0_12 = memcmp(&var_78, x0_9, 0x40) free(x0_9) int64_t var_c0_1 = 0 free(x0_6) int64_t var_b0_1 = 0 return zx.q(x0_12) __assert_fail(assertion: "expected.length == PKBDF2_OUT_SI…", file: "firmware.c", line: 0x8d, function: "validate_code") noreturn
The aim of this endeavor is to generate a Python script that emulates the user input process with the purpose of brute-forcing this character string in order to acquire the keypad’s code.
Consequently, we delineate the distinct stages employed to produce the string within the code:
The code entered on the keypad is concatenated with the string :1N50QU357
The newly constructed string is then hashed using MD5.
Application of the pbkdf2_hmac algorithm with the subsequent parameters specified in the function’s invocation
- Length:
64
- Salt:
0x33c12ea236cc81d7deb97e432aedc9d4
- Iterations:
100
- Algorithm:
SHA-512
According to the challenge administrator, the length of the code was a minimum of ten characters. The variable code containing the user input is enclosed in a 20-character buffer. Due to the fourteen potential characters on the keypad, it is impossible to brute-force a password within the allotted time for the competition. This suggests that an element necessary to restrict the number of possibilities is absent.
Recall that badges were present on table number 1, in addition to the USB key. The objective is to open the door by sliding the emblem between the door and the frame in order to engage the latch that is currently immobilizing it.
Code for the solution
import itertools import binascii import hashlib from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC from cryptography.hazmat.backends import default_backend def generate_hash(code): # Concatenate the special string full_code = code + ":1N50QU357" # Hash the complete code string using MD5 md5_hasher = hashlib.md5() md5_hasher.update(full_code.encode()) code_hash = md5_hasher.digest() # Fixed salt salt = binascii.unhexlify("33c12ea236cc81d7deb97e432aedc9d4") # Create a PBKDF2HMAC object kdf = PBKDF2HMAC( algorithm=hashes.SHA512(), length=64, # The output size in bytes salt=salt, iterations=100, backend=default_backend() ) # Generate and return the PBKDF2 hash return kdf.derive(code_hash) # Expected hash prefix for comparison expected_prefix = "6d47dd806bdf0677abfdca6" # Initialize counter for controlling the printing frequency print_counter = 0 # Bruteforce loop for i in range(4, 19): # Loop from length 4 to 18 characters = '14B#' n = i # Number of positions # Generating all possible combinations of length n for combination in itertools.product(characters, repeat=n): print_counter += 1 trying = ''.join(combination) current_hash = generate_hash(trying) current_hex = binascii.hexlify(current_hash).decode() # Print only every 10,000 tests or if the correct hash is found if print_counter % 10000 == 0 or current_hex.startswith(expected_prefix): print(f"Testing {print_counter}: Trying: {trying}, Generated Hash: {current_hex}") if current_hex.startswith(expected_prefix): print("Found it! Matching prefix:", expected_prefix) break if current_hex.startswith(expected_prefix): break
We enter the code and obtain the first part of the flag : 1-5054fd46a2cfcf70a598c2faded5cb
Second Part
In table number 2, we scan a QR code in order to obtain three volumes, an encrypted zip archive, and Mifare 1K NFC tags and if we look into the books we find a password: moonlight
which decrypts the archive.
After decrypting the archive, we find a handwritten note from a fictitious company employee instructing us to refer to page 305 of one of the books listed in Table 2.
We discover upon landing on the page that multiple pages have been adhered together. We also locate an NFC identifier, which can be copied to your mobile device using the Mifare Classic Tools application.
By modifying the NFC tag, we can change user=pi
and modify sector 2 on Mifare tag, for example: ;cat flag.txt
to create bash command like groups;cat flag.txt
, which would display the contents of flag.txt.
user=>/dev/null;cat flag.txt
We can also redirect stdout
to /dev/null
to empty stdout and therefore 15 characters will be left for the flag to be displayed but if we know that the flag is more than 15 characters so the command will be modified to include cut
.
user=>/dev/null;cat flag.txt|cut -c 1-15
2‑393b8f365f4893c73b8b5c197d02c9
CTF Walkthrough Playlist