[๐Ÿฅ‰ Bronze 3] ์ฃผ๊ธฐ 16 ๋น„์ œ๋„ค๋ฅด๋ฅผ ์—ด๋ณ„ ๋นˆ๋„๋กœ ๊นจ๋‹ค โ€” DreamHack EVER-igen ํ’€์ด

2026-07-13ยท1๋ถ„ ์ฝ๊ธฐยท

[๐Ÿฅ‰ Bronze 3] ์ฃผ๊ธฐ 16 ๋น„์ œ๋„ค๋ฅด๋ฅผ ์—ด๋ณ„ ๋นˆ๋„๋กœ ๊นจ๋‹ค โ€” DreamHack EVER-igen ํ’€์ด

62์ž ์•ŒํŒŒ๋ฒณ ์œ„์—์„œ 16๊ธ€์ž ํ‚ค๋กœ ๋Œ์•„๊ฐ€๋Š” ๋น„์ œ๋„ค๋ฅด ์•”ํ˜ธ. ํ‚ค๊ณต๊ฐ„์ด 62^16์ด๋ผ ์ „์ˆ˜์กฐ์‚ฌ๋Š” ๋ถˆ๊ฐ€๋Šฅํ•˜์ง€๋งŒ, ๊ฐ™์€ ํ‚ค๋ฅผ ์“ฐ๋Š” ์ž๋ฆฌ๋ผ๋ฆฌ 16๊ฐœ ์—ด๋กœ ๋‚˜๋ˆ„๋ฉด ๊ฐ ์—ด์€ ๋‹จ์ผ ์‹œํ”„ํŠธ ๋ฌธ์ œ๊ฐ€ ๋œ๋‹ค. ์˜์–ด ํ‰๋ฌธ์€ ์†Œ๋ฌธ์ž๊ฐ€ ์••๋„์ ์ด๋ผ, ์—ด๋งˆ๋‹ค ์†Œ๋ฌธ์ž ๋นˆ๋„๋ฅผ ์ตœ๋Œ€๋กœ ๋งŒ๋“œ๋Š” ์‹œํ”„ํŠธ๊ฐ€ ๊ณง ๊ทธ ์ž๋ฆฌ์˜ ํ‚ค๋‹ค.

๋ฌธ์ œ: DreamHack โ€” EVER-igen ๋ถ„๋ฅ˜: Crypto ๋‚œ์ด๋„: ๐Ÿฅ‰ Bronze 3 FLAG: DH{10587bd243d302e95d4b4b0fe331166605b85dd6660e18a305e1fc3db72bf3be}

๋น„์ œ๋„ค๋ฅด ์•”ํ˜ธ๋กœ ๋ฌธ์žฅ์„ ๊ฐ€๋ฆฐ ๋ฌธ์ œ๋‹ค. ํ‚ค๊ฐ€ 16๊ธ€์ž๋ผ ๊ทธ๋ƒฅ ์ „๋ถ€ ์‹œ๋„ํ•  ์ˆ˜๋Š” ์—†์ง€๋งŒ, ๋น„์ œ๋„ค๋ฅด์˜ ๊ตฌ์กฐ์ƒ ๊ฐ™์€ ํ‚ค๊ฐ€ ๊ทœ์น™์ ์œผ๋กœ ๋ฐ˜๋ณต๋œ๋‹ค. ๊ทธ ๋ฐ˜๋ณต์„ ์—ด๋กœ ์ชผ๊ฐœ๋ฉด ์–ด๋ ค์šด ๋‹ค์ค‘์น˜ํ™˜์ด ์‰ฌ์šด ๋‹จ์ผ์น˜ํ™˜ 16๊ฐœ๋กœ ๋ถ„ํ•ด๋œ๋‹ค.

ํ‚ค ์ฃผ๊ธฐ 16์ด๋ผ 16๊ฐœ ์—ด๋กœ ๋‚˜๋ˆ„๋ฉด ๊ฐ ์—ด์€ ๋‹จ์ผ ์‹œํ”„ํŠธ, ์†Œ๋ฌธ์ž ๋นˆ๋„๊ฐ€ ์ตœ๋Œ€์ธ ์‹œํ”„ํŠธ๊ฐ€ ๊ทธ ์ž๋ฆฌ ํ‚ค
ํ‚ค ์ฃผ๊ธฐ 16์ด๋ผ 16๊ฐœ ์—ด๋กœ ๋‚˜๋ˆ„๋ฉด ๊ฐ ์—ด์€ ๋‹จ์ผ ์‹œํ”„ํŠธ, ์†Œ๋ฌธ์ž ๋นˆ๋„๊ฐ€ ์ตœ๋Œ€์ธ ์‹œํ”„ํŠธ๊ฐ€ ๊ทธ ์ž๋ฆฌ ํ‚ค

๋ฌธ์ œ ๊ฐœ์š”

ํ•ญ๋ชฉ๋‚ด์šฉ
๋ฌธ์ œ๋ช…EVER-igen
๋‚œ์ด๋„๐Ÿฅ‰ Bronze 3
๋ถ„๋ฅ˜Crypto
์ œ๊ณต ํŒŒ์ผprob.py, output.txt
ํ•ต์‹ฌ ๊ธฐ๋ฒ•์ฃผ๊ธฐ 16 ๋น„์ œ๋„ค๋ฅด๋ฅผ ์—ด๋ณ„ ๋นˆ๋„๋ถ„์„์œผ๋กœ ๋ถ„ํ•ด

output.txt์˜ ์•”ํ˜ธ๋ฌธ๋งŒ์œผ๋กœ ์˜คํ”„๋ผ์ธ์—์„œ ํ’€ ์ˆ˜ ์žˆ๋‹ค.



๐Ÿ”ฌ ์ฝ”๋“œ ์ •์ฐฐ โ€” 62์ž ์•ŒํŒŒ๋ฒณ ๋น„์ œ๋„ค๋ฅด

prob.py์˜ ์•”ํ˜ธ๋Š” ๋Œ€๋ฌธ์žยท์†Œ๋ฌธ์žยท์ˆซ์ž 62์ž๋ฅผ ํ•œ ์•ŒํŒŒ๋ฒณ์œผ๋กœ ์“ฐ๋Š” ๋น„์ œ๋„ค๋ฅด๋‹ค.

words = ascii_uppercase + ascii_lowercase + digits   # 62์ž
 
class Vigenere:
    def shift(self, a, d):
        if a not in words: return a                  # ๊ณต๋ฐฑยท๋ฌธ์žฅ๋ถ€ํ˜ธ๋Š” ๊ทธ๋Œ€๋กœ
        return words[(words.index(a) + d) % len(words)]
 
    def encrypt(self, pt):
        return "".join(self.shift(pt[i], self._key[i % len(self._key)]) for i in range(len(pt)))
 
key = [random.randint(0, len(words)) for _ in range(16)]   # 16๊ธ€์ž ํ‚ค

๊ฐ ๊ธ€์ž๋ฅผ key[i % 16]๋งŒํผ ์‹œํ”„ํŠธํ•œ๋‹ค. words์— ์—†๋Š” ๋ฌธ์ž(๊ณต๋ฐฑ, ๋งˆ์นจํ‘œ ๋“ฑ)๋Š” ์‹œํ”„ํŠธํ•˜์ง€ ์•Š๊ณ  ํ†ต๊ณผ์‹œํ‚ค์ง€๋งŒ, ํ‚ค ์ธ๋ฑ์Šค i๋Š” ์ „์ฒด ์œ„์น˜๋ฅผ ๋”ฐ๋ผ ๊ณ„์† ์ฆ๊ฐ€ํ•œ๋‹ค. ํ‚ค๋Š” 16๊ธ€์ž, ๊ฐ ๊ฐ’์€ 0~62์ด๋ผ ํ‚ค๊ณต๊ฐ„์ด 62^16์ด๋‹ค. ํ†ต์งธ๋กœ ์ „์ˆ˜์กฐ์‚ฌํ•˜๊ธฐ์—” ๋„ˆ๋ฌด ํฌ๋‹ค.


๐Ÿ’ฃ ํ•ต์‹ฌ โ€” ์ฃผ๊ธฐ๋ฅผ ์—ด๋กœ ์ชผ๊ฐ ๋‹ค

๋น„์ œ๋„ค๋ฅด์˜ ์•ฝ์ ์€ ํ‚ค๊ฐ€ ์ฃผ๊ธฐ์ ์œผ๋กœ ๋ฐ˜๋ณต๋œ๋‹ค๋Š” ๊ฒƒ์ด๋‹ค. ํ‚ค ๊ธธ์ด๊ฐ€ 16์ด๋‹ˆ, ์ „์ฒด ๋ฌธ์ž์—ด์—์„œ ์œ„์น˜ i๊ฐ€ i mod 16์œผ๋กœ ๊ฐ™์€ ์ž๋ฆฌ๋“ค์€ ์ „๋ถ€ ๊ฐ™์€ ํ‚ค ๊ฐ’์œผ๋กœ ์‹œํ”„ํŠธ๋๋‹ค.

๊ทธ๋ž˜์„œ ์•”ํ˜ธ๋ฌธ์„ i mod 16 ๊ธฐ์ค€์œผ๋กœ 16๊ฐœ ์—ด๋กœ ๋‚˜๋ˆ„๋ฉด, ๊ฐ ์—ด์€ ๋‹จ ํ•˜๋‚˜์˜ ์‹œํ”„ํŠธ๋งŒ ์ ์šฉ๋œ ๋‹จ์ผ์น˜ํ™˜(์นด์ด์‚ฌ๋ฅด) ์•”ํ˜ธ๊ฐ€ ๋œ๋‹ค. 62๊ฐ€์ง€ ์‹œํ”„ํŠธ๋งŒ ์‹œ๋„ํ•˜๋ฉด ๋˜๋Š” ๋ฌธ์ œ 16๊ฐœ๋กœ ์ชผ๊ฐœ์ง„ ๊ฒƒ์ด๋‹ค.

๋งž๋Š” ์‹œํ”„ํŠธ๋Š” ์–ด๋–ป๊ฒŒ ๊ณ ๋ฅผ๊นŒ. ํ‰๋ฌธ์ด ์˜์–ด ๋ฌธ์žฅ์ด๋ผ๋Š” ์ ์„ ์“ด๋‹ค. ์˜์–ด๋Š” ์†Œ๋ฌธ์ž๊ฐ€ ์••๋„์ ์œผ๋กœ ๋งŽ๋‹ค(e, t, a, o โ€ฆ). ์–ด๋–ค ์‹œํ”„ํŠธ๋กœ ๋ณตํ˜ธํ–ˆ์„ ๋•Œ ๊ทธ ์—ด์˜ ๋ฌธ์ž๋“ค์ด ์†Œ๋ฌธ์ž ์˜์–ด ๊ธ€์ž์— ๋ชฐ๋ฆฌ๋ฉด, ๊ทธ๊ฒŒ ์ •๋‹ต ์‹œํ”„ํŠธ๋‹ค. ์—ด๋งˆ๋‹ค ์†Œ๋ฌธ์ž ๋นˆ๋„ ์ ์ˆ˜๊ฐ€ ์ตœ๋Œ€์ธ ์‹œํ”„ํŠธ๋ฅผ ๊ณ ๋ฅด๋ฉด ํ‚ค ํ•œ ๊ธ€์ž๊ฐ€ ์ •ํ•ด์ง„๋‹ค.



๐ŸŽฏ ํ’€์ด โ€” ์—ด๋ณ„ ๋นˆ๋„๋กœ ํ‚ค 16๊ธ€์ž ๋ณต์›

๊ฐ ์—ด์— ๋Œ€ํ•ด 62๊ฐ€์ง€ ์‹œํ”„ํŠธ๋ฅผ ๋Œ๋ฆฌ๊ณ , ๋ณตํ˜ธ ๊ฒฐ๊ณผ์˜ ์†Œ๋ฌธ์ž ์˜์–ด ๋นˆ๋„ ์ ์ˆ˜๊ฐ€ ๊ฐ€์žฅ ๋†’์€ ์‹œํ”„ํŠธ๋ฅผ ๊ทธ ์ž๋ฆฌ ํ‚ค๋กœ ์‚ผ๋Š”๋‹ค.

# solve.py (ํ•ต์‹ฌ)
ct = open("output.txt").read().split(">",1)[1].strip()
 
def score(chars):                                   # ์˜์–ด๋‹ค์›€ ๋กœ๊ทธ๊ฐ€๋Šฅ๋„
    s = 0.0
    for c in chars:
        if c in ascii_lowercase: s += math.log(freq.get(c, 0.05) / 100 * 0.90)
        elif c in ascii_uppercase: s += math.log(0.02)
        else: s += math.log(0.01)                   # ์ˆซ์ž
    return s
 
key = []
for col in range(16):
    col_chars = [ct[i] for i in range(len(ct)) if i % 16 == col and ct[i] in words]
    best = max(range(62), key=lambda sh: score(
        [words[(words.index(c) - sh) % 62] for c in col_chars]))
    key.append(best)
 
pt = "".join(words[(words.index(c) - key[i % 16]) % 62] if c in words else c
             for i, c in enumerate(ct))
print(f"key: {key}")
print(pt)
solve.py ์‹คํ–‰ ํ™”๋ฉด. ์—ด๋ณ„ ๋นˆ๋„๋ถ„์„์œผ๋กœ 16๊ธ€์ž ํ‚ค 47, 22, 41, 1, 29 โ€ฆ๋ฅผ ๋ณต์›ํ•˜๊ณ , ๋ณตํ˜ธ๋œ ๋น„์ œ๋„ค๋ฅด ์œ ๋ž˜ ์„ค๋ช… ์˜์–ด ๋ฌธ๋‹จ์ด ๊ทธ๋Œ€๋กœ ๋‚˜์˜ค๋ฉฐ ๊ทธ ๋์— DH ๋กœ ์‹œ์ž‘ํ•˜๋Š” ํ”Œ๋ž˜๊ทธ๊ฐ€ ๋ถ™์–ด ์žˆ๋‹ค
solve.py ์‹คํ–‰ ํ™”๋ฉด. ์—ด๋ณ„ ๋นˆ๋„๋ถ„์„์œผ๋กœ 16๊ธ€์ž ํ‚ค 47, 22, 41, 1, 29 โ€ฆ๋ฅผ ๋ณต์›ํ•˜๊ณ , ๋ณตํ˜ธ๋œ ๋น„์ œ๋„ค๋ฅด ์œ ๋ž˜ ์„ค๋ช… ์˜์–ด ๋ฌธ๋‹จ์ด ๊ทธ๋Œ€๋กœ ๋‚˜์˜ค๋ฉฐ ๊ทธ ๋์— DH ๋กœ ์‹œ์ž‘ํ•˜๋Š” ํ”Œ๋ž˜๊ทธ๊ฐ€ ๋ถ™์–ด ์žˆ๋‹ค

๋ณตํ˜ธํ•˜๋ฉด ๋น„์ œ๋„ค๋ฅด ์•”ํ˜ธ์˜ ์œ ๋ž˜๋ฅผ ์„ค๋ช…ํ•˜๋Š” ์˜์–ด ๋ฌธ๋‹จ์ด ๋‚˜์˜ค๊ณ , ๊ทธ ๋์— ํ”Œ๋ž˜๊ทธ๊ฐ€ ๋ถ™์–ด ์žˆ๋‹ค.

... OK, here comes the flag. DH{10587bd243d302e95d4b4b0fe331166605b85dd6660e18a305e1fc3db72bf3be}

FLAG: DH{10587bd243d302e95d4b4b0fe331166605b85dd6660e18a305e1fc3db72bf3be}



๐Ÿ“ ๊ฒฐ๋ก 

์ฃผ๊ธฐ๊ฐ€ ๋“œ๋Ÿฌ๋‚˜๋ฉด ๋น„์ œ๋„ค๋ฅด๋Š” ์นด์ด์‚ฌ๋ฅด ์—ฌ๋Ÿฌ ๊ฐœ๋‹ค.

๋น„์ œ๋„ค๋ฅด๊ฐ€ ๋‹จ์ผ์น˜ํ™˜๋ณด๋‹ค ๊ฐ•ํ•œ ์ด์œ ๋Š” ์ž๋ฆฌ๋งˆ๋‹ค ํ‚ค๊ฐ€ ๋ฐ”๋€Œ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ํ•˜์ง€๋งŒ ํ‚ค๊ฐ€ ๋ฐ˜๋ณต๋˜๋Š” ์ˆœ๊ฐ„, ๊ฐ™์€ ํ‚ค๋ฅผ ์“ฐ๋Š” ์ž๋ฆฌ๋ผ๋ฆฌ ๋ชจ์œผ๋ฉด ๊ฐ ๋ฌถ์Œ์€ ๋‹ค์‹œ ๋‹จ์ผ์น˜ํ™˜์œผ๋กœ ๋ฌด๋„ˆ์ง„๋‹ค. ํ‚ค ๊ธธ์ด๋งŒ ์•Œ๋ฉด(๋˜๋Š” ๊ฐ€์ •ํ•˜๋ฉด) ๋‹ค์ค‘์น˜ํ™˜์ด ๊ทธ๋งŒํผ์˜ ๋‹จ์ˆœ ๋ฌธ์ œ๋กœ ๋ถ„ํ•ด๋œ๋‹ค.

์–ธ์–ด ํ†ต๊ณ„๊ฐ€ ํŒ๋ณ„์ž๋‹ค.

62๊ฐ€์ง€ ํ›„๋ณด ์ค‘ ์ •๋‹ต์„ ๊ณ ๋ฅด๋Š” ๊ฑด ๋นˆ๋„๊ฐ€ ํ•ด ์ค€๋‹ค. ํ‰๋ฌธ์ด ์ž์—ฐ์–ด๋ผ๋Š” ์‚ฌ์‹ค ํ•˜๋‚˜๋กœ, ์†Œ๋ฌธ์ž ๋น„์ค‘์ด๋ผ๋Š” ๊ฐ’์‹ผ ์ง€ํ‘œ๊ฐ€ ๊ฐ ์—ด์˜ ์ •๋‹ต ์‹œํ”„ํŠธ๋ฅผ ์งš์–ด ์ค€๋‹ค. ํ‚ค๋ฅผ ๋ชฐ๋ผ๋„ ํ‰๋ฌธ์˜ ์„ฑ์งˆ์„ ์•Œ๋ฉด ์—ด์‡ ๊ฐ€ ์Šค์Šค๋กœ ๋“œ๋Ÿฌ๋‚˜๋Š” ์…ˆ์ด๋‹ค.

์ด ๊ธ€์ด ๋„์›€์ด ๋๋‚˜์š”?

Comments

๋Œ“๊ธ€

0๊ฐœ

๋Œ“๊ธ€์„ ๋‚จ๊ธฐ๋ ค๋ฉด ๋กœ๊ทธ์ธ์ด ํ•„์š”ํ•ด์š”. (๋„ค์ด๋ฒ„ ยท ๊ตฌ๊ธ€ ๊ณ„์ •)

๋Œ“๊ธ€ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘โ€ฆ

Related

๊ด€๋ จ ๊ธ€

3๊ฐœ
[๐Ÿฅ‰ Bronze 4] 4๋ฐ”์ดํŠธ ํ‚ค๊ฐ€ ๋ถ€์ˆœ RSA โ€” DreamHack TOP secret! ํ’€์ด
blog

[๐Ÿฅ‰ Bronze 4] 4๋ฐ”์ดํŠธ ํ‚ค๊ฐ€ ๋ถ€์ˆœ RSA โ€” DreamHack TOP secret! ํ’€์ด

ํ”Œ๋ž˜๊ทธ๋Š” 4๋ฐ”์ดํŠธ ํ‚ค์˜ Vigenรจre๋กœ, ๊ทธ ํ‚ค๋Š” RSA(e=5)๋กœ ์•”ํ˜ธํ™”ํ•ด ๊ฑด๋„ค์ฃผ๋Š” ๋ฌธ์ œ. ํ‚ค๊ฐ€ 32๋น„ํŠธ๋ฟ์ด๋ผ key^5์ด 2048๋น„ํŠธ N๋ณด๋‹ค ํ›จ์”ฌ ์ž‘์•„ ๋ชจ๋“ˆ๋Ÿฌ ์ถ•์•ฝ์ด ์ผ์–ด๋‚˜์ง€ ์•Š๋Š”๋‹ค. enc2์˜ ์ •์ˆ˜ 5์ œ๊ณฑ๊ทผ์œผ๋กœ ํ‚ค๋ฅผ ๋ณต์›ํ•˜๊ณ , ๊ทธ ํ‚ค๋กœ Vigenรจre ๋ง์…ˆ์„ ๋˜๋Œ๋ฆฌ๋ฉด ํ”Œ๋ž˜๊ทธ๊ฐ€ ๋‚˜์˜จ๋‹ค.
#dreamhack#ctf#crypto+3
2026-07-13#dreamhack +4
[๐Ÿฅ‰ Bronze 4] RSA-2048 ์„ ์•„ํ™‰ ๊ฐ€์ง€๋กœ ๋‘๋“ค๊ธฐ๊ณ  ๋Œ์•„์˜ค๋‹ˆ ๋‹ต์€ ์†Œ์Šค์— ์žˆ์—ˆ๋‹ค โ€” DreamHack fakeday revenge ํ’€์ด
blog

[๐Ÿฅ‰ Bronze 4] RSA-2048 ์„ ์•„ํ™‰ ๊ฐ€์ง€๋กœ ๋‘๋“ค๊ธฐ๊ณ  ๋Œ์•„์˜ค๋‹ˆ ๋‹ต์€ ์†Œ์Šค์— ์žˆ์—ˆ๋‹ค โ€” DreamHack fakeday revenge ํ’€์ด

๋งŒ์šฐ์ ˆ์— ์˜ฌ๋ผ์˜จ ๋ฌธ์ œ. ๋ฐฐํฌ๋ณธ prob.py ๊ฐ€ 2048๋น„ํŠธ RSA ๋กœ flag ๋ฅผ ์•”ํ˜ธํ™”ํ•˜๊ณ  N, e, c ๋งŒ ๋‚จ๊ธฐ๋Š”๋ฐ ์†Œ์Šค์˜ flag ์ž๋ฆฌ์—๋Š” ๊ฐ€์งœ๋กœ ๋ณด์ด๋Š” ๋ฆฌํ„ฐ๋Ÿด์ด ๋ฐ•ํ˜€ ์žˆ๋‹ค. Fermat, Pollard p-1, rho, ์ €์ง€์ˆ˜, factordb ๊นŒ์ง€ ์•„ํ™‰ ํ•ญ๋ชฉ์„ ๋Œ๋ ค๋„ ํ ์ง‘์ด ์—†๊ณ , ์„ค๋ช…์ด ๊ฐ€๋ฆฌํ‚จ Shor ๋Š” 4099 ํ๋น—์ด ํ•„์š”ํ•˜๋‹ค. ๋‹ต์€ ๊ทธ ๋ฆฌํ„ฐ๋Ÿด ์ž์ฒด์˜€๋‹ค.
#dreamhack#ctf#crypto+7
2026-08-22#dreamhack +3
[๐Ÿฅ‰ Bronze 1] assert ํ•œ ์ค„์ด MISTY1 ์„ 20๋น„ํŠธ๋กœ โ€” DreamHack ๋”ธ๊น ํ’€์ด
blog

[๐Ÿฅ‰ Bronze 1] assert ํ•œ ์ค„์ด MISTY1 ์„ 20๋น„ํŠธ๋กœ โ€” DreamHack ๋”ธ๊น ํ’€์ด

RFC 2994 ์ˆœ์ • MISTY1 ๊ตฌํ˜„์— assert ํ•œ ์ค„์ด ์–นํ˜€ ์žˆ๋‹ค. bytes ๋น„๊ต๊ฐ€ ์‚ฌ์ „์‹์ด๋ผ 128๋น„ํŠธ ํ‚ค๊ฐ€ 2^20 ์œผ๋กœ ์ž˜๋ฆฌ๊ณ , ๊ทธ 100๋งŒ ๊ฐœ๋ฅผ ์ „์ˆ˜ ๋ณตํ˜ธํ•˜๋ฉด DH{...} ํ˜•์‹ ํ‰๋ฌธ์ด ๋”ฑ ํ•˜๋‚˜ ๋‚จ๋Š”๋‹ค. RFC ์›๋ฌธ๊ณผ์˜ ๊ธฐ๊ณ„ ๋Œ€์กฐ, ์‹œํ—˜๋ฒกํ„ฐ ์žฌํ˜„, C ๋…๋ฆฝ ๊ตฌํ˜„ ๊ต์ฐจ๊ฒ€์ฆ๊นŒ์ง€.
#dreamhack#ctf#crypto+5
2026-08-20#dreamhack +3