[SCTF 2020] T express

 

오랜만의 힙 문제였지만, 크립토 문제를 풀다가 재미가 없어서 갈아탄 문제였다.

 

IDA 분석 & 취약점

void __cdecl use_ticket()
{
  unsigned int v0; // eax
  unsigned int index; // [rsp+4h] [rbp-Ch]
  pass5 *p; // [rsp+8h] [rbp-8h]

  printf("Index of ticket: ");
  index = read_l();
  if ( index <= 6 && passes[index] )
  {
    p = passes[index];
    if ( p->ticket_type )
    {
      free(p);
    }
    else
    {
      puts("1) meal 2) safari 3) gift 4) ride");
      printf("(1/2/3/4): ");
      v0 = read_l();
      if ( v0 == 2 )
      {
        if ( p->safari_pass )
          --p->safari_pass;
      }
      else if ( v0 > 2 )
      {
        if ( v0 == 3 )
        {
          if ( p->giftshop_coupon )
            --p->giftshop_coupon;
        }
        else if ( v0 == 4 )
        {
          ++p->ride_count;
        }
      }
      else if ( v0 == 1 && p->meal_ticket )
      {
        --p->meal_ticket;
      }
      if ( !p->meal_ticket && !p->safari_pass && !p->giftshop_coupon )
        free(p);
    }
    puts("Thank you. Have a good time!");
  }
  else
  {
    puts("Wrong ticket!");
  }
}

view_ticket 함수에서 보면 index가 signed_int라서 0~6까지 뿐만 아니라, 음수값을 입력하여 libc_leak이 가능했다.

그리고, free후에 딱히 다른 처리를 해주지 않아서 free한 청크에도 view를 할 수 있었고, 그래서 pie_leak또한 가능했다.

 

[+] PIE / LIBC leak

 

 

void __cdecl read_str(char *buf, unsigned int size)
{
  ssize_t len; // [rsp+18h] [rbp-8h]

  len = read(0, buf, size);
  if ( len <= 0 )
  {
    puts("read error");
    exit(0);
  }
  if ( buf[len - 1] == 10 )
    buf[len - 1] = 0;
  else
    buf[len] = 0;

항상 힙 문제에서는 위처럼 read 함수를 만들어서 사용하는데 여기서 buf[len] = 0 을 사용함으로써 1바이트 오버플로우가 났다.

 

void __cdecl buy_ticket()
{
  __int64 i; // [rsp+0h] [rbp-10h]
  pass5 **p; // [rsp+8h] [rbp-8h]

  LODWORD(i) = 0;
  for ( p = passes; *p; ++p )
    ;
  if ( (char *)p - (char *)passes <= 48 )
  {
    while ( 1 )
    {
      puts("Which do you want buy?");
      puts("1. One ride ticket");
      puts("2. One day ticket (3 meals, 1 safari pass, unlimited rides, 1 giftshop coupon)");
      printf("(1/2): ", i);
      HIDWORD(i) = read_l();
      if ( HIDWORD(i) == 1 )
      {
        *p = (pass5 *)malloc(0x18uLL);
        (*p)->ticket_type = 1LL;
        goto LABEL_11;
      }
      if ( HIDWORD(i) == 2 )
        break;
      puts("No such ticket!");
    }
    *p = (pass5 *)malloc(0x30uLL);
    (*p)->ticket_type = 0LL;
    (*p)->meal_ticket = 3;
    (*p)->safari_pass = 1;
    (*p)->giftshop_coupon = 1;
    (*p)->ride_count = 0LL;
LABEL_11:
    printf("First name: ");
    read_str((*p)->firstname, 8u);
    printf("Last name: ", 8LL);
    read_str((*p)->lastname, 8u);
  }
  else
  {
    puts("Sold out!");
  }
}

위 함수에서  one ride ticket과 one day ticket을 구분하는 부분이 바로 fd / bk 영역의 다음 8바이트였는데 

one ride ticket을 선택하여 malloc(0x18) 할당을 했지만 위에서 널 바이트 오버플로우를 이용하여 이를 one day ticket인 척 사용 가능하다

 

여기서, malloc(0x18) 후 one day ticket인 척 흉내낸다면, use 함수를 이용하여 다음 청크의 size / fd / bk 를 조작할 수 있다.

 

Exploit

malloc(0x30) => free

위 청크 사이즈 변경 후 한번 더 free.   (Double Free !!)

 

double_free 를 트리거 했으니 fd 조작이 가능하기 때문에 fd를 malloc/free_hook 으로 덮어서 사용하려고 했다.

그런데 20.04에서는 one_gadget 조건이 까다로워서, free 할 청크 자리에 /bin/sh를 적어두고 free_hook을 system으로 덮어서 쉘을 땄다.

 

 

## 알게된 점

1. free 할 청크에 /bin/sh를 써두고 free_hook을 system으로 덮는 방법

2. tcache를 다룰 때, tcache에 들어가있는 개수가 저장되어 있음 => 개수가 0개로 되어있을 때는 tcache에서 꺼내오지 않기 때문에 유의할 것

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

[SECCON CTF 2021] pppp  (0) 2021.12.28
[SCTF 2020] Eat the pie  (0) 2020.08.19
[Hack.lu 2018] Baby_Kernel_1  (1) 2020.04.04
[zer0pts] dirty laundry  (0) 2020.03.12
[zer0pts] nibelung  (0) 2020.03.12
  Comments,     Trackbacks