Pwnstar

diceCTF flippidy 본문

CTF

diceCTF flippidy

포너블처돌이 2021. 2. 8. 11:48

좀 까다로운 힙 문제였다.

까나리와 NX bit 그리고 Full relro가 걸려있다. malloc_hook이나 free_hook을 덮으면 된다.

main

void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
  signed int v3; // [rsp+Ch] [rbp-4h]

  setbuf(stdout, 0LL);
  setbuf(stdin, 0LL);
  setbuf(stderr, 0LL);
  sub_401211();
  printf("%s", "To get started, first tell us how big your notebook will be: ");
  size = input_size();
  table = malloc(8 * size);
  memset(table, 0, 8 * size);
  while ( 1 )
  {
    print_menu();
    printf(": ", 0LL);
    v3 = input_size();
    if ( v3 == 3 )
    {
      puts("Goodbye!");
      exit(0);
    }
    if ( v3 > 3 )
    {
LABEL_11:
      puts("Invalid choice.");
    }
    else if ( v3 == 1 )
    {
      add_notebook();
    }
    else
    {
      if ( v3 != 2 )
        goto LABEL_11;
      flip_notebook();
    }
  }
}

메인함수는 간단하다. 

먼저 notebook의 크기가 얼마나 될 지 입력하는 부분이 있는데, 이 갯수에 따라만들 수 있는 notebook의 수가 결정되고, 그 사이즈 * 8만큼 malloc을 실행한다.

1을 입력하면 add_notebook, 2를 입력하면 flip_notebook이 실행된다.

add_notebook

int add_notebook()
{
  void **v1; // rbx
  int v2; // [rsp+Ch] [rbp-14h]

  printf("Index: ");
  v2 = input_size();
  if ( v2 < 0 || v2 >= size )
    return puts("Invalid index.");
  v1 = (void **)((char *)table + 8 * v2);
  *v1 = malloc(0x30uLL);
  printf("Content: ");
  return (unsigned __int64)fgets(*((char **)table + v2), 0x30, stdin);
}

원하는 인덱스에 0x30만큼 할당을 받고 fgets함수로 0x30만큼 값을 입력할 수 있다.

 

flip_notebook

unsigned __int64 flip_notebook()
{
  void **v0; // rbx
  void **v1; // rbx
  char v3; // [rsp+Ah] [rbp-A6h]
  char v4; // [rsp+Bh] [rbp-A5h]
  int i; // [rsp+Ch] [rbp-A4h]
  char s; // [rsp+10h] [rbp-A0h]
  char dest; // [rsp+50h] [rbp-60h]
  unsigned __int64 v8; // [rsp+98h] [rbp-18h]

  v8 = __readfsqword(0x28u);
  for ( i = 0; i <= size / 2; ++i )
  {
    memset(&s, 0, 0x40uLL);
    memset(&dest, 0, 0x40uLL);
    v3 = 0;
    v4 = 0;
    if ( *((_QWORD *)table + i) )
    {
      strcpy(&s, *((const char **)table + i));
      free(*((void **)table + i));
    }
    else
    {
      v3 = 1;
    }
    if ( *((_QWORD *)table + size - i - 1) )
    {
      strcpy(&dest, *((const char **)table + size - i - 1));
      free(*((void **)table + size - i - 1));
    }
    else
    {
      v4 = 1;
    }
    *((_QWORD *)table + i) = 0LL;
    *((_QWORD *)table + size - i - 1) = 0LL;
    if ( v3 == 1 )
    {
      *((_QWORD *)table + size - i - 1) = 0LL;
    }
    else
    {
      v0 = (void **)((char *)table + 8 * (size - i) - 8);
      *v0 = malloc(0x30uLL);
      strcpy(*((char **)table + size - i - 1), &s);
    }
    if ( v4 == 1 )
    {
      *((_QWORD *)table + i) = 0LL;
    }
    else
    {
      v1 = (void **)((char *)table + 8 * i);
      *v1 = malloc(0x30uLL);
      strcpy(*((char **)table + i), &dest);
    }
  }
  return v8 - __readfsqword(0x28u);
}

복잡해 보이는데, 별 것 없다.

만약 notebook의 개수가 10개라고 가정하고, 0부터 4인덱스까지 값을 넣어주면, 절반인 5만큼 for문을 돌면서 0인덱스부터 값이 존재하는 인덱스를 검사하면서 free해주고, 9인덱스에 malloc을 해서 0인덱스의 값을 9인덱스에 그대로 복사해준다. 그러면 다음과 같이 값이 옮겨가게 된다

0 -> 9

1 -> 8

2 -> 7

3 -> 6

4 -> 5

이렇게 되는데, 여기서 double free bug를 발생시킬 수 있다.

만약 notebook의 개수가 7과 같은 홀수라고 치자. 그러면 3번 인덱스는

if ( *((_QWORD *)table + i) )
    {
      strcpy(&s, *((const char **)table + i));
      free(*((void **)table + i));
    }
    else
    {
      v3 = 1;
    }
    if ( *((_QWORD *)table + size - i - 1) )
    {
      strcpy(&dest, *((const char **)table + size - i - 1));
      free(*((void **)table + size - i - 1));
    }

이 부분에 의해서 free를 두 번 진행하게 된다. i가 3이면, 첫번째 if문에서 table + 3을 free하게 되고, 다음 if문에서 table + (7(size) - 1 - 3) = 3

0 -> 6, 1 -> 5, 2 -> 4, 3 -> 3

이걸 이용해서 원하는 곳에 값을 써 넣고, 먼저 leak을 진행해야하는데, 뭐 값을 출력해주는 메뉴가 없어서 어떻게 leak을 해야하나 고민을 많이 했다. 그러다 찾은 부분이 메뉴를 출력해주는 부분인데,

print_menu

int print_menu()
{
  int result; // eax
  signed int i; // [rsp+Ch] [rbp-4h]

  result = puts("\n");
  for ( i = 0; i <= 3; ++i )
    result = puts(off_404020[i]);
  return result;
}

메뉴를 0x404020의 위치에 있는 값들을 참조해서 출력을 해준다.

다행히도 이 부분에 쓰기 권한이 있음.

그러면 우선 0x404020 + dummy를 조금 입력해주고, free를 두 번 진행해보자.

이렇게 1번 인덱스에 값을 넣고,

첫 번째 free

두 번째 free가 진행되고 나면 그 다음엔 0x404020에 값을 넣을 수 있을테니, 여기엔 puts_got를 넣어주자.

여기서 뒤에는 0x404030의 주소를 더 넣어주었는데, 이 이유는 이후 tcache posioning을 진행하기 위한 초석이다. 조금 더 뒤에 자세히 설명할 것.

우선 이렇게 입력하면 libc를 알 수 있고, 여기서 free_hook과 oneshot gadget의 주소를 모두 알아놓자.

 

이 다음에 add로 다시 malloc을 진행하면 원래 0x404020에 들어있는 주소에 값을 입력할 수 있다 원래는 0x404040의 주소가 들어있는데, 현재까지의 tcache는 대충 이렇게 된다. (libc를 적용해서 디버깅하면 heap info 등의 명령어가 먹히지 않아서 약간 뇌피셜이다.)

0x404040 -> 0x404030 -> 0x404030

여기서 dummy값을 한 번 입력해주자

다음 malloc에서는 0x404030에 값을 입력할 수 있으니 여기에 free_hook의 주소를 입력해주면 tcache poisoning이 가능하다.

0x404030 -> 0x404030 -> free_hook

여기서 또 dummy값을 넣어줘서 0x404030에 있는 값을 제껴주고, 또 한 번 malloc을 진행하면,

free_hook에 원하는 값을 입력할 수 있다. 왜인지 모르겠는데, 로컬에서는 이게 될때가 있고 안될때가 있다. 서버에서는 잘 됨.

이렇게 하면 플래그를 볼 수 있다.

 

좀 어려워서 하루종일 잡고 풀었는데 그래도 재밌는 문제였다.

'CTF' 카테고리의 다른 글

nahamconCTF Rock Paper Scissors  (0) 2021.03.15
nahamcon CTF Sort It!  (0) 2021.03.15
zer0ptsCTF oneshot  (0) 2021.03.08
CSAW CTF bard's fail  (0) 2020.09.18
SSTF2020 eat_the_pie  (0) 2020.08.18
Comments