포스트

[Dreamhack] Dream Restaurant

[Dreamhack] Dream Restaurant

문제 링크

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

문제 설명

드림 레스토랑이 오픈했어요.
취약점을 익스플로잇해 “flag” 파일을 읽으세요.
플래그의 형식은 DH{…} 입니다.

문제 분석

메뉴 이름을 입력하면 스레드가 생성되며, 해당 스레드에서 전역 변수 dish를 조작하여 요리를 한다.

1
2
3
4
struct dish_t {
    uint8_t food_type;
    void *food_ptr;
};

요리는 총 2가지 타입(rice, noodle)이 있으며, 각 요리 타입을 처리하는 함수는 다음과 같다.

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
void CookRiceMenu(const struct food_menu_t *food_menu) {
    struct rice_t *rice_food;

    printf("\n\nCooking rice food '%s'...\n", food_menu->name);
    usleep(food_menu->cooking_time);
    dish.food_type = food_menu->food_type;
    usleep(31337);
    rice_food = (struct rice_t *)dish.food_ptr;
    memset(rice_food, 0x00, FOOD_SIZE);
    strcpy(rice_food->name, food_menu->name);
    rice_food->price = food_menu->price;
    rice_food->review_len = food_menu->review_len;
    rice_food->cooking_time = food_menu->cooking_time;

    puts("\nYour dish has arrived!");
}

void CookNoodleMenu(const struct food_menu_t *food_menu) {
    struct noodle_t *noodle_food;

    printf("\n\nCooking noodle food '%s'...\n", food_menu->name);
    usleep(food_menu->cooking_time);
    dish.food_type = food_menu->food_type;
    noodle_food = (struct noodle_t *)dish.food_ptr;
    memset(noodle_food, 0x00, FOOD_SIZE);
    strcpy(noodle_food->name, food_menu->name);
    noodle_food->price = food_menu->price;
    noodle_food->review_len = food_menu->review_len;
    noodle_food->cooking_time = food_menu->cooking_time;
    puts("\nYour dish has arrived!");
}
  • rice 음식은 noodle 음식과 달리 dish.food_type을 변경하고 usleep(31337)을 호출하는 것을 확인할 수 있다.
  • 따라서, 두 스레드를 적절한 타이밍에 생성하고 실행하면 Type Confusion이 발생할 수 있다.
    • 타입은 rice이지만 noodle 음식
    • 타입은 noodle이지만 rice 음식

타입이 중요한 이유는, rice와 noodle 음식의 구조체 형태에 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
struct rice_t {
    char name[16];
    size_t review_len;
    unsigned int price;
    unsigned int cooking_time;
};

struct noodle_t {
    char name[12];
    size_t review_len;
    unsigned int price;
    unsigned int cooking_time;
};
  • rice 음식은 name이 16바이트이지만, noodle 음식은 name이 12바이트이다.
  • 따라서, 음식은 rice이지만 타입은 noodle일 경우 음식 이름의 일부가 review_len으로써 사용될 여지가 있다.

review_len은 프로그램을 종료할 때 사용된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void LeaveReviewForRiceMenu() {
    char review[MAX_REVIEW_SIZE]; // 128
    struct rice_t *rice_menu;

    rice_menu = dish.food_ptr;
    printf("How about our rice food '%s'? ", rice_menu->name);
    fgets(review, rice_menu->review_len, stdin);
    puts("Thank you for writing review. It will help improve our restaurant.");
}

void LeaveReviewForNoodleMenu() {
    char review[MAX_REVIEW_SIZE]; // 128
    struct noodle_t *noodle_menu;

    noodle_menu = dish.food_ptr;
    printf("How about our noodle food '%s'? ", noodle_menu->name);
    fgets(review, noodle_menu->review_len, stdin);
    puts("Thank you for writing review. It will help improve our restaurant.");
}
  • 현재 음식의 타입(dish->food_type)에 따라 두 함수 중 하나가 호출된다.
  • 스택에 128바이트의 배열이 선언되어 있고, 그 배열에 최대 review_len바이트만큼 입력할 수 있다.
  • 만약 Type Confusion이 발생하여 review_len이 큰 수로 지정되면, 스택 버퍼 오버플로우가 발생할 수 있다.

PIE와 Stack Canary가 적용되어 있지 않고, 바이너리는 static link이므로 libc base leak이 필요가 없다.
그냥 ROP를 이용해서 셸을 얻고 플래그를 읽으면 된다.
다만 system 함수나 execve 함수가 존재하지 않으므로, 직접 syscall을 호출하여야 한다.

새롭게 알게된 점

레이스 컨디션을 익스플로잇할 때 적용할 딜레이 시간(sleep)을 잘 모르겠다면, 이분 탐색을 활용하도록 하자…

1
2
3
4
5
6
7
8
9
10
low, high = 0,4
while True:
    mid = (low+high)/2

    # Exploit...
    time.sleep(mid)
    # Exploit...

    # 결과가 맞으면 처리
    # 결과가 다르면 low=mid 혹은 high=mid를 적용하여 윈도우 조절
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.

"Dreamhack-Pwn" 카테고리의 게시물