Post

CSAW CTF Qualification Round 2024

CSAW CTF Qualification Round 2024, three (3) forensics writeups out of five (5).

ZipZipZip [Forensics]

Solves: 481 Points: 50 Author: knight_

Description: Brighten up at last with the flag

challenge.zip


This challenge provides us with a zipfile containing chunks of zips, first chunk is chunk_0.

1
2
3
4
5
└─$ zipinfo challenge         
Archive:  challenge.zip
Zip file size: 7793947 bytes, number of entries: 1
-rw-a--     3.0 fat  7793702 bx stor 24-Sep-05 19:56 chunk_0.zip
1 file, 7793702 bytes uncompressed, 7793702 bytes compressed:  0.0%

Unzipping the challenge zipfile gives chunk_0.zip, which itself contains an additional chunk called chunk_1.zip and a text file chunk_0.txt containing uncomplete data.

1
2
3
4
5
6
└─$ zipinfo chunk_0.zip 
Archive:  chunk_0.zip
Zip file size: 7793702 bytes, number of entries: 2
?rw-------  2.0 fat        5 b- stor 24-Sep-06 00:41 chunk_0.txt
-rw-rw-rw-  2.0 fat  7793479 b- stor 24-Sep-06 00:41 chunk_1.zip
2 files, 7793484 bytes uncompressed, 7793484 bytes compressed:  0.0%

The idea is clear, unzip recursively all zipfiles and concatenate the uncomplete contents of text files into a single file, to do this, a python script was used.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
import os
import zipfile
import shutil

def extract_zip(zip_path, extract_to):
    """ Extract a ZIP file to the specified directory. """
    with zipfile.ZipFile(zip_path, 'r') as zip_ref:
        zip_ref.extractall(extract_to)

def process_chunks(base_path):
    """ Process ZIP files and concatenate text files. """
    result_string = ""
    # Create a queue to manage files and directories to be processed
    queue = [base_path]

    while queue:
        current_path = queue.pop(0)
        
        if not os.path.isdir(current_path):
            # Skip non-directory items (e.g., ZIP files that are being processed)
            continue

        for item in os.listdir(current_path):
            item_path = os.path.join(current_path, item)
            
            if os.path.isfile(item_path):
                if item.endswith('.txt'):
                    with open(item_path, 'r') as file:
                        result_string += file.read() + "\n"  # Concatenate content with newline separator
                elif item.endswith('.zip'):
                    # Extract the new ZIP file directly in the base_path
                    extract_path = os.path.join(base_path, item.replace('.zip', ''))
                    os.makedirs(extract_path, exist_ok=True)
                    extract_zip(item_path, extract_path)
                    # Add the newly extracted files to the queue for processing
                    queue.append(extract_path)
            
            elif os.path.isdir(item_path):
                # Add directories to the queue to process their contents
                queue.append(item_path)
        
        # After processing the current directory, delete it
        if current_path != base_path:
            shutil.rmtree(current_path, ignore_errors=True)

    return result_string

def main():
    # Define paths
    base_dir = './result'  # Use relative path to ensure correct base directory
    challenge_zip = 'challenge.zip'
    
    # Create the result directory if it doesn't exist
    os.makedirs(base_dir, exist_ok=True)
    
    # Extract the initial ZIP file
    extract_zip(challenge_zip, base_dir)
    
    # Process the extracted contents
    result = process_chunks(base_dir)
    
    # Save the result to a file or print it
    with open('final_result.txt', 'w') as result_file:
        result_file.write(result)
    
    print("Processing complete. The result has been saved to 'final_result.txt'.")

if __name__ == "__main__":
    main()

The content of final result is base64 encoded, we decode it then check file format.

1
2
3
└─$ cat final_result.txt | base64 -d > final_result
└─$ file final_result    
final_result: PNG image data, 1024 x 1024, 8-bit colormap, non-interlaced

It’s an image, loading it reveals the flag.

alt text

Flag: csawctf{ez_r3cur5iv3ne55_right7?}

The Triple Illusion [Forensics]

Solves: 215 Points: 228 Author: ScriptKiddo

Description: Some things are hidden in plain sight. Use your knowledge of forensics and crypto to solve the challenge

images.zip


Thie time we are provided with a zipfile with three images.

1
2
3
4
5
6
7
8
└─$ zipinfo images.zip 
Archive:  images.zip
Zip file size: 13975320 bytes, number of entries: 4
drwxrwxr-x  6.3 unx        0 bx stor 24-Sep-07 06:14 images/
-rw-rw-r--  6.3 unx   202140 bx defN 24-Sep-06 19:46 images/datavsmetadata.png
-rw-rw-r--  6.3 unx  1422389 bx stor 24-Sep-05 23:57 images/hibiscus.png
-rw-rw-r--  6.3 unx 12366166 bx defN 24-Sep-05 23:57 images/roses.png
4 files, 13990695 bytes uncompressed, 13974716 bytes compressed:  0.1%

Checking the first one’s metadata reveals hidden information.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
└─$ exiftool images/datavsmetadata.png 
ExifTool Version Number         : 12.76
File Name                       : datavsmetadata.png
Directory                       : images
File Size                       : 202 kB
File Modification Date/Time     : 2024:09:06 19:46:15+01:00
File Access Date/Time           : 2024:09:09 15:47:33+01:00
File Inode Change Date/Time     : 2024:09:07 06:14:32+01:00
File Permissions                : -rw-rw-r--
File Type                       : PNG
File Type Extension             : png
MIME Type                       : image/png
Image Width                     : 1400
Image Height                    : 734
Bit Depth                       : 8
Color Type                      : RGB with Alpha
Compression                     : Deflate/Inflate
Filter                          : Adaptive
Interlace                       : Noninterlaced
Pixels Per Unit X               : 2835
Pixels Per Unit Y               : 2835
Pixel Units                     : meters
Exif Byte Order                 : Big-endian (Motorola, MM)
Orientation                     : Horizontal (normal)
Comment                         : Can you crack my secret? Here's a list of numbers: See what they reveal. 0 0 0 0 0 0 0 0 15 23 23 4 7 0 22 1 23 28 0 18 10 12 0 7 23 2 17 18 21 16 0 0 0 0 0 28 7 16 17 16 6 17 11 0 1 0 21 23 4 24 0 0 0 0 0 0
Image Size                      : 1400x734
Megapixels                      : 1.0

It seems we need a key to xor with the numbers provided. We check second and third images for hidden data and we find some.

1
2
3
4
5
6
7
8
└─$ zsteg -a images/roses.png 
b1,rgb,lsb,xy       .. text: "csawctf{heres_akey_now_decrypt_the_vigenere_cipher_text} "
b1,rgb,msb,xy       .. file: OpenPGP Public Key
<SNIP>
└─$ zsteg -a images/hibiscus.png 
imagedata           .. file: shared library
b1,rgb,lsb,xy       .. text: "ekasemk{oiiik_axiu_xsu_gieiwem_moi_nmivrxks_tmklec_ypxz}"
<SNIP>

it’s vigenere’s encryption, CyberChef to the rescue.

alt text

We get another key csawctf{heres_anew_key_decrypt_the_secretto_reveal_flag}, this one is for the xor operation, we used a python script once again.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def xor_decode(data, key):
    """ XOR decode with multi-byte key. """
    key_length = len(key)
    return bytes([data[i] ^ key[i % key_length] for i in range(len(data))])

# Convert the string to bytes
string = 'csawctf{heres_anew_key_decrypt_the_secretto_reveal_flag}'
string_bytes = string.encode('utf-8')

# Numbers from the array
numbers = [0, 0, 0, 0, 0, 0, 0, 0, 15, 23, 23, 4, 7, 0, 22, 1, 23, 28, 0, 18, 10, 12, 0, 7, 23, 2, 17, 18, 21, 16, 0, 0, 0, 0, 0, 28, 7, 16, 17, 16, 6, 17, 11, 0, 1, 0, 21, 23, 4, 24, 0, 0, 0, 0, 0, 0]

numbers_bytes = bytes(numbers)

# XOR the string bytes with the numbers bytes
decoded_bytes = xor_decode(string_bytes, numbers_bytes)
try:
    decoded_str = decoded_bytes.decode('utf-8')
    print(f"Decoded result: {decoded_str}")
except UnicodeDecodeError:
    print("Failed to decode the result as UTF-8.")

Running it reveals the flag.

1
2
└─$ python img_decode.py 
Decoded result: csawctf{great_work_you_cracked_the_obscured_secret_flag}

Flag: csawctf{great_work_you_cracked_the_obscured_secret_flag}

Covert [Forensics]

Solves: 237 Points: 169 Author: cpan57

Description: It appears there's been some shady communication going on in our network...

covert.zip


This challenge provides a zipfile containing a pcapng file with key decryption file for TLS traffic.

1
2
3
4
5
6
└─$ zipinfo challenge/covert.zip 
Archive:  challenge/covert.zip
Zip file size: 126668 bytes, number of entries: 2
-rw-rw-r--  6.3 unx   134924 bx defN 24-Sep-06 17:03 chall.pcapng
-rw-rw-r--  6.3 unx    69468 bx defN 24-Sep-06 17:03 keys.log
2 files, 204392 bytes uncompressed, 126382 bytes compressed:  38.2%

Inspecting http traffic after TLS decryption shows a shady python script being used for communication, it reveals a key of two characters that was used for communication encryption on id field of IP header, the destination ip and source ip used ports of five numbers.

alt text

The only suspicious traffic that checks these conditions is this one in red which means suspected retransmission.

alt text

A python script was used to brute force the key to get the flag.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import itertools
from scapy.all import rdpcap, IP, TCP

# Load the PCAP file
packets = rdpcap('<REDACTED>/chall.pcapng')

# Collect all non-zero IP IDs into an array
ip_ids = []

for packet in packets:
    if IP in packet and TCP in packet:
        if packet[IP].id != 0:  # ip.id != 0
            if packet[IP].src == "172.20.10.5" and packet[IP].dst == "172.57.57.57":  # IP source and destination match
                ip_ids.append(packet[IP].id)
print("length: ",len(ip_ids))
# Define a function to decode using a brute-forced two-byte key
def decode_ids(ip_ids, key1, key2):
    decoded_message = ''
    for ip_id in ip_ids:
        try:
            # Use both parts of the key
            decoded_char = chr(ip_id // (key1 * key2))  # Assuming ord(letter) * key1 * key2 = ip.id
            decoded_message += decoded_char
        except:
            continue
    return decoded_message

# Brute-force possible two-character keys and look for a specific pattern
def brute_force_key(ip_ids):
    # Generate all possible two-character key combinations (1-255 for both)
    for key1, key2 in itertools.product(range(1, 256), repeat=2):
        decoded_message = decode_ids(ip_ids, key1, key2)
        if "csaw" in decoded_message:  # Look for the known flag pattern
            print(f"Key found: {key1}, {key2}")
            print(f"Decoded message: {decoded_message}")
            break

# Execute the brute-force attack
brute_force_key(ip_ids)

Running it reveals the flag.

1
2
3
4
└─$ python challenge/dec.py 
length:  41
Key found: 1, 55
Decoded message: csawctf{licen$e_t0_tr@nsmit_c0vertTCP$$$}

Flag: csawctf{licen$e_t0_tr@nsmit_c0vertTCP$$$}

This post is licensed under CC BY 4.0 by the author.

Trending Tags