GlacierCTF2024 - ksmaze

ksmaze is a linux kernel related challenge I authored in GlacierCTF2024. It had 1 solve 2h before the end of the 24h CTF. It categorizes in the hard side of the challenges.

You have the original CTFd distfile with a locally deployable version in [3]

The challenge is an unprivileged SSH instance of qemu running a custom rootfs and kernel:

 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
[ecomaikgolf@laptop ~/]$ nc 78.47.52.31 1337
[+] Please solve the following PoW (apt install hashcash):
[$] hashcash -mb29 -r tjkcjhhqniei
[>] Hashcash token: [...]
[+] Proof of Work passed

Press [ENTER] to start the instance

[+] Generating personal access key...

-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
QyNTUxOQAAACArqc/cNxvAEV3aCJ4igsyosNpDfEU2IZWGl5MhYvH2uwAAAJAfcmsLH3Jr
CwAAAAtzc2gtZWQyNTUxOQAAACArqc/cNxvAEV3aCJ4igsyosNpDfEU2IZWGl5MhYvH2uw
AAAEDWURNk8XZ98EeiheeUt17ptV93x7XaoLnVMt1ZNxOq1yupz9w3G8ARXdoIniKCzKiw
2kN8RTYhlYaXkyFi8fa7AAAAC3BsYXllckBnY3RmAQI=
-----END OPENSSH PRIVATE KEY-----

[+] 1. Paste the key to a file 'key'
[+] 2. Fix key permissions with 'chmod 600 key'
[+] 3. Wait 10-30 seconds for the system to boot
[+] 4. Clean known_hosts with ssh-keygen -R "[78.47.52.31]:20743"
[+] 5. Connect with 'ssh -p20743 -i key user@78.47.52.31'
[+] 6. Copy exploit files with 'scp -P 20743 -i key ./LOCALEXPLOIT user@78.47.52.31:~/'

[+] You have 500 seconds to solve it. Avoid timeouts by running it locally.

Press [ENTER] to stop the instance

We are running unprivileged and in a custom kernel:

GlacierCTF2024 - Schrödinger Compiler

Schrödinger Compiler is a C++ compiler related challenge I authored in GlacierCTF2024. It had 19 solves 3h before the end of the 24h CTF. It categorizes in the medium side of the challenges.

You have the original CTFd distfile with a locally deployable version in [1]

The challenge is jailed per connection and has the following behaviour:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#!/bin/sh

echo "[+] Welcome to the Schrödinger Compiler"
echo "[+] We definitely don't have a flag in /flag.txt"
echo "[+] Timeout is 3 seconds, you can run it locally with deploy.sh"

echo ""

echo "[+] Submit the base64 (EOF with a '@') of a .tar.gz compressed "
echo "    .cpp file and we'll compile it for you"
echo "[+] Example: tar cz main.cpp | base64 ; echo "@""
echo "[>] --- BASE64 INPUT START ---"
read -d @ FILE
echo "[>] --- BASE64 INPUT END ---"

DIR=$(mktemp -d)
cd ${DIR} &> /dev/null
echo "${FILE}" | base64 -d 2>/dev/null | tar -xzO > main.cpp 2> /dev/null
echo "[+] Compiling with g++ main.cpp &> /dev/null"
g++ main.cpp &> /dev/null
# ./main
# oops we fogot to run it
echo "[+] Bye, it was a pleasure! Come back soon!"

It basically receives a tarfile of a main.cpp, compiles with no output and returns.

GlacierCTF2024 - typstastic

typstastic is a typst related challenge I authored in GlacierCTF2024. It had 50 solves 3h before the end of the 24h CTF. It categorizes in the easier side of the challenges.

You have the original CTFd distfile with a locally deployable version in [1]

The challenge is jailed per connection and has the following behaviour:

 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
#!/bin/sh

echo "[+] Welcome to the typstastic v0.12.0 typst PDF builder CI"
echo "[+] Give us your typst document and we'll compile it for you!"
echo "[+] We definitely don't have a flag in /flag.txt"

echo ""

echo "[+] Submit the base64 (EOF with a '@') of a .tar.gz file containing your "
echo "    document (main.typ) in the root folder"
echo "[+] Example: tar cz main.typ | base64 ; echo "@""
echo "[>] --- BASE64 INPUT START ---"
read -d @ FILE
echo "[>] --- BASE64 INPUT END ---"

DIR=$(mktemp -d)
cd ${DIR} &> /dev/null
echo "${FILE}" | base64 -d 2>/dev/null | tar xz &> /dev/null
rm -rf main.pdf &> /dev/null
typst compile --root ${DIR} main.typ &> /dev/null

if [ ! -e main.pdf ]
then
  echo "[!] Compilation failed :("
  exit 1
else
  echo "[+] --- BASE64 OUTPUT START ---"
  tar cz main.pdf 2> /dev/null | base64 
  echo "[+] --- BASE64 OUTPUT END ---"
  echo "[+] Example: ... | base64 -d | tar xz > main.pdf"
  exit 0
fi

It basically receives a tarfile with a typst project, compiles it with typst 0.12.0 and provides the resulting PDF back.

R3CTF - Thief Writeup

This was an easy challenge related to Computer Vision:

 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
import torch
from torch import nn
import torchvision
import torchvision.transforms as transforms
import torchvision.models as models
import torch.nn.functional as F
import pandas as pd
import random
import os

print("""
...
""")
print(r"The model effect of Cain's is always not as good as that of rec.")
print(r"A little bird told me that rec only used a portion of the training set.")
print(r"Can you tell me the data below, which ones are in this model's training set")

train_indices = pd.read_csv('/app/train_indices.csv').iloc[1:, 0].tolist()
all_numbers = list(range(0, 10000))
trained = random.sample(train_indices, 250)
untrained = random.sample(list(set(all_numbers) - set(train_indices)), 250)
num = trained + untrained
random.shuffle(num)

transform = transforms.Compose([
        transforms.ToTensor(),
        transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])
trainset = torchvision.datasets.CIFAR100(root="/app/data", train=False, transform=transform)

rec_model = models.resnet18(weights=None)
rec_model.fc = nn.Linear(in_features=rec_model.fc.in_features, out_features=100)
rec_model.load_state_dict(torch.load(r"/app/rec's model.pth", map_location='cpu'))
rec_model.eval()

score = 0.75 + 0.1
cnt = 0
for idx in num:
    output = rec_model(trainset[idx][0].unsqueeze(0))
    prob = F.softmax(output, dim=1) 
    top_10, _ = torch.topk(prob, 10)

    print(f"idx : {idx}")
    print(f"top_10_pred : {top_10.tolist()}")
    inp = input('Is this picture in the training set?')
    ismember = 1 if idx in train_indices else 0
    if ismember == int(inp):
        cnt+=1

if cnt/len(num) > score:
    print(os.environ.get("GZCTF_FLAG"))
    exit()

print("No No No.You can't be the THEIF!!!")

You had to predict in a image recognition model if a certain image was used to train the model or not.

R3CTF - h1de@ndSe3k 2 Writeup

This is a second stage of the “hidenandseek” R3CTF challenge.

In this case we have that ben, the NPC with the flag:

  • Is invisible
  • Randomly teleports between (0, -50, 0) (512, 50, 512)
  • newtp only works in “ben’s range”

The main issue is that the teleportation range is much wider now, we can’t have many tries. Sitting there and waiting for ben to appear wasted some time, he only spawned a few times and was too far away.

R3CTF - hideandseek Writeup

This challenge involves a Minecraft server where an NPC is teleported each 10 seconds randomly in the (0, 0, 0) (128, -50, 128) range.

You have a command /newtp X Y Z that lets you teleport.

One of the main issues is that the NPC tends to suffocate as it spawns generally between blocks without any space (it doesn’t check for an empty space to teleport to).

In this challenge we got a bit lucky and found the NPC without the need of an script as we got him in a big cave.

R3CTF - r1system Writeup

This challenge was the continuation of r0system and also wasn’t involved with crypto that much.

I still don’t know if they release r1system as the “real” final stage of r0system but they had a mistake or if the mistake was actually intended:

1
2
3
4
5
elif option == 3:
    username = bytes.fromhex(input(b"Username[HEX]: ".decode()))
    if username == AliceUsername or username == AliceUsername:
	print(b"You can't!")
	return

r1system had a few differences from r0system, the main one was being able to send messages through the “PublicChannel”.

R3CTF - r0system Writeup

This challenge wasn’t that much about crypto. You had a login system via passwords and you can also register new users.

After you registered a new user you could reset the password, here was the misuse, as you could reset the password from other users. There was also a functionality that printed the private and public keys of the users.

So you had to register a new user, reset the password from Alice and Bob and then log as them. Finally you have to get both pub/priv keypairs from both and recover the encrypted password.

luksury Writeup - Insomnihack Final 2024

This post shows the writeup for the “luksury” challenge from he Insomnihack Final of 2024.

Challenge consisted in a LUKS2 encrypted disk image you had to bruteforce:

1
2
[ecomaikgolf@laptop ../insomnihack/luks/]$ file disk.img 
disk.img: LUKS encrypted file, ver 2, header size 16384, ID 4, algo sha256, salt 0xad7174d78159f31..., UUID: 6dbc6504-4250-4be3-a6d1-40625f28fcc7, crc 0xc1daabbc4f25841c..., at 0x1000 {"keyslots":{"1":{"type":"luks2","key_size":64,"af":{"type":"luks1","stripes":4000,"hash":"sha256"},"area":{"type":"raw","offse

The challenge also clearly hinted the usage of rockyou.txt to bruteforce the password.

As it’s LUKS2, we couldn’t directly use hashcash and we used bruteforce-luks as it seemed good & quick enough. The problem was that the program would hang on trying the first key, also if you tried to interact with the disk file in your own system, it also hanged.