일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
Tags
- ctf player
- 2022 Fall GoN Open Qual CTF
- christmas ctf
- crypto
- KAIST
- h4cking game
- 워게임
- System Hacking
- writeup
- hack
- hacking game
- python
- webhacking.kr
- Wargame
- typhooncon
- pwnable
- 웹해킹
- CTF
- dreamhack
- Wreckctf
- 해킹
- webhacking
- TeamH4C
- reversing
- got overwrite
- Gon
- WEB
- cryptography
- hacking
- Buffer Overflow
Archives
- Today
- Total
deayzl's blog
[2022 Fall GoN Open Qual CTF] Checkers writeup 본문
CTF writeup/GoN Open Qual CTF
[2022 Fall GoN Open Qual CTF] Checkers writeup
deayzl 2022. 8. 31. 21:00#!/usr/bin/python3
import random
CHARSET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ_/"
def key_gen():
tmp = list(CHARSET)
random.shuffle(tmp)
p = "".join(tmp)
k1, k2 = random.sample(range(1, 10), 2)
k3_len = random.randint(5, 10)
k3 = tuple(random.randrange(1, 10) for _ in range(k3_len))
p_li = list(p)
p_li.append(p_li[k1])
p_li.append(p_li[k2])
p_li[k1] = " "
p_li[k2] = " "
p = "".join(p_li)
return (p, k1, k2, k3)
def read_flag():
flag = ""
try:
with open("flag", "r") as f:
flag = f.read().strip()
except FileNotFoundError:
print("fatal error: contact to admin")
exit(1)
assert(valid_msg(flag))
return flag
def valid_msg(m):
return all(c in CHARSET for c in m)
def straddling_checkerboard(pt, perm, k1, k2):
assert(set(CHARSET + " ") == set(perm))
assert(len(CHARSET) == len(perm) - 2)
assert(k1 in range(1, 10))
assert(k2 in range(1, 10))
idx = perm.index(pt)
if idx < 10:
return (idx,)
else:
return ((0, k1, k2)[idx // 10], idx % 10)
def straddling_checkerboard_inv(ct, perm, k1, k2):
assert(set(CHARSET + " ") == set(perm))
assert(len(CHARSET) == len(perm) - 2)
assert(k1 in range(1, 10))
assert(k2 in range(1, 10))
res = ""
while ct:
if ct[0] == k1 and len(ct) > 1:
res += perm[10 + ct[1]]
ct = ct[2:]
elif ct[0] == k2 and len(ct) > 1:
res += perm[20 + ct[1]]
ct = ct[2:]
else:
res += perm[ct[0]]
ct = ct[1:]
return res
def non_carry_add(n1, n2):
res = 0
cnt = 1
while n1 != 0 or n2 != 0:
res += (((n1 % 10) + (n2 % 10)) % 10) * cnt
n1 //= 10
n2 //= 10
cnt *= 10
return res
def substitute(pt, k3):
assert(all(k in range(1, 10) for k in k3))
res = []
for i, c in enumerate(pt):
res.append(non_carry_add(c, k3[i % len(k3)]))
return res
def encrypt(pt, perm, k1, k2, k3):
pt_checked = []
for c in pt:
pt_checked.extend(straddling_checkerboard(c, perm, k1, k2))
pt_subed = substitute(pt_checked, k3)
return straddling_checkerboard_inv(pt_subed, perm, k1, k2)
if __name__ == "__main__":
PERM, K1, K2, K3 = key_gen()
FLAG = read_flag()
while True:
print("--------------------")
print("1. Encrypt Message")
print("2. Decrypt Message")
print("3. Encrypt Flag")
print("4. Exit")
try:
op = int(input())
except ValueError:
print("[-] Unexpected input...")
continue
if op == 1:
print("[*] Please give me the plaintext message")
msg = input()
if valid_msg(msg):
print("[+] Encrypted message :%s" % encrypt(msg, PERM, K1, K2 ,K3))
else:
print("[-] Message format is invalid...")
elif op == 2:
print("[!] Ask Decryption oracle for ancient cipher??? HOW DARE YOU")
break
elif op == 3:
print("[+] Encrypted Flag :GoN{%s}" % encrypt(FLAG, PERM, K1, K2, K3))
elif op == 4:
break
else:
print("[-] Unexpected input...")
제공된 chal.py 파일의 소스코드이다.
encrypt 된 flag 를 주는 것을 보아, decrypt 를 직접 해야하는 문제인듯 보인다.
근데 encrypt 하는 과정을 살펴보니 머리가 어지러워지기 시작한다.
그래서 일단 1번 기능을 이용하여, charset 의 문자 하나하나를 encrypt 해보았다.
A -> LK
B -> ZN
AB -> LK__
AC -> LK_Z
일단 첫번째 글자일 때의 암호화된 값은 두번째 글자일 때와 다르다.
그리고 그 전 글자를 암호화한 값이 그대로 남아 있는 것을 볼 수 있다.
그렇다면
1. CHARSET 의 문자 모두 암호화한 값을 추출
2. 암호화된 flag 값의 처음 부분과 같은 값으로 암호화되는 문자를 찾고
3. 찾은 문자와 다시 CHARSET 문자를 합친 후, 합친 문자열을 다시 암호화한 값을 추출
4. 2로 돌아가 반복
하면 flag 를 찾을 수 있을 것 같아보인다.
DFS 를 이용하여 서치해주면, flag 의 원본 값을 알아낼 수 있다.
from pwn import *
CHARSET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ_/"
while(True):
p = remote('host3.dreamhack.games', 9660)
def Encrypt(msg):
p.recvuntil(b'4. Exit\n')
p.send_raw(b'1\n')
p.recvline()
p.send_raw(msg.encode() + b'\n')
p.recvuntil(b':')
return p.recvline()[:-1]
def EncryptFlag():
p.recvuntil(b'4. Exit\n')
p.send_raw(b'3\n')
p.recvuntil(b':')
return p.recvline()[:-1]
def DFS_find_flag(depth, prev_flag_encrypted, flag_encrypted, flag):
print('depth : {0}\nprev_flag_encrypted : {1}, flag_encrypted : {2}, flag : {3}'.format(depth, prev_flag_encrypted, flag_encrypted, flag))
CHARSET_ENCRYPTED = list()
for i in range(len(CHARSET)):
CHARSET_ENCRYPTED.append(Encrypt(flag + CHARSET[i]).decode().strip().replace(prev_flag_encrypted, ''))
#print('{0} : {1}({2})'.format(CHARSET[i], CHARSET_ENCRYPTED[i], len(CHARSET_ENCRYPTED[i])))
POSSIBLE_CHARSET_ENCRYPTED_INDEX = list()
for i in range(len(CHARSET_ENCRYPTED)):
if(CHARSET_ENCRYPTED[i] == ' '):
POSSIBLE_CHARSET_ENCRYPTED_INDEX.append(i)
elif(len(CHARSET_ENCRYPTED[i]) == 1):
if(flag_encrypted[0] == CHARSET_ENCRYPTED[i]):
POSSIBLE_CHARSET_ENCRYPTED_INDEX.append(i)
else:
if(flag_encrypted[:len(CHARSET_ENCRYPTED[i])] == CHARSET_ENCRYPTED[i]):
POSSIBLE_CHARSET_ENCRYPTED_INDEX.append(i)
print('possible encrypted charset len : ' + str(len(POSSIBLE_CHARSET_ENCRYPTED_INDEX)))
if(len(POSSIBLE_CHARSET_ENCRYPTED_INDEX) == 0):
print("fuckedup")
return "fuckedup"
for i in range(len(POSSIBLE_CHARSET_ENCRYPTED_INDEX)):
print('{0} -> {1}({2}) going deeper..'.format(i, CHARSET_ENCRYPTED[POSSIBLE_CHARSET_ENCRYPTED_INDEX[i]],
CHARSET[POSSIBLE_CHARSET_ENCRYPTED_INDEX[i]]))
if(len(flag_encrypted) == len(CHARSET_ENCRYPTED[POSSIBLE_CHARSET_ENCRYPTED_INDEX[i]])):
return flag + CHARSET[POSSIBLE_CHARSET_ENCRYPTED_INDEX[i]]
result = DFS_find_flag(depth + 1, prev_flag_encrypted + CHARSET_ENCRYPTED[POSSIBLE_CHARSET_ENCRYPTED_INDEX[i]]
, flag_encrypted[len(CHARSET_ENCRYPTED[POSSIBLE_CHARSET_ENCRYPTED_INDEX[i]]):], flag + CHARSET[POSSIBLE_CHARSET_ENCRYPTED_INDEX[i]])
if(result == "fuckedup"):
continue
else:
return result
return "fuckedup"
flag_encrypted = EncryptFlag().decode()
print('flag encrypted : ' + flag_encrypted)
flag_encrypted = flag_encrypted[4:-1]
print('-- start --')
print(flag_encrypted)
if(flag_encrypted.find(' ') != -1):
p.close()
continue
"""CHARSET_ENCRYPTED = list()
for i in range(len(CHARSET)):
CHARSET_ENCRYPTED.append(Encrypt(flag + CHARSET[i]).decode())
print('{0} : {1}'.format(CHARSET[i], CHARSET_ENCRYPTED[i]))"""
flag = ''
print('GoN{' + DFS_find_flag(0, '', flag_encrypted, flag) + '}')
break
p.interactive()
스크립트를 만들 때 유의할 점은 암호화된 값 중에 ' ' 공백 문자가 나오는 경우가 있다.
직접 다음 단계에서 문자를 합치고 encrypt 해보면 ' ' 공백 문자가 사라지는 것을 보았기에
그냥 flag 에 ' ' 공백 문자가 포함되면 다시 연결하고,
암호화하면서 depth 가 깊어지는 과정에서 ' ' 공백 문자를 strip 함수로 없애주었다.
잠깐 배워놓았던 알고리즘이 여기서 쓰일줄이야..
'CTF writeup > GoN Open Qual CTF' 카테고리의 다른 글
[2022 Fall GoN Open Qual CTF] Private Storage writeup (0) | 2022.08.31 |
---|---|
[2022 Fall GoN Open Qual CTF] Bomblab - Hard writeup (0) | 2022.08.31 |
[2022 Fall GoN Open Qual CTF] SleepingShark writeup (0) | 2022.08.31 |
[2022 Fall GoN Open Qual CTF] Zero Gravity writeup (0) | 2022.08.31 |
[2022 Fall GoN Open Qual CTF] Api Portal writeup (0) | 2022.08.31 |
Comments