๋ฌธ์ : DreamHack โ the present ๋ถ๋ฅ: crypto ๋์ด๋: ๐ฅ Silver 4 FLAG:
DH{M3rry_ChrI5tM4s_And_4_h4pPy_N3w_y34R_3verY0n3}
์ ๋ฌผ์์๋ฅผ ์ด์๋๋ ํฌ๋ฆฌ์ค๋ง์ค CTF ์ด๋๊ถ๊ณผ ์ํธํ๋ ์ชฝ์ง, ๊ทธ๋ฆฌ๊ณ ๊ทธ ์ชฝ์ง๋ฅผ ๋ง๋ ์ฝ๋๊ฐ ๊ฐ์ด ๋ค์ด ์์๋ค๋ ์ค์ ์ด๋ค. ๋ฌธ์ ํ์ด์ง์๋ "ํด๋น ๋ฌธ์ ๋ 2022 Christmas CTF ์ ์ถ์ ๋ ๋ฌธ์ ์ ๋๋ค"๋ผ๊ณ ๋ถ์ด ์๋ค.
๋ฐ์ ์ฝ๋๋ ์ค๋ฌด ์ค์ด ์ ๋๋ค. ์งง์ crypto ๋ฌธ์ ๋ ๋๊ฐ ๋ ์ค ํ๋์ธ๋ฐ, ์ํ์ ์ผ๋ก ๋ซ๋ ค ์๊ฑฐ๋ ํ๋ผ๋ฏธํฐ ํ๋๊ฐ ์๋ชป ๊ณจ๋ผ์ ธ ์๋ค. ์ด ๋ฌธ์ ๋ ํ์๋ค.

๋ฌธ์ ๊ฐ์
| ํญ๋ชฉ | ๋ด์ฉ |
|---|---|
| ๋ฌธ์ ๋ช | the present |
| ๋์ด๋ | ๐ฅ Silver 4 |
| ๋ถ๋ฅ | crypto |
| ์ ๊ณต ํ์ผ | prob.py (์ํธํ ์ฝ๋), output.txt (M, rand) |
| ์๋ฒ | ์์ โ ์์ ์คํ๋ผ์ธ |
| ํต์ฌ ์ทจ์ฝ์ | ์ ๊ณฑ ๋๋จน์ PRNG์ ๋ชจ๋๋ฌ๊ฐ ํฉ์ฑ์๊ฐ ์๋๋ผ ์์ ํ๋ |
| ์ฌ์ฉ ๋๊ตฌ | Python(pycryptodome), OpenSSL, SageMath |
ํ๋ฆ์ ํ ์ค๋ก ์ค์ด๋ฉด ์ด๋ ๋ค. ํ๋๊ทธ๋ฅผ ์ ์๋ก ๋ฐ๊ฟ ์๋๋ก ๋ฃ๊ณ ์ ๊ณฑ์ ๋ ๋ฒ ๋๋ฆฐ ๊ฐ์ด rand๋ก ๊ณต๊ฐ๋๋, ์ ๊ณฑ๊ทผ์ ๋ ๋ฒ ๊ฑฐ์ฌ๋ฌ ์ฌ๋ผ๊ฐ๋ฉด ์๋๊ฐ ๋์จ๋ค. ๊ทธ ์ ๊ณฑ๊ทผ์ด ๊ณ์ฐ ๊ฐ๋ฅํ ์ด์ ๊ฐ ์ด ๋ฌธ์ ์ ์ ๋ถ๋ค.
๐ฌ ์ ์ฐฐ โ ๋ฐ์ ๊ฑด ์ฝ๋ ํ ์ฅ๊ณผ ์ซ์ ๋ ๊ฐ
์์ถ์ ํ๋ฉด ํ์ผ์ด ๋๋ฟ์ด๋ค. ์คํ ํ์ผ๋, ์๋ฒ ์ฃผ์๋ ์๋ค.
ls -l extracted && file extracted/*
์ํธํ ์ฝ๋๋ถํฐ ๋ณธ๋ค.
cat extracted/prob.py
๋ด์ฉ์ ์ด๊ฒ ์ ๋ถ๋ค.
from Crypto.Util.number import *
flag = bytes_to_long(b'DH{?????????????????????????????????????????????}')
class PRNG():
def __init__(self, seed):
self.seed = seed
self.M = getStrongPrime(512)
def next(self):
self.seed = self.seed ** 2 % self.M
return self.seed
prng = PRNG(flag)
prng.next()
print(f'M = {prng.M}')
print(f'rand = {prng.next()}')์ฝ์ ๊ณณ์ ์ธ ๊ตฐ๋ฐ๋ค.
-
PRNG(flag)โ ์๋๊ฐ ๋์๊ฐ ์๋๋ผ ํ๋๊ทธ ์์ฒด๋ค. ๋ณต์ ๋์์ด ๊ณง ๋ด๋ถ ์ํ๋ค. -
next()โ ์ํ ์ ์ด๊ฐseed = seedยฒ mod Mํ๋๋ฟ์ด๋ค. ์ถ๋ ฅ ๊ฐ๊ณต๋, ๋นํธ ์๋ฅด๊ธฐ๋ ์๋ค. ์ํ๋ฅผ ๊ทธ๋๋ก ๋ฑ๋๋ค. -
next()ํธ์ถ์ด ๋ ๋ฒ โ ํ ๋ฒ์ ๋ฐํ๊ฐ์ ๋ฒ๋ฆฌ๊ณ , ๋ ๋ฒ์งธ ๊ฒ๋ง ์ถ๋ ฅํ๋ค. ๊ทธ๋์ ๊ณต๊ฐ๋ ๊ฐ์flagยฒ๊ฐ ์๋๋ผflagโด mod M์ด๋ค. ์ด ํ ์ค์ ํ๋ ค ์ฝ์ผ๋ฉด ๋ค์์ ์กฐ์ฉํ ํ๋ฆฐ๋ค.
์ซ์ ์ชฝ๋ ํ์ธํ๋ค.
cat extracted/output.txt
์ ๋ฆฌํ๋ฉด ์ฐ๋ฆฌ๊ฐ ์๋ ๊ฒ์ M, rand = flagโด mod M ๋ ๊ฐ๊ณ , ๊ตฌํ ๊ฒ์ flag๋ค.
๐งฉ ๋ฐฐ๊ฒฝ โ ์ ๊ณฑ ๋๋จน์ PRNG๋ ์ ์์ ํ๋ค๊ณ ํ๋
x โ xยฒ mod N ํํ์ ๋์ ์์ฑ๊ธฐ๋ Blum Blum Shub(BBS)๋ผ๋ ์ด๋ฆ์ผ๋ก ์ ์๋ ค์ ธ ์๋ค. ์ํ๋ฅผ ์ ๊ณฑํด์ ๋ค์ ์ํ๋ก ์ฐ๊ณ , ๋ณดํต์ ์ตํ์ ๋นํธ ๋ช ๊ฐ๋ง ์ถ๋ ฅ์ผ๋ก ํ๋ฆฐ๋ค.
์ด ๊ตฌ์กฐ์ ์์ ์ฑ์ ์ ์ ์ผ๋ก ๋ชจ๋๋ฌ N์ด ๋ ์์์ ๊ณฑ์ด๋ผ๋ ๋ฐ์ ๋์จ๋ค. ํฉ์ฑ์ ์์์ ์ ๊ณฑ๊ทผ์ ๊ตฌํ๋ ๋ฌธ์ ๋ N์ ์ธ์๋ถํดํ๋ ๊ฒ๊ณผ ๊ฐ์ ๋์ด๋๋ผ, ์ธ์๋ฅผ ๋ชจ๋ฅด๋ฉด ํ ์คํ
๋ ๋๋๋ฆด ์ ์๋ค. ๋ผ๋น(Rabin) ์ํธ๊ฐ "์ธ์๋ถํด๋งํผ ์ด๋ ต๋ค"๊ณ ๋งํ ๋์ ๊ทธ ์ฑ์ง์ด๋ค.
๋ฐ๋๋ก ๋ชจ๋๋ฌ๊ฐ ์์ ํ๋๋ฉด ์ด์ผ๊ธฐ๊ฐ ๋๋๋ค. ์์ p์์๋ ์ด๋ค ์๊ฐ ์ ๊ณฑ์์ธ์ง ์ค์ผ๋ฌ ํ์ ๋ฒ์ผ๋ก ์ฆ์ ์ ์ ์๊ณ , ์ ๊ณฑ๊ทผ๋ TonelliโShanks ์๊ณ ๋ฆฌ์ฆ์ผ๋ก ๋คํญ์๊ฐ์ ๋์จ๋ค. ๋๋จน์์ ๋ช ๋ฒ ๋๋ ธ๋ ๊ทธ ํ์๋งํผ ๊ฑฐ์ฌ๋ฌ ์ฌ๋ผ๊ฐ๋ฉด ๋๋ค.
์ด ๋ฌธ์ ์ M์ getStrongPrime(512)๋ก ๋ง๋ค์ด์ก๋ค. ์ด๋ฆ ๊ทธ๋๋ก "๊ฐํ ์์"์ง, ๋ ์์์ ๊ณฑ์ด ์๋๋ค.

๐ฃ ํต์ฌ โ ์์๋ฉด ์ ๊ณฑ๊ทผ์ด ๊ฑฐ๋ญ์ ๊ณฑ ํ ๋ฒ
๋จผ์ M์ด ์ ๋ง ์์์ธ์ง๋ถํฐ ํ์ธํ๋ค. ์ฝ๋๊ฐ getStrongPrime์ ๋ถ๋ฅธ๋ค๊ณ ํด์ ๋ฐฐํฌ๋ ์ซ์๊ฐ ๊ทธ๋ ๋ค๋ ๋ณด์ฅ์ ์์ผ๋, ๋ฐ์ ๊ฐ์ ์ง์ ํ์ ํ๋ค. OpenSSL์ ์์ ํ์ ๊ธฐ๊ฐ ๋ถ์ด ์์ด ๊ทธ๋๋ก ์ธ ์ ์๋ค.
openssl prime $(grep -oP "(?<=M = )\d+" extracted/output.txt)
์์์ธ ๊ฑธ ํ์ธํ์ผ๋ฉด ๋ค์์ M mod 4๋ค. ์ด ๊ฐ์ด 3์ด๋ฉด TonelliโShanks๋ฅผ ๋๋ฆด ํ์๋ ์๋ค. ์ด์ฐจ์์ฌ a์ ์ ๊ณฑ๊ทผ์ด ๊ฑฐ๋ญ์ ๊ณฑ ํ ๋ฒ์ผ๋ก ๋์ค๊ธฐ ๋๋ฌธ์ด๋ค.
r = a^((M+1)/4) mod M
rยฒ = a^((M+1)/2) = a ยท a^((M-1)/2) = a ยท 1 = a (a ๊ฐ ์ด์ฐจ์์ฌ์ผ ๋)๋ง์ง๋ง ๋ฑ์์ด ์ฑ๋ฆฝํ๋ ๊ฑด a๊ฐ ์ด์ฐจ์์ฌ๋ผ ์ค์ผ๋ฌ ํ์ ๋ฒ์์ a^((M-1)/2) โก 1์ด๊ธฐ ๋๋ฌธ์ด๋ค. ์ค์ ๋ก ์ด ๋ฌธ์ ์ M์ M mod 4 = 3์ด๊ณ , rand๋ ์ด์ฐจ์์ฌ๋ค.
์ฌ๊ธฐ์ ํ ๋ฒ ๋ ์๊ฐํ ๊ฒ ์๋ค. ์ ๊ณฑ๊ทผ์ ํญ์ ๋ ๊ฐ(r๊ณผ M-r)๋ผ, ๋ ๋จ๊ณ๋ฅผ ๊ฑฐ์น๋ฉด ํ๋ณด๊ฐ ๋ค ๊ฐ๋ก ๋ถ์ด๋ ๊ฒ์ฒ๋ผ ๋ณด์ธ๋ค. ๊ทธ๋ฐ๋ฐ ์ค์ ๋ก๋ ๋ ๊ฐ๋ง ๋์จ๋ค.
M mod 4 = 3์ด๋ฉด -1์ด ๋น์์ฌ๋ค. ๊ทธ๋์ ๋ถํธ๊ฐ ๋ค๋ฅธ ๋ ๊ทผ s์ M-s ์ค ์ ํํ ํ์ชฝ๋ง ๋ค์ ์ด์ฐจ์์ฌ๊ฐ ๋๊ณ , ๋๋จธ์ง ํ์ชฝ์ ์ ๊ณฑ๊ทผ ์์ฒด๊ฐ ์กด์ฌํ์ง ์๋๋ค. ๊ฐ์ง ํ๋๊ฐ ๊ทธ ์๋ฆฌ์์ ๋๊ธด๋ค.

์ด์๋จ์ ๋ ํ๋ณด๋ฅผ ๊ฐ๋ฅด๋ ๊ฒ๋ ๊ฐ๋จํ๋ค. ํ๋๊ทธ๋ 49๋ฐ์ดํธ, ์ฆ 392๋นํธ๋ผ 512๋นํธ ๋ชจ๋๋ฌ๋ณด๋ค ํ์ฐธ ์๋ค. ๋๋จธ์ง ์ฐ์ฐ์ผ๋ก ์๋ ค๋๊ฐ ์ ๋ณด๊ฐ ์๋ค๋ ๋ป์ด๊ณ , ๋์์ ํ๋ณด ์ค ํ๋๋ง 391๋นํธ๋ผ๋ ๋ป์ด๊ธฐ๋ ํ๋ค. ๋๋จธ์ง ํ๋๋ 512๋นํธ๋ก ๊ธธ์ด๋ถํฐ ์ด๊ธ๋๋ค.
๐ ์ฝ์ง โ next()๋ฅผ ๋ช ๋ฒ ์
๋
โถ๐ ์ฝ์ง โ ์ ๊ณฑ๊ทผ์ ํ ๋ฒ๋ง ์ทจํ๋ฉด ๋ญ๊ฐ ๋์ค๋
prng.next()๊ฐ ์ถ๋ ฅ ์์ด ํธ์ถ๋๋ ์ค์ ํ๋ฉด์ ์กด์ฌ๊ฐ์ด ๊ฑฐ์ ์๋ค. rand = flagยฒ mod M์ด๋ผ๊ณ ์ฝ์ด ๋ฒ๋ฆฌ๋ฉด ์ ๊ณฑ๊ทผ์ ํ ๋ฒ๋ง ์ทจํ๊ฒ ๋๋๋ฐ, ๊ทธ๋ ์์ ๋ญ๊ฐ ๋จ๋์ง ์ค์ ๋ก ์ฐ์ด ๋ดค๋ค. ๋ค์ผ๋ก "๊ฐ์ง ํ๋๊ฐ ๋๊ธด๋ค"๋ ์์ ์ฃผ์ฅ๋ ๊ฐ์ด ํ์ธํ๋ค.
#!/usr/bin/env python3
"""์ ๊ณฑ๊ทผ์ ํ ๋ฒ๋ง ์ทจํ๋ฉด ๋ฌด์์ด ๋์ค๋ โ next() ํธ์ถ ํ์๋ฅผ ์ธ๋ ์ด์
prob.py ๋ print ํ๊ธฐ ์ ์ prng.next() ๋ฅผ ํ ๋ฒ ๋ฒ๋ฆฐ๋ค. ๊ทธ ํ ์ค์ ๋ชป ๋ณด๊ณ
rand = flag^2 ๋ผ๊ณ ์ฝ์ผ๋ฉด ์ ๊ณฑ๊ทผ์ ํ ๋ฒ๋ง ์ทจํ๊ฒ ๋๋๋ฐ, ๊ทธ๋ ์์ ๋จ๋ ๊ฐ์ด
๋ฌด์์ธ์ง ์ค์ ๋ก ์ฐ์ด ๋ณธ๋ค. ๋ค์ผ๋ก {s, M-s} ์ค ํ๋๋ง ๋ค์ ์ ๊ณฑ๊ทผ์ ๊ฐ์ง๋ค๋
๊ฒ(= ํ๋ณด๊ฐ ๋ ๋ฐฐ๋ก ๋์ง ์๋ ์ด์ )๋ ๊ฐ์ด ํ์ธํ๋ค.
"""
import re
import pathlib
from Crypto.Util.number import long_to_bytes, bytes_to_long
HERE = pathlib.Path(__file__).resolve().parent
FLAG = b"DH{M3rry_ChrI5tM4s_And_4_h4pPy_N3w_y34R_3verY0n3}"
text = (HERE / "extracted" / "output.txt").read_text()
M = int(re.search(r"M\s*=\s*(\d+)", text).group(1))
rand = int(re.search(r"rand\s*=\s*(\d+)", text).group(1))
s = pow(rand, (M + 1) // 4, M) # M % 4 == 3 ์ด๋ผ ์ ๊ณฑ๊ทผ์ด ๊ฑฐ๋ญ์ ๊ณฑ ํ ๋ฒ
for label, v in (("s ", s), ("M - s ", M - s)):
raw = long_to_bytes(v)
qr = pow(v, (M - 1) // 2, M) == 1
print(f"[{label}] {v.bit_length()}๋นํธ ์ด์ฐจ์์ฌ={qr} "
f"์ถ๋ ฅ๊ฐ๋ฅ={all(32 <= c < 127 for c in raw)} ์16B={raw[:16].hex()}")
print()
print(f"[ํ์ธ] s == flag^2 mod M ? {s == pow(bytes_to_long(FLAG), 2, M)}")
print(" ํ ๋ฒ๋ง ์ทจํ ์ ๊ณฑ๊ทผ์ flag ๊ฐ ์๋๋ผ ๋ฒ๋ ค์ง ์ฒซ next() ์ ๊ฒฐ๊ณผ๋ค")python3 wrong_once.py
ํ ๋ฒ๋ง ์ทจํ ์ ๊ณฑ๊ทผ์ 512๋นํธ์ง๋ฆฌ ์๋ฌด ์๋ฏธ ์๋ ๋ฐ์ดํธ์ด์ด๊ณ , ๋ง์ง๋ง ์ค์ด ํ์ธํด ์ฃผ๋ฏ ๊ทธ ๊ฐ์ ์ ํํ flagยฒ mod M โ ์ฝ๋๊ฐ ๋ฒ๋ฆฐ ์ฒซ next()์ ๊ฒฐ๊ณผ๋ค. ํ๋๊ทธ ํ์์ด ์๋๋ผ๊ณ "๋ณตํธ๊ฐ ํ๋ ธ๋" ์์ฌํ๋ฉฐ ์๊ณ ๋ฆฌ์ฆ์ ๋ค์ ๋ฏ์ ๊ฒ ์๋๋ผ, ๊ทธ๋ฅ ํ ๋ฒ ๋ ์ ๊ณฑ๊ทผ์ ์ทจํ๋ฉด ๋๋ ์ํฉ์ด์๋ค.
๊ฐ์ ์ถ๋ ฅ์์ M - s๊ฐ ๋น์์ฌ(์ด์ฐจ์์ฌ=False)๋ก ์ฐํ๋ ๊ฒ๋ ๋ณด์ธ๋ค. ์ด ๊ฐ์ง์์๋ ๋ค์ ์ ๊ณฑ๊ทผ์ ๊ตฌํ ์ ์์ผ๋ ํ๋ณด๊ฐ ๋ค ๊ฐ๋ก ๋์ง ์๋๋ค.
๐ฏ ์ต์คํ๋ก์ โ ๋ค์ ๊ณฑ๊ทผ ๋ ๊ฐ ์ค ์ฝํ๋ ์ชฝ
์ฌ๊ธฐ๊น์ง ์ค๋ฉด ์ฝ๋๋ ์งง๋ค. output.txt๋ฅผ ํ์ฑํ๊ณ , ์ ๊ณฑ๊ทผ์ ๋ ๋ฒ ์ทจํ๊ณ , ๋์จ ํ๋ณด๋ฅผ ๋ฐ์ดํธ๋ก ๋๋๋ ค DH{๋ก ์์ํ๋ ๊ฒ์ ๊ณ ๋ฅด๋ฉด ๋์ด๋ค.
ํ๋ณด ๊ฒ์ฆ์ ๋์ผ๋ก ํ์ง ์๊ณ ์คํฌ๋ฆฝํธ๊ฐ ์ง์ ํ๋ค. ๊ฐ ํ๋ณด๋ฅผ ๋ฌธ์ ์ ์ํธํ ๊ทธ๋๋ก ๋ค์ ๊ณฑํด์ rand์ ๊ฐ์์ง ๋์กฐํ๋ ์ค(pow(t, 4, M) == rand)์ด ๊ทธ ์ญํ ์ด๋ค. ํ๋๊ทธ ํ์๋ง ๋ณด๊ณ ๊ณ ๋ฅด๋ฉด ํ์์ด ์ฐ์ฐํ ๋ง๋ ๊ฐ์ ์์ ์ฌ์ง๊ฐ ๋จ๋๋ค.
#!/usr/bin/env python3
"""the present (DreamHack, Silver 4, crypto) โ flag ๋ณต๊ตฌ
prob.py ๋ flag ๋ฅผ ์ ์๋ก ๋ง๋ค์ด PRNG ์ seed ๋ก ๋ฃ๊ณ next() ๋ฅผ ๋ ๋ฒ ๋ถ๋ฅธ๋ค.
next() ๊ฐ seed <- seed^2 mod M ์ด๋ฏ๋ก ์ถ๋ ฅ๋ rand ๋ flag^4 mod M ์ด๋ค.
M ์ด getStrongPrime(512) ๋ก ๋ง๋ '์์'๋ผ์ 4์ ๊ณฑ๊ทผ์ ๊ณง๋ฐ๋ก ๊ณ์ฐ๋๋ค.
M % 4 == 3 ์ด๋ฉด QR x ์ ์ ๊ณฑ๊ทผ์ด x^((M+1)/4) mod M ๋ก ํ ๋ฒ์ ๋์ค๊ณ ,
-1 ์ด ๋น์์ฌ๋ผ {s, M-s} ์ค ์ ํํ ํ๋๋ง ๋ค์ QR ์ด ๋๋ค. ๊ทธ ํ๋๋ง ๋ฐ๋ผ๊ฐ๋ฉด
4์ ๊ณฑ๊ทผ ํ๋ณด๋ ๋ ๊ฐ(t, M-t)๋ฟ์ด๋ผ ๋ธ๋ฃจํธํฌ์ค๊ฐ ํ์ ์๋ค.
"""
import re
import pathlib
from Crypto.Util.number import long_to_bytes
HERE = pathlib.Path(__file__).resolve().parent
OUT = HERE / "extracted" / "output.txt"
def load_output(path):
"""output.txt ์์ M ๊ณผ rand ๋ฅผ ์ฝ๋๋ค."""
text = path.read_text()
M = int(re.search(r"M\s*=\s*(\d+)", text).group(1))
rand = int(re.search(r"rand\s*=\s*(\d+)", text).group(1))
return M, rand
def is_qr(x, p):
"""์ค์ผ๋ฌ ํ์ ๋ฒ: x ๊ฐ p ์ ์ด์ฐจ์์ฌ์ธ๊ฐ."""
return pow(x, (p - 1) // 2, p) == 1
def sqrt_mod_p3mod4(x, p):
"""p % 4 == 3 ์ธ ์์์์ x ์ ์ ๊ณฑ๊ทผ ๋ ๊ฐ. QR ์ด ์๋๋ฉด ๋น ๋ฆฌ์คํธ."""
if x % p == 0:
return [0]
if not is_qr(x, p):
return []
r = pow(x, (p + 1) // 4, p)
return [r, p - r]
def fourth_roots(x, p):
"""x ์ 4์ ๊ณฑ๊ทผ ์ ๋ถ (p % 4 == 3 ๊ฐ์ )."""
roots = []
for s in sqrt_mod_p3mod4(x, p):
roots += sqrt_mod_p3mod4(s, p)
return sorted(set(roots))
def main():
M, rand = load_output(OUT)
assert M % 4 == 3, "์ด ์คํฌ๋ฆฝํธ๋ M mod 4 == 3 ์ ์ฉ ์ง๋ฆ๊ธธ์ ์ด๋ค"
print(f"[*] M ({M.bit_length()}๋นํธ, M mod 4 = {M % 4}) = {M}")
print(f"[*] rand ({rand.bit_length()}๋นํธ) = {rand}")
print(f"[*] rand ๋ ์ด์ฐจ์์ฌ์ธ๊ฐ? {is_qr(rand, M)}")
roots = fourth_roots(rand, M)
print(f"[*] 4์ ๊ณฑ๊ทผ ํ๋ณด {len(roots)}๊ฐ")
flag = None
for i, t in enumerate(roots):
raw = long_to_bytes(t)
ok = pow(t, 4, M) == rand # ๋ฌธ์ ์ ์ํธํ๋ฅผ ๊ทธ๋๋ก ๋๋๋ ค ์์ฒด ๊ฒ์ฆ
printable = all(32 <= c < 127 for c in raw)
head = raw[:16] if printable else raw[:16].hex()
print(f" [{i}] {t.bit_length():3d}๋นํธ ๊ฒ์ฐ={ok} ์ถ๋ ฅ๊ฐ๋ฅ={printable} ์๋ถ๋ถ={head}")
if ok and printable and raw.startswith(b"DH{") and raw.endswith(b"}"):
flag = raw.decode()
if flag:
print(f"\n[+] FLAG = {flag}")
else:
print("\n[-] ์กฐ๊ฑด์ ๋ง์กฑํ๋ ํ๋ณด๊ฐ ์๋ค")
if __name__ == "__main__":
main()python3 solve.py
ํ๋ณด๋ ์์๋๋ก ๋ ๊ฐ๋ค. ๋ ๋ค ๋ค์ ๊ณฑํ๋ฉด rand๊ฐ ๋์ง๋ง, 391๋นํธ์ง๋ฆฌ๋ง ์ ๋ถ ์ถ๋ ฅ ๊ฐ๋ฅํ ๋ฌธ์๋ก ๋จ์ด์ง๋ค. 512๋นํธ ์ชฝ์ e5fba265โฆ๋ก ์์ํ๋ ๋ฐ์ดํธ์ด์ด๋ผ ๋ณผ ๊ฒ๋ ์๋ค.
ํ๋๊ทธ๋ DH{M3rry_ChrI5tM4s_And_4_h4pPy_N3w_y34R_3verY0n3}. Christmas CTF ์ถ์ ์์ด๋ผ๋ ๋ฌธ์ ํ์ด์ง ์๋ด์ ๋ด์ฉ์ด ๋ง์๋จ์ด์ง๋ค.
๐งช ๊ต์ฐจ ๊ฒ์ฆ โ SageMath๋ก ํ ๋ฒ ๋
์ง์ ์ง ์ ๊ณฑ๊ทผ ๋ฃจํด์ ์ง๋ฆ๊ธธ(M mod 4 = 3)์ ๊ธฐ๋๊ณ ์์ด์, ๊ทธ ๊ฐ์ ์ด ํ๋ ธ๋ค๋ฉด ์กฐ์ฉํ ์๋ฑํ ๊ฐ์ ๋ฑ๋๋ค. ๊ทธ๋์ ๊ณ์ฐ ๋์ ์์คํ
์ผ๋ก ๊ฐ์ ๊ฐ์ ๋
๋ฆฝ์ ์ผ๋ก ๊ตฌํด ๋์กฐํ๋ค. Sage์ nth_root(4, all=True)๋ ์ง๋ฆ๊ธธ์ ์ฐ์ง ์๊ณ ์ผ๋ฐ์ ์ธ ๋ฐฉ๋ฒ์ผ๋ก ๋ค์ ๊ณฑ๊ทผ์ ์ ๋ถ ์ฐพ์ ์ค๋ค.
# verify.sage โ SageMath ๋ก ๋
๋ฆฝ ๊ฒ์ฆ
# 1) M ์ด ์ ๋ง ์์์ธ๊ฐ (์์๋ฉด ์ ๊ณฑ๊ทผ์ด ๋คํญ์๊ฐ์ ๊ณ์ฐ๋๋ค)
# 2) rand ์ 4์ ๊ณฑ๊ทผ์ Sage ์ nth_root ๋ก ์ ๋ถ ๊ตฌํด solve.py ๊ฒฐ๊ณผ์ ๋์กฐ
M = 12045184283108733282877246918018456795629932264538806786957088609817236359725183272225617590814403324378457302095025664842376149624271113548645334599894051
rand = 321609188014782817530081099017386870899897457530475334351599692725892773281779472409503390776505529932157130216692885548245652532540341302761980076422023
print("is_prime(M) =", is_prime(M))
print("M.nbits() =", Integer(M).nbits())
print("M mod 4 =", M % 4)
print("rand is QR =", kronecker(rand, M) == 1)
roots = Integers(M)(rand).nth_root(4, all=True)
print("๋ค์ ๊ณฑ๊ทผ ๊ฐ์ =", len(roots))
for r in sorted(Integer(x) for x in roots):
raw = int(r).to_bytes((r.nbits() + 7) // 8, "big")
ok = power_mod(int(r), 4, M) == rand
tag = raw.decode() if all(32 <= c < 127 for c in raw) else raw.hex()
print(f" {r.nbits():3d}๋นํธ ๋ค์ ๊ณฑ_๊ฒ์ฐ:{ok} -> {tag}")sage verify.sage
is_prime(M) = True, M mod 4 = 3, ๋ค์ ๊ณฑ๊ทผ ๊ฐ์ 2๊ฐ, ๊ทธ๋ฆฌ๊ณ ๊ทธ์ค ํ๋๊ฐ ๊ฐ์ ํ๋๊ทธ. ์์ผ๋ก ์ง ์ง๋ฆ๊ธธ๊ณผ Sage์ ์ผ๋ฐ ๋ฃจํด์ด ๊ฐ์ ๋ต์ ๋๋ฌํ๋ค.
์ฐธ๊ณ ๋ก kronecker(rand, M) == 1์ ์ค์ผ๋ฌ ํ์ ๋ฒ๊ณผ ๊ฐ์ ์๊ธฐ๋ค. Sage์์๋ ๋ฅด์ฅ๋๋ฅด ๊ธฐํธ๋ฅผ ๋ฐ๋ก ๋ถ๋ฅผ ์ ์์ด ๊ฑฐ๋ญ์ ๊ณฑ์ ์ง์ ๋๋ฆด ํ์๊ฐ ์๋ค.
๐ ์ฌํ โ ํ์ผ ๋ ๊ฐ๋ฉด ๋
์ด ๋ฌธ์ ๋ ์๋ฒ๋ VM๋ ํ์ ์์ด์, ๋ฐฐํฌ๋ณธ๊ณผ ์คํฌ๋ฆฝํธ๋ง ์์ผ๋ฉด ์ธ์ ๋ ๋ค์ ๋๋ฆด ์ ์๋ค. ์ฌํ ์คํฌ๋ฆฝํธ๋ ํ๋๊ทธ๋ฅผ ๋์ผ๋ก ๋์กฐํ๊ฒ ๋์ง ์๊ณ ๋ฌธ์ .json์ ๊ธฐ๋ก๋ ์ ๋ต๊ณผ ์ง์ ๋น๊ตํ๋ค.
#!/usr/bin/env bash
# the present (DreamHack Silver 4, crypto) โ ํ ๋ฐฉ ์ฌํ.
# extracted/output.txt ์ (M, rand) ๋ง์ผ๋ก flag ๋ฅผ ๋ณต์ํ๊ณ ์ ๋ต๊ณผ ๋์กฐํ๋ค.
# ์ธ๋ถ ์๋ฒยทVM ์ด ํ์ ์๋ ์์ ์คํ๋ผ์ธ ๋ฌธ์ ๋ผ ์ด ์คํฌ๋ฆฝํธ๋ง์ผ๋ก ๋๋๋ค.
set -eu
cd "$(dirname "$(readlink -f "$0")")"
EXPECT=$(python3 -c "import json;print(json.load(open('๋ฌธ์ .json'))['flag'])" 2>/dev/null || echo '')
python3 -c "import Crypto" 2>/dev/null || { echo "pycryptodome ์ด ํ์ํ๋ค: pip install pycryptodome"; exit 1; }
OUT=$(timeout 120 python3 solve.py 2>&1) || true
echo "$OUT" | tail -8
FLAG=$(printf '%s' "$OUT" | grep -aoE 'DH\{[^}]+\}' | head -1)
if [ -n "$FLAG" ] && { [ -z "$EXPECT" ] || [ "$FLAG" = "$EXPECT" ]; }; then
echo; echo "โ
PASS $FLAG"; exit 0
fi
echo; echo "โ FAIL (์ป์ ๊ฐ: '${FLAG:-์์}' / ๊ธฐ๋: '$EXPECT')"; exit 1./reproduce.sh
๋ก์ปฌ ํ๊ฒฝ: Ubuntu 25.10, Python 3.13.7(pycryptodome 3.23.0), OpenSSL 3.5.3, SageMath 10.9. Sage๋ nth_root ๊ต์ฐจ๊ฒ์ฆ์๋ง ์ฐ์ด๋ ์์ด๋ solve.py๋ ๊ทธ๋๋ก ๋์๊ฐ๋ค.
๐ ๊ฒฐ๋ก
๋ชจ๋๋ฌ ํ๋๋ฅผ ์๋ชป ๊ณ ๋ฅด๋ฉด ๊ตฌ์กฐ ์ ์ฒด๊ฐ ๋ฌด๋์ง๋ค
x โ xยฒ mod N ๋๋จน์์ ๊ทธ ์์ฒด๋ก๋ ๋ฉ์ฉกํ ์ค๊ณ๋ค. ๋ฌด๋์ง ๊ฑด N์ ๋ ์์์ ๊ณฑ์ด ์๋๋ผ ์์ ํ๋๋ก ์ก์ ์ง์ ์ด๊ณ , ๋๋จธ์ง ์ฝ๋๋ ์๋ฌด ์๋ชป์ด ์๋ค. ์ํธ ๊ตฌํ์ ๋ณผ ๋ ์๊ณ ๋ฆฌ์ฆ ์ด๋ฆ๋ณด๋ค ํ๋ผ๋ฏธํฐ๊ฐ ์ด๋ป๊ฒ ๋ง๋ค์ด์ง๋์ง๋ฅผ ๋จผ์ ๋ณด๋ ์ด์ ๋ค.
๋๋๋ฆด ํ์๋ ์ถ๋ ฅ์ด ์๋๋ผ ํธ์ถ ํ์๊ฐ ์ ํ๋ค
๊ณต๊ฐ๋ ๊ฐ์ ํ๋์์ง๋ง next()๋ ๋ ๋ฒ ๋ถ๋ ธ๋ค. ์ถ๋ ฅ ๊ฐ์๋ง ์ธ๊ณ ์ ๊ณฑ๊ทผ์ ํ ๋ฒ๋ง ์ทจํ๋ฉด ๋ฒ๋ ค์ง ์ค๊ฐ ์ํ๋ฅผ ์์ ์ฅ๊ณ "๋ณตํธ๊ฐ ์ ๋๋ค"๋ฉฐ ํค๋งค๊ฒ ๋๋ค. ์ํ ๊ธฐ๊ณ๋ฅผ ๋๋๋ฆด ๋๋ ํ๋ฉด์ ์ฐํ ๊ฐ์ด ๋ช ๋ฒ์งธ ์ํ์ธ์ง๋ถํฐ ์ธ๋ ๊ฒ ๋ง๋ค.
ํ๋ณด๊ฐ ์ฌ๋ฟ์ด๋ฉด ๊ฒ์ฐ์ผ๋ก ์ขํ๋ค
๋ค์ ๊ณฑ๊ทผ์ ์๋ฆฌ์ ์ฌ๋ฌ ๊ฐ์ผ ์ ์๋ค. ํ๋๊ทธ ํ์์ผ๋ก ๊ณ ๋ฅด๋ ๊ฑด ํธํ์ง๋ง, ๊ทธ ์ ์ ํ๋ณด๋ฅผ ๋ฌธ์ ์ ์ํธํ ๊ทธ๋๋ก ๋ค์ ๋๋ ค ์๋ณธ๊ณผ ๋ง๋์ง ํ์ธํ๋ ๊ฒ ๋จผ์ ๋ค. ์ด ๋ฌธ์ ์์๋ pow(t, 4, M) == rand ํ ์ค์ด ๊ทธ ์ญํ ์ ํ๊ณ , ๊ธธ์ด(391๋นํธ ๋ 512๋นํธ)๊ฐ ๋ณด์กฐ ์งํ๊ฐ ๋๋ค.
๋ฐฉ์ด ์ชฝ์์ ์ ๋ฆฌํ๋ฉด
BBS ๊ณ์ด ์์ฑ๊ธฐ๋ฅผ ์ธ ๊ฑฐ๋ผ๋ฉด ๋ชจ๋๋ฌ๋ ๋ฐ๋์ ๋ ๊ฐ์ ํฐ ์์ ๊ณฑ(๊ฐ ์์๋ 4๋ก ๋๋ ๋๋จธ์ง๊ฐ 3์ธ Blum ์์)์ด์ด์ผ ํ๊ณ , ์ธ์๋ ํ๊ธฐํด์ผ ํ๋ค. ์ํ๋ฅผ ๊ทธ๋๋ก ์ถ๋ ฅํ๋ ๊ฒ๋ ๋ฌธ์ ๋ค. ์๋ BBS๋ ์ํ ์ ์ฒด๊ฐ ์๋๋ผ ์ตํ์ ๋นํธ ์์๋ง ํ๋ฆฌ๋๋ฐ, ์ด ์ฝ๋๋ seed๋ฅผ ํต์งธ๋ก ๋ฑ์ด์ ๋๋๋ฆด ์ง์ ์ ๊ทธ๋๋ก ์๋ ค ์ค๋ค.
์๋์ ๋น๋ฐ๊ฐ์ ๋ฃ๋ ๊ฒ ์ญ์ ์ํํ๋ค. ์ฌ๊ธฐ์๋ ํ๋๊ทธ๊ฐ ์๋์๊ธฐ ๋๋ฌธ์ ์ํ ๋ณต์์ด ๊ณง ๋น๋ฐ ๋ณต์์ด ๋๋ค. ๋น๋ฐ์ ์๋๊ฐ ์๋๋ผ ํค๋ก ๋ค๋ฃจ๊ณ , ์์ฑ๊ธฐ ์ํ๋ ๋น๋ฐ๊ณผ ๋ถ๋ฆฌํด์ผ ํ๋ค.
Comments
๋๊ธ
๋๊ธ์ ๋จ๊ธฐ๋ ค๋ฉด ๋ก๊ทธ์ธ์ด ํ์ํด์. (๋ค์ด๋ฒ ยท ๊ตฌ๊ธ ๊ณ์ )
๋๊ธ ๋ถ๋ฌ์ค๋ ์คโฆ