Skip to content

Commit 7171e2e

Browse files
committed
SekaiCTF 2022 Challenges
1 parent 8ded2a6 commit 7171e2e

File tree

575 files changed

+3556488
-2
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

575 files changed

+3556488
-2
lines changed

README.md

+6-2
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,6 @@
1-
# sekaictf-2022
2-
Source code and writeup for SekaiCTF 2022. https://ctftime.org/event/1619
1+
# SekaiCTF 2022
2+
3+
This repo contains source code and writeups for SekaiCTF 2022.
4+
5+
## Challenges
6+

crypto/README.md

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<img src="https://files.catbox.moe/lwcdks.svg" align="right" width=300>
2+
3+
# Cryptography
4+
5+
| Name | Author | Difficulty | Solves |
6+
|---------------------------------------------------------|--------------------------------------------|------------|-------------------------------------------------|
7+
| [Time Capsule](time-capsule/) | sahuang | Easy (1) | 178 |
8+
| [FaILProof](failproof/) | deuterium | Normal (2) | 62 |
9+
| [Secure Image Encryption](secure-image-encryption/) | Yanhu1 & sahuang | Normal (2) | 49 |
10+
| [Diffecient](diffecient/) | deuterium | Hard (3) | 7 |
11+
| [EZMaze](ezmaze/) | Utaha | Hard (3) | 11 |
12+
| [Robust CBC](robust-cbc/) | sahuang & Yanhu1 | Expert (4) | 10 |
13+
| [FaILProof Revenge](failproof-revenge/) | deuterium | Expert (4) | 16 |

crypto/diffecient/README.md

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# diffecient
2+
3+
## Description
4+
5+
Welcome to the Diffecient Security Key Database API, for securely and efficiently saving tons of long security keys! Feel *free* to query your security keys, and pay a little to add your own to our state-of-the-art database.
6+
7+
We trust our product so much that we even save our own keys here!
8+
9+
**First Solve**: Maple Bacon
+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
FROM python:3.9-slim-buster
2+
3+
RUN apt-get update && \
4+
apt-get install -y lib32z1 xinetd && \
5+
pip3 install requests mmh3 && \
6+
apt-get clean && \
7+
rm -rf /var/lib/apt/lists/*
8+
9+
RUN useradd -m user && \
10+
chown -R root:root /home/user
11+
12+
COPY app /home/user/
13+
COPY xinetd /etc/xinetd.d/user
14+
15+
WORKDIR /home/user
16+
17+
EXPOSE 9999
18+
19+
CMD ["/usr/sbin/xinetd", "-dontfork"]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
import math
2+
import random
3+
import re
4+
import mmh3
5+
6+
def randbytes(n): return bytes ([random.randint(0,255) for i in range(n)])
7+
8+
class BloomFilter:
9+
def __init__(self, m, k, hash_func=mmh3.hash):
10+
self.__m = m
11+
self.__k = k
12+
self.__i = 0
13+
self.__digests = set()
14+
self.hash = hash_func
15+
16+
def security(self):
17+
false_positive = pow(
18+
1 - pow(math.e, -self.__k * self.__i / self.__m), self.__k)
19+
try:
20+
return int(1 / false_positive).bit_length()
21+
except (ZeroDivisionError, OverflowError):
22+
return float('inf')
23+
24+
def _add(self, item):
25+
self.__i += 1
26+
for i in range(self.__k):
27+
self.__digests.add(self.hash(item, i) % self.__m)
28+
29+
def check(self, item):
30+
return all(self.hash(item, i) % self.__m in self.__digests
31+
for i in range(self.__k))
32+
33+
def num_passwords(self):
34+
return self.__i
35+
36+
def memory_consumption(self):
37+
return 4*len(self.__digests)
38+
39+
40+
class PasswordDB(BloomFilter):
41+
def __init__(self, m, k, security, hash_func=mmh3.hash):
42+
super().__init__(m, k, hash_func)
43+
self.add_keys(security)
44+
self.addition_quota = 1
45+
self.added_keys = set()
46+
47+
def add_keys(self, thresh_security):
48+
while self.security() > thresh_security:
49+
self._add(randbytes(256))
50+
print("Added {} security keys to DB".format(self.num_passwords()))
51+
print("Original size of keys {} KB vs {} KB in DB".format(
52+
self.num_passwords()//4, self.memory_consumption()//1024))
53+
54+
def check_admin(self, key):
55+
if not re.match(b".{32,}", key):
56+
print("Admin key should be atleast 32 characters long")
57+
return False
58+
if not re.match(b"(?=.*[a-z])", key):
59+
print("Admin key should contain atleast 1 lowercase character")
60+
return False
61+
if not re.match(b"(?=.*[A-Z])", key):
62+
print("Admin key should contain atleast 1 uppercase character")
63+
return False
64+
if not re.match(br"(?=.*\d)", key):
65+
print("Admin key should contain atleast 1 digit character")
66+
return False
67+
if not re.match(br"(?=.*\W)", key):
68+
print("Admin key should contain atleast 1 special character")
69+
return False
70+
if key in self.added_keys:
71+
print("Admin account restricted for free tier")
72+
return False
73+
return self.check(key)
74+
75+
def query_db(self, key):
76+
if self.check(key):
77+
print("Key present in DB")
78+
else:
79+
print("Key not present in DB")
80+
81+
def add_sample(self, key):
82+
if self.addition_quota > 0:
83+
self._add(key)
84+
self.added_keys.add(key)
85+
self.addition_quota -= 1
86+
print("key added successfully to DB")
87+
else:
88+
print("API quota exceeded")
89+
90+
91+
BANNER = r"""
92+
____ ____ ____ ____ ____ ___ ____ ____ _ _ ____
93+
( _ \(_ _)( ___)( ___)( ___)/ __)(_ _)( ___)( \( )(_ _)
94+
)(_) )_)(_ )__) )__) )__)( (__ _)(_ )__) ) ( )(
95+
(____/(____)(__) (__) (____)\___)(____)(____)(_)\_) (__)
96+
97+
Welcome to diffecient security key database API for securely
98+
and efficiently saving tonnes of long security keys!
99+
Feel FREE to query your security keys and pay a little to
100+
add your own security keys to our state of the art DB!
101+
We trust our product so much that we even save our own keys here
102+
"""
103+
print(BANNER)
104+
PASSWORD_DB = PasswordDB(2**32 - 5, 47, 768, mmh3.hash)
105+
while True:
106+
try:
107+
option = int(input("Enter API option:\n"))
108+
if option == 1:
109+
key = bytes.fromhex(input("Enter key in hex\n"))
110+
PASSWORD_DB.query_db(key)
111+
elif option == 2:
112+
key = bytes.fromhex(input("Enter key in hex\n"))
113+
PASSWORD_DB.add_sample(key)
114+
elif option == 3:
115+
key = bytes.fromhex(input("Enter key in hex\n"))
116+
if PASSWORD_DB.check_admin(key):
117+
from flag import flag
118+
print(flag)
119+
else:
120+
print("No Admin no flag")
121+
elif option == 4:
122+
exit(0)
123+
except:
124+
print("Something wrong happened")
125+
exit(1)
+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
flag = b'SEKAI{56f066a1b13fd350ac4a4889efe22cb1825651843e9d0ccae0f87844d1d65190}'

crypto/diffecient/challenge/xinetd

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
service challenge
2+
{
3+
disable = no
4+
type = UNLISTED
5+
socket_type = stream
6+
protocol = tcp
7+
port = 9999
8+
wait = no
9+
user = user
10+
server = /usr/local/bin/python
11+
server_args = /home/user/diffecient.py
12+
}

crypto/diffecient/solution/solve.py

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import mmh3
2+
import pwn
3+
4+
5+
def forward_block(block):
6+
"""
7+
initial transformation of 4 byte block in murmurhash3
8+
"""
9+
n = int.from_bytes(block, 'little')
10+
n = (n * 0xcc9e2d51) & 0xffffffff
11+
n = (n >> 17) | (n << 15) & 0xffffffff
12+
n = (n * 0x1b873593) & 0xffffffff
13+
return n.to_bytes(4, 'little')
14+
15+
16+
def invert_block(block):
17+
"""
18+
invert a 4 byte block in murmurhash3
19+
"""
20+
n = int.from_bytes(block, 'little')
21+
n = (n * 0x56ed309b) & 0xffffffff
22+
n = (n >> 15) | (n << 17) & 0xffffffff
23+
n = (n * 0xdee13bb1) & 0xffffffff
24+
return n.to_bytes(4, 'little')
25+
26+
27+
def forward(blocks):
28+
"""
29+
forward_block applied to all 4 byte chunks
30+
"""
31+
return b''.join(forward_block(blocks[i:i + 4])
32+
for i in range(0, len(blocks), 4))
33+
34+
35+
def invert(blocks):
36+
"""
37+
invert applied to all 4 byte chunks
38+
"""
39+
return b''.join(invert_block(blocks[i:i + 4])
40+
for i in range(0, len(blocks), 4))
41+
42+
43+
DIFF = b'\x00\x00\x04\x00\x00\x00\x00\x80'
44+
45+
46+
def collision8(text):
47+
return invert(bytes(i ^ j for i, j in zip(DIFF, forward(text))))
48+
49+
50+
sample_valid_password = b'A' * 28 + b'Aa1!'
51+
sample_colliding_password = collision8(
52+
sample_valid_password[:8]) + sample_valid_password[8:]
53+
54+
HOST, PORT = "challs.ctf.sekai.team", 3003
55+
REM = pwn.remote(HOST, PORT)
56+
REM.sendline('2') # store sample key
57+
REM.sendline(sample_colliding_password.hex())
58+
REM.sendline('3') # claim flag
59+
REM.sendline(sample_valid_password.hex())
60+
REM.sendline('4') # exit
61+
print(REM.recvall().decode())

crypto/ezmaze/README.md

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# EZMaze
2+
3+
## Description
4+
5+
Can you escape the Maze? OwO
6+
7+
**First Solve**: Black Bauhinia

crypto/ezmaze/challenge/Dockerfile

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
FROM python:3.9-slim-buster
2+
3+
RUN apt-get update && \
4+
apt-get install -y lib32z1 xinetd && \
5+
pip3 install requests mmh3 && \
6+
apt-get clean && \
7+
rm -rf /var/lib/apt/lists/*
8+
9+
RUN pip3 install pycryptodome
10+
11+
RUN useradd -m user && \
12+
chown -R root:root /home/user
13+
14+
ADD app /home/user/
15+
COPY xinetd /etc/xinetd.d/user
16+
17+
WORKDIR /home/user
18+
19+
EXPOSE 9999
20+
21+
CMD ["/usr/sbin/xinetd", "-dontfork"]

crypto/ezmaze/challenge/app/chall.py

+76
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
#!/usr/bin/env python3
2+
import os
3+
import random
4+
from Crypto.Util.number import *
5+
6+
from flag import flag
7+
8+
directions = "LRUD"
9+
SOLUTION_LEN = 64
10+
11+
def toPath(x: int):
12+
s = bin(x)[2:]
13+
if len(s) % 2 == 1:
14+
s = "0" + s
15+
16+
path = ""
17+
for i in range(0, len(s), 2):
18+
path += directions[int(s[i:i+2], 2)]
19+
return path
20+
21+
def toInt(p: str):
22+
ret = 0
23+
for d in p:
24+
ret = ret * 4 + directions.index(d)
25+
return ret
26+
27+
def final_position(path: str):
28+
return (path.count("R") - path.count("L"), path.count("U") - path.count("D"))
29+
30+
class RSA():
31+
def __init__(self):
32+
self.p = getPrime(512)
33+
self.q = getPrime(512)
34+
self.n = self.p * self.q
35+
self.phi = (self.p - 1) * (self.q - 1)
36+
self.e = 65537
37+
self.d = pow(self.e, -1, self.phi)
38+
39+
def encrypt(self, m: int):
40+
return pow(m, self.e, self.n)
41+
42+
def decrypt(self, c: int):
43+
return pow(c, self.d, self.n)
44+
45+
def main():
46+
solution = "".join([random.choice(directions) for _ in range(SOLUTION_LEN)])
47+
sol_int = toInt(solution)
48+
print("I have the solution of the maze, but I'm not gonna tell you OwO.")
49+
50+
rsa = RSA()
51+
print(f"n = {rsa.n}")
52+
print(f"e = {rsa.e}")
53+
print(hex(rsa.encrypt(sol_int)))
54+
55+
while True:
56+
try:
57+
opt = int(input("Options: 1. Decrypt 2. Check answer.\n"))
58+
if opt == 1:
59+
c = int(input("Ciphertext in hex: "), 16)
60+
path = toPath(rsa.decrypt(c))
61+
print(final_position(path))
62+
elif opt == 2:
63+
path = input("Solution: ").strip()
64+
if path == solution:
65+
print(flag)
66+
else:
67+
print("Wrong solution.")
68+
exit(0)
69+
else:
70+
print("Invalid option.")
71+
exit(0)
72+
except:
73+
exit(0)
74+
75+
if __name__ == "__main__":
76+
main()

crypto/ezmaze/challenge/app/flag.py

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
flag = b"SEKAI{parity_reveals_everything_:<_8f1261a517796b4d}"

crypto/ezmaze/challenge/xinetd

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
service challenge
2+
{
3+
disable = no
4+
type = UNLISTED
5+
socket_type = stream
6+
protocol = tcp
7+
port = 9999
8+
wait = no
9+
user = user
10+
server = /usr/local/bin/python
11+
server_args = /home/user/chall.py
12+
}

crypto/ezmaze/solution/README.md

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Writeup
2+
3+
https://hackmd.io/@Utaha1228/r1TGg7t-s

0 commit comments

Comments
 (0)