MetaCTF November 2024 Flash CTF consists of 5 challenges.

Slithering Security

Help me test my sssecurity, can you get the flag from this ssssecure sssscript? Download the challenge file here.

We are provided with a small Python script that prompts the user for a password. If the correct password is entered, the script reveals the flag.

Here’s the code:

#!/usr/bin/env python3

SECRET_FLAG=b"\x54\x57\x56\x30\x59\x55\x4e\x55\x52\x6e\x74\x6b\x4d\x47\x34\x33\x58\x7a\x64\x79\x64\x58\x4d\x33\x58\x32\x4e\x73\x4d\x57\x34\x33\x63\x31\x39\x33\x61\x54\x64\x6f\x58\x33\x4d\x7a\x59\x33\x49\x7a\x4e\x33\x4e\x7a\x63\x33\x4e\x7a\x63\x33\x4e\x39"
HASHED_PASSWORD = b'\x12\x1eW\x98\x00\xc1C\xff\xe3\xa9\x15\xde\xd9\x00\x9b\xc9'

from base64 import b64decode
from hashlib import md5

def check_password(password):
    m = md5()
    m.update(password)
    return m.digest() == HASHED_PASSWORD

def main():
    while True:
        inp = input("Please enter your passssssword: ").encode()
        if check_password(inp):
            print(f"Well done, your flag isssssss {b64decode(SECRET_FLAG).decode()}")
            exit()
        else:
            print("Passsssssword incorrect, please try again.")

if __name__ == "__main__":
    main()

Both the password and the flag are represented in hexadecimal format, but the flag is further encoded in Base64.
To extract the flag, we can use CyberChef or the terminal. Using awk, we can strip out the \x delimiters, convert the hexadecimal to ASCII, and then pipe the result into base64 for decoding.

echo -n '\x54\x57\x56\x30\x59\x55\x4e\x55\x52\x6e\x74\x6b\x4d\x47\x34\x33\x58\x7a\x64\x79\x64\x58\x4d\x33\x58\x32\x4e\x73\x4d\x57\x34\x33\x63\x31\x39\x33\x61\x54\x64\x6f\x58\x33\x4d\x7a\x59\x33\x49\x7a\x4e\x33\x4e\x7a\x63\x33\x4e\x7a\x63\x33\x4e\x39' | awk '{gsub(/\\x/, ""); print}' | base64 --decode

decode and flag

Admin portal

I’m writing a webpage for admins to check on their flags, can you do me a favor and check it out to make sure there aren’t any issues? Check out the website here.

We are given the URL to a webpage. Upon visiting, the following message is displayed:

Access denied. This page is only available by administrators.

To gain access, we need to obtain administrator privileges.

  • Open the Developer tools by pressing F12.
  • After inspecting the page, we find no traces of JavaScript.
  • Switch to the Network tab to analyze the request and response headers.

In the headers, we find the following cookie:

Cookie: role=user

Exploiting the Cookie

The role cookie is set to user. We can modify it, and try to gain access:

  • Navigate to the Application tab in the developer tools.
  • Select Cookies from the left-side menu and locate the role cookie.
  • Change its value from user to admin.

change the role cookie

Reload the page.
After the cookie is changed and the page is reloaded, we are “authorized” and the flag is displayed.

admin portal flag

Steg64

You’ve heard of Base64, but I present to you Steg64! Download the challenge file here.

VEh=
QT==
Tl==
S5==
IF==
Wd==
Tx==
VY==
IF==
SA==
Qd==
Q1==
Sx==
RR==
Up==
Ie==
Cs==
Ct==
Qh==
VX==
VN==
IJ==
T4==
Vc==
Ut==
IN==
Rt==
TH==
Qc==
R8==
IN==
Se==
Ux==
IN==
SR==
Ts==
II==
Qd==
Th==
T3==
VN==
SI==
RY==
Us==
IF==
Q9==
QQ==
U9==
VF==
TP==
RU==
IQ==

The description suggests that the challenge involves steganography- the practice of concealing information within other data in a way that isn’t obvious.

Every line is a string encoded in Base64. Let’s break down Base64 principles.

Understanding Base64

  • It takes 3 bytes (24 bits) of data at a time.
  • It splits these bits into 4 groups of 6 bits each.
  • Maps each 6-bit group to one of 64 printable characters: a-z, A-Z, 0-9, +, /.
  • These 3 bytes are now turned into 4 printable ASCII characters.

If the number of bytes is completely divisible by 3, leaving no remainder, then the mapping is perfect and no additional processing is needed.
In cases where it’s not, padding is needed to mark the unused bits.
= is appended at the end for every unused 2 bits. E.g, QUI= (AB = 2 bytes of input) or QQ==(A = 1 byte of input).

base64 padding

The reference table for the conversion can be found here.

The decoders ignore those zero’d bits and the padding; thus, this gives the oppurtunity to hide information.

Here’s a script that reads the steg64.txt file, processes and decodes every line, and extracts the hidden message.

import base64

def base64_to_6bit_values(line):
    # Converts a base64 encoded string to a string of 6-bit binary values.
    base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
    char_to_6bit = {char: f"{index:06b}" for index, char in enumerate(base64_chars)}
    
    binary_string = ""
    for char in line.strip():
        if char in char_to_6bit:
            binary_string += char_to_6bit[char]
    return binary_string

def extract_unused_bits(line):
    # Extracts unused bits from the base64 encoded line.
    # Base64 encodes data in 6-bit chunks, and padding characters '=' are used to
    # mark unused bits.
    padding_count = line.count('=')
    if padding_count == 0:
        return ""
    # Calculate unused bits: 2 for one padding '=', 4 for two '=='
    unused_bits = padding_count * 2
    binary_string = base64_to_6bit_values(line)
    return binary_string[-unused_bits:] if unused_bits > 0 else ""

def decode_steganography(file_path):
    # Reads the file, extracts unused bits, and converts them to readable text.
    unused_bits_accumulator = ""
    temp_msg = ""
   
    with open(file_path, 'r') as file:
        for line in file:
            b64_line = base64.b64decode(line)
            b64_line = b64_line.decode('utf-8')
            temp_msg += b64_line
            unused_bits_accumulator += extract_unused_bits(line)
    
    # This is the decoded main message.
    decoded_msg_set(temp_msg)
    
    # Break the binary string into chunks of 8 bits and convert to bytes incrementally
    byte_array = bytearray()
    for i in range(0, len(unused_bits_accumulator), 8):
        byte_chunk = unused_bits_accumulator[i:i + 8]
        if len(byte_chunk) == 8:  # Ignore incomplete byte at the end
            byte_array.append(int(byte_chunk, 2))
    
    # Convert bytes to readable text
    return byte_array.decode('utf-8', errors='ignore')
    

# File path
file_path = "steg64.txt"

decoded_msg = [""]

def decoded_msg_set(s):
    decoded_msg[0] = s

# Decode the steganographic content
try:
    steganography = decode_steganography(file_path)
    print("Decoded message:")
    print(decoded_msg[0])
    print("\n")
    print("Decoded steganography:")
    print(steganography)
except FileNotFoundError:
    print(f"The file '{file_path}' was not found.")
except Exception as e:
    print(f"An error occurred: {e}")

base64 flag

Talk to me

We managed to tap the headphones of a member of a prolific cyber actor group. Can you listen to their secret plan? Download the wiretap here.

We have a PCAP file with USB packets inside.

We open the file in Wireshark and can see a little bit over 1000 USB packets. To extract the usb.data.iso field with its payload, we will need tshark (the command-line version of Wireshark).

Extract the bytes, pipe to tr to delete every new line, and finally xxd to convert to binary.

tshark -r talktome.pcap -T fields -e usb.iso.data 'usb.src == "host"' | tr -d '\n' > raw.txt | xxd -r -p > flag.raw
  • -r talktome.pcap: Reads the PCAP file.
  • -T fields -e usb.iso.data: Extracts only the usb.iso.data field.
  • ‘usb.src == “host”’: Filters packets originating from the host.
  • tr -d ‘\n’: Removes newline characters to get a clean byte stream.
  • xxd -r -p: Converts to binary format and writes it to flag.raw.

Play the file via the terminal or import it to any software that supports raw data. (Make sure it’s set to 32-bit.)

play -r 44100 -b 32 -c 1 -e signed-integer flag.raw speed 0.7
  • -r 44100: Sample rate 44.1 kHz.
  • -b 32: 32-bit audio depth.
  • -c 1: Mono (1 channel).
  • -e signed-integer: Interprets the audio data as signed integers.
  • speed 0.7: Playback speed to 70%

talktome flag