본문 바로가기

Cryptography/Cryptography CTF

[Dreamhack] addition-quiz

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

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

 

소스파일

// Name: chall.c
// Compile Option: gcc chall.c -o chall -fno-stack-protector

#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <time.h>

#define FLAG_SIZE 0x45

void alarm_handler() {
    puts("TIME OUT");
    exit(1);
}

void initialize() {
    setvbuf(stdin, NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 0);

    signal(SIGALRM, alarm_handler);
}

int main(void) {
    int fd;
    char *flag;

    initialize();
    srand(time(NULL)); 

    flag = (char *)malloc(FLAG_SIZE);
    fd = open("./flag", O_RDONLY);
    read(fd, flag, FLAG_SIZE);
    close(fd);

    int num1 = 0;
    int num2 = 0;
    int inpt = 0; 

    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;
}

 

소스코드 분석

void alarm_handler() {
    puts("TIME OUT");
    exit(1);
}

 

main 반복문에 alarm(1) → 1초 내에 입력하지 않으면 이 함수를 실행하고 종료

 

void initialize() {
    setvbuf(stdin, NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 0);
    signal(SIGALRM, alarm_handler);
}

 

initialize() 함수는 C에서 입출력 처리 속도와 시그널 처리를 조절하는 초기 설정 함수

setvbuf(stdin, NULL, _IONBF, 0);

→ 표준 입력 버퍼링 제거

setvbuf(stdout, NULL, _IONBF, 0);

→ 표준 출력 버퍼링 제거

⇒ 입출력 내용이 바로바로 화면에 보이게 된다.

  • c의 입출력 함수 (scanf, getchar 등)은 내부적으로 버퍼를 사용
  • stdin(표준입력)은 줄 단위로 버퍼링됨 = 사용자가 enter를 눌러야 입력이 전달

 

_IOFBF
완전한 버퍼링(Full Buffering)
 
_IOLBF
행 버퍼링(Line Buffering)
버퍼가 채워지거나 개행문자가 입력되었다면 데이터가 버퍼에서 출력됨. 입력시에는 버퍼가 개행 문자를 만날 때까지 버퍼를 채우게 됨
_IONBF
버퍼링 사용 안함(No Buffering)
버퍼를 사용하지 않고 요청 즉시 진행

signal(SIGALRM, alarm_handler);

SIGALRM 시그널이 발생하면 alarm_handler() 실행

⇒ alarm()함수로 예약된 1초의 시간이 지나면 alarm_handler()를 실행

  • SIGALRM은 alarm() 함수로 예약된 타이머가 끝났을 때 발생하는 시그널

 

flag = (char *)malloc(FLAG_SIZE);
fd = open("./flag", O_RDONLY);
read(fd, flag, FLAG_SIZE);
close(fd);
  • ./flag 파일을 열고 내용을 flag 포인터에 저장함
  • O_RDONLY : 파일을 읽기 전용으로 open

 

for (int i = 0; i < 50; i++){
    alarm(1); // 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);
  • 50번 반복하는 반복문임
  • 0~9999 사이의 숫자 2개 랜덤 생성
  • 입력된 값이 랜덤 숫자 2개의 합과 다르면 바로 Wrong 출력 후 종료
  • 총 50번 전부 맞으면 Nice 출력 후 flag 출력

⇒ 반복문이 50번 실행될 때마다 랜덤 숫자 2개의 합을 1초 안에 입력해야 flag를 얻을 수 있음

 

풀이

 

원격 서버 접속

 

덧셈 숫자 수신

덧셈을 수행할 두 숫자를 recv()로 받아와야한다

첫 번째 숫자를 얻기 위해서 “+”까지 출력을 받은 뒤

두 번째 숫자를 얻기 위해서 “=”까지 출력을 받는다.

 

 

 

first_number = int(r.recvuntil(b'+', drop=True))

서버가 printf("%d+%d=?\n", num1, num2); 과 같은 수식을 출력하기 때문에

  • recvuntil(b’+’) → + 기호가 나올 때까지 수신
  • drop=True → +는 버리고 첫 번째 숫자만 받는다
  • int()로 정수로 변환한다.

 

second_number = int(r.recvuntil(b'=?\n', drop=True))

  • recvuntil(b’=?\n’) → =?\n가 나올 때까지 수신
  • drop = True → =?\n은 버리고 두 번째 숫자만 받는다
  • int()로 정수로 변환

 

덧셈 결과 전송

context.log_level = 'debug'

  • 전송된 것이 정답으로 처리되는지 여부를 확인하기 위해서 데이터 송수신 내역을 확인

r.sendline(str(first_number + second_number).encode())

  • 두 수를 더한 후 문자열로 바꾸고 .encode()해서 바이트로 전송

 

플래그 얻기

  • 앞 과정을 50번 반복하는 반복문 구현 추가

 

50번 계산된 로그가 나온 뒤 flag가 출력됨

'Cryptography > Cryptography CTF' 카테고리의 다른 글

[Dreamhack] No shift please!  (0) 2025.11.07
[Dreamhack] No sub please!  (0) 2025.11.06
[Dreamhack] chinese what?  (0) 2025.10.09
[Dreamhack] Exploit Tech: Meet-in-the-middle Attack  (0) 2025.10.09
[Dreamhack] flag-shop  (0) 2025.10.09