[๐Ÿฅ‰ Bronze 2] LFSR ํ‚ค์ŠคํŠธ๋ฆผ 32๋น„ํŠธ๋กœ ์‹œ๋“œ๋ฅผ ๋˜๊ฐ๋‹ค โ€” DreamHack STREME REVERIE ํ’€์ด

2026-08-17ยท1๋ถ„ ์ฝ๊ธฐยท

[๐Ÿฅ‰ Bronze 2] LFSR ํ‚ค์ŠคํŠธ๋ฆผ 32๋น„ํŠธ๋กœ ์‹œ๋“œ๋ฅผ ๋˜๊ฐ๋‹ค โ€” DreamHack STREME REVERIE ํ’€์ด

32๋น„ํŠธ LFSR ์ŠคํŠธ๋ฆผ ์•”ํ˜ธ๋กœ ํ”Œ๋ž˜๊ทธ ํ•œ ์ค„๋งŒ ์•”ํ˜ธํ™”ํ•ด์„œ ์ค€๋‹ค. ์ถœ๋ ฅ ๋น„ํŠธ๊ฐ€ ๊ทธ๋Œ€๋กœ ๋‹ค์Œ ์ƒํƒœ๋กœ ๋“ค์–ด๊ฐ€๋Š” ๊ตฌ์กฐ๋ผ, ์—ฐ์†๋œ ํ‚ค์ŠคํŠธ๋ฆผ 32๋น„ํŠธ๋ฅผ ์•Œ๋ฉด ๋‚ด๋ถ€ ์ƒํƒœ๊ฐ€ ํ†ต์งธ๋กœ ๋ณต์›๋œ๋‹ค. ์•Œ๋ ค์ง„ ํ‰๋ฌธ ์„ธ ๊ธ€์ž๋กœ 24๋น„ํŠธ๋ฅผ ํ™•๋ณดํ•˜๊ณ  ๋‚จ์€ 8๋น„ํŠธ๋งŒ 256๊ฐ€์ง€ ํ›‘์€ ๋’ค, ์‹œํ”„ํŠธ๋ฅผ 32๋ฒˆ ๋˜๊ฐ์•„ ์‹œ๋“œ๋ฅผ ๋ณต์›ํ–ˆ๋‹ค.

๋ฌธ์ œ: DreamHack โ€” STREME REVERIE ๋ถ„๋ฅ˜: crypto ๋‚œ์ด๋„: ๐Ÿฅ‰ Bronze 2 FLAG: DH{855083595b1557aeb8f60e7422e49405672b8ddf793700d884dae710909f9d0f}

๋ฌธ์ œ ์„ค๋ช…์€ ๋‘ ์ค„์ด ์ „๋ถ€๋‹ค. "Stream Cipher, to the Extreme!", ํ•œ๊ตญ์–ด๋กœ๋Š” "์ŠคํŠธ๋ฆผ ์•”ํ˜ธ๋ฅผ ์ต์ŠคํŠธ๋ฆผํ•˜๊ฒŒ ๋ฐ”๊ฟ”๋ดค์Šต๋‹ˆ๋‹ค!".

์ต์ŠคํŠธ๋ฆผํ•˜๊ฒŒ ๋ฐ”๊ฟจ๋‹ค๋Š” ๊ฒŒ ๋ญ”๊ฐ€ ์‹ถ์–ด ํŒŒ์ผ์„ ์—ด์–ด ๋ณด๋ฉด, ํ‰๋ฒ”ํ•œ 32๋น„ํŠธ LFSR ํ•˜๋‚˜๊ฐ€ ๋ฐ”์ดํŠธ๋งˆ๋‹ค 8๋น„ํŠธ์”ฉ ํ‚ค์ŠคํŠธ๋ฆผ์„ ๋ฝ‘์•„ XOR ํ•˜๋Š” ๊ตฌ์กฐ๋‹ค. ์ต์ŠคํŠธ๋ฆผํ•œ ๊ฑด ์•”ํ˜ธ๊ฐ€ ์•„๋‹ˆ๋ผ ํƒญ ์œ„์น˜(32, 22, 2, 1)์˜ ์ƒ๊น€์ƒˆ ์ชฝ์— ๊ฐ€๊น๋‹ค.


๋ฌธ์ œ ๊ฐœ์š”

ํ•ญ๋ชฉ๋‚ด์šฉ
๋ฌธ์ œ๋ช…STREME REVERIE (DreamHack #1923)
๋‚œ์ด๋„๐Ÿฅ‰ Bronze 2
๋ถ„๋ฅ˜crypto
์ œ๊ณต ํŒŒ์ผcipher.py ยท prob.py ยท output.txt ยท flag(๋”๋ฏธ)
์„œ๋ฒ„์—†์Œ โ€” ์˜คํ”„๋ผ์ธ ๋ฌธ์ œ
ํ•ต์‹ฌ ์ทจ์•ฝ์ LFSR ์ถœ๋ ฅ ๋น„ํŠธ๊ฐ€ ๊ทธ๋Œ€๋กœ ๋‚ด๋ถ€ ์ƒํƒœ๋กœ ๋˜๋จน์ž„ โ†’ ํ‚ค์ŠคํŠธ๋ฆผ 32๋น„ํŠธ = ์ƒํƒœ ์ „์ฒด

ํ’€์ด ํ๋ฆ„์€ ์งง๋‹ค. ํ”Œ๋ž˜๊ทธ๊ฐ€ DH{ ๋กœ ์‹œ์ž‘ํ•œ๋‹ค๋Š” ์‚ฌ์‹ค์—์„œ ํ‚ค์ŠคํŠธ๋ฆผ 24๋น„ํŠธ๋ฅผ ์–ป๊ณ , ๋ชจ์ž๋ž€ 8๋น„ํŠธ๋ฅผ 256๊ฐ€์ง€๋กœ ํ›‘์–ด 32๋น„ํŠธ๋ฅผ ์ฑ„์šด๋‹ค. ๊ทธ 32๋น„ํŠธ๊ฐ€ ๊ณง ์–ด๋А ์‹œ์ ์˜ ๋‚ด๋ถ€ ์ƒํƒœ์ด๋ฏ€๋กœ, ์‹œํ”„ํŠธ๋ฅผ 32๋ฒˆ ๊ฑฐ๊พธ๋กœ ๋Œ๋ฆฌ๋ฉด ์‹œ๋“œ๊ฐ€ ๋‚˜์˜จ๋‹ค.



๐Ÿงฉ ๋ฐฐ๊ฒฝ โ€” LFSR ์€ ์–ด๋–ป๊ฒŒ ๋„๋Š”๊ฐ€

LFSR(Linear Feedback Shift Register)์€ ๋น„ํŠธ ๋ ˆ์ง€์Šคํ„ฐ๋ฅผ ํ•œ ์นธ์”ฉ ๋ฐ€๋ฉด์„œ, ์ง€์ •๋œ ๋ช‡ ์ž๋ฆฌ(ํƒญ)๋ฅผ XOR ํ•œ ๊ฐ’์„ ๋นˆ ์ž๋ฆฌ์— ์ฑ„์›Œ ๋„ฃ๋Š” ๊ตฌ์กฐ๋‹ค. ์•„๋ž˜๋Š” ์œ„ํ‚ค๋ฏธ๋””์–ด์— ์žˆ๋Š” 16๋น„ํŠธ ํ”ผ๋ณด๋‚˜์น˜ LFSR ๊ทธ๋ฆผ์ด๋‹ค. ํƒญ์ด 11ยท13ยท14ยท16๋ฒˆ ์ž๋ฆฌ์— ๊ฑธ๋ ค ์žˆ๊ณ , ๊ทธ XOR ๊ฒฐ๊ณผ๊ฐ€ ๋งจ ์•ž์œผ๋กœ ๋“ค์–ด๊ฐ„๋‹ค.

์œ„ํ‚ค๋ฏธ๋””์–ด ์ปค๋จผ์ฆˆ์˜ 16๋น„ํŠธ ํ”ผ๋ณด๋‚˜์น˜ LFSR ๋„์‹ โ€” ํƒญ 11ยท13ยท14ยท16 ์ž๋ฆฌ์˜ ๊ฐ’์„ XOR ๊ฒŒ์ดํŠธ ์„ธ ๊ฐœ๋กœ ๋ฌถ์–ด ๋ ˆ์ง€์Šคํ„ฐ ๋งจ ์•ž ์นธ์œผ๋กœ ๋˜๋จน์ด๋Š” ๊ตฌ์กฐ๋ฅผ ๋ณด์—ฌ์ค€๋‹ค
์œ„ํ‚ค๋ฏธ๋””์–ด ์ปค๋จผ์ฆˆ์˜ 16๋น„ํŠธ ํ”ผ๋ณด๋‚˜์น˜ LFSR ๋„์‹ โ€” ํƒญ 11ยท13ยท14ยท16 ์ž๋ฆฌ์˜ ๊ฐ’์„ XOR ๊ฒŒ์ดํŠธ ์„ธ ๊ฐœ๋กœ ๋ฌถ์–ด ๋ ˆ์ง€์Šคํ„ฐ ๋งจ ์•ž ์นธ์œผ๋กœ ๋˜๋จน์ด๋Š” ๊ตฌ์กฐ๋ฅผ ๋ณด์—ฌ์ค€๋‹ค

์ถœ์ฒ˜: Wikimedia Commons, KCAuXy4p, CC0

ํƒญ ์œ„์น˜๋ฅผ ๋‹คํ•ญ์‹์œผ๋กœ ์ ๋Š” ๊ด€๋ก€๊ฐ€ ์žˆ๋‹ค. ์ด ๋ฌธ์ œ ์ฝ”๋“œ ์ฃผ์„์— ์ ํžŒ x^32 + x^22 + x^2 + x + 1 ์ด ๊ทธ๊ฒƒ์ด๋‹ค. ์ด ๋‹คํ•ญ์‹์ด GF(2) ์œ„์—์„œ ์›์‹œ(primitive)๋ฉด ๋ ˆ์ง€์Šคํ„ฐ๋Š” 0์ด ์•„๋‹Œ ๋ชจ๋“  ์ƒํƒœ๋ฅผ ํ•œ ๋ฒˆ์”ฉ ํ›‘๊ณ  ๋Œ์•„์˜จ๋‹ค. ์ฃผ๊ธฐ๊ฐ€ 2^32 โˆ’ 1 ์ด๋ผ๋Š” ๋œป์ด๊ณ , ์‹ค์ œ๋กœ ๊ทธ๋ ‡๋‹ค๋Š” ๊ฑด ๋’ค์—์„œ SageMath ๋กœ ํ™•์ธํ•œ๋‹ค.

์ฃผ๊ธฐ๊ฐ€ ๊ธธ๋‹ค๋Š” ๊ฑด ํ†ต๊ณ„์ ์œผ๋กœ ๋žœ๋คํ•ด ๋ณด์ธ๋‹ค๋Š” ๋œป์ผ ๋ฟ, ์˜ˆ์ธก ๋ถˆ๊ฐ€๋Šฅํ•˜๋‹ค๋Š” ๋œป์ด ์•„๋‹ˆ๋‹ค. LFSR ์€ ์ด๋ฆ„ ๊ทธ๋Œ€๋กœ ์„ ํ˜•์ด๋ผ ์ถœ๋ ฅ ๋น„ํŠธ๋“ค์ด ์ „๋ถ€ ์ดˆ๊ธฐ ์ƒํƒœ์˜ XOR ์กฐํ•ฉ์ด๋‹ค. ๋ช‡ ๋น„ํŠธ๋งŒ ์•Œ๋ฉด ๋‚˜๋จธ์ง€๊ฐ€ ๊ฒฐ์ •๋œ๋‹ค. ์ŠคํŠธ๋ฆผ ์•”ํ˜ธ์˜ ํ‚ค์ŠคํŠธ๋ฆผ ์ƒ์„ฑ๊ธฐ๋กœ LFSR ์„ ๋‚ ๊ฒƒ ๊ทธ๋Œ€๋กœ ์“ฐ๋ฉด ์•ˆ ๋˜๋Š” ์ด์œ ๊ฐ€ ์—ฌ๊ธฐ ์žˆ๋‹ค.



๐Ÿ”ฌ ์ •์ฐฐ โ€” ๋ฐฐํฌ๋ณธ ์„ธ ํŒŒ์ผ

๋ฐ›์€ zip ์„ ํ’€๋ฉด ํŒŒ์ผ์ด ๋„ท์ด๋‹ค. flag ๋Š” ๋กœ์ปฌ ํ…Œ์ŠคํŠธ์šฉ ๋”๋ฏธ(DH{FAKE_FLAG})๋ผ ๋ณผ ๊ฒŒ ์—†๊ณ , ์ง„์งœ๋Š” output.txt ํ•œ ์ค„์ด๋‹ค.

ls -l extracted && cat extracted/output.txt

๋ฐฐํฌ๋ณธ ํŒŒ์ผ ๋ชฉ๋ก๊ณผ output.txt ๋‚ด์šฉ โ€” cipher.py 1465๋ฐ”์ดํŠธ, prob.py 328๋ฐ”์ดํŠธ, ๊ทธ๋ฆฌ๊ณ  encrypted flag ๋กœ ์‹œ์ž‘ํ•˜๋Š” 136์ž๋ฆฌ hex ๋ฌธ์ž์—ด ํ•œ ์ค„์ด ์ „๋ถ€๋‹ค
๋ฐฐํฌ๋ณธ ํŒŒ์ผ ๋ชฉ๋ก๊ณผ output.txt ๋‚ด์šฉ โ€” cipher.py 1465๋ฐ”์ดํŠธ, prob.py 328๋ฐ”์ดํŠธ, ๊ทธ๋ฆฌ๊ณ  encrypted flag ๋กœ ์‹œ์ž‘ํ•˜๋Š” 136์ž๋ฆฌ hex ๋ฌธ์ž์—ด ํ•œ ์ค„์ด ์ „๋ถ€๋‹ค

hex 136์ž๋ฆฌ, ์ฆ‰ ์•”ํ˜ธ๋ฌธ์€ 68๋ฐ”์ดํŠธ๋‹ค. ํ”Œ๋ž˜๊ทธ ํ˜•์‹์ด DH{ + 64๊ธ€์ž + } ๋ผ๋ฉด ๋”ฑ ๋“ค์–ด๋งž๋Š”๋‹ค. ์•ˆ์ชฝ 64๊ธ€์ž๋Š” ์•„๋งˆ sha256 hex ์ผ ๊ฒƒ์ด๋‹ค.

prob.py ๋Š” ์งง๋‹ค.

#!/usr/bin/env python3
from cipher import STREAM
import random
 
 
if __name__ == "__main__":
    with open("flag", "rb") as f:
        flag = f.read()
 
    assert flag[:3] == b'DH{' and flag[-1:] == b'}'
 
    seed = random.getrandbits(32)
    stream = STREAM(seed, 32)
 
    print(f"encrypted flag > {stream.encrypt(flag).hex()}")

์—ฌ๊ธฐ์„œ ๋‘ ๊ฐ€์ง€๊ฐ€ ํ™•์ •๋œ๋‹ค. ์‹œ๋“œ๋Š” 32๋น„ํŠธ๊ณ , ํ”Œ๋ž˜๊ทธ๋Š” DH{ ๋กœ ์‹œ์ž‘ํ•ด } ๋กœ ๋๋‚œ๋‹ค. assert ๊ฐ€ ๋Œ€๋†“๊ณ  ์•Œ๋ ค์ฃผ๋Š” ์…ˆ์ด๋ผ ์•Œ๋ ค์ง„ ํ‰๋ฌธ 3๋ฐ”์ดํŠธ๋ฅผ ๊ณต์งœ๋กœ ๋ฐ›๋Š”๋‹ค.

์‹œ๋“œ๊ฐ€ random.getrandbits(32) ์—์„œ ๋‚˜์˜ค๋Š” ๊ฒƒ๋„ ์ž ๊น ๋ˆˆ๊ธธ์ด ๊ฐ”๋‹ค. ๋ฉ”๋ฅด์„ผ ํŠธ์œ„์Šคํ„ฐ๋ผ ์ถœ๋ ฅ 624๊ฐœ๋ฅผ ๋ชจ์œผ๋ฉด ๋‚ด๋ถ€ ์ƒํƒœ๋ฅผ ๋ณต์›ํ•  ์ˆ˜ ์žˆ๋Š” ๊ฑธ๋กœ ์œ ๋ช…ํ•œ๋ฐ, ์—ฌ๊ธฐ์„œ๋Š” ๋”ฑ ํ•œ ๋ฒˆ ๋ฝ‘๊ณ  ๋์ด๋‹ค. ๊ฐ’ ํ•˜๋‚˜๋กœ๋Š” ์•„๋ฌด๊ฒƒ๋„ ๋ชป ํ•œ๋‹ค. ๊ทธ์ชฝ ๊ธธ์€ ๋ง‰ํ˜€ ์žˆ๋‹ค.

์•”ํ˜ธ ๋ณธ์ฒด๋Š” cipher.py ๋‹ค.

class STREAM:
    def __init__(self, seed, size):
        self.state = self.num2bits(seed, size)
        # x^32 + x^22 + x^2 + x^1 + 1
        self.taps = (32, 22, 2, 1)
 
    def num2bits(self, num, size):
        assert num < (1 << size)
 
        return bin(num)[2:].zfill(size)
 
    def bits2num(self, bits):
        return int('0b' + bits, 2)
 
    def shift(self):
        new_bit = 0
        for tap in self.taps:
            new_bit ^= int(self.state[tap - 1])
        new_bit = str(new_bit)
        self.state = new_bit + self.state[:-1]
        return new_bit
 
    def getNbits(self, num):
        sequence = ""
        for _ in range(num):
            sequence += self.shift()
 
        return sequence
 
    def encrypt(self, plaintext):
        ciphertext = b""
        for p in plaintext:
            stream = self.bits2num(self.getNbits(8))
            c = p ^ stream
            ciphertext += bytes([c])
 
        return ciphertext

์ƒํƒœ๋ฅผ ๋น„ํŠธ ๋ฌธ์ž์—ด๋กœ ๋“ค๊ณ  ๋‹ค๋‹ˆ๋Š” ๊ตฌํ˜„์ด๋‹ค. shift() ๋ฅผ ํ•œ ์ค„์”ฉ ๋ณด๋ฉด ์ด๋ ‡๋‹ค.

  • ํƒญ ์ž๋ฆฌ state[31], state[21], state[1], state[0] ์„ XOR ํ•ด์„œ new_bit ์„ ๋งŒ๋“ ๋‹ค.
  • state = new_bit + state[:-1] โ€” ์ƒˆ ๋น„ํŠธ๋ฅผ ๋งจ ์•ž์— ๋ถ™์ด๊ณ  ๋งจ ๋’ค ํ•œ ์นธ์„ ๋ฒ„๋ฆฐ๋‹ค.
  • ๊ทธ๋ฆฌ๊ณ  ๊ทธ new_bit ์„ ๊ทธ๋Œ€๋กœ ๋ฐ˜ํ™˜ํ•œ๋‹ค. ์ด๊ฒŒ ํ‚ค์ŠคํŠธ๋ฆผ ๋น„ํŠธ๋‹ค.

๋ฐ”์ดํŠธ ํ•˜๋‚˜๋ฅผ ์•”ํ˜ธํ™”ํ•  ๋•Œ๋งˆ๋‹ค getNbits(8) ๋กœ 8๋น„ํŠธ๋ฅผ ๋ฝ‘์•„ ์ •์ˆ˜๋กœ ๋ฐ”๊พผ ๋’ค XOR ํ•œ๋‹ค. ๋จผ์ € ๋‚˜์˜จ ๋น„ํŠธ๊ฐ€ ์ƒ์œ„ ๋น„ํŠธ๋‹ค.

โ–ถ๐Ÿ› ์‚ฝ์งˆ โ€” ์•”ํ˜ธ๋ฌธ์„ 68๋ฐ”์ดํŠธ๊ฐ€ ์•„๋‹ˆ๋ผ 1๋ฐ”์ดํŠธ๋กœ ์ฝ์—ˆ๋‹ค

output.txt ์—์„œ hex ๋งŒ ์ž˜๋ผ๋‚ด๋ ค๊ณ  ์ •๊ทœ์‹์„ ๋Œ€์ถฉ ์ผ๋‹ค๊ฐ€ ์‹œ๊ฐ„์„ ๋ฒ„๋ ธ๋‹ค. ํŒŒ์ผ ๋‚ด์šฉ์ด encrypted flag > c615... ๋ผ์„œ ์ฒซ ๋งค์น˜๋ฅผ ๊ทธ๋Œ€๋กœ ์“ฐ๋ฉด ์•ž์ชฝ ์˜์–ด ๋‹จ์–ด์— ๋ฐ•ํžŒ ์กฐ๊ฐ์ด ์žกํžŒ๋‹ค. encrypted ์•ˆ์˜ ed ๊ฐ€ ๊ทธ๊ฒƒ์ด๋‹ค. ๋‘˜ ๋‹ค [0-9a-f] ๋กœ๋งŒ ์ด๋ฃจ์–ด์ ธ ์žˆ์œผ๋‹ˆ ์ •๊ทœ์‹์€ ์•„๋ฌด ์ž˜๋ชป์ด ์—†๋‹ค.

#!/usr/bin/env python3
"""์‚ฝ์งˆ ์žฌํ˜„ โ€” output.txt ๋ฅผ ์ •๊ทœ์‹์œผ๋กœ ์ž˜๋ผ๋‚ด๋‹ค 1๋ฐ”์ดํŠธ๋งŒ ์ง‘์€ ์‚ฌ๊ฑด.
 
`encrypted flag > c615...` ์˜ ์•ž์ชฝ ์˜์–ด ๋‹จ์–ด๋„ [0-9a-f] ๋กœ๋งŒ ์ด๋ฃจ์–ด์ง„ ์กฐ๊ฐ์„
ํ’ˆ๊ณ  ์žˆ์–ด์„œ, ์ฒซ ๋งค์น˜๋ฅผ ๊ทธ๋Œ€๋กœ ์“ฐ๋ฉด ์•”ํ˜ธ๋ฌธ์ด ์•„๋‹ˆ๋ผ ๊ทธ ์กฐ๊ฐ์ด ์žกํžŒ๋‹ค.
"""
import re
 
txt = open("extracted/output.txt").read()
 
first = re.search(r"([0-9a-f]{2,})", txt).group(1)
longest = max(re.findall(r"[0-9a-f]{2,}", txt), key=len)
 
print(f"[์ฒซ ๋งค์น˜ ] {first!r}  โ†’ {len(bytes.fromhex(first))}๋ฐ”์ดํŠธ")
print(f"[๊ฐ€์žฅ ๊ธด ] {longest[:24]}...  โ†’ {len(bytes.fromhex(longest))}๋ฐ”์ดํŠธ")
print()
print("[*] 'encrypted' ์•ˆ์˜ ์กฐ๊ฐ:", [m for m in re.findall(r"[0-9a-f]{2,}", txt)][:3])
python3 sapjil_regex.py

sapjil_regex.py ์‹คํ–‰ ๊ฒฐ๊ณผ โ€” ์ •๊ทœ์‹ ์ฒซ ๋งค์น˜๊ฐ€ encrypted ์˜ ed ๋ผ์„œ 1๋ฐ”์ดํŠธ, ๊ฐ€์žฅ ๊ธด ๋งค์น˜๋ฅผ ๊ณจ๋ผ์•ผ 68๋ฐ”์ดํŠธ ์•”ํ˜ธ๋ฌธ์ด ๋‚˜์˜จ๋‹ค๋Š” ๊ฒƒ์„ ๋‚˜๋ž€ํžˆ ๋ณด์—ฌ์ค€๋‹ค
sapjil_regex.py ์‹คํ–‰ ๊ฒฐ๊ณผ โ€” ์ •๊ทœ์‹ ์ฒซ ๋งค์น˜๊ฐ€ encrypted ์˜ ed ๋ผ์„œ 1๋ฐ”์ดํŠธ, ๊ฐ€์žฅ ๊ธด ๋งค์น˜๋ฅผ ๊ณจ๋ผ์•ผ 68๋ฐ”์ดํŠธ ์•”ํ˜ธ๋ฌธ์ด ๋‚˜์˜จ๋‹ค๋Š” ๊ฒƒ์„ ๋‚˜๋ž€ํžˆ ๋ณด์—ฌ์ค€๋‹ค

์ฆ์ƒ์ด ๊ณ ์•ฝํ–ˆ๋˜ ๊ฑด ์—๋Ÿฌ๊ฐ€ ์•ˆ ๋‚ฌ๋‹ค๋Š” ์ ์ด๋‹ค. 1๋ฐ”์ดํŠธ ์•”ํ˜ธ๋ฌธ์œผ๋กœ๋„ ํ‚ค์ŠคํŠธ๋ฆผ 8๋น„ํŠธ๋Š” ๋ฉ€์ฉกํžˆ ๋ฝ‘ํžˆ๊ณ , ๊ทธ๋‹ค์Œ ์ƒํƒœ ๋ณต์› ๋‹จ๊ณ„์—์„œ ๋ฌธ์ž์—ด ์ธ๋ฑ์Šค๊ฐ€ ๋ฒ”์œ„๋ฅผ ๋ฒ—์–ด๋‚˜ ์ฃฝ๋Š”๋‹ค. ์—‰๋šฑํ•œ ๊ณณ์—์„œ ํ„ฐ์ง€๋‹ˆ ์›์ธ์„ ์•ž์ชฝ์—์„œ ์ฐพ๊ฒŒ ๋œ๋‹ค. ๊ฒฐ๊ตญ ๊ฐ€์žฅ ๊ธด ๋งค์น˜๋ฅผ ๊ณ ๋ฅด๋Š” ๊ฑธ๋กœ ๋ฐ”๊ฟจ๋‹ค.



๐Ÿ’ฃ ํ•ต์‹ฌ โ€” ์ถœ๋ ฅ ๋น„ํŠธ๊ฐ€ ๊ณง ์ƒํƒœ๋‹ค

state = new_bit + state[:-1] ์ด ํ•œ ์ค„์ด ์ด ๋ฌธ์ œ์˜ ์ „๋ถ€๋‹ค.

์ƒˆ๋กœ ๋งŒ๋“  ๋น„ํŠธ๋ฅผ ์•ž์— ๋ฐ€์–ด ๋„ฃ๊ณ , ๊ทธ ๋น„ํŠธ๋ฅผ ๊ทธ๋Œ€๋กœ ๋ฐ–์œผ๋กœ ๋‚ด๋ณด๋‚ธ๋‹ค. ๊ทธ๋Ÿฌ๋‹ˆ 32์Šคํ…์„ ๋Œ๋ฆฌ๋ฉด ์›๋ž˜ ์žˆ๋˜ 32๋น„ํŠธ๋Š” ์ „๋ถ€ ์˜ค๋ฅธ์ชฝ์œผ๋กœ ๋ฐ€๋ ค ์‚ฌ๋ผ์ง€๊ณ , ๋ ˆ์ง€์Šคํ„ฐ ์•ˆ์—๋Š” ๋ฐฉ๊ธˆ ๋ฐ–์œผ๋กœ ๋‚ด๋ณด๋‚ธ ํ‚ค์ŠคํŠธ๋ฆผ 32๋น„ํŠธ๋งŒ ๋‚จ๋Š”๋‹ค. ์ˆœ์„œ๋งŒ ๋’ค์ง‘ํ˜€ ์žˆ์„ ๋ฟ์ด๋‹ค.

์ง์ ‘ ๊ทธ๋ฆฐ ๋„์‹ โ€” 32์นธ ๋ ˆ์ง€์Šคํ„ฐ์˜ ํƒญ 0ยท1ยท21ยท31 ์„ XOR ํ•œ ๊ฐ’์ด ๋งจ ์•ž์œผ๋กœ ๋˜๋จน์—ฌ์ง€๋Š” ๋™์‹œ์— ํ‚ค์ŠคํŠธ๋ฆผ์œผ๋กœ ๋‚˜๊ฐ€๊ณ , 32์Šคํ… ๋’ค์—๋Š” ๋ ˆ์ง€์Šคํ„ฐ๊ฐ€ ์ตœ๊ทผ ํ‚ค์ŠคํŠธ๋ฆผ 32๋น„ํŠธ๋กœ ๊ฐ€๋“ ์ฐฌ๋‹ค
์ง์ ‘ ๊ทธ๋ฆฐ ๋„์‹ โ€” 32์นธ ๋ ˆ์ง€์Šคํ„ฐ์˜ ํƒญ 0ยท1ยท21ยท31 ์„ XOR ํ•œ ๊ฐ’์ด ๋งจ ์•ž์œผ๋กœ ๋˜๋จน์—ฌ์ง€๋Š” ๋™์‹œ์— ํ‚ค์ŠคํŠธ๋ฆผ์œผ๋กœ ๋‚˜๊ฐ€๊ณ , 32์Šคํ… ๋’ค์—๋Š” ๋ ˆ์ง€์Šคํ„ฐ๊ฐ€ ์ตœ๊ทผ ํ‚ค์ŠคํŠธ๋ฆผ 32๋น„ํŠธ๋กœ ๊ฐ€๋“ ์ฐฌ๋‹ค

์ฆ‰ ํ‚ค์ŠคํŠธ๋ฆผ 32๋น„ํŠธ๋ฅผ ๊ด€์ธกํ•˜๋ฉด ๊ทธ ์‹œ์ ์˜ ๋‚ด๋ถ€ ์ƒํƒœ๋ฅผ ์•ˆ๋‹ค. ์ด๊ฑด ์œ ๋„๊ฐ€ ์•„๋‹ˆ๋ผ ์ •์˜์— ๊ฐ€๊น๋‹ค. ๊ทธ๋ž˜๋„ ์ฝ”๋“œ๋กœ ํ•œ ๋ฒˆ ํ™•์ธํ•˜๊ณ  ๋„˜์–ด๊ฐ€๋Š” ๊ฒŒ ๋‚ซ๋‹ค. ์‹œ๋“œ๋ฅผ ์•„๋ฌด๊ฑฐ๋‚˜ ํ•˜๋‚˜ ๋„ฃ๊ณ  32์Šคํ…์„ ๋Œ๋ฆฐ ๋’ค, ์ƒํƒœ์™€ ํ‚ค์ŠคํŠธ๋ฆผ์„ ๋‚˜๋ž€ํžˆ ์ฐ์–ด ๋ดค๋‹ค.

#!/usr/bin/env python3
"""LFSR ์˜ '์ถœ๋ ฅ ๋น„ํŠธ = ๋‹ค์Œ ์ƒํƒœ' ๊ด€๊ณ„๋ฅผ ๋ˆˆ์œผ๋กœ ํ™•์ธํ•œ๋‹ค.
 
32์Šคํ…์„ ๋Œ๋ฆฐ ๋’ค์˜ ๋‚ด๋ถ€ ์ƒํƒœ๊ฐ€ ๊ทธ๋™์•ˆ ๋ฑ‰์€ ํ‚ค์ŠคํŠธ๋ฆผ 32๋น„ํŠธ๋ฅผ ๋’ค์ง‘์€ ๊ฒƒ๊ณผ
๊ฐ™์€์ง€ ์ง์ ‘ ๋น„๊ตํ•œ๋‹ค. ๊ฐ™๋‹ค๋ฉด ํ‚ค์ŠคํŠธ๋ฆผ 32๋น„ํŠธ๋งŒ ์•Œ์•„๋„ ์ƒํƒœ๊ฐ€ ๋ณต์›๋œ๋‹ค๋Š” ๋œป์ด๋‹ค.
"""
import sys
sys.path.insert(0, "extracted")
from cipher import STREAM
 
SEED = 0xFE5DB0A9
 
s = STREAM(SEED, 32)
print(f"[*] seed  = 0x{SEED:08x}")
print(f"[*] state = {s.state}   (state[0] ์ด ์ตœ์‹  ๋น„ํŠธ ์ž๋ฆฌ)")
print()
 
ks = ""
for step in range(1, 9):
    bit = s.shift()
    ks += bit
    print(f"  step {step:2d}: ์ถœ๋ ฅ={bit}  state={s.state}")
 
print()
ks += s.getNbits(24)          # ํ•ฉ์ณ์„œ 32๋น„ํŠธ
print(f"[*] 32์Šคํ… ํ‚ค์ŠคํŠธ๋ฆผ      : {ks}")
print(f"[*] 32์Šคํ… ๋’ค ๋‚ด๋ถ€ ์ƒํƒœ  : {s.state}")
print(f"[*] ํ‚ค์ŠคํŠธ๋ฆผ์„ ๋’ค์ง‘์€ ๊ฒƒ : {ks[::-1]}")
print(f"[+] ์ƒํƒœ == ๋’ค์ง‘์€ ํ‚ค์ŠคํŠธ๋ฆผ ? {s.state == ks[::-1]}")
python3 trace_lfsr.py

trace_lfsr.py ์‹คํ–‰ ๊ฒฐ๊ณผ โ€” ์Šคํ…๋งˆ๋‹ค ์ถœ๋ ฅ ๋น„ํŠธ๊ฐ€ ์ƒํƒœ ๋งจ ์•ž์— ๋ถ™๋Š” ๋ชจ์Šต๊ณผ, 32์Šคํ… ๋’ค ๋‚ด๋ถ€ ์ƒํƒœ๊ฐ€ ํ‚ค์ŠคํŠธ๋ฆผ 32๋น„ํŠธ๋ฅผ ๋’ค์ง‘์€ ๊ฒƒ๊ณผ ์ •ํ™•ํžˆ ์ผ์น˜ํ•œ๋‹ค๋Š” True ํŒ์ •
trace_lfsr.py ์‹คํ–‰ ๊ฒฐ๊ณผ โ€” ์Šคํ…๋งˆ๋‹ค ์ถœ๋ ฅ ๋น„ํŠธ๊ฐ€ ์ƒํƒœ ๋งจ ์•ž์— ๋ถ™๋Š” ๋ชจ์Šต๊ณผ, 32์Šคํ… ๋’ค ๋‚ด๋ถ€ ์ƒํƒœ๊ฐ€ ํ‚ค์ŠคํŠธ๋ฆผ 32๋น„ํŠธ๋ฅผ ๋’ค์ง‘์€ ๊ฒƒ๊ณผ ์ •ํ™•ํžˆ ์ผ์น˜ํ•œ๋‹ค๋Š” True ํŒ์ •

์Šคํ… 1์—์„œ ์ถœ๋ ฅ 1 ์ด ์ƒํƒœ ๋งจ ์•ž์— ๋ถ™๊ณ , ์›๋ž˜ ์ƒํƒœ๊ฐ€ ํ•œ ์นธ์”ฉ ์˜ค๋ฅธ์ชฝ์œผ๋กœ ๋ฐ€๋ฆฌ๋Š” ๊ฒŒ ๊ทธ๋Œ€๋กœ ๋ณด์ธ๋‹ค. ๋งˆ์ง€๋ง‰ ์„ธ ์ค„์ด ๊ฒฐ๋ก ์ด๋‹ค. 32์Šคํ… ๋’ค ์ƒํƒœ์™€ ํ‚ค์ŠคํŠธ๋ฆผ 32๋น„ํŠธ์˜ ์—ญ์ˆœ์ด ํ•œ ๊ธ€์ž๋„ ๋‹ค๋ฅด์ง€ ์•Š๋‹ค.

์ด์ œ ํ•„์š”ํ•œ ๊ฑด ์—ฐ์†๋œ ํ‚ค์ŠคํŠธ๋ฆผ 32๋น„ํŠธ๋ฟ์ด๋‹ค. ์šฐ๋ฆฌ๊ฐ€ ์•„๋Š” ๊ฑด 24๋น„ํŠธ๋‹ค.

โ–ถ๐Ÿ› ์‚ฝ์งˆ โ€” ์‹œ๋“œ 2^32 ๊ฐœ๋ฅผ ํ†ต์งธ๋กœ ํ›‘์„ ๋ป”ํ–ˆ๋‹ค

๊ตฌ์กฐ๋ฅผ ๋ณด๊ธฐ ์ „์— ๋จผ์ € ๋“  ์ƒ๊ฐ์€ "32๋น„ํŠธ๋ฉด ๊ทธ๋ƒฅ ๋‹ค ๋Œ๋ ค๋„ ๋˜๋Š” ๊ฑฐ ์•„๋‹Œ๊ฐ€" ์˜€๋‹ค. ์‹ค์ œ๋กœ ์–ผ๋งˆ๋‚˜ ๊ฑธ๋ฆฌ๋Š”์ง€ ์žฌ ๋ดค๋‹ค. ํ›„๋ณด ํ•˜๋‚˜๋‹น 3๋ฐ”์ดํŠธ๋งŒ ๋ณตํ˜ธํ•ด DH{ ์ธ์ง€ ํ™•์ธํ•˜๋Š”, ๊ฐ€๋Šฅํ•œ ํ•œ ์‹ผ ํŒ์ •์œผ๋กœ ์žก์•˜๋‹ค.

#!/usr/bin/env python3
"""์‹œ๋“œ 32๋น„ํŠธ๋ฅผ ํ†ต์งธ๋กœ ํ›‘๋Š” ๋ฌด์‹ํ•œ ๋ฐฉ๋ฒ•์ด ์‹ค์ œ๋กœ ์–ผ๋งˆ๋‚˜ ๊ฑธ๋ฆฌ๋Š”์ง€ ์žฌ ๋ณธ๋‹ค.
 
ํ•œ ํ›„๋ณด๋ฅผ ํ™•์ธํ•˜๋ ค๋ฉด ์ตœ์†Œ 3๋ฐ”์ดํŠธ(=24์Šคํ…)๋ฅผ ๋ณตํ˜ธํ•ด 'DH{' ์ธ์ง€ ๋ด์•ผ ํ•œ๋‹ค.
๊ทธ ๋น„์šฉ์„ 1000๊ฐœ๋กœ ์‹ค์ธกํ•œ ๋’ค 2^32 ๋กœ ํ™˜์‚ฐํ•œ๋‹ค.
"""
import sys
import time
 
sys.path.insert(0, "extracted")
from cipher import STREAM
 
ct = bytes.fromhex(open("extracted/output.txt").read().split(">")[1].strip())
N = 1000
 
t0 = time.perf_counter()
for seed in range(N):
    if STREAM(seed, 32).decrypt(ct[:3]) == b"DH{":
        print(f"  (์šฐ์—ฐํžˆ ๋งž์€ ์‹œ๋“œ: {seed})")
dt = time.perf_counter() - t0
 
per = dt / N
total = per * (1 << 32)
print(f"[*] ํ›„๋ณด {N}๊ฐœ ํ™•์ธ์— {dt:.3f}์ดˆ  โ†’  ๊ฐœ๋‹น {per * 1e6:.1f}us")
print(f"[*] 2^32 = {1 << 32:,} ๊ฐœ ์ „๋ถ€๋ฉด {total:,.0f}์ดˆ = {total / 86400:.1f}์ผ")
print(f"[*] ๋ฐ˜๋ฉด ์šฐ๋ฆฌ๊ฐ€ ์‹ค์ œ๋กœ ํ›‘์€ ํ›„๋ณด ์ˆ˜: 2^8 = 256 ๊ฐœ ({per * 256:.4f}์ดˆ)")
python3 bench_bruteforce.py

bench_bruteforce.py ์‹คํ–‰ ๊ฒฐ๊ณผ โ€” ํ›„๋ณด 1000๊ฐœ์— 0.016์ดˆ, ๊ฐœ๋‹น 16.2us ๋ผ์„œ 2^32 ์ „๋ถ€๋ฅผ ํ›‘์œผ๋ฉด 69721์ดˆ ์ฆ‰ 0.8์ผ์ด ๊ฑธ๋ฆฐ๋‹ค๋Š” ์‹ค์ธก์น˜๋ฅผ ๋ณด์—ฌ์ค€๋‹ค
bench_bruteforce.py ์‹คํ–‰ ๊ฒฐ๊ณผ โ€” ํ›„๋ณด 1000๊ฐœ์— 0.016์ดˆ, ๊ฐœ๋‹น 16.2us ๋ผ์„œ 2^32 ์ „๋ถ€๋ฅผ ํ›‘์œผ๋ฉด 69721์ดˆ ์ฆ‰ 0.8์ผ์ด ๊ฑธ๋ฆฐ๋‹ค๋Š” ์‹ค์ธก์น˜๋ฅผ ๋ณด์—ฌ์ค€๋‹ค

๊ฐœ๋‹น 16๋งˆ์ดํฌ๋กœ์ดˆ, ์ „๋ถ€ ํ›‘์œผ๋ฉด ํ•˜๋ฃจ๊ฐ€ ์ฑ„ ์•ˆ ๊ฑธ๋ฆฐ๋‹ค. ํŒŒ์ด์ฌ ์ˆœ์ˆ˜ ๊ตฌํ˜„์œผ๋กœ ์ด ์ •๋„๋ฉด C ๋กœ ์งœ์„œ ์ฝ”์–ด ๋ช‡ ๊ฐœ ๋ถ™์ด๋ฉด ๋ช‡ ๋ถ„์งœ๋ฆฌ๋‹ค. ์ฆ‰ "๋ชป ํ‘ธ๋Š”" ๊ฑด ์•„๋‹ˆ๋‹ค.

๊ทธ๋ž˜๋„ ์ด ๊ธธ๋กœ ๊ฐ€๋ฉด ๋ฌธ์ œ๋ฅผ ํ‘ผ ๊ฒŒ ์•„๋‹ˆ๋ผ ๊ธฐ๋‹ค๋ฆฐ ๊ฒŒ ๋œ๋‹ค. ๊ตฌ์กฐ๋ฅผ ๋ณด๋ฉด 256๋ฒˆ์ด๋ฉด ๋๋‚˜๋Š”๋ฐ 43์–ต ๋ฒˆ์„ ๋Œ ์ด์œ ๊ฐ€ ์—†๋‹ค. ๊ทธ๋ž˜์„œ ์—ฌ๊ธฐ์„œ ๋ฉˆ์ถ”๊ณ  ์ฝ”๋“œ๋ฅผ ๋‹ค์‹œ ์ฝ์—ˆ๋‹ค.


๐ŸŽฏ ๋˜๊ฐ๊ธฐ โ€” unshift ๋ฅผ 32๋ฒˆ

ํ‚ค์ŠคํŠธ๋ฆผ 32๋น„ํŠธ๋ฅผ ์†์— ๋„ฃ์—ˆ๋‹ค๊ณ  ์น˜์ž. ๊ทธ๊ฑด 32์Šคํ… ๋’ค์˜ ์ƒํƒœ๋‹ค. ์šฐ๋ฆฌ๊ฐ€ ์›ํ•˜๋Š” ๊ฑด ์‹œ๋“œ, ์ฆ‰ 0์Šคํ… ์‹œ์ ์˜ ์ƒํƒœ๋‹ค. ์‹œํ”„ํŠธ๋ฅผ ๊ฑฐ๊พธ๋กœ ๋Œ๋ ค์•ผ ํ•œ๋‹ค.

ํ•œ ์Šคํ…์„ ๋˜๊ฐ๋Š” ๊ฑด ์–ด๋ ต์ง€ ์•Š๋‹ค. ์ •๋ฐฉํ–ฅ์ด ์ด๋žฌ๋‹ค.

new  = prev[31] ^ prev[21] ^ prev[1] ^ prev[0]
cur  = new + prev[:-1]

๋‘ ๋ฒˆ์งธ ์ค„์—์„œ cur[0] = new, ๊ทธ๋ฆฌ๊ณ  cur[1..31] = prev[0..30] ์ด๋‹ค. ๊ทธ๋Ÿฌ๋‹ˆ ์ด์ „ ์ƒํƒœ์˜ ์•ž 31์นธ์€ ํ˜„์žฌ ์ƒํƒœ์— ๊ทธ๋Œ€๋กœ ๋‚จ์•„ ์žˆ๋‹ค. ์‚ฌ๋ผ์ง„ ๊ฑด prev[31] ํ•˜๋‚˜๋ฟ์ธ๋ฐ, ๊ทธ๊ฑด ์ฒซ ์ค„์„ ์˜ฎ๊ธฐ๋ฉด ๋‚˜์˜จ๋‹ค.

prev[31] = new ^ prev[21] ^ prev[1] ^ prev[0]
         = cur[0] ^ cur[22] ^ cur[2] ^ cur[1]

prev[21] ์€ cur[22], prev[1] ์€ cur[2], prev[0] ์€ cur[1] ์ด๋ฏ€๋กœ ์ „๋ถ€ ํ˜„์žฌ ์ƒํƒœ์˜ ๊ฐ’์œผ๋กœ ๋ฐ”๋€๋‹ค. ์ •๋ณด๊ฐ€ ํ•˜๋‚˜๋„ ์•ˆ ๋ฒ„๋ ค์กŒ์œผ๋‹ˆ ๋˜๊ฐ๊ธฐ๊ฐ€ ์œ ์ผํ•˜๊ฒŒ ๊ฒฐ์ •๋œ๋‹ค.

def unshift(state):
    prev31 = int(state[0]) ^ int(state[22]) ^ int(state[2]) ^ int(state[1])
    return state[1:] + str(prev31)

์ด๊ฑธ 32๋ฒˆ ๋ถ€๋ฅด๋ฉด ์‹œ๋“œ๋‹ค.

๋‚จ์€ ๊ฑด ํ‚ค์ŠคํŠธ๋ฆผ 32๋น„ํŠธ๋ฅผ ์ฑ„์šฐ๋Š” ์ผ์ด๋‹ค. ํ‰๋ฌธ DH{ ์™€ ์•”ํ˜ธ๋ฌธ ์•ž 3๋ฐ”์ดํŠธ๋ฅผ XOR ํ•˜๋ฉด 24๋น„ํŠธ๊ฐ€ ๋‚˜์˜จ๋‹ค. ๋ชจ์ž๋ž€ 8๋น„ํŠธ๋Š” ๋„ค ๋ฒˆ์งธ ํ‰๋ฌธ ๊ธ€์ž์— ์“ฐ์ธ ํ‚ค์ŠคํŠธ๋ฆผ์ด๋ผ ์•Œ ์ˆ˜ ์—†๋‹ค. ๊ทธ๋ž˜์„œ ๊ทธ ํ•œ ๋ฐ”์ดํŠธ๋ฅผ 256๊ฐ€์ง€๋กœ ํ›‘๋Š”๋‹ค. ํƒ์ƒ‰ ๊ณต๊ฐ„์ด 2^32 ์—์„œ 2^8 ๋กœ ์ค„์—ˆ๋‹ค.

ํ›„๋ณด๊ฐ€ ํ•˜๋‚˜๋กœ ์ขํ˜€์ง€๋Š”์ง€๋„ ํ™•์ธํ•ด ๋‘˜ ํ•„์š”๊ฐ€ ์žˆ๋‹ค. 256๊ฐœ ์ „๋ถ€๋ฅผ ๋ณตํ˜ธํ•ด์„œ ์กฐ๊ฑด๋ณ„๋กœ ์„ธ์–ด ๋ดค๋‹ค.

#!/usr/bin/env python3
"""ํ›„๋ณด 256๊ฐœ๊ฐ€ ๊ฐ๊ฐ ์–ด๋–ค ํ‰๋ฌธ์„ ๋ฑ‰๋Š”์ง€, ์–ด๋–ค ์กฐ๊ฑด์—์„œ ํ•˜๋‚˜๋กœ ์ขํ˜€์ง€๋Š”์ง€ ์„ผ๋‹ค.
 
์•ž 3๋ฐ”์ดํŠธ๋Š” ์–ด๋–ค ํ›„๋ณด๋ฅผ ๋„ฃ์–ด๋„ 'DH{' ๊ฐ€ ๋‚˜์˜จ๋‹ค โ€” ๊ทธ๊ฑธ๋กœ ์ƒํƒœ๋ฅผ ๋งŒ๋“ค์—ˆ์œผ๋‹ˆ ๋‹น์—ฐํ•˜๋‹ค.
๊ทธ๋ž˜์„œ 4๋ฒˆ์งธ ๊ธ€์ž๋ถ€ํ„ฐ๊ฐ€ ํŒ๋ณ„ ๊ทผ๊ฑฐ๋‹ค.
"""
import string
import sys
 
sys.path.insert(0, "extracted")
from cipher import STREAM
from solve import load_ct, unshift
 
ct = load_ct()
ks = "".join(f"{c ^ p:08b}" for c, p in zip(ct, b"DH{"))
printable = set(string.printable[:-5].encode())
 
ok_close, ok_all, hits = 0, 0, []
for guess in range(0x100):
    state = (ks + f"{guess:08b}")[::-1]
    for _ in range(32):
        state = unshift(state)
    pt = STREAM(int(state, 2), 32).decrypt(ct)
 
    if guess < 3:
        print(f"  ํ›„๋ณด {guess:3d}: {pt[:40]!r}")
    if pt[-1:] == b"}":
        ok_close += 1
    if set(pt) <= printable:
        ok_all += 1
        hits.append((guess, pt))
 
print()
print(f"[*] 'DH{{' ๋กœ ์‹œ์ž‘ํ•˜๋Š” ํ›„๋ณด : 256 / 256   (์ƒํƒœ๋ฅผ ๊ทธ๋ ‡๊ฒŒ ๋งŒ๋“ค์—ˆ์œผ๋‹ˆ ์ „๋ถ€)")
print(f"[*] '}}' ๋กœ ๋๋‚˜๋Š” ํ›„๋ณด     : {ok_close} / 256")
print(f"[*] ์ „๋ถ€ ์ถœ๋ ฅ๊ฐ€๋Šฅ ๋ฌธ์ž     : {ok_all} / 256")
for guess, pt in hits:
    print(f"[+] ์œ ์ผ ํ›„๋ณด (4๋ฒˆ์งธ ๊ธ€์ž ํ‚ค์ŠคํŠธ๋ฆผ {guess:08b}): {pt.decode()}")
python3 candidates.py

candidates.py ์‹คํ–‰ ๊ฒฐ๊ณผ โ€” ํ›„๋ณด 0ยท1ยท2 ๋Š” DH ์—ฌ๋Š” ์ค‘๊ด„ํ˜ธ ๋’ค๊ฐ€ ์ „๋ถ€ ๊นจ์ง„ ๋ฐ”์ดํŠธ์ด๊ณ , 256๊ฐœ ์ค‘ ๋‹ซ๋Š” ์ค‘๊ด„ํ˜ธ๋กœ ๋๋‚˜๋Š” ๊ฒƒ๋„ ์ „๋ถ€ ์ถœ๋ ฅ ๊ฐ€๋Šฅํ•œ ๊ฒƒ๋„ ๊ฐ๊ฐ 1๊ฐœ๋ฟ์ด๋ผ ํ›„๋ณด๊ฐ€ ํ•˜๋‚˜๋กœ ํ™•์ •๋œ๋‹ค
candidates.py ์‹คํ–‰ ๊ฒฐ๊ณผ โ€” ํ›„๋ณด 0ยท1ยท2 ๋Š” DH ์—ฌ๋Š” ์ค‘๊ด„ํ˜ธ ๋’ค๊ฐ€ ์ „๋ถ€ ๊นจ์ง„ ๋ฐ”์ดํŠธ์ด๊ณ , 256๊ฐœ ์ค‘ ๋‹ซ๋Š” ์ค‘๊ด„ํ˜ธ๋กœ ๋๋‚˜๋Š” ๊ฒƒ๋„ ์ „๋ถ€ ์ถœ๋ ฅ ๊ฐ€๋Šฅํ•œ ๊ฒƒ๋„ ๊ฐ๊ฐ 1๊ฐœ๋ฟ์ด๋ผ ํ›„๋ณด๊ฐ€ ํ•˜๋‚˜๋กœ ํ™•์ •๋œ๋‹ค

์•ž ์„ธ ๊ธ€์ž๋Š” ์–ด๋–ค ํ›„๋ณด๋ฅผ ๋„ฃ์–ด๋„ DH{ ๊ฐ€ ๋‚˜์˜จ๋‹ค. ๊ทธ๋ ‡๊ฒŒ ๋˜๋„๋ก ์ƒํƒœ๋ฅผ ๋งŒ๋“ค์—ˆ์œผ๋‹ˆ ํŒ๋ณ„์— ๋ชป ์“ด๋‹ค. ๋„ค ๋ฒˆ์งธ ๊ธ€์ž๋ถ€ํ„ฐ๊ฐ€ ๊ทผ๊ฑฐ๋‹ค. ๋งˆ์ง€๋ง‰ ๋ฐ”์ดํŠธ๊ฐ€ } ์ธ ํ›„๋ณด๊ฐ€ 1๊ฐœ, ์ „๋ถ€ ์ถœ๋ ฅ ๊ฐ€๋Šฅํ•œ ๋ฌธ์ž์ธ ํ›„๋ณด๋„ 1๊ฐœ. ๋‘˜์ด ๊ฐ™์€ ํ›„๋ณด๋‹ค.



๐Ÿš€ Full Exploit

์ •๋ฆฌํ•˜๋ฉด ์ด๋ ‡๋‹ค. ์•”ํ˜ธ๋ฌธ์„ ์ฝ๊ณ , ์•Œ๋ ค์ง„ ํ‰๋ฌธ์œผ๋กœ 24๋น„ํŠธ๋ฅผ ๋งŒ๋“ค๊ณ , 8๋น„ํŠธ๋ฅผ ๋ถ™์—ฌ 32๋น„ํŠธ๋ฅผ ์ฑ„์šฐ๊ณ , ๋’ค์ง‘์–ด์„œ ์ƒํƒœ๋กœ ์‚ผ๊ณ , 32๋ฒˆ ๋˜๊ฐ์•„ ์‹œ๋“œ๋ฅผ ์–ป๊ณ , ๊ทธ ์‹œ๋“œ๋กœ ์ „์ฒด๋ฅผ ๋ณตํ˜ธํ•œ๋‹ค. ํ•„ํ„ฐ๋Š” ๋‘ ๊ฐœ๋ฉด ์ถฉ๋ถ„ํ•˜๋‹ค.

#!/usr/bin/env python3
"""STREME REVERIE (DreamHack #1923, Bronze 2, crypto) ํ’€์ด.
 
32๋น„ํŠธ LFSR ์˜ ์ถœ๋ ฅ ๋น„ํŠธ๊ฐ€ ๊ทธ๋Œ€๋กœ ๋‹ค์Œ ์ƒํƒœ๋กœ ๋“ค์–ด๊ฐ€๋ฏ€๋กœ,
์—ฐ์†๋œ ํ‚ค์ŠคํŠธ๋ฆผ 32๋น„ํŠธ๋ฅผ ์•Œ๋ฉด ๊ทธ ์‹œ์ ์˜ ๋‚ด๋ถ€ ์ƒํƒœ๊ฐ€ ํ†ต์งธ๋กœ ๋ณต์›๋œ๋‹ค.
์•Œ๋ ค์ง„ ํ‰๋ฌธ "DH{" ๋Š” 24๋น„ํŠธ๋ฟ์ด๋ผ ๋‚จ์€ 8๋น„ํŠธ(=ํ”Œ๋ž˜๊ทธ 4๋ฒˆ์งธ ๊ธ€์ž์— ์“ฐ์ธ ํ‚ค์ŠคํŠธ๋ฆผ)๋งŒ
256๊ฐ€์ง€๋กœ ํ›‘๊ณ , ์ƒํƒœ๋ฅผ 32์Šคํ… ๋˜๊ฐ์•„ ์‹œ๋“œ๋ฅผ ์–ป๋Š”๋‹ค.
 
์‚ฌ์šฉ๋ฒ•: python3 solve.py            (๊ฐ™์€ ํด๋”์˜ extracted/output.txt ๋ฅผ ์ฝ๋Š”๋‹ค)
"""
import re
import string
from pathlib import Path
 
HERE = Path(__file__).resolve().parent
TAPS = (32, 22, 2, 1)          # x^32 + x^22 + x^2 + x + 1
 
 
# โ”€โ”€ ๋ฌธ์ œ์˜ STREAM ์„ ๊ทธ๋Œ€๋กœ ์˜ฎ๊ธด ๊ฒƒ (๊ฒ€์ฆ์šฉ) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
class STREAM:
    def __init__(self, seed, size):
        self.state = bin(seed)[2:].zfill(size)
        self.taps = TAPS
 
    def shift(self):
        new_bit = 0
        for tap in self.taps:
            new_bit ^= int(self.state[tap - 1])
        new_bit = str(new_bit)
        self.state = new_bit + self.state[:-1]
        return new_bit
 
    def getNbits(self, num):
        return "".join(self.shift() for _ in range(num))
 
    def decrypt(self, ciphertext):
        out = b""
        for c in ciphertext:
            out += bytes([c ^ int(self.getNbits(8), 2)])
        return out
 
 
# โ”€โ”€ LFSR ์„ ํ•œ ์Šคํ… ๋˜๊ฐ๊ธฐ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
def unshift(state):
    """state = new_bit + prev[:-1] ์ด๋ฏ€๋กœ prev[:31] ์€ ๊ทธ๋Œ€๋กœ ๋ณด์ด๊ณ ,
    ์‚ฌ๋ผ์ง„ prev[31] ์€ ํƒญ ๊ด€๊ณ„์‹์—์„œ ๋˜์‚ด๋ฆฐ๋‹ค."""
    prev31 = int(state[0]) ^ int(state[22]) ^ int(state[2]) ^ int(state[1])
    return state[1:] + str(prev31)
 
 
def load_ct():
    txt = (HERE / "extracted" / "output.txt").read_text()
    # "encrypted flag > ..." ์˜ ์•ž๋ถ€๋ถ„๋„ [0-9a-f] ๋กœ ์ฝํžˆ๋ฏ€๋กœ ๊ธธ์ด๋กœ ๊ณจ๋ผ๋‚ธ๋‹ค
    return bytes.fromhex(max(re.findall(r"[0-9a-f]{2,}", txt), key=len))
 
 
def main():
    ct = load_ct()
    known = b"DH{"
    # ์•Œ๋ ค์ง„ ํ‰๋ฌธ 3๋ฐ”์ดํŠธ โ†’ ํ‚ค์ŠคํŠธ๋ฆผ 24๋น„ํŠธ (๋จผ์ € ๋‚˜์˜จ ๋น„ํŠธ๊ฐ€ MSB)
    ks = "".join(f"{c ^ p:08b}" for c, p in zip(ct, known))
    print(f"[*] ์•”ํ˜ธ๋ฌธ {len(ct)}๋ฐ”์ดํŠธ, ์•Œ๋ ค์ง„ ํ‚ค์ŠคํŠธ๋ฆผ {len(ks)}๋น„ํŠธ: {ks}")
 
    printable = set(string.printable[:-5].encode())
    for guess in range(0x100):
        bits = ks + f"{guess:08b}"          # k1..k32
        # 32์Šคํ… ๋’ค์˜ ์ƒํƒœ๋Š” ๋ฐฉ๊ธˆ ๋‚˜์˜จ ๋น„ํŠธ๊ฐ€ ์•ž์— ์˜ค๋„๋ก ๋’ค์ง‘ํžŒ ์ˆœ์„œ๋‹ค
        state = bits[::-1]
        for _ in range(32):
            state = unshift(state)
 
        seed = int(state, 2)
        flag = STREAM(seed, 32).decrypt(ct)
        if flag[:3] != known or flag[-1:] != b"}":
            continue
        if not set(flag) <= printable:
            continue
        print(f"[+] seed = {seed} (0x{seed:08x}), 4๋ฒˆ์งธ ๊ธ€์ž ํ‚ค์ŠคํŠธ๋ฆผ = {guess:08b}")
        print(f"[+] FLAG: {flag.decode()}")
        return
 
    print("[-] ํ›„๋ณด 256๊ฐœ ๋ชจ๋‘ ์‹คํŒจ")
 
 
if __name__ == "__main__":
    main()
python3 solve.py

solve.py ์‹คํ–‰ ๊ฒฐ๊ณผ โ€” ์•Œ๋ ค์ง„ ํ‚ค์ŠคํŠธ๋ฆผ 24๋น„ํŠธ๋ฅผ ์ฐ์€ ๋’ค ์‹œ๋“œ 0xfe5db0a9 ์™€ ๋„ค ๋ฒˆ์งธ ๊ธ€์ž ํ‚ค์ŠคํŠธ๋ฆผ 11110011 ์„ ๋ณต์›ํ•˜๊ณ  68๋ฐ”์ดํŠธ ํ”Œ๋ž˜๊ทธ ์ „์ฒด๋ฅผ ์ถœ๋ ฅํ•œ๋‹ค
solve.py ์‹คํ–‰ ๊ฒฐ๊ณผ โ€” ์•Œ๋ ค์ง„ ํ‚ค์ŠคํŠธ๋ฆผ 24๋น„ํŠธ๋ฅผ ์ฐ์€ ๋’ค ์‹œ๋“œ 0xfe5db0a9 ์™€ ๋„ค ๋ฒˆ์งธ ๊ธ€์ž ํ‚ค์ŠคํŠธ๋ฆผ 11110011 ์„ ๋ณต์›ํ•˜๊ณ  68๋ฐ”์ดํŠธ ํ”Œ๋ž˜๊ทธ ์ „์ฒด๋ฅผ ์ถœ๋ ฅํ•œ๋‹ค

์‹œ๋“œ๋Š” 0xfe5db0a9, ํ”Œ๋ž˜๊ทธ๋Š” DH{ ๋’ค์— sha256 ์ฒ˜๋Ÿผ ์ƒ๊ธด hex 64๊ธ€์ž๊ฐ€ ๋ถ™์€ ํ˜•ํƒœ๋‹ค. ์ฒด๊ฐ ์‹คํ–‰ ์‹œ๊ฐ„์€ 0.01์ดˆ์ฏค์ด๋‹ค.


๐Ÿงช ๊ต์ฐจ๊ฒ€์ฆ ๋‘ ๊ฐ€์ง€

ํ”Œ๋ž˜๊ทธ๊ฐ€ ๋‚˜์™”๋‹ค๊ณ  ๋๋‚ด๋ฉด ์•„์‰ฝ๋‹ค. ์‹œ๋“œ๋ฅผ ์ œ๋Œ€๋กœ ๋ณต์›ํ•œ ๊ฒŒ ๋งž๋Š”์ง€ ๋‘ ๋ฐฉํ–ฅ์—์„œ ํ™•์ธํ–ˆ๋‹ค.

์ฒซ์งธ, ๋ณต์›ํ•œ ์‹œ๋“œ๋กœ ํ”Œ๋ž˜๊ทธ๋ฅผ ๋‹ค์‹œ ์•”ํ˜ธํ™”ํ•ด์„œ ๋ฐฐํฌ๋œ output.txt ์™€ ๋ฐ”์ดํŠธ ๋‹จ์œ„๋กœ ๋Œ€์กฐํ•œ๋‹ค. ๋ณตํ˜ธ๊ฐ€ ์šฐ์—ฐํžˆ ๊ทธ๋Ÿด๋“ฏํ•˜๊ฒŒ ๋‚˜์˜จ ๊ฒŒ ์•„๋‹ˆ๋ผ ์‹œ๋“œ ์ž์ฒด๊ฐ€ ๋งž๋‹ค๋Š” ๋œป์ด ๋œ๋‹ค.

#!/usr/bin/env python3
"""๋ณต์›ํ•œ ์‹œ๋“œ๋กœ flag ๋ฅผ ๋‹ค์‹œ ์•”ํ˜ธํ™”ํ•ด ๋ฐฐํฌ๋œ output.txt ์™€ ๋ฐ”์ดํŠธ ๋‹จ์œ„๋กœ ๋Œ€์กฐํ•œ๋‹ค."""
import sys
 
sys.path.insert(0, "extracted")
from cipher import STREAM
 
SEED = 0xFE5DB0A9
FLAG = b"DH{855083595b1557aeb8f60e7422e49405672b8ddf793700d884dae710909f9d0f}"
 
ct = bytes.fromhex(open("extracted/output.txt").read().split(">")[1].strip())
mine = STREAM(SEED, 32).encrypt(FLAG)
 
print(f"[*] ๋ฐฐํฌ๋œ ์•”ํ˜ธ๋ฌธ : {ct.hex()[:48]}... ({len(ct)}๋ฐ”์ดํŠธ)")
print(f"[*] ์žฌ์•”ํ˜ธํ™” ๊ฒฐ๊ณผ : {mine.hex()[:48]}... ({len(mine)}๋ฐ”์ดํŠธ)")
print(f"[+] ์™„์ „ ์ผ์น˜? {mine == ct}")
print(f"[+] ๋ณตํ˜ธ ๊ฒฐ๊ณผ   : {STREAM(SEED, 32).decrypt(ct).decode()}")
python3 verify.py

verify.py ์‹คํ–‰ ๊ฒฐ๊ณผ โ€” ๋ณต์›ํ•œ ์‹œ๋“œ 0xfe5db0a9 ๋กœ ํ”Œ๋ž˜๊ทธ๋ฅผ ๋‹ค์‹œ ์•”ํ˜ธํ™”ํ•œ 68๋ฐ”์ดํŠธ๊ฐ€ ๋ฐฐํฌ๋œ output.txt ์™€ ์™„์ „ ์ผ์น˜ํ•œ๋‹ค๋Š” True ํŒ์ •๊ณผ ๋ณตํ˜ธ ๊ฒฐ๊ณผ๋ฅผ ํ•จ๊ป˜ ๋ณด์—ฌ์ค€๋‹ค
verify.py ์‹คํ–‰ ๊ฒฐ๊ณผ โ€” ๋ณต์›ํ•œ ์‹œ๋“œ 0xfe5db0a9 ๋กœ ํ”Œ๋ž˜๊ทธ๋ฅผ ๋‹ค์‹œ ์•”ํ˜ธํ™”ํ•œ 68๋ฐ”์ดํŠธ๊ฐ€ ๋ฐฐํฌ๋œ output.txt ์™€ ์™„์ „ ์ผ์น˜ํ•œ๋‹ค๋Š” True ํŒ์ •๊ณผ ๋ณตํ˜ธ ๊ฒฐ๊ณผ๋ฅผ ํ•จ๊ป˜ ๋ณด์—ฌ์ค€๋‹ค

๋ฌธ์ œ๊ฐ€ ์ค€ cipher.py ๋ฅผ ๊ทธ๋Œ€๋กœ import ํ•ด์„œ ๋Œ๋ ธ๋‹ค. ์šฐ๋ฆฌ ๊ตฌํ˜„์ด ์•„๋‹ˆ๋ผ ์ถœ์ œ์ž ๊ตฌํ˜„์œผ๋กœ ์žฌ์•”ํ˜ธํ™”ํ•œ ๊ฒฐ๊ณผ๊ฐ€ ์ผ์น˜ํ•œ๋‹ค.

๋‘˜์งธ๋Š” SageMath ์ชฝ์ด๋‹ค. ์ฃผ์„์— ์ ํžŒ ๋‹คํ•ญ์‹์ด ์ •๋ง ์›์‹œ์ธ์ง€, ๊ทธ๋ฆฌ๊ณ  ์ฝ”๋“œ๊ฐ€ ์‹ค์ œ๋กœ ๋„๋Š” ์ ํ™”์‹์ด ๋ฌด์—‡์ธ์ง€ ํ™•์ธํ–ˆ๋‹ค.

ํƒญ ์ธ๋ฑ์‹ฑ ๋ฐฉ์‹ ๋•Œ๋ฌธ์— ์žฌ๋ฏธ์žˆ๋Š” ์–ด๊ธ‹๋‚จ์ด ํ•˜๋‚˜ ์žˆ๋‹ค. ์ฝ”๋“œ๊ฐ€ ๋„๋Š” ์ ํ™”์‹์€ k_t = k_(t-1) + k_(t-2) + k_(t-22) + k_(t-32) ์ธ๋ฐ, ์ด๊ฑด ์ฃผ์„์˜ x^32 + x^22 + x^2 + x + 1 ์ด ์•„๋‹ˆ๋ผ ๊ทธ ์—ญ์ˆœ ๋‹คํ•ญ์‹ x^32 + x^31 + x^30 + x^10 + 1 ์— ํ•ด๋‹นํ•œ๋‹ค. ์›์‹œ ๋‹คํ•ญ์‹์˜ ์—ญ์ˆœ๋„ ์›์‹œ๋ผ์„œ ์ฃผ๊ธฐ๋Š” ๋˜‘๊ฐ™์ด 2^32 โˆ’ 1 ์ด๋‹ค. ๊ทธ๋Ÿฌ๋‹ˆ ์‹ค์ „์—์„œ ๋ฌธ์ œ๊ฐ€ ๋˜์ง€๋Š” ์•Š๋Š”๋‹ค.

๋ง๋กœ๋งŒ ํ•˜๋ฉด ๋ฏฟ๊ธฐ ์–ด๋ ค์šฐ๋‹ˆ ๋ณต์›ํ•œ ํ‚ค์ŠคํŠธ๋ฆผ 544๋น„ํŠธ์— Berlekampโ€“Massey ๋ฅผ ๊ฑธ์–ด ์ตœ์†Œ ๋‹คํ•ญ์‹์„ ๋ฝ‘์•˜๋‹ค.

from sage.matrix.berlekamp_massey import berlekamp_massey
 
R.<x> = GF(2)[]
f = x^32 + x^22 + x^2 + x + 1          # cipher.py ์ฃผ์„์˜ ๋‹คํ•ญ์‹
g = f.reverse()                        # ์ฝ”๋“œ๊ฐ€ ์‹ค์ œ๋กœ ๋„๋Š” ์ ํ™”์‹
 
print("[1] ์ฃผ์„ ๋‹คํ•ญ์‹ f =", f)
print("    ๊ธฐ์•ฝ(irreducible)?", f.is_irreducible(), " ์›์‹œ(primitive)?", f.is_primitive())
print("    โ†’ ์ฃผ๊ธฐ 2^32-1 =", 2^32 - 1)
print()
print("[2] ์—ญ์ˆœ ๋‹คํ•ญ์‹ g =", g)
print("    ์›์‹œ(primitive)?", g.is_primitive())
print("    ์ฝ”๋“œ์˜ ์ ํ™”์‹: k_t = k_(t-1) + k_(t-2) + k_(t-22) + k_(t-32)")
print()
 
flag = b'DH{855083595b1557aeb8f60e7422e49405672b8ddf793700d884dae710909f9d0f}'
ct = bytes.fromhex(open('extracted/output.txt').read().split('>')[1].strip())
bits = ''.join(format(c ^^ p, '08b') for c, p in zip(ct, flag))
seq = [GF(2)(int(b)) for b in bits]
 
print("[3] ๋ณต์›ํ•œ ํ‚ค์ŠคํŠธ๋ฆผ", len(seq), "๋น„ํŠธ์— Berlekamp-Massey")
bm = berlekamp_massey(seq)
print("    ์ตœ์†Œ ๋‹คํ•ญ์‹ =", bm, " (์ฐจ์ˆ˜", bm.degree(), ")")
print("    == g ?", bm == g, "   == f ?", bm == f)
sage lfsr_poly.sage

SageMath ๋กœ ๋‹คํ•ญ์‹์„ ๊ฒ€์ฆํ•œ ํ™”๋ฉด โ€” ์ฃผ์„ ๋‹คํ•ญ์‹๊ณผ ๊ทธ ์—ญ์ˆœ ๋ชจ๋‘ ์›์‹œ๋ผ์„œ ์ฃผ๊ธฐ๊ฐ€ 2^32-1 ์ด๊ณ , ํ‚ค์ŠคํŠธ๋ฆผ 544๋น„ํŠธ์— Berlekamp-Massey ๋ฅผ ๊ฑด ์ตœ์†Œ ๋‹คํ•ญ์‹์ด ์—ญ์ˆœ ๋‹คํ•ญ์‹๊ณผ ์ผ์น˜ํ•œ๋‹ค
SageMath ๋กœ ๋‹คํ•ญ์‹์„ ๊ฒ€์ฆํ•œ ํ™”๋ฉด โ€” ์ฃผ์„ ๋‹คํ•ญ์‹๊ณผ ๊ทธ ์—ญ์ˆœ ๋ชจ๋‘ ์›์‹œ๋ผ์„œ ์ฃผ๊ธฐ๊ฐ€ 2^32-1 ์ด๊ณ , ํ‚ค์ŠคํŠธ๋ฆผ 544๋น„ํŠธ์— Berlekamp-Massey ๋ฅผ ๊ฑด ์ตœ์†Œ ๋‹คํ•ญ์‹์ด ์—ญ์ˆœ ๋‹คํ•ญ์‹๊ณผ ์ผ์น˜ํ•œ๋‹ค

BM ์ด ๋Œ๋ ค์ค€ ์ตœ์†Œ ๋‹คํ•ญ์‹์€ ์ฐจ์ˆ˜ 32, ๊ฐ’์€ x^32 + x^31 + x^30 + x^10 + 1. ์—ญ์ˆœ ๋‹คํ•ญ์‹๊ณผ ์ •ํ™•ํžˆ ๊ฐ™๋‹ค. ํ‚ค์ŠคํŠธ๋ฆผ๋งŒ ๋ณด๊ณ ๋„ LFSR ์˜ ๊ธธ์ด์™€ ํƒญ์ด ๋ณต์›๋œ๋‹ค๋Š” ๋œป์ด๊ณ , ์ด๊ฒŒ LFSR ์„ ํ‚ค์ŠคํŠธ๋ฆผ ์ƒ์„ฑ๊ธฐ๋กœ ์“ธ ๋•Œ ์ง„์งœ ๋ฌด์„œ์šด ์ง€์ ์ด๋‹ค. ์•Œ๋ ค์ง„ ํ‰๋ฌธ์ด 64๋น„ํŠธ๋งŒ ์žˆ์œผ๋ฉด BM ํ•˜๋‚˜๋กœ ๋‚˜๋จธ์ง€ ์ „๋ถ€๊ฐ€ ์˜ˆ์ธก๋œ๋‹ค. ์šฐ๋ฆฌ๋Š” DH{ 24๋น„ํŠธ๋ฐ–์— ์—†์–ด์„œ 8๋น„ํŠธ๋ฅผ ํ›‘์—ˆ์„ ๋ฟ์ด๋‹ค.



โ™ป๏ธ ์žฌํ˜„

๋ฐฐํฌ๋ณธ๊ณผ ์Šคํฌ๋ฆฝํŠธ๋งŒ ์žˆ์œผ๋ฉด ์„œ๋ฒ„ ์—†์ด ์–ธ์ œ๋“  ๋‹ค์‹œ ๋Œ์•„๊ฐ„๋‹ค.

#!/usr/bin/env bash
# STREME REVERIE (DreamHack #1923, Bronze 2, crypto) โ€” ํ•œ ๋ฐฉ ์žฌํ˜„.
# ๋ฐฐํฌ๋œ extracted/output.txt ๋งŒ์œผ๋กœ flag ๋ฅผ ๋ณต์›ํ•˜๊ณ  ์ •๋‹ต๊ณผ ๋Œ€์กฐํ•œ๋‹ค. ์„œ๋ฒ„ ๋ถˆํ•„์š”.
set -eu
cd "$(dirname "$(readlink -f "$0")")"
EXPECT=$(python3 -c "import json;print(json.load(open('๋ฌธ์ œ.json'))['flag'])" 2>/dev/null || echo '')
 
OUT=$(timeout 300 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

reproduce.sh ์‹คํ–‰ ๊ฒฐ๊ณผ โ€” solve.py ์ถœ๋ ฅ์„ ๊ทธ๋Œ€๋กœ ํ˜๋ฆฐ ๋’ค ๋ณต์›ํ•œ ํ”Œ๋ž˜๊ทธ๋ฅผ ๋ฌธ์ œ.json ์˜ ์ •๋‹ต๊ณผ ๋Œ€์กฐํ•ด PASS ๋กœ ๋๋‚œ๋‹ค
reproduce.sh ์‹คํ–‰ ๊ฒฐ๊ณผ โ€” solve.py ์ถœ๋ ฅ์„ ๊ทธ๋Œ€๋กœ ํ˜๋ฆฐ ๋’ค ๋ณต์›ํ•œ ํ”Œ๋ž˜๊ทธ๋ฅผ ๋ฌธ์ œ.json ์˜ ์ •๋‹ต๊ณผ ๋Œ€์กฐํ•ด PASS ๋กœ ๋๋‚œ๋‹ค

๋กœ์ปฌ ํ™˜๊ฒฝ์€ Ubuntu 25.10 ยท Python 3.13 ยท SageMath 10.9 ๋‹ค. ์Šคํฌ๋ฆฝํŠธ๋Š” ์ „๋ถ€ ํ‘œ์ค€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋งŒ ์“ฐ๊ณ , sage ๋Š” ๊ต์ฐจ๊ฒ€์ฆ์—๋งŒ ํ•„์š”ํ•˜๋‹ค.



๐Ÿ“ ๊ฒฐ๋ก 

LFSR ์€ ์ถœ๋ ฅ์ด ๊ณง ์ƒํƒœ๋‹ค

์ด ๋ฌธ์ œ์˜ ์ทจ์•ฝ์ ์€ ํƒญ ์œ„์น˜๋‚˜ ์‹œ๋“œ ํฌ๊ธฐ๊ฐ€ ์•„๋‹ˆ๋ผ ๊ตฌ์กฐ ์ž์ฒด์— ์žˆ๋‹ค. ํ”ผ๋ณด๋‚˜์น˜ํ˜• LFSR ์€ ์ƒˆ๋กœ ๋งŒ๋“  ๋น„ํŠธ๋ฅผ ๋ ˆ์ง€์Šคํ„ฐ์— ๋„ฃ์œผ๋ฉด์„œ ๋™์‹œ์— ๋ฐ–์œผ๋กœ ๋‚ด๋ณด๋‚ธ๋‹ค. ๋ ˆ์ง€์Šคํ„ฐ ๊ธธ์ด๋งŒํผ์˜ ์ถœ๋ ฅ์„ ๊ด€์ธกํ•˜๋ฉด ๊ทธ ์‹œ์ ์˜ ๋‚ด๋ถ€ ์ƒํƒœ๊ฐ€ ๊ทธ๋Œ€๋กœ ๋…ธ์ถœ๋œ๋‹ค. ์‹œ๋“œ๊ฐ€ 32๋น„ํŠธ๋“  128๋น„ํŠธ๋“  ๋งˆ์ฐฌ๊ฐ€์ง€๊ณ , ๊ธธ์ด๋ฅผ ๋Š˜๋ฆฌ๋ฉด ๊ด€์ธกํ•ด์•ผ ํ•  ๋น„ํŠธ๋งŒ ๋Š˜์–ด๋‚œ๋‹ค.

์ฃผ๊ธฐ์™€ ์˜ˆ์ธก ๋ถˆ๊ฐ€๋Šฅ์„ฑ์€ ๋‹ค๋ฅธ ์ด์•ผ๊ธฐ๋‹ค

์ฃผ์„์˜ ๋‹คํ•ญ์‹์€ ์›์‹œ์˜€๊ณ  ์ฃผ๊ธฐ๋Š” 2^32 โˆ’ 1 ์ด์—ˆ๋‹ค. ํ†ต๊ณ„ ๊ฒ€์ •์„ ํ†ต๊ณผํ•  ๋งŒํผ ๋žœ๋คํ•ด ๋ณด์ธ๋‹ค๋Š” ๋œป์ด์ง€๋งŒ, ๊ทธ๊ฑด ์˜ˆ์ธก์„ ๋ชป ํ•œ๋‹ค๋Š” ๋ณด์žฅ์ด ์•„๋‹ˆ๋‹ค. Berlekampโ€“Massey ๋Š” ๊ธธ์ด L ์ธ LFSR ์˜ ์ถœ๋ ฅ 2L ๋น„ํŠธ๋งŒ ์žˆ์œผ๋ฉด ๊ทธ LFSR ์„ ํ†ต์งธ๋กœ ๋ณต์›ํ•œ๋‹ค. ์ด ๋ฌธ์ œ์—์„œ๋Š” 64๋น„ํŠธ๋ฉด ์ถฉ๋ถ„ํ–ˆ๋‹ค.

์•Œ๋ ค์ง„ ํ‰๋ฌธ ์„ธ ๊ธ€์ž๊ฐ€ ๋Œ€๋ถ€๋ถ„์„ ํ•ด์คฌ๋‹ค

assert flag[:3] == b'DH{' ํ•œ ์ค„์ด 24๋น„ํŠธ๋ฅผ ๊ณต์งœ๋กœ ๋„˜๊ฒจ์คฌ๊ณ , ๋‚จ์€ 8๋น„ํŠธ๋งŒ ํ›‘์œผ๋ฉด ๋๋‹ค. ํ”Œ๋ž˜๊ทธ ํฌ๋งท์ด ๊ณ ์ •๋œ CTF ๋ฌธ์ œ์—์„œ๋Š” ์•ž๋จธ๋ฆฌ ๋ช‡ ๋ฐ”์ดํŠธ๊ฐ€ ํ•ญ์ƒ ๊ณต์งœ ํ‰๋ฌธ์ด๋‹ค. ์ŠคํŠธ๋ฆผ ์•”ํ˜ธ ๋ฌธ์ œ๋ฅผ ๋งŒ๋‚ฌ์„ ๋•Œ ์ œ์ผ ๋จผ์ € ๊ณ„์‚ฐํ•ด ๋ณผ ๊ฒƒ์ด ์ด "๊ณต์งœ ํ‚ค์ŠคํŠธ๋ฆผ"์˜ ๊ธธ์ด๋‹ค.

๊ทธ๋ž˜์„œ ์‹ค๋ฌด์—์„œ๋Š”

LFSR ์„ ๊ตณ์ด ์จ์•ผ ํ•œ๋‹ค๋ฉด ์ถœ๋ ฅ์„ ๊ทธ๋Œ€๋กœ ์“ฐ์ง€ ์•Š๋Š”๋‹ค. ์—ฌ๋Ÿฌ LFSR ์˜ ์ถœ๋ ฅ์„ ๋น„์„ ํ˜• ํ•จ์ˆ˜๋กœ ์„ž๊ฑฐ๋‚˜(A5/1, E0), ํด๋Ÿญ์„ ๋ถˆ๊ทœ์น™ํ•˜๊ฒŒ ํ•˜๊ฑฐ๋‚˜, ํ•„ํ„ฐ ํ•จ์ˆ˜๋ฅผ ์”Œ์›Œ ์„ ํ˜•์„ฑ์„ ๊นฌ๋‹ค. ๋‹ค๋งŒ ๊ทธ๋ ‡๊ฒŒ ๋งŒ๋“  ๋ฐฉ์‹๋“ค๋„ ํ•˜๋‚˜์”ฉ ๊นจ์ ธ ์™”๋‹ค. ์ง€๊ธˆ ์ƒˆ๋กœ ์„ค๊ณ„ํ•œ๋‹ค๋ฉด ChaCha20 ๊ฐ™์€ ๊ฒ€์ฆ๋œ ์ŠคํŠธ๋ฆผ ์•”ํ˜ธ๋ฅผ ์“ฐ๋Š” ์ชฝ์ด ๋งž๋‹ค.

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

Comments

๋Œ“๊ธ€

0๊ฐœ

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

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

Related

๊ด€๋ จ ๊ธ€

3๊ฐœ
[๐Ÿฅˆ Silver 2] ์ƒ์ž๋ฅผ 16์นธ์œผ๋กœ ์ค„์ธ ๋Œ€๊ฐ€ โ€” DreamHack Nice forest picture ํ’€์ด
blog

[๐Ÿฅˆ Silver 2] ์ƒ์ž๋ฅผ 16์นธ์œผ๋กœ ์ค„์ธ ๋Œ€๊ฐ€ โ€” DreamHack Nice forest picture ํ’€์ด

RC4 ๋ฅผ ๊ทธ๋Œ€๋กœ ์˜ฎ๊ฒจ ๋†“๊ณ  S ๋ฐ•์Šค๋งŒ 256์นธ์—์„œ 16์นธ์œผ๋กœ ์ค„์ธ ์ŠคํŠธ๋ฆผ ์•”ํ˜ธ๋‹ค. ์›์†Œ๊ฐ€ 0์—์„œ 15 ๋ฟ์ด๋ผ ์ถœ๋ ฅ๋„ ๋‹ˆ๋ธ” ํ•˜๋‚˜์—ฌ์„œ ํ‰๋ฌธ์˜ ์ƒ์œ„ ์ ˆ๋ฐ˜์ด ์•”ํ˜ธ๋ฌธ์— ๊ทธ๋Œ€๋กœ ๋‚จ๊ณ , ์ถœ๋ ฅ์ด S ๋‘ ์นธ์˜ XOR ์ด๋ผ ํ•œ ์นธ๋งŒ ๊ฐ€์ •ํ•˜๋ฉด ๋‚˜๋จธ์ง€๊ฐ€ ์ค„์ค„์ด ๊ฐ•์ œ๋œ๋‹ค. PNG ์‹œ๊ทธ๋‹ˆ์ฒ˜ 16๋ฐ”์ดํŠธ๋ฅผ ๊ฒน์ณ ์ดˆ๊ธฐ ์ˆœ์—ด์„ 0.7์ดˆ์— ๋˜์ฐพ๊ณ  4.3MB ๋ฅผ ํ†ต์งธ๋กœ ๋ณตํ˜ธํ–ˆ๋‹ค.
#dreamhack#ctf#crypto+6
2026-08-21#dreamhack +5
[๐Ÿฅˆ Silver 1] ํ‚ค์ŠคํŠธ๋ฆผ 1๋น„ํŠธ๋กœ ๋˜๋Œ๋ฆฐ SNOW 3G โ€” DreamHack snow ํ’€์ด
blog

[๐Ÿฅˆ Silver 1] ํ‚ค์ŠคํŠธ๋ฆผ 1๋น„ํŠธ๋กœ ๋˜๋Œ๋ฆฐ SNOW 3G โ€” DreamHack snow ํ’€์ด

20KB ์งœ๋ฆฌ ์ŠคํŠธ๋ฆฝํŠธ ELF ์•ˆ์— 3GPP ํ‘œ์ค€ ์ŠคํŠธ๋ฆผ ์•”ํ˜ธ SNOW 3G ๊ฐ€ ํ†ต์งธ๋กœ ๋“ค์–ด ์žˆ์—ˆ๋‹ค. ํ‚ค๋Š” "Merry-Christmas!" ๋กœ ๊ณ ์ •์ด๊ณ  IV ๋Š” ์šฐ๋ฆฌ๊ฐ€ ๋„ฃ๋Š” ์ž…๋ ฅ์˜ ์•ž 16๋ฐ”์ดํŠธ โ€” ์ฆ‰ ๋ณต์›ํ•ด์•ผ ํ•  ๊ฐ’์ด ์ž๊ธฐ ์ž์‹ ์„ ์•”ํ˜ธํ™”ํ•œ๋‹ค. ์›Œ๋“œ๋งˆ๋‹ค ํ•„์š”ํ•œ ์ •๋ณด๊ฐ€ ํ‚ค์ŠคํŠธ๋ฆผ์˜ ์ตœํ•˜์œ„ 1๋น„ํŠธ๋ฟ์ด๋ผ๋Š” ์ ์„ ์ด์šฉํ•ด 256๊ฐ€์ง€๋งŒ ํ™•์ธํ•˜๊ณ  32๊ธ€์ž๋ฅผ ๋˜๋Œ๋ ธ๋‹ค.
#dreamhack#ctf#reversing+6
2026-08-17#dreamhack +4
[๐Ÿฅ‰ Bronze 3] 16๋น„ํŠธ ์‹œ๋“œ์งœ๋ฆฌ ์ŠคํŠธ๋ฆผ ์•”ํ˜ธ ์ „์ˆ˜์กฐ์‚ฌ โ€” DreamHack STREAMer-Prototype ํ’€์ด
blog

[๐Ÿฅ‰ Bronze 3] 16๋น„ํŠธ ์‹œ๋“œ์งœ๋ฆฌ ์ŠคํŠธ๋ฆผ ์•”ํ˜ธ ์ „์ˆ˜์กฐ์‚ฌ โ€” DreamHack STREAMer-Prototype ํ’€์ด

16๋น„ํŠธ ์ƒํƒœ๋ฅผ ๋น„ํŠธ ํšŒ์ „์‹œ์ผœ ํ‚ค์ŠคํŠธ๋ฆผ์„ ๋งŒ๋“œ๋Š” ์ŠคํŠธ๋ฆผ ์•”ํ˜ธ๋กœ ํ”Œ๋ž˜๊ทธ๋ฅผ XORํ•œ ๋ฌธ์ œ. ์‹œ๋“œ๊ฐ€ getrandbits(16)์ด๋ผ ๊ฒฝ์šฐ์˜ ์ˆ˜๊ฐ€ 65,536๊ฐœ๋ฟ์ด๊ณ , ํ”Œ๋ž˜๊ทธ๊ฐ€ DH๋กœ ์‹œ์ž‘ํ•œ๋‹ค๋Š” ์กฐ๊ฑด์œผ๋กœ ์ •๋‹ต ์‹œ๋“œ๋ฅผ ๊ฐ€๋ ค๋‚ธ๋‹ค. ์‹ค์ œ๋กœ ์ „์ˆ˜์กฐ์‚ฌ๋ฅผ ๋Œ๋ ค๋ณด๋ฉด 65536๊ฐœ ์ค‘ ์ ‘๋‘์‚ฌ ์กฐ๊ฑด์„ ๋งŒ์กฑํ•˜๋Š” ์‹œ๋“œ๋Š” ์ •ํ™•ํžˆ ํ•˜๋‚˜๋ฟ์ด๋ผ, ์šฐ์—ฐํžˆ ๊ฑธ๋ฆด ์œ„ํ—˜ ์—†์ด ํ™•์‹คํ•˜๊ฒŒ ์ •๋‹ต์ด ๊ฐ€๋ ค์ง„๋‹ค.
#dreamhack#ctf#crypto+3
2026-07-13#dreamhack +4