WACON 2023 Writeup
MISC - mic check
문제 사이트에 들어가자마자 Not Found 에러가 뜬다. 공지를 보니 의도된 거라고 해서 계속 찾아봤다.
혹시나 해서 robots.txt를 이용해 접근할 수 있는 경로를 알아봤는데 다음과 같이 W/A/C/O/N/2/
가 있었다.
그래서 해당 경로에 가면 flag를 얻을 수 있을 것 같아서 접속해봤는데 달라는 flag는 안주고 Not Found 가 뜬다.
이 이후로는 경로를 만지작 거리다가 혹시 보이는 거만 저렇게 보이고 status code는 다르지 않을까라고 생각해서 status code를 보았는데 http://58.225.56.196:5000/W/A/C/O/N/2
해당 경로에 접속했을 때 200 OK 코드를 받는 걸 볼 수 있었다.
반대로 올바른 경로가 아니면 실제로 404 Not found가 뜬다.
그래서 status code가 200인 경로를 찾아서 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
import requests
import string
url = 'http://58.225.56.196:5000/W/A/C/O/N/2/0/2/3/{'
table = string.hexdigits
def go(_url):
for i in table:
url_tmp = _url + '/' + i
print(url_tmp)
res = requests.get(url_tmp)
if res.status_code == 200:
print(i)
return i
return '}'
while True:
path = go(url)
if path == '}':
break
url = url + '/' + path
url += '/}'
print(url)
FLAG : WACON2023{2060923e53fa205a48b2f9ad47d943c4}
Crypto - White arts(easy)
prob.py
소스코드를 주는데 Generator1, Generator2, Generator3를 통과하면 flag를 던져준다.
해당 문제는 gen(0) 모드인지 random(1) 모드인지 맞히면 다음단계로 넘어가는 형식이다. 그리고 q에 입력값을 저장하고 G.calc(q, inverse).hex()
를 통해 랜덤으로 정해진 모드를 통해 q값을 연산하고 출력한다.
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
import math
import os
from Generator import Generator1, Generator2, Generator3, Generator4, Generator5
query_left = 266
def guess_mode(G, query_num):
for _ in range(query_num):
q = bytes.fromhex(input("q? > "))
inverse = input("inverse(y/n)? > ") == 'y'
assert len(q) == G.input_size
print(G.calc(q, inverse).hex())
# Time to guess
assert input("mode? > ") == str(G.mode) # 0(gen) / 1(random)
def challenge_generator(challenge_name, Generator):
global query_left
print(f"#### Challenge = {challenge_name}")
query_num = int(input(f"How many queries are required to solve {challenge_name}? > "))
query_left -= query_num
for _ in range(40):
G = Generator()
guess_mode(G, query_num)
challenge_generator("Generator1", Generator1)
challenge_generator("Generator2", Generator2)
if query_left < 0:
print("You passed all challenges for EASY but query limit exceeded. Try harder :(")
exit(-1)
print("(Only for a junior division) Good job! flag_baby =", open("flag_baby.txt").read())
challenge_generator("Generator3", Generator3)
if query_left < 0:
print("You passed all challenges for EASY but query limit exceeded. Try harder :(")
exit(-1)
print("Good job! flag_easy =", open("flag_easy.txt").read())
challenge_generator("Generator4", Generator4)
challenge_generator("Generator5", Generator5)
if query_left < 0:
print("You passed all challenges for HARD but query limit exceeded. Try harder :(")
exit(-1)
print("(Only for general/global divisions) Good job! flag_hard =", open("flag_hard.txt").read())
Generator1
Generator1의 func_gen(self, q)
을 보면 입력 값의 하위 8bytes가 상위로 오게된다.
- 따라서 본인이 입력한 하위의 8bytes가 상위의 8bytes로 표시되는 지 여부에 따라 mode를 구분할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Generator1:
def __init__(self):
self.mode = os.urandom(1)[0] & 1
self.n = 8
self.input_size = 2 * self.n
self.RF_gen = RandomFunction(self.n)
self.RF_random = RandomFunction(2 * self.n)
def func_gen(self, q):
L, R = q[:self.n], q[self.n:]
L, R = R, xor(L, self.RF_gen.query(R))
return L+R
def func_random(self, q):
return self.RF_random.query(q)
def calc(self, q, inverse):
assert inverse == False, "inverse query is not allowed for Generator1"
ret_gen = self.func_gen(q)
ret_random = self.func_random(q)
if self.mode == 0:
return ret_gen
else:
return ret_random
Generator2
RF_gen.query(R)
을 보면 아래와 같이 q를 통해서 만들어 진 랜덤 값은 self.domain_cache[x]
에 저장되는데 이는 q값이 달라지지 않으면 항상 같은 값을 반환한다.
1
2
3
4
5
def query(self, q):
x = q
if x not in self.domain_cache:
self.domain_cache[x] = os.urandom(self.n)
return self.domain_cache[x]
- 1 라운드에서 입력값 16바이트를 입력
- 2 라운드에서는 1라운드의 출력 값 중 상위 8바이트를 2 라운드의 상위 8바이트로 그리고 1 라운드의 입력 값 중 하위 8바이트를 2라운드의 하위 8바이트로 입력해준다.
- 그리고 나서 2라운드의 출력 값 중 상위 8바이트의 값이 1라운드 입력 값 중 상위 8바이트 값과 비교해서 동일한지 여부를 따진다.
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
class Generator2:
def __init__(self):
self.mode = os.urandom(1)[0] & 1
self.n = 8
self.input_size = 2 * self.n
self.RF_gen = RandomFunction(self.n)
self.RF_random = RandomFunction(2 * self.n)
def func_gen(self, q):
L, R = q[:self.n], q[self.n:]
L, R = R, xor(L, self.RF_gen.query(R))
L, R = R, xor(L, self.RF_gen.query(R))
return L+R
def func_random(self, q):
return self.RF_random.query(q)
def calc(self, q, inverse):
assert inverse == False, "inverse query is not allowed for Generator2"
ret_gen = self.func_gen(q)
ret_random = self.func_random(q)
if self.mode == 0:
return ret_gen
else:
return ret_random
Generator3
Generator3은 inverse를 사용할 수 있다.
- 1, 2라운드 모두 같은 값을 입력한다.
- 1라운드에서는 상위 8바이트 2라운드에서는 하위 8바이트가 동일한 지 여부를 판단한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Generator3:
def __init__(self):
self.mode = os.urandom(1)[0] & 1
self.n = 8
self.input_size = 2 * self.n
self.RF_gen = RandomFunction(self.n)
self.RF_random = RandomPermutation(2 * self.n)
def func_gen(self, q, inverse):
if not inverse:
L, R = q[:self.n], q[self.n:]
L, R = R, xor(L, self.RF_gen.query(R))
L, R = R, xor(L, self.RF_gen.query(R))
L, R = R, xor(L, self.RF_gen.query(R))
else:
L, R = q[:self.n], q[self.n:]
L, R = xor(R, self.RF_gen.query(L)), L
L, R = xor(R, self.RF_gen.query(L)), L
L, R = xor(R, self.RF_gen.query(L)), L
return L+R
Code
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
from pwn import *
context.log_level = 'debug'
p = remote('175.118.127.63', 2821)
def gen1():
p.sendlineafter(b'Generator1? > ', '1')
for i in range(40):
payload = '11' * 16
p.sendlineafter(b'q? > ', payload)
p.sendlineafter(b'inverse(y/n)? > ', b'n')
result = p.recvline().strip().decode()
if result[:16] == payload[:16]:
p.sendlineafter(b'mode? > ', b'0')
else:
p.sendlineafter(b'mode? > ', b'1')
def gen2():
p.sendlineafter(b'Generator2? > ', '2')
for i in range(40):
payload = '00' * 16
p.sendlineafter(b'q? > ', payload)
p.sendlineafter(b'inverse(y/n)? > ', b'n')
result = p.recvline().strip().decode()
payload = result[:16] + '00' * 8
p.sendlineafter(b'q? > ', payload)
p.sendlineafter(b'inverse(y/n)? > ', b'n')
result = p.recvline().strip().decode()
if result[:16] == '00' * 8:
p.sendlineafter(b'mode? > ', b'0')
else:
p.sendlineafter(b'mode? > ', b'1')
def gen3():
p.sendlineafter(b'Generator3? > ', '2')
for i in range(40):
payload = '00' * 16
p.sendlineafter(b'q? > ', payload)
p.sendlineafter(b'inverse(y/n)? > ', b'n')
result1 = p.recvline().strip().decode()
payload = '00' * 16
p.sendlineafter(b'q? > ', payload)
p.sendlineafter(b'inverse(y/n)? > ', b'y')
result2 = p.recvline().strip().decode()
if result1[:16] == result2[16:]:
p.sendlineafter(b'mode? > ', b'0')
else:
p.sendlineafter(b'mode? > ', b'1')
gen1()
gen2()
gen3()
p.interactive()
1
Good job! flag_easy = WACon2023{930db8b4dedb8cb86f309521011a1039}