Dreamhack - addition-quiz

·2025년 10월 3일
1

Dreamhack-Writeups

목록 보기
43/46

addition-quiz

문제 링크

https://dreamhack.io/wargame/challenges/1114

문제 설명

랜덤한 2개의 숫자를 더한 결과가 입력 값과 일치하는지 확인하는 과정을 50번 반복하는 프로그램입니다. 모두 일치하면 flag 파일에 있는 플래그를 출력합니다. 알맞은 값을 입력하여 플래그를 획득하세요.

플래그 형식은 DH{...} 입니다.


Hint : pwntools

풀이과정

  1. 주어진 VM에 접속해 보았습니다. 천의자리 수 두개를 더하란 문제가 나오고, 1초 내에 바로 Time Out 이라며 프로그램이 꺼졌습니다.

  1. 코드를 확인해 보았습니다.
    for (int i = 0; i < 50; i++){
        alarm(1);
        num1 = rand() % 10000;
        num2 = rand() % 10000;
        printf("%d+%d=?\n", num1, num2);
        scanf("%d", &inpt);

        if(inpt != num1 + num2){
            printf("Wrong...\n");
            return 0;
        }
    } 
    puts("Nice!");
    puts(flag);

    return 0;
}

1초동안 50번, 연산을 해야 결과가 출력됩니다. 인간의 힘으로는 풀 수 없는 문제입니다.

  1. 문제 힌트로 pwntools 을 활용하라고 나와있습니다.
  • pwntools 이란?

스템 해킹 문제를 빠르고 효율적으로 풀기 위해 설계된 파이썬(Python) 라이브러리이자 프레임워크입니다.

해킹 스크립트를 작성하기 위한 강력한 도구 모음이라고 생각하면 됩니다.

pwntools 툴을 활용해 1초에 50번 계산하는 코드를 짜고, 문제를 풀어야 함을 유추해볼 수 있었습니다.

  1. pwntools 툴을 깔아줍니다. 이때 venv 가상머신을 활용해야 합니다.

우분투에서는 현재 사용자가 임의로 파이썬 환경에 라이브러리를 설치하는걸 막고 있습니다. 따라서 바로 명령어를 입력하였을 땐 설치가 되지 않습니다. 따라서 우분투 내에서 가상환경인 venv를 활용하여 pwntools 을 설치해야 합니다.

python3 -m venv venv  # 가상 환경 생성

source venv/bin/activate  # 가상 환경 활성화

pip install pwntools # pwntools 설치

위의 명령어를 차례로 입력하여 venv 와 pwntools 을 설치합니다.

  1. 문제 해결을 위한 파이썬 스크립트를 작성합니다. 저는 제미나이를 활용하여 코드를 생성했습니다.

우분투 내의 텍스트 편집기를 열고, 저장을 파일명.py 로 하면 파이썬 코드를 저장,활용할 수 있습니다.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

from pwn import * # pwntools를 쓰기 위해 호출해야 함
import re

# 연결 설정 (로컬 파일 또는 원격 서버)
# ------------------------------------
HOST = 'host8.dreamhack.games'  # 실제 서버 주소로 변경하세요
PORT = 24238                    # 실제 포트 번호로 변경하세요
# ------------------------------------

def solve_math_challenge():
    # 서버에 연결을 시도합니다.
    try:
        p = remote(HOST, PORT)
        print(f"[*] 연결 성공! {HOST}:{PORT}")
    except Exception as e:
        print(f"[ERROR] 서버 연결 실패: {e}")
        return

    print("[*] 50개의 덧셈 문제를 시작합니다.")

    # 1. 첫 번째 문제 수신 (루프 진입 전에 한 번만 수행)
    # 서버가 시작하면 바로 첫 번째 문제를 보냅니다.
    try:
        line = p.recvline().decode('utf-8').strip()
    except EOFError:
        print("[-] 초기 문제 수신 실패 또는 연결 종료.")
        p.close()
        return

    # 50번 반복 (문제는 총 50개)
    for i in range(50):
        try:
            # 2. 현재 라인 파싱
            match = re.search(r'(\d+)\+(\d+)=\?', line)
            
            if not match:
                # 파싱에 실패하면, 서버가 이상한 메시지(TIME OUT 등)를 보낸 것입니다.
                print(f"[-] 문제 파싱 실패: {line}")
                p.close()
                return

            num1 = int(match.group(1))
            num2 = int(match.group(2))
            answer = num1 + num2
            
            # 3. 정답 전송
            p.sendline(str(answer).encode())
            print(f"[+] {i+1}/50: {num1}+{num2}={answer} -> 전송 성공")

            # 4. 서버 응답 확인 및 다음 문제 수신
            # 서버는 답을 받으면 '아무 응답 없이' 바로 다음 문제("X+Y=?")를 보냅니다.
            # recvline()을 사용하여 다음 문제 프롬프트를 한 줄 읽습니다.
            line = p.recvline(timeout=1.5).decode('utf-8').strip()
            
            # 응답에 "Wrong"이나 "TIME OUT"이 포함되어 있는지 확인합니다.
            if "Wrong" in line or "TIME OUT" in line:
                print(f"[-] 서버 응답 (오답/시간 초과): {line}")
                p.close()
                return
            
            # line 변수에는 이제 다음 문제("numX+numY=?")가 들어 있습니다.

        except EOFError:
            print("[-] 50문제 해결 전에 연결이 끊어졌습니다 (EOF).")
            p.close()
            return
        except PwnlibException as e:
            # 50문제를 모두 풀고 나면, 서버가 더 이상 출력을 보내지 않아 이 예외로 빠집니다.
            print("[*] 모든 문제를 풀었거나, 통신 오류 발생.")
            break

    # 50문제를 모두 풀었다면, 최종 플래그를 받아야 합니다.
    print("[*] 최종 응답을 기다립니다...")
    
    # 서버가 플래그 출력 전에 보낼 수 있는 "Nice!"를 포함한 모든 잔여 출력을 읽습니다.
    # .recvall()로 연결이 끊길 때까지 모든 데이터를 읽습니다.
    final_output = p.recvall(timeout=2).decode('utf-8', errors='ignore').strip()
    
    p.close()

    # 출력에서 플래그 패턴을 찾습니다.
    if "Nice!" in final_output:
        flag_lines = [line for line in final_output.split('\n') if line and "Nice!" not in line]
        if flag_lines:
            flag = flag_lines[0].strip()
            print("\n" + "="*50)
            print(f"[SUCCESS] 플래그를 찾았습니다: {flag}")
            print("="*50 + "\n")
            return
    
    print("\n" + "="*50)
    print("[-] 플래그를 찾지 못했습니다. 최종 응답:")
    print(final_output)
    print("="*50 + "\n")

if __name__ == "__main__":
    solve_math_challenge()
  1. 터미널에서 작성한 파이썬 코드를 불러옵니다.
python3 dreamhack.py # dreamhack.py : 내가 설정한 파이썬 코드 파일 이름.

실행하면, 성공적으로 플래그를 획득할 수 있었습니다.

+) 플래그를 찾았는데 왜 찾지 못했다고 뜨는지는 잘 모르겠습니다….. 아마 코드 문제인 것 같은데, 플래그 자체는 획득 완료했습니다!


배운점

  • pwntools 툴을 처음 사용해보았습니다. 시스템 해킹을 쉽게 하기 위해 도와주는 툴이 있다는걸 알게되었고, 이 툴을 앞으로 워게임을 풀 때 더 잘 활용해야겠다 느꼈습니다. 특히 이번 문제에서 사용한, 연산을 빠르게 해주는 툴도 있음을 알게 되었습니다.
  • 플래그를 획득할 수 있었지만, 찾지 못했다는 글이 떴기에 챗지피티나 제미나이같은 LLM이 짜주는 코드는 완벽하지 않음을 느꼈습니다.

Summary (English)

  • The challenge requires correctly answering 50 random addition problems (num1 + num2 = ?) sent by the service; each iteration sets alarm(1), giving only 1 second per question.
  • Manual solving is infeasible due to the 1-second timeout per question; automation is necessary.
  • The hint suggests pwntools; used pwn.remote() to connect, recvline() + regex (\d+)\+(\d+)=\? to parse each problem, compute the sum, and sendline() the answer immediately.
  • Repeat the parse-compute-send cycle 50 times; after completion, read remaining output to capture Nice! and the flag (format DH{...}).
  • Verified the approach against the VM/remote service and successfully obtained the flag; a parsing edge-case caused the script to report “flag not found” despite the flag being present in the output.
  • Lessons learned: use automation libraries (pwntools) for timing-critical CTF tasks, make output parsing robust (handle extra messages like TIME OUT/Wrong), and validate final-response parsing to reliably extract flags.
profile
CTF 풀이 및 실습 중심 학습을 기록합니다.

0개의 댓글