seed.py 소스코드
# DREAMHACK CHALLENGE - INSECURE SEED #
import os
import random
from typing import List
class Seed:
def __init__ (self) -> None:
self.a: int = os.urandom(1)
@staticmethod
def GenerateSeed() -> List[int]:
seed: bytearray = bytearray(random.getrandbits(8) for x in range(4))
return list(seed)
def CalculateKey(self, seed: List[int]) -> bool:
key: List[int] = [0] * len(seed)
for i in range(len(seed)):
result: bytes = bytes([self.a[j] ^ seed[i] for j in range(len(self.a))])
key[i] = int.from_bytes(result, byteorder='little')
return key
def Authentication(self, seed: List[int], k: List[int]) -> bool:
key = self.CalculateKey(seed)
if key == k:
print('Correct!!')
return True
else:
print('Invalid Key!!')
return False
if __name__ == "__main__":
s = Seed()
seed = s.GenerateSeed()
print(f"Seed: {seed}")
while 1:
k = input("Key: ").strip().split() # input ex) 41 42 43 44
kl = [int(x) for x in k]
if s.Authentication(seed, kl):
break
print('DH{fake_flag}')
🔎 DREAMHACK CHALLENGE - INSECURE SEED 코드 분석
이 코드는 Seed 클래스를 이용한 간단한 인증 시스템을 구현하고 있어.
한 줄씩 분석하면서 동작 원리를 이해해볼게.
📌 1️⃣ 라이브러리 및 타입 힌트 선언
import os
import random
from typing import List
- os: 난수를 생성하기 위해 사용 (os.urandom())
- random: 랜덤한 seed 값을 생성하기 위해 사용 (random.getrandbits(8))
- List: **타입 힌트(Type Hinting)**를 위해 사용 (List[int])
📌 2️⃣ Seed 클래스 정의
class Seed:
def __init__ (self) -> None:
self.a: int = os.urandom(1)
- Seed 클래스가 생성될 때, self.a에 1바이트 크기의 난수를 저장함.
- os.urandom(1): 암호학적으로 안전한 1바이트 난수를 생성 (하지만 이 코드에는 보안상 문제가 있음).
📌 3️⃣ GenerateSeed() - 4바이트 랜덤 Seed 생성
@staticmethod
def GenerateSeed() -> List[int]:
seed: bytearray = bytearray(random.getrandbits(8) for x in range(4))
return list(seed)
- random.getrandbits(8): 0~255(1바이트) 범위에서 랜덤한 값을 생성.
- for x in range(4): 4개의 바이트 값을 생성해서 bytearray로 만듦.
- return list(seed): bytearray를 리스트로 변환하여 반환.
📌 예제 실행 결과
Seed: [173, 45, 89, 250] # 랜덤한 4바이트 값이 출력됨
📌 4️⃣ CalculateKey() - Key 계산
def CalculateKey(self, seed: List[int]) -> List[int]:
key: List[int] = [0] * len(seed)
for i in range(len(seed)):
result: bytes = bytes([self.a[j] ^ seed[i] for j in range(len(self.a))])
key[i] = int.from_bytes(result, byteorder='little')
return key
- 주어진 seed를 기반으로 키(key)를 생성하는 함수.
- self.a[j] ^ seed[i]:
- XOR 연산을 수행하여 키를 생성 (self.a[j]와 seed[i]를 XOR 연산).
- XOR 연산의 특징: 같은 값을 XOR하면 다시 원래 값이 됨 (X ^ Y ^ Y = X).
- int.from_bytes(result, byteorder='little'):
- 바이트 값을 정수(int)로 변환 (작은 엔디언 방식).
- 결과적으로 seed를 XOR 변환한 새로운 4바이트 크기의 키 리스트를 반환.
📌 예제 실행
self.a = b'\x5a' # self.a는 1바이트 난수 (예: 0x5A = 90)
seed = [173, 45, 89, 250]
key = [90 ^ 173, 90 ^ 45, 90 ^ 89, 90 ^ 250] # XOR 연산 수행
# key = [247, 119, 3, 160]
📌 5️⃣ Authentication() - 키 인증
def Authentication(self, seed: List[int], k: List[int]) -> bool:
key = self.CalculateKey(seed)
if key == k:
print('Correct!!')
return True
else:
print('Invalid Key!!')
return False
- 사용자가 입력한 키(k)와 CalculateKey(seed)에서 계산한 키가 같은지 비교.
- 일치하면 "Correct!!" 출력 후 True 반환.
- 일치하지 않으면 "Invalid Key!!" 출력 후 False 반환.
📌 6️⃣ main() - 실행 코드
if __name__ == "__main__":
s = Seed()
seed = s.GenerateSeed()
print(f"Seed: {seed}")
while 1:
k = input("Key: ").strip().split() # input ex) 41 42 43 44
kl = [int(x) for x in k]
if s.Authentication(seed, kl):
break
print('DH{fake_flag}')
🔹 실행 과정
- Seed 객체를 생성 (s = Seed()).
- 4바이트 크기의 랜덤 seed를 생성 (seed = s.GenerateSeed()).
- seed 값을 출력 (print(f"Seed: {seed}")).
- 사용자가 Key를 입력하면 split()을 사용해 리스트로 변환 (kl = [int(x) for x in k]).
- Authentication() 함수로 입력된 키를 검증.
- 정답이면 "Correct!!" 출력 후 종료.
- 틀리면 "Invalid Key!!" 출력 후 다시 입력받음.
- "DH{fake_flag}"를 출력하여 종료.
💡 코드 실행 예제
✅ 올바른 입력을 했을 경우
Seed: [173, 45, 89, 250]
Key: 247 119 3 160 # 사용자가 올바른 Key를 입력
Correct!!
DH{fake_flag}
- CalculateKey(seed)에서 나온 값과 동일한 값을 입력하면 정답.
❌ 잘못된 입력을 했을 경우
Seed: [173, 45, 89, 250]
Key: 100 100 100 100
Invalid Key!!
- 올바른 키를 입력하지 않으면 계속 반복됨.
📌 7️⃣ 취약점 분석
- seed 값이 랜덤하지만 매우 작은 크기
- GenerateSeed()는 **4바이트 크기의 seed**만 생성.
- brute-force 공격이 최대 2^32(약 42억) 개로 비교적 작은 편.
- XOR 연산을 사용한 단순 암호화
- XOR 연산의 특징을 이용하면 쉽게 복호화 가능.
- CalculateKey()가 self.a[j] ^ seed[i]로 XOR을 수행하므로, 같은 연산을 반대로 수행하면 원래 seed 값을 알아낼 수 있음.
- self.a가 os.urandom(1)로 고정되어 있음
- self.a는 1바이트(0~255) 난수이므로 256개의 경우만 테스트하면 쉽게 찾을 수 있음.
🛠️ 8️⃣ 공격 방법
- Step 1: 프로그램 실행 후 seed 값을 확인.
- Step 2: self.a의 256개 후보를 생성하여 가능한 모든 XOR 결과를 계산.
- Step 3: CalculateKey(seed)의 결과를 맞출 때까지 brute-force 실행.
- Step 4: 올바른 키를 입력하여 인증 우회.
s = Seed()
seed = [173, 45, 89, 250] # 주어진 seed 값
for a in range(256):
key = [a ^ seed[i] for i in range(len(seed))]
print(f"Trying a={a}: Key={key}")
# 정답인 경우
if s.Authentication(seed, key):
print(f"Found correct key: {key}")
break
➡ 결과적으로 256번 이하의 연산으로 정답을 찾을 수 있음.
🔥 결론
✅ 코드는 XOR 연산을 사용한 간단한 인증 시스템
✅ self.a가 1바이트로 제한되어 brute-force 공격이 가능
✅ 256번 이하의 연산으로 정답을 찾아 인증 우회 가능
💡 해결 방법:
- self.a를 1바이트가 아닌 16~32바이트로 증가
- GenerateSeed()에서 더 많은 랜덤 바이트를 생성
- key 값을 HMAC, SHA-256 등으로 해시 처리하여 공격을 어렵게 만듦.
브포를 하면 될 것 같은데, 서버에 exploit하는 공격 코드가
p.recvuntil, p =remote...나도 ㅜㅠ 쉬운 코드는 직접 짜보고싶은데 열심히,,눈팅 중...
https://tmddus0129.tistory.com/136
[CRYPTO] Insecure-Seed
문제 분석먼저 주어진 소스코드를 자세히 보자.1234567891011121314if __name__ == "__main__": s = Seed() seed = s.GenerateSeed() print(f"Seed: {seed}") while 1: k = input("Key: ").st
tmddus0129.tistory.com
이제 기존의 pwn 라이브러리를 사용하여 서버에 연결하고, Seed 값을 받아서 key를 brute-force하는 코드를 개선해줄게.
🔧 개선된 Exploit 코드 (자동화 + 최적화)
from pwn import *
# 서버에 연결
p = remote("host1.dreamhack.games", 21853)
context.log_level = "debug"
# Seed 값 수신
p.recvuntil(b"Seed: [")
seed = []
# 4개의 Seed 값을 리스트로 저장
for _ in range(3): # 처음 3개 값은 ", "로 구분됨
s = p.recvuntil(b", ").decode()[:-2] # ", " 제거
seed.append(int(s))
s = p.recvuntil(b"]").decode()[:-1] # 마지막 값은 "]"로 구분됨
seed.append(int(s))
success(f"Received Seed: {seed}") # Seed 값 출력
# Brute-force로 key 찾기
for i in range(256):
key = [str(i ^ seed[j]).encode() for j in range(4)] # XOR 연산
success(f"Trying a={i}, Key: {key}") # 시도하는 key 출력
# Key 값을 "00 00 00 00" 형태로 변환
st = b" ".join(key)
p.sendline(st)
# 서버 응답 받기
response = p.recvline().strip()
print(f"[Server Response] {response.decode()}")
# 정답을 찾으면 종료
if b"Correct!!" in response:
success(f"✅ Found correct key: {st.decode()}")
break
# 종료
p.interactive()
📌 개선된 점
✅ Seed 값을 더 간결하게 수신 (recvuntil(b", ")을 활용)
✅ Key를 찾는 과정을 success()를 활용해 디버깅 가능
✅ 서버 응답을 바로 확인하여 올바른 Key가 나오면 즉시 종료
✅ Brute-force 과정에서 서버가 차단되지 않도록 정리된 방식으로 진행
🚀 실행 방법
python3 exploit.py
- 실행하면 서버에서 Seed 값을 받아 Key를 계산 후 자동 입력.
- "Correct!!" 응답이 나오면 정답을 찾은 것.
- DreamHack 플래그를 획득할 수 있음! 🎯
이제 실행해서 플래그를 획득해보자! 🚀
번외
요건 gpt가 짜준 소켓 코드
💡 해결 방법
- 서버와의 연결을 유지하여 각 세션에서 시드를 받아오고 그에 맞는 key 값을 실시간으로 찾아서 전송하는 방식.
- 각 시드에 대해 brute-force 방식으로 key를 찾아 전송하는 방법을 사용하여 자동으로 인증을 통과하도록 해야 함.
🔧 1️⃣ 서버와의 연결 후, 시드를 받아서 key를 찾고 인증을 수행
서버와 상호작용을 통해 시드 값을 받고 brute-force를 수행하여 key를 찾는 방식
import socket
# 서버 정보
HOST = "host1.dreamhack.games"
PORT = 21853
# 서버에 연결하는 함수
def connect_to_server():
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))
return s
# brute-force로 key를 찾는 함수
def brute_force_key(seed, s):
for a in range(256):
key = [a ^ seed[i] for i in range(len(seed))] # XOR 역연산
key_str = " ".join(f"{x:02X}" for x in key) # "00 00 00 00" 형식으로 변환
print(f"Trying a={a}: Key={key_str}")
# key 값을 서버에 전송
s.sendall((key_str + "\n").encode())
# 서버 응답 받기
response = s.recv(1024).decode().strip()
print(f"[Server Response] {response}")
# 정답을 찾으면 종료
if "Correct!!" in response:
print(f"✅ Found correct key: {key_str}")
return key_str # 올바른 key를 반환
return None # 아무 key도 맞지 않으면 None 반환
# 서버와 상호작용하는 함수
def interact_with_server():
with connect_to_server() as s:
# 서버에서 초기 메시지 받기 (예: Seed 값 요청)
data = s.recv(1024).decode()
print(f"[Server] {data}")
# seed 값만 정확히 추출하도록 수정
try:
seed_str = data.split("\n")[0] # 첫 번째 줄만 가져옴
seed_str = seed_str.split("[")[1].split("]")[0] # 숫자 리스트 부분만 남김
seed = [int(x.strip()) for x in seed_str.split(",")] # 리스트로 변환
except Exception as e:
print(f"❌ Seed parsing error: {e}")
return
print(f"[Debug] Received Seed: {seed}")
# brute-force로 key 찾기
key_str = brute_force_key(seed, s)
if key_str:
print(f"🎯 Exploit successful! Key: {key_str}")
else:
print("❌ Exploit failed. No valid key found.")
# 실행
interact_with_server()
🔎 코드 설명
- connect_to_server():
- host1.dreamhack.games:21853에 TCP 연결을 생성하고 서버와 상호작용을 시작합니다.
- brute_force_key():
- seed 값을 받아서, XOR 연산을 통해 key 값을 brute-force로 찾습니다.
- key_str는 00 00 00 00 형식으로 변환되어 서버에 전송됩니다.
- interact_with_server():
- 서버로부터 시드 값을 받아 brute-force로 key를 찾고, 이를 서버로 전송합니다.
- 서버의 응답을 받아 올바른 키를 찾을 때까지 계속 시도할 수 있습니다.
- "Correct!!" 응답이 나오면 정답을 찾은 것입니다.
🚀 실행 방법
python3 exploit.py
이제 서버와 상호작용하면서 매번 새로운 시드를 받고, 그에 맞는 key를 찾아서 전송하게 됩니다.
- 서버가 매번 다른 시드를 보내므로 그에 맞춰 key를 찾아 전송하는 방식입니다.
🔥 결론
✅ 서버에서 시드를 받아 그에 맞는 key를 brute-force 방식으로 찾아 서버에 전송하는 코드
✅ 올바른 key를 찾았을 때 "Correct!!" 메시지를 출력하고, 인증이 완료됩니다.
✅ 반복적으로 key를 입력하여 인증 우회를 시도합니다.
이제 실행하면 서버에서 주어진 시드 값에 맞는 key를 찾아 인증을 우회할 수 있습니다. 🚀
너가 준 코드보다 확실히 pwn exploit 코드가 더 좋지?
✅ pwn을 이용한 Exploit 코드가 더 좋은 이유
네, 확실히 pwn 라이브러리를 활용한 exploit 코드가 더 강력하고 효율적이야.
이를 일반 소켓(socket) 코드와 비교했을 때 왜 pwn이 더 적합한지 정리해볼게.
📌 1️⃣ pwn 라이브러리의 강점
- 간결한 코드
- p.recvuntil(b"Seed: [") 같은 함수로 빠르게 원하는 데이터를 수신할 수 있음.
- 일반적인 socket.recv()을 사용할 경우, 데이터를 수동으로 파싱해야 함.
- 자동화된 디버깅 지원
- context.log_level = "debug"를 설정하면 패킷 송수신 정보를 자동으로 확인 가능.
- success(), info(), warn(), error() 등의 함수를 활용해 exploit 과정을 추적할 수 있음.
- 편리한 입력 및 출력 처리
- p.sendline() → 자동으로 개행 추가 (\n), 따로 인코딩할 필요 없음.
- p.recvline() → 서버 응답을 간결하게 받음, 일반 socket 코드보다 직관적.
- 인터랙티브 모드 (p.interactive()) 지원
- Exploit 성공 후, 남은 세션을 직접 조작할 수 있음.
- 일반 socket 코드에서는 따로 구현해야 하는 기능.
🔧 2️⃣ 비교: pwn 코드 vs 일반 socket 코드
🔹 pwn 코드
from pwn import *
p = remote("host3.dreamhack.games", 17660)
context.log_level = "debug"
p.recvuntil(b"Seed: [")
seed = [int(p.recvuntil(b", ").decode()[:-2]) for _ in range(3)]
seed.append(int(p.recvuntil(b"]").decode()[:-1]))
success(f"Received Seed: {seed}")
for i in range(256):
key = [str(i ^ seed[j]).encode() for j in range(4)]
success(f"Trying a={i}, Key: {key}")
p.sendline(b" ".join(key))
response = p.recvline().strip()
print(f"[Server Response] {response.decode()}")
if b"Correct!!" in response:
success(f"✅ Found correct key: {b' '.join(key).decode()}")
break
p.interactive()
🔹 일반 socket 코드
import socket
HOST = "host3.dreamhack.games"
PORT = 17660
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.connect((HOST, PORT))
data = s.recv(1024).decode()
print(f"[Server] {data}")
seed = list(map(int, data.split("[")[1].split("]")[0].split(",")))
print(f"[Debug] Received Seed: {seed}")
for a in range(256):
key = [a ^ seed[i] for i in range(len(seed))]
key_str = " ".join(map(str, key))
print(f"Trying a={a}: Key={key_str}")
s.sendall((key_str + "\n").encode())
response = s.recv(1024).decode()
print(f"[Server Response] {response}")
if "Correct!!" in response:
print(f"✅ Found correct key: {key_str}")
break
🚀 3️⃣ 결론
✅ Exploit을 빠르게 실행하려면 pwn이 압도적으로 유리
✅ pwn 라이브러리는 recvuntil(), sendline(), interactive() 등의 기능을 제공하여 편리한 조작 가능
✅ 로그 디버깅(context.log_level = "debug") 기능 덕분에 문제가 발생할 경우 바로 추적 가능
✅ CTF에서는 socket 코드보다 pwn 코드가 훨씬 간결하고 효율적
🎯 최종 결론: pwn 라이브러리를 사용한 Exploit 코드가 훨씬 강력하고 효율적이므로 CTF에서는 최우선적으로 사용하는 것이 좋다! 🚀
'Dreamhack > Dreamhack Wargame (Challenge)' 카테고리의 다른 글
[154] IT 비전공자 [dreamhack]Badge문제 풀기 (0) | 2025.02.11 |
---|---|
[153] IT 비전공자 [dreamhack]INVERSE문제 풀기 (0) | 2025.02.10 |
[151] IT 비전공자 [dreamhack]Password in the gift box문제 풀기 (0) | 2025.02.10 |
[150] IT 비전공자 [dreamhack]No shift please!문제 풀기 (0) | 2025.02.07 |
[149] IT 비전공자 [dreamhack]Arm Training-v2문제 풀기 (0) | 2025.02.06 |